Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions plugins/memory/honcho/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,17 @@ def get_peer_card(self, session_key: str, peer: str = "user") -> list[str]:

try:
observer_peer_id, target_peer_id = self._resolve_observer_target(session, peer)
return self._fetch_peer_card(observer_peer_id, target=target_peer_id)
card = self._fetch_peer_card(observer_peer_id, target=target_peer_id)
if card:
return card
# Honcho self-hosted v3 stores the peer card on the peer itself
# (GET /peers/{id}/card). The observer-target slot used above is
# only populated when writes also go through that path. Fall back
# to the target peer's own card so honcho_profile works regardless
# of which write path populated it.
if target_peer_id:
return self._fetch_peer_card(target_peer_id)
return []
except Exception as e:
logger.debug("Failed to fetch peer card from Honcho: %s", e)
return []
Expand Down Expand Up @@ -1164,13 +1174,22 @@ def set_peer_card(self, session_key: str, card: list[str], peer: str = "user") -
if not session:
return None
try:
peer_id = self._resolve_peer_id(session, peer)
if peer_id is None:
observer_peer_id, target_peer_id = self._resolve_observer_target(session, peer)
if observer_peer_id is None:
logger.warning("Could not resolve peer '%s' for set_peer_card in session '%s'", peer, session_key)
return None
peer_obj = self._get_or_create_peer(peer_id)
result = peer_obj.set_card(card)
logger.info("Updated peer card for %s (%d facts)", peer_id, len(card))
peer_obj = self._get_or_create_peer(observer_peer_id)
result = (
peer_obj.set_card(card, target=target_peer_id)
if target_peer_id is not None
else peer_obj.set_card(card)
)
logger.info(
"Updated peer card observer=%s target=%s (%d facts)",
observer_peer_id,
target_peer_id or observer_peer_id,
len(card),
)
return result
except Exception as e:
logger.error("Failed to set peer card: %s", e)
Expand Down
33 changes: 33 additions & 0 deletions tests/honcho_plugin/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,39 @@ def test_get_peer_card_uses_direct_peer_lookup(self):
assert mgr.get_peer_card(session.key) == ["Name: Robert"]
assistant_peer.get_card.assert_called_once_with(target=session.user_peer_id)

def test_get_peer_card_falls_back_to_target_peer_own_card(self):
# When the observer-target card slot is empty (returns None/[]), fall
# back to the target peer's own card. Self-hosted Honcho v3 stores the
# peer card on the peer itself; the observer-target slot is only
# populated when writes also go through that path.
mgr, session = self._make_cached_manager()
assistant_peer = MagicMock()
assistant_peer.get_card.return_value = None # observer-target slot empty
user_peer = MagicMock()
user_peer.get_card.return_value = ["Prefers: dark mode"]

def _peer(peer_id: str) -> MagicMock:
return assistant_peer if peer_id == session.assistant_peer_id else user_peer

mgr._get_or_create_peer = MagicMock(side_effect=_peer)

assert mgr.get_peer_card(session.key) == ["Prefers: dark mode"]
assistant_peer.get_card.assert_called_once_with(target=session.user_peer_id)
user_peer.get_card.assert_called_once_with()

def test_set_peer_card_uses_observer_target_in_ai_observe_others_mode(self):
# Writes must go to the same observer-target slot that reads check,
# so that a subsequent honcho_profile read returns what was written.
mgr, session = self._make_cached_manager()
assistant_peer = MagicMock()
assistant_peer.set_card.return_value = ["Role: user"]
mgr._get_or_create_peer = MagicMock(return_value=assistant_peer)

result = mgr.set_peer_card(session.key, ["Role: user"])

assert result == ["Role: user"]
assistant_peer.set_card.assert_called_once_with(["Role: user"], target=session.user_peer_id)

def test_search_context_uses_assistant_perspective_with_target(self):
mgr, session = self._make_cached_manager()
assistant_peer = MagicMock()
Expand Down