From 792be0e8e3fc2e5a2862fe48f67a0a6ca49a8b2a Mon Sep 17 00:00:00 2001 From: Erosika Date: Mon, 9 Mar 2026 17:55:31 -0400 Subject: [PATCH] feat(honcho): add honcho_conclude tool for writing facts back to memory New tool lets Hermes persist conclusions about the user (preferences, corrections, project context) directly to Honcho via the conclusions API. Feeds into the user's peer card and representation. --- honcho_integration/session.py | 35 ++++++++++++++++++++++++ run_agent.py | 16 ++++++----- tools/honcho_tools.py | 51 +++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/honcho_integration/session.py b/honcho_integration/session.py index 4a49ca430..384d42f57 100644 --- a/honcho_integration/session.py +++ b/honcho_integration/session.py @@ -805,6 +805,41 @@ class HonchoSessionManager: logger.debug("Honcho search_context failed: %s", e) return "" + def create_conclusion(self, session_key: str, content: str) -> bool: + """Write a conclusion about the user back to Honcho. + + Conclusions are facts the AI peer observes about the user — + preferences, corrections, clarifications, project context. + They feed into the user's peer card and representation. + + Args: + session_key: Session to associate the conclusion with. + content: The conclusion text (e.g. "User prefers dark mode"). + + Returns: + True on success, False on failure. + """ + if not content or not content.strip(): + return False + + session = self._cache.get(session_key) + if not session: + logger.warning("No session cached for '%s', skipping conclusion", session_key) + return False + + assistant_peer = self._get_or_create_peer(session.assistant_peer_id) + try: + conclusions_scope = assistant_peer.conclusions_of(session.user_peer_id) + conclusions_scope.create([{ + "content": content.strip(), + "session_id": session.honcho_session_id, + }]) + logger.info("Created conclusion for %s: %s", session_key, content[:80]) + return True + except Exception as e: + logger.error("Failed to create conclusion: %s", e) + return False + def seed_ai_identity(self, session_key: str, content: str, source: str = "manual") -> bool: """ Seed the AI peer's Honcho representation from text content. diff --git a/run_agent.py b/run_agent.py index 0984f703d..fb20f0671 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1595,22 +1595,24 @@ class AIAgent: ) elif recall_mode == "tools": honcho_block += ( - "Memory tools (most capable first; use cheaper tools when sufficient):\n" - " query_user_context — dialectic Q&A, LLM-synthesized answer\n" + "Memory tools:\n" + " query_user_context — ask Honcho a question, LLM-synthesized answer\n" " honcho_search — semantic search, raw excerpts, no LLM\n" - " honcho_profile — peer card, key facts, no LLM\n" + " honcho_profile — user's peer card, key facts, no LLM\n" + " honcho_conclude — write a fact about the user to memory\n" ) - else: # auto + else: # hybrid honcho_block += ( "Honcho context (user representation, peer card, and recent session summary) " "is pre-loaded into this system prompt below. Use it to answer continuity " "questions ('where were we?', 'what were we working on?') WITHOUT calling " "any tools. Only call memory tools when you need information beyond what is " "already present in the Honcho Memory section.\n" - "Memory tools (most capable first; use cheaper tools when sufficient):\n" - " query_user_context — dialectic Q&A, LLM-synthesized answer\n" + "Memory tools:\n" + " query_user_context — ask Honcho a question, LLM-synthesized answer\n" " honcho_search — semantic search, raw excerpts, no LLM\n" - " honcho_profile — peer card, key facts, no LLM\n" + " honcho_profile — user's peer card, key facts, no LLM\n" + " honcho_conclude — write a fact about the user to memory\n" ) honcho_block += ( "Management commands (refer users here instead of explaining manually):\n" diff --git a/tools/honcho_tools.py b/tools/honcho_tools.py index 62987dc60..311b03745 100644 --- a/tools/honcho_tools.py +++ b/tools/honcho_tools.py @@ -164,6 +164,49 @@ def _handle_query_user_context(args: dict, **kw) -> str: return json.dumps({"error": f"Failed to query user context: {e}"}) +# ── honcho_conclude ── + +_CONCLUDE_SCHEMA = { + "name": "honcho_conclude", + "description": ( + "Write a conclusion about the user back to Honcho's memory. " + "Conclusions are persistent facts that build the user's profile — " + "preferences, corrections, clarifications, project context, or anything " + "the user tells you that should be remembered across sessions. " + "Use this when the user explicitly states a preference, corrects you, " + "or shares something they want remembered. " + "Examples: 'User prefers dark mode', 'User's project uses Python 3.11', " + "'User corrected: their name is spelled Eri not Eric'." + ), + "parameters": { + "type": "object", + "properties": { + "conclusion": { + "type": "string", + "description": "A factual statement about the user to persist in memory.", + } + }, + "required": ["conclusion"], + }, +} + + +def _handle_honcho_conclude(args: dict, **kw) -> str: + conclusion = args.get("conclusion", "") + if not conclusion: + return json.dumps({"error": "Missing required parameter: conclusion"}) + if not _session_manager or not _session_key: + return json.dumps({"error": "Honcho is not active for this session."}) + try: + ok = _session_manager.create_conclusion(_session_key, conclusion) + if ok: + return json.dumps({"result": f"Conclusion saved: {conclusion}"}) + return json.dumps({"error": "Failed to save conclusion."}) + except Exception as e: + logger.error("Error creating Honcho conclusion: %s", e) + return json.dumps({"error": f"Failed to save conclusion: {e}"}) + + # ── Registration ── from tools.registry import registry @@ -191,3 +234,11 @@ registry.register( handler=_handle_query_user_context, check_fn=_check_honcho_available, ) + +registry.register( + name="honcho_conclude", + toolset="honcho", + schema=_CONCLUDE_SCHEMA, + handler=_handle_honcho_conclude, + check_fn=_check_honcho_available, +)