Merge PR #736: feat(honcho): async writes, memory modes, session title integration, setup CLI
Authored by erosika. Builds on #38 and #243. Adds async write support, configurable memory modes, context prefetch pipeline, 4 new Honcho tools (honcho_context, honcho_profile, honcho_search, honcho_conclude), full 'hermes honcho' CLI, session strategies, AI peer identity, recallMode A/B, gateway lifecycle management, and comprehensive docs. Cherry-picks fixes from PRs #831/#832 (adavyas). Co-authored-by: erosika <erosika@users.noreply.github.com> Co-authored-by: adavyas <adavyas@users.noreply.github.com>
This commit is contained in:
commit
475dd58a8e
26 changed files with 4688 additions and 355 deletions
|
|
@ -1,8 +1,16 @@
|
|||
"""Honcho tool for querying user context via dialectic reasoning.
|
||||
"""Honcho tools for user context retrieval.
|
||||
|
||||
Registers ``query_user_context`` -- an LLM-callable tool that asks Honcho
|
||||
about the current user's history, preferences, goals, and communication
|
||||
style. The session key is injected at runtime by the agent loop via
|
||||
Registers three complementary tools, ordered by capability:
|
||||
|
||||
honcho_context — dialectic Q&A (LLM-powered, direct answers)
|
||||
honcho_search — semantic search (fast, no LLM, raw excerpts)
|
||||
honcho_profile — peer card (fast, no LLM, structured facts)
|
||||
|
||||
Use honcho_context when you need Honcho to synthesize an answer.
|
||||
Use honcho_search or honcho_profile when you want raw data to reason
|
||||
over yourself.
|
||||
|
||||
The session key is injected at runtime by the agent loop via
|
||||
``set_session_context()``.
|
||||
"""
|
||||
|
||||
|
|
@ -34,54 +42,6 @@ def clear_session_context() -> None:
|
|||
_session_key = None
|
||||
|
||||
|
||||
# ── Tool schema ──
|
||||
|
||||
HONCHO_TOOL_SCHEMA = {
|
||||
"name": "query_user_context",
|
||||
"description": (
|
||||
"Query Honcho to retrieve relevant context about the user based on their "
|
||||
"history and preferences. Use this when you need to understand the user's "
|
||||
"background, preferences, past interactions, or goals. This helps you "
|
||||
"personalize your responses and provide more relevant assistance."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"A natural language question about the user. Examples: "
|
||||
"'What are this user's main goals?', "
|
||||
"'What communication style does this user prefer?', "
|
||||
"'What topics has this user discussed recently?', "
|
||||
"'What is this user's technical expertise level?'"
|
||||
),
|
||||
}
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ── Tool handler ──
|
||||
|
||||
def _handle_query_user_context(args: dict, **kw) -> str:
|
||||
"""Execute the Honcho context query."""
|
||||
query = args.get("query", "")
|
||||
if not query:
|
||||
return json.dumps({"error": "Missing required parameter: query"})
|
||||
|
||||
if not _session_manager or not _session_key:
|
||||
return json.dumps({"error": "Honcho is not active for this session."})
|
||||
|
||||
try:
|
||||
result = _session_manager.get_user_context(_session_key, query)
|
||||
return json.dumps({"result": result})
|
||||
except Exception as e:
|
||||
logger.error("Error querying Honcho user context: %s", e)
|
||||
return json.dumps({"error": f"Failed to query user context: {e}"})
|
||||
|
||||
|
||||
# ── Availability check ──
|
||||
|
||||
def _check_honcho_available() -> bool:
|
||||
|
|
@ -89,14 +49,201 @@ def _check_honcho_available() -> bool:
|
|||
return _session_manager is not None and _session_key is not None
|
||||
|
||||
|
||||
# ── honcho_profile ──
|
||||
|
||||
_PROFILE_SCHEMA = {
|
||||
"name": "honcho_profile",
|
||||
"description": (
|
||||
"Retrieve the user's peer card from Honcho — a curated list of key facts "
|
||||
"about them (name, role, preferences, communication style, patterns). "
|
||||
"Fast, no LLM reasoning, minimal cost. "
|
||||
"Use this at conversation start or when you need a quick factual snapshot. "
|
||||
"Use honcho_context instead when you need Honcho to synthesize an answer."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _handle_honcho_profile(args: dict, **kw) -> str:
|
||||
if not _session_manager or not _session_key:
|
||||
return json.dumps({"error": "Honcho is not active for this session."})
|
||||
try:
|
||||
card = _session_manager.get_peer_card(_session_key)
|
||||
if not card:
|
||||
return json.dumps({"result": "No profile facts available yet. The user's profile builds over time through conversations."})
|
||||
return json.dumps({"result": card})
|
||||
except Exception as e:
|
||||
logger.error("Error fetching Honcho peer card: %s", e)
|
||||
return json.dumps({"error": f"Failed to fetch profile: {e}"})
|
||||
|
||||
|
||||
# ── honcho_search ──
|
||||
|
||||
_SEARCH_SCHEMA = {
|
||||
"name": "honcho_search",
|
||||
"description": (
|
||||
"Semantic search over Honcho's stored context about the user. "
|
||||
"Returns raw excerpts ranked by relevance to your query — no LLM synthesis. "
|
||||
"Cheaper and faster than honcho_context. "
|
||||
"Good when you want to find specific past facts and reason over them yourself. "
|
||||
"Use honcho_context when you need a direct synthesized answer."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "What to search for in Honcho's memory (e.g. 'programming languages', 'past projects', 'timezone').",
|
||||
},
|
||||
"max_tokens": {
|
||||
"type": "integer",
|
||||
"description": "Token budget for returned context (default 800, max 2000).",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _handle_honcho_search(args: dict, **kw) -> str:
|
||||
query = args.get("query", "")
|
||||
if not query:
|
||||
return json.dumps({"error": "Missing required parameter: query"})
|
||||
if not _session_manager or not _session_key:
|
||||
return json.dumps({"error": "Honcho is not active for this session."})
|
||||
max_tokens = min(int(args.get("max_tokens", 800)), 2000)
|
||||
try:
|
||||
result = _session_manager.search_context(_session_key, query, max_tokens=max_tokens)
|
||||
if not result:
|
||||
return json.dumps({"result": "No relevant context found."})
|
||||
return json.dumps({"result": result})
|
||||
except Exception as e:
|
||||
logger.error("Error searching Honcho context: %s", e)
|
||||
return json.dumps({"error": f"Failed to search context: {e}"})
|
||||
|
||||
|
||||
# ── honcho_context (dialectic — LLM-powered) ──
|
||||
|
||||
_QUERY_SCHEMA = {
|
||||
"name": "honcho_context",
|
||||
"description": (
|
||||
"Ask Honcho a natural language question and get a synthesized answer. "
|
||||
"Uses Honcho's LLM (dialectic reasoning) — higher cost than honcho_profile or honcho_search. "
|
||||
"Can query about any peer: the user (default), the AI assistant, or any named peer. "
|
||||
"Examples: 'What are the user's main goals?', 'What has hermes been working on?', "
|
||||
"'What is the user's technical expertise level?'"
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "A natural language question.",
|
||||
},
|
||||
"peer": {
|
||||
"type": "string",
|
||||
"description": "Which peer to query about: 'user' (default) or 'ai'. Omit for user.",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _handle_honcho_context(args: dict, **kw) -> str:
|
||||
query = args.get("query", "")
|
||||
if not query:
|
||||
return json.dumps({"error": "Missing required parameter: query"})
|
||||
if not _session_manager or not _session_key:
|
||||
return json.dumps({"error": "Honcho is not active for this session."})
|
||||
peer_target = args.get("peer", "user")
|
||||
try:
|
||||
result = _session_manager.dialectic_query(_session_key, query, peer=peer_target)
|
||||
return json.dumps({"result": result or "No result from Honcho."})
|
||||
except Exception as e:
|
||||
logger.error("Error querying Honcho context: %s", e)
|
||||
return json.dumps({"error": f"Failed to query 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
|
||||
|
||||
registry.register(
|
||||
name="query_user_context",
|
||||
name="honcho_profile",
|
||||
toolset="honcho",
|
||||
schema=HONCHO_TOOL_SCHEMA,
|
||||
handler=_handle_query_user_context,
|
||||
schema=_PROFILE_SCHEMA,
|
||||
handler=_handle_honcho_profile,
|
||||
check_fn=_check_honcho_available,
|
||||
)
|
||||
|
||||
registry.register(
|
||||
name="honcho_search",
|
||||
toolset="honcho",
|
||||
schema=_SEARCH_SCHEMA,
|
||||
handler=_handle_honcho_search,
|
||||
check_fn=_check_honcho_available,
|
||||
)
|
||||
|
||||
registry.register(
|
||||
name="honcho_context",
|
||||
toolset="honcho",
|
||||
schema=_QUERY_SCHEMA,
|
||||
handler=_handle_honcho_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,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue