sdks: add set peer card function (#371)

* feat: add set peer card to SDK, bump version, document

* fix: pytest -> pytest -x

* chore: deprecate .card(), move to .getCard() / .get_card()

* fix: get_or_create when crudding peer cards

* chore: review nits

* chore: document .get_card / .set_card

* chore: (docs) update language from deriver to dreamer agent

* fix: get_peer_card should not create peer/workspace

* fix: change api contract to return ResourceNotFoundException (#375)

* fix: PR nitpicks

---------

Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com>
This commit is contained in:
doria 2026-02-09 15:35:57 -05:00 committed by GitHub
parent 3eab54374c
commit 2522cc5ee6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 417 additions and 94 deletions

View File

@ -65,7 +65,7 @@ repos:
# Run main application tests
- id: pytest-main
name: pytest (main app)
entry: uv run pytest tests/ --ignore=tests/alembic/
entry: uv run pytest -x tests/ --ignore=tests/alembic/
language: system
files: ^(src/|tests/).*\.py$
stages: [pre-push]

View File

@ -4,27 +4,23 @@ description: "Compatibility guide for Honcho's SDKs and API"
icon: "shield-check"
---
This guide helps you understand which versions of Honcho's API are compatible with which SDK versions.
This guide helps you match the right SDK version to your Honcho API version. Newer SDK patch versions are always backward-compatible within the same major version — install the latest patch for your range.
## Version Compatibility
### Honcho API v3.0.2 (Current)
## Current Versions
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js">
**Compatible Version:** v2.0.0
**Latest:** v2.0.1
Install with:
```bash
npm install @honcho-ai/sdk@2.0.0
npm install @honcho-ai/sdk
```
</Card>
<Card title="Python SDK" icon="python">
**Compatible Version:** v2.0.0
**Latest:** v2.0.1
Install with:
```bash
pip install honcho-ai==2.0.0
pip install honcho-ai
```
</Card>
</CardGroup>
@ -34,9 +30,9 @@ This guide helps you understand which versions of Honcho's API are compatible wi
| Honcho API Version | TypeScript SDK | Python SDK |
|-------------------|---------------|------------|
| v3.0.2 (Current) | v2.0.0 | v2.0.0 |
| v3.0.1 | v2.0.0 | v2.0.0 |
| v3.0.0 | v2.0.0 | v2.0.0 |
| v3.0.2 (Current) | v2.0.0+ | v2.0.0+ |
| v3.0.1 | v2.0.0+ | v2.0.0+ |
| v3.0.0 | v2.0.0+ | v2.0.0+ |
| v2.5.1 | v1.6.0 | v1.6.0 |
| v2.5.0 | v1.6.0 | v1.6.0 |
| v2.4.3 | v1.5.0 | v1.5.0 |

View File

@ -280,6 +280,12 @@ metadata = alice.get_metadata()
metadata["location"] = "Paris"
alice.set_metadata(metadata)
# Peer card management
card = alice.get_card() # Get peer card
card = alice.get_card(target="bob") # Get card about another peer
updated = alice.set_card(["Likes Python", "Lives in NYC"]) # Set peer card
updated = alice.set_card(["Works at Acme"], target="bob") # Set card about another peer
# Get peer context (representation + peer card in one call)
context = alice.context()
context = alice.context(target="bob") # What alice knows about bob
@ -334,6 +340,12 @@ await alice.setMetadata({
location: "Paris"
});
// Peer card management
const card = await alice.getCard(); // Get peer card
const targetCard = await alice.getCard("bob"); // Get card about another peer
const updated = await alice.setCard(["Likes TypeScript", "Lives in NYC"]); // Set peer card
const updatedTarget = await alice.setCard(["Works at Acme"], "bob"); // Set card about another peer
// Get peer context (representation + peer card in one call)
const context = await alice.context();
const targetContext = await alice.context({ target: "bob" }); // What alice knows about bob
@ -396,6 +408,46 @@ const searchedContext = await alice.context({
```
</CodeGroup>
### Peer Card
The peer card contains stable biographical facts about a peer (name, preferences, background). Use `get_card()` / `getCard()` to retrieve it and `set_card()` / `setCard()` to overwrite it:
<CodeGroup>
```python Python
# Get peer's own card
card = alice.get_card()
print(card) # ["Likes Python", "Lives in NYC", ...]
# Get card about another peer (local representation)
bob_card = alice.get_card(target="bob")
# Set peer's own card
updated = alice.set_card(["Likes Python", "Lives in NYC"])
# Set card about another peer
updated = alice.set_card(["Works at Acme", "Enjoys hiking"], target="bob")
```
```typescript TypeScript
// Get peer's own card
const card = await alice.getCard();
console.log(card); // ["Likes TypeScript", "Lives in NYC", ...]
// Get card about another peer (local representation)
const bobCard = await alice.getCard("bob");
// Set peer's own card
const updated = await alice.setCard(["Likes TypeScript", "Lives in NYC"]);
// Set card about another peer
const updatedBob = await alice.setCard(["Works at Acme", "Enjoys hiking"], "bob");
```
</CodeGroup>
<Info>
Peer cards are automatically maintained by the dreaming agent during message processing. Use `set_card()` / `setCard()` when you need to manually override or seed the card — the peer will be created automatically if it doesn't already exist.
</Info>
### Conclusions
Peers can access their conclusions (facts derived from messages) through the `conclusions` property and `conclusions_of()` method:

View File

@ -274,7 +274,7 @@ Additional features with **no Mem0 equivalent**:
| Honcho Method | Description | Use Case |
|---------------|-------------|----------|
| `peer.card()` | Stable biographical facts (name, preferences, background) | User profiles, personalization |
| `peer.get_card()` / `peer.set_card()` | Stable biographical facts (name, preferences, background) | User profiles, personalization |
| `session.representation(peer)` | Cached psychological analysis (mental state, intentions) | Real-time adaptation |
| `session.summaries()` | Auto-generated short/long session summaries | Conversation continuity |
| `SessionPeerConfig` | Configure observation settings (who learns about whom) | Privacy controls, role-based learning |

View File

@ -274,7 +274,7 @@ Additional features with **no Mem0 equivalent**:
| Honcho Method | Description | Use Case |
|---------------|-------------|----------|
| `peer.card()` | Stable biographical facts (name, preferences, background) | User profiles, personalization |
| `peer.get_card()` / `peer.set_card()` | Stable biographical facts (name, preferences, background) | User profiles, personalization |
| `session.representation(peer)` | Cached psychological analysis (mental state, intentions) | Real-time adaptation |
| `session.summaries()` | Auto-generated short/long session summaries | Conversation continuity |
| `SessionPeerConfig` | Configure observation settings (who learns about whom) | Privacy controls, role-based learning |

View File

@ -1,6 +1,6 @@
[project]
name = "honcho-ai"
version = "2.0.0"
version = "2.0.1"
description = "Official DX Optimized Python SDK for Honcho"
dynamic = ["readme"]
license = "Apache-2.0"

View File

@ -66,7 +66,7 @@ from .types import (
DialecticStreamResponse,
)
__version__ = "2.0.0"
__version__ = "2.0.1"
__author__ = "Plastic Labs"
__email__ = "hello@plasticlabs.ai"

View File

@ -23,6 +23,7 @@ from __future__ import annotations
import json
import logging
import warnings
from collections.abc import AsyncGenerator
from datetime import datetime
from typing import TYPE_CHECKING, Any, ClassVar, Literal
@ -588,7 +589,7 @@ class PeerAio(AsyncMetadataConfigMixin):
]
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
async def card(
async def get_card(
self,
target: str | PeerBase | None = None,
) -> list[str] | None:
@ -604,6 +605,51 @@ class PeerAio(AsyncMetadataConfigMixin):
response = PeerCardResponse.model_validate(data)
return response.peer_card
async def card(
self,
target: str | PeerBase | None = None,
) -> list[str] | None:
"""Deprecated: use get_card() instead."""
warnings.warn(
"card() is deprecated, use get_card() instead",
DeprecationWarning,
stacklevel=2,
)
return await self.get_card(target=target)
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
async def set_card(
self,
peer_card: list[str],
target: str | PeerBase | None = None,
) -> list[str] | None:
"""
Set the peer card for this peer.
Makes an API call to set the peer card. If a target is provided, sets this
peer's local card of the target peer.
Args:
peer_card: A list of strings to set as the peer card.
target: Optional target peer for local card. If provided, sets this
peer's card of the target peer. Can be a Peer object or peer ID string.
Returns:
A list of strings representing the updated peer card, or None if none is available
"""
await self._peer._honcho._ensure_workspace_async()
target_id = resolve_id(target)
query = {"target": target_id} if target_id else None
data = await self._peer._honcho._async_http_client.put(
routes.peer_card(self._peer.workspace_id, self._peer.id),
body={"peer_card": peer_card},
query=query,
)
response = PeerCardResponse.model_validate(data)
return response.peer_card
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
async def representation(
self,

View File

@ -5,6 +5,7 @@ from __future__ import annotations
import datetime
import logging
import warnings
from collections.abc import Generator
from typing import TYPE_CHECKING, Any, Literal
@ -446,7 +447,7 @@ class Peer(PeerBase, MetadataConfigMixin):
]
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
def card(
def get_card(
self,
target: str | PeerBase | None = None,
) -> list[str] | None:
@ -476,6 +477,50 @@ class Peer(PeerBase, MetadataConfigMixin):
return response.peer_card
def card(
self,
target: str | PeerBase | None = None,
) -> list[str] | None:
"""Deprecated: use get_card() instead."""
warnings.warn(
"card() is deprecated, use get_card() instead",
DeprecationWarning,
stacklevel=2,
)
return self.get_card(target=target)
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
def set_card(
self,
peer_card: list[str],
target: str | PeerBase | None = None,
) -> list[str] | None:
"""
Set the peer card for this peer.
Makes an API call to set the peer card. If a target is provided, sets this
peer's local card of the target peer.
Args:
peer_card: A list of strings to set as the peer card.
target: Optional target peer for local card. If provided, sets this
peer's card of the target peer. Can be a Peer object or peer ID string.
Returns:
A list of strings representing the updated peer card, or None if none is available
"""
self._honcho._ensure_workspace()
target_id = resolve_id(target)
query = {"target": target_id} if target_id else None
data = self._honcho._http.put(
routes.peer_card(self.workspace_id, self.id),
body={"peer_card": peer_card},
query=query,
)
response = PeerCardResponse.model_validate(data)
return response.peer_card
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
def representation(
self,

View File

@ -389,6 +389,9 @@ describe('Peer', () => {
describe('POST /peers/:id/card', () => {
test('card returns string array or null', async () => {
const peer = await client.peer('card-peer')
const session = await client.session('card-session')
await session.addPeers([peer.id])
await session.addMessages([peer.message('hello')])
const card = await peer.card()
@ -399,6 +402,9 @@ describe('Peer', () => {
test('card with target peer', async () => {
const observer = await client.peer('card-observer')
const observed = await client.peer('card-observed')
const session = await client.session('card-target-session')
await session.addPeers([observer.id, observed.id])
await session.addMessages([observer.message('hello'), observed.message('hi')])
const card = await observer.card(observed)
@ -408,6 +414,10 @@ describe('Peer', () => {
test('card with target ID string', async () => {
const peer = await client.peer('card-string-peer')
const target = await client.peer('some-target-id')
const session = await client.session('card-string-session')
await session.addPeers([peer.id, target.id])
await session.addMessages([peer.message('hello'), target.message('hi')])
const card = await peer.card('some-target-id')
@ -430,6 +440,48 @@ describe('Peer', () => {
})
})
// ===========================================================================
// Set Peer Card (PUT /peers/:id/card)
// ===========================================================================
describe('PUT /peers/:id/card', () => {
test('setCard sets and returns peer card', async () => {
const peer = await client.peer('setcard-peer')
const cardData = ['fact one', 'fact two']
const result = await peer.setCard(cardData)
expect(result).toEqual(cardData)
// Verify with get
const card = await peer.getCard()
expect(card).toEqual(cardData)
})
test('setCard with target peer', async () => {
const observer = await client.peer('setcard-observer')
const observed = await client.peer('setcard-observed')
const cardData = ['target likes TypeScript', 'target is clever']
const result = await observer.setCard(cardData, observed)
expect(result).toEqual(cardData)
// Verify with get
const card = await observer.getCard(observed)
expect(card).toEqual(cardData)
})
test('setCard with target ID string', async () => {
const peer = await client.peer('setcard-string-peer')
const cardData = ['some fact']
const result = await peer.setCard(cardData, 'setcard-string-target')
expect(result).toEqual(cardData)
})
})
// ===========================================================================
// Peer Context (POST /peers/:id/context)
// ===========================================================================
@ -437,6 +489,9 @@ describe('Peer', () => {
describe('POST /peers/:id/context', () => {
test('context returns representation and card', async () => {
const peer = await client.peer('context-peer')
const session = await client.session('context-session')
await session.addPeers([peer.id])
await session.addMessages([peer.message('hello')])
const context = await peer.context()
@ -450,6 +505,9 @@ describe('Peer', () => {
test('context with target peer', async () => {
const observer = await client.peer('context-observer')
const observed = await client.peer('context-observed')
const session = await client.session('context-target-session')
await session.addPeers([observer.id, observed.id])
await session.addMessages([observer.message('hello'), observed.message('hi')])
const context = await observer.context({ target: observed })
@ -459,6 +517,9 @@ describe('Peer', () => {
test('context with options', async () => {
const peer = await client.peer('context-options-peer')
const session = await client.session('context-options-session')
await session.addPeers([peer.id])
await session.addMessages([peer.message('hello')])
const context = await peer.context({
searchQuery: 'interests',

View File

@ -1,6 +1,6 @@
{
"name": "@honcho-ai/sdk",
"version": "2.0.0",
"version": "2.0.1",
"description": "Official DX Optimized TypeScript SDK for Honcho",
"author": "Plastic Labs <hello@plasticlabs.ai>",
"license": "Apache-2.0",

View File

@ -28,6 +28,7 @@ import {
MessageConfigurationSchema,
MessageContentSchema,
MessageMetadataSchema,
PeerCardContentSchema,
type PeerConfig,
PeerConfigSchema,
PeerGetRepresentationParamsSchema,
@ -295,6 +296,18 @@ export class Peer {
)
}
private async _setCard(params: {
peer_card: string[]
target?: string
}): Promise<PeerCardResponse> {
await this._ensureWorkspace()
const { peer_card, ...query } = params
return this._http.put<PeerCardResponse>(
`/${API_VERSION}/workspaces/${this.workspaceId}/peers/${this.id}/card`,
{ body: { peer_card }, query }
)
}
// ===========================================================================
// Public Methods
// ===========================================================================
@ -652,7 +665,7 @@ export class Peer {
* @returns Promise resolving to an array of strings containing the peer card items,
* or null if no peer card exists
*/
async card(target?: string | Peer): Promise<string[] | null> {
async getCard(target?: string | Peer): Promise<string[] | null> {
const validatedTarget = CardTargetSchema.parse(target)
const response = await this._getCard({
@ -662,6 +675,38 @@ export class Peer {
return response.peer_card
}
/**
* @deprecated Use {@link getCard} instead.
*/
async card(target?: string | Peer): Promise<string[] | null> {
return this.getCard(target)
}
/**
* Set the peer card for this peer.
*
* Makes an API call to set the peer card. If a target is provided, sets this
* peer's local card of the target peer.
*
* @param peerCard - An array of strings to set as the peer card.
* @param target - Optional target peer for local card. If provided, sets this
* peer's card of the target peer. Can be a Peer object or peer ID string.
* @returns Promise resolving to an array of strings containing the updated peer card items,
* or null if no peer card exists
*/
async setCard(
peerCard: string[],
target?: string | Peer
): Promise<string[] | null> {
const validatedPeerCard = PeerCardContentSchema.parse(peerCard)
const validatedTarget = CardTargetSchema.parse(target)
const response = await this._setCard({
peer_card: validatedPeerCard,
target: validatedTarget,
})
return response.peer_card
}
/**
* Get a subset of Honcho's Representation of a peer.
*

View File

@ -376,6 +376,11 @@ export const CardTargetSchema = z
val ? (typeof val === 'string' ? val : val.id) : undefined
)
/**
* Schema for peer card content (array of strings).
*/
export const PeerCardContentSchema = z.array(z.string())
/**
* Schema for peer addition to session.
*/

View File

@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src import exceptions, models, schemas
from src.cache.client import cache
from src.crud.peer import get_peer, peer_cache_key
from src.crud.peer import get_or_create_peers, get_peer, peer_cache_key
logger = logging.getLogger(__name__)
@ -32,18 +32,18 @@ async def get_peer_card(
observer: Peer name of the observer
Returns:
The peer's card text if present, otherwise None (also None if peer not found).
The peer's card text if present, otherwise None.
Raises:
ResourceNotFoundException: If the peer does not exist.
"""
try:
peer = await get_peer(db, workspace_name, schemas.PeerCreate(name=observer))
return cast(
list[str] | None,
peer.internal_metadata.get(
construct_peer_card_label(observer=observer, observed=observed)
),
)
except exceptions.ResourceNotFoundException:
return None
peer = await get_peer(db, workspace_name, schemas.PeerCreate(name=observer))
return cast(
list[str] | None,
peer.internal_metadata.get(
construct_peer_card_label(observer=observer, observed=observed)
),
)
async def set_peer_card(
@ -66,9 +66,10 @@ async def set_peer_card(
observed: Peer name of the peer described in the peer card
observer: Peer name of the observer
Raises:
ResourceNotFoundException: If the peer does not exist
"""
# Ensure the peer exists (get-or-create)
await get_or_create_peers(db, workspace_name, [schemas.PeerCreate(name=observer)])
stmt = (
update(models.Peer)
.where(models.Peer.workspace_name == workspace_name)
@ -82,11 +83,9 @@ async def set_peer_card(
}
)
)
.returning(models.Peer)
)
result = await db.execute(stmt)
updated_peer = result.scalar_one_or_none()
if updated_peer is None:
if result.rowcount == 0:
raise exceptions.ResourceNotFoundException(
f"Peer {observer} not found in workspace {workspace_name}"
)

View File

@ -8,7 +8,7 @@ using the DialecticAgent.
import logging
from collections.abc import AsyncIterator
from src import crud
from src import crud, schemas
from src.config import ReasoningLevel
from src.dependencies import tracked_db
from src.dialectic.core import DialecticAgent
@ -40,6 +40,11 @@ async def agentic_chat(
The synthesized answer string
"""
async with tracked_db("dialectic.agentic_chat") as db:
# Validate that the peers exist before proceeding
await crud.get_peer(db, workspace_name, schemas.PeerCreate(name=observer))
if observer != observed:
await crud.get_peer(db, workspace_name, schemas.PeerCreate(name=observed))
# Resolve configuration to check if peer cards should be used
session = None
if session_name:
@ -101,6 +106,11 @@ async def agentic_chat_stream(
Chunks of the response text as they are generated
"""
async with tracked_db("dialectic.agentic_chat_stream") as db:
# Validate that the peers exist before proceeding
await crud.get_peer(db, workspace_name, schemas.PeerCreate(name=observer))
if observer != observed:
await crud.get_peer(db, workspace_name, schemas.PeerCreate(name=observed))
# Resolve configuration to check if peer cards should be used
session = None
if session_name:

View File

@ -20,7 +20,7 @@ from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from src import crud
from src import crud, schemas
from src.config import settings
from src.schemas import ResolvedConfiguration
from src.telemetry import prometheus_metrics
@ -142,6 +142,11 @@ class BaseSpecialist(ABC):
task_name = f"dreamer_{self.name}_{run_id}"
start_time = time.perf_counter()
# Validate that the peers exist before proceeding
await crud.get_peer(db, workspace_name, schemas.PeerCreate(name=observer))
if observer != observed:
await crud.get_peer(db, workspace_name, schemas.PeerCreate(name=observed))
# Determine if peer card tools should be included
peer_card_enabled = configuration is None or configuration.peer_card.create

View File

@ -338,11 +338,7 @@ async def set_peer_card(
observed=observed,
)
# Return the updated peer card
peer_card = await crud.get_peer_card(
db, workspace_id, observer=peer_id, observed=observed
)
return schemas.PeerCardResponse(peer_card=peer_card)
return schemas.PeerCardResponse(peer_card=peer_card_data.peer_card)
@router.get(

View File

@ -51,62 +51,30 @@ async def test_peer_card_get_set_roundtrip(
@pytest.mark.asyncio
async def test_set_peer_card_missing_peer_raises(
async def test_get_peer_card_missing_peer_raises(
db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
"""Setting a peer card for a non-existent peer should raise ResourceNotFoundException."""
"""Getting a peer card for a non-existent peer should raise ResourceNotFoundException."""
workspace, _existing_peer = sample_data
with pytest.raises(ResourceNotFoundException):
await set_peer_card(
await get_peer_card(
db_session,
workspace.name,
["card"],
observer="missing-peer",
observed="missing-peer",
)
@pytest.mark.asyncio
async def test_get_peer_card_missing_peer_returns_none(
async def test_get_peer_card_missing_workspace_raises(
db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
"""Getting a peer card for a non-existent peer should return None."""
workspace, _existing_peer = sample_data
result = await get_peer_card(
db_session,
workspace.name,
observer="missing-peer",
observed="missing-peer",
)
assert result is None
@pytest.mark.asyncio
async def test_get_peer_card_missing_workspace_returns_none(
db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
"""Getting a peer card for a non-existent workspace should return None."""
_workspace, peer = sample_data
result = await get_peer_card(
db_session,
"missing-workspace",
observer=peer.name,
observed=peer.name,
)
assert result is None
@pytest.mark.asyncio
async def test_set_peer_card_missing_workspace_raises(
db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
"""Setting a peer card for a non-existent workspace should raise ResourceNotFoundException."""
"""Getting a peer card for a non-existent workspace should raise ResourceNotFoundException."""
_workspace, peer = sample_data
with pytest.raises(ResourceNotFoundException):
await set_peer_card(
await get_peer_card(
db_session,
"missing-workspace",
["card"],
observer=peer.name,
observed=peer.name,
)

View File

@ -909,3 +909,52 @@ async def test_get_peer_card_with_data(
assert response.status_code == 200
data = response.json()
assert data["peer_card"] == target_card_content
def test_set_peer_card(client: TestClient, sample_data: tuple[Workspace, Peer]):
"""Test setting peer cards via the PUT endpoint."""
test_workspace, observer_peer = sample_data
# Create a target peer
target_peer_name = str(generate_nanoid())
response = client.post(
f"/v3/workspaces/{test_workspace.name}/peers",
json={"name": target_peer_name},
)
assert response.status_code in [200, 201]
# Set the observer's own card
self_card = ["I am a test peer", "I like writing tests"]
response = client.put(
f"/v3/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card",
json={"peer_card": self_card},
)
assert response.status_code == 200
data = response.json()
assert data["peer_card"] == self_card
# Verify with GET
response = client.get(
f"/v3/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card"
)
assert response.status_code == 200
assert response.json()["peer_card"] == self_card
# Set a card for the target peer
target_card = ["Target is helpful", "Target knows Python"]
response = client.put(
f"/v3/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card",
params={"target": target_peer_name},
json={"peer_card": target_card},
)
assert response.status_code == 200
data = response.json()
assert data["peer_card"] == target_card
# Verify with GET
response = client.get(
f"/v3/workspaces/{test_workspace.name}/peers/{observer_peer.name}/card",
params={"target": target_peer_name},
)
assert response.status_code == 200
assert response.json()["peer_card"] == target_card

View File

@ -104,3 +104,41 @@ def test_message_and_chat_operations(honcho_test_client: Honcho):
# This is a mock response from the agent
_response = user.chat("What did I ask about?")
def test_peer_card_operations(honcho_test_client: Honcho):
"""
Tests setting and getting peer cards.
"""
peer = honcho_test_client.peer(id="card-test-peer")
target = honcho_test_client.peer(id="card-test-target")
# Create the peers on the server by adding a message
session = honcho_test_client.session(id="card-test-session")
session.add_messages([peer.message("hello"), target.message("hi")])
# Initially card should be None
card = peer.get_card()
assert card is None
# Set own card
own_card = ["I am a helpful assistant", "I enjoy learning"]
result = peer.set_card(own_card)
assert result == own_card
# Verify with get
card = peer.get_card()
assert card == own_card
# Set card for target
target_card = ["Target likes Python", "Target is friendly"]
result = peer.set_card(target_card, target=target)
assert result == target_card
# Verify with get
card = peer.get_card(target=target)
assert card == target_card
# Own card should still be unchanged
card = peer.get_card()
assert card == own_card

View File

@ -93,7 +93,7 @@ async def test_peer_card_global(client_fixture: tuple[Honcho, str]):
await session.aio.add_messages([peer.message("I like pizza")])
# Get global peer card
card_response = await peer.aio.card()
card_response = await peer.aio.get_card()
assert card_response is None or isinstance(card_response, list)
else:
peer = honcho_client.peer(id="test-card-global-peer")
@ -103,7 +103,7 @@ async def test_peer_card_global(client_fixture: tuple[Honcho, str]):
session.add_messages([peer.message("I like pizza")])
# Get global peer card
card_response = peer.card()
card_response = peer.get_card()
assert card_response is None or isinstance(card_response, list)
@ -125,11 +125,11 @@ async def test_peer_card_local(client_fixture: tuple[Honcho, str]):
)
# Get local peer card with target as Peer object
card_response = await observer.aio.card(target=target)
card_response = await observer.aio.get_card(target=target)
assert card_response is None or isinstance(card_response, list)
# Get local peer card with target as string
card_response = await observer.aio.card(target=target.id)
card_response = await observer.aio.get_card(target=target.id)
assert card_response is None or isinstance(card_response, list)
else:
observer = honcho_client.peer(id="test-card-local-observer")
@ -140,11 +140,11 @@ async def test_peer_card_local(client_fixture: tuple[Honcho, str]):
session.add_messages([observer.message("Hello"), target.message("Hi there")])
# Get local peer card with target as Peer object
card_response = observer.card(target=target)
card_response = observer.get_card(target=target)
assert card_response is None or isinstance(card_response, list)
# Get local peer card with target as string
card_response = observer.card(target=target.id)
card_response = observer.get_card(target=target.id)
assert card_response is None or isinstance(card_response, list)
@ -159,15 +159,23 @@ async def test_peer_card_with_empty_target(client_fixture: tuple[Honcho, str]):
if client_type == "async":
peer = await honcho_client.aio.peer(id="test-card-validation-peer")
session = await honcho_client.aio.session(id="test-card-validation-session")
# Create the peer on the server by adding a message
await session.aio.add_messages([peer.message("hello")])
# Empty target is treated as no target (same as None)
result = await peer.aio.card(target="")
result = await peer.aio.get_card(target="")
assert result is None or isinstance(result, list)
else:
peer = honcho_client.peer(id="test-card-validation-peer")
session = honcho_client.session(id="test-card-validation-session")
# Create the peer on the server by adding a message
session.add_messages([peer.message("hello")])
# Empty target is treated as no target (same as None)
result = peer.card(target="")
result = peer.get_card(target="")
assert result is None or isinstance(result, list)

View File

@ -345,7 +345,7 @@ class UnifiedTestExecutor:
raise ValueError("peer_id required for get_peer_card")
peer = await self.client.aio.peer(id=step.observer_peer_id)
card = await peer.aio.card(
card = await peer.aio.get_card(
step.observed_peer_id
if step.observed_peer_id
else step.observer_peer_id

View File

@ -1147,7 +1147,7 @@ dev = [
[[package]]
name = "honcho-ai"
version = "2.0.0"
version = "2.0.1"
source = { editable = "sdks/python" }
dependencies = [
{ name = "httpx" },