feat: integrate Honcho with USER.md memory system

When Honcho is active:
- System prompt uses Honcho prefetch instead of USER.md
- memory tool target=user add routes to Honcho
- MEMORY.md untouched in all cases

When disabled, everything works as before.

Also wires up contextTokens config to cap prefetch size.
This commit is contained in:
Erosika 2026-02-26 18:02:24 -05:00
parent ab4bbf2fb2
commit 1fd0fcddb2
2 changed files with 54 additions and 17 deletions

View file

@ -42,6 +42,8 @@ class HonchoClientConfig:
# Toggles # Toggles
enabled: bool = False enabled: bool = False
save_messages: bool = True save_messages: bool = True
# Prefetch budget
context_tokens: int | None = None
# Session resolution # Session resolution
session_strategy: str = "per-directory" session_strategy: str = "per-directory"
session_peer_prefix: bool = False session_peer_prefix: bool = False
@ -105,6 +107,7 @@ class HonchoClientConfig:
linked_hosts=linked_hosts, linked_hosts=linked_hosts,
enabled=raw.get("enabled", False), enabled=raw.get("enabled", False),
save_messages=raw.get("saveMessages", True), save_messages=raw.get("saveMessages", True),
context_tokens=raw.get("contextTokens") or host_block.get("contextTokens"),
session_strategy=raw.get("sessionStrategy", "per-directory"), session_strategy=raw.get("sessionStrategy", "per-directory"),
session_peer_prefix=raw.get("sessionPeerPrefix", False), session_peer_prefix=raw.get("sessionPeerPrefix", False),
sessions=raw.get("sessions", {}), sessions=raw.get("sessions", {}),

View file

@ -435,6 +435,7 @@ class AIAgent:
self._honcho = HonchoSessionManager( self._honcho = HonchoSessionManager(
honcho=client, honcho=client,
config=hcfg, config=hcfg,
context_tokens=hcfg.context_tokens,
) )
# Resolve session key: explicit arg > global sessions map > fallback # Resolve session key: explicit arg > global sessions map > fallback
if not self._honcho_session_key: if not self._honcho_session_key:
@ -1126,6 +1127,27 @@ class AIAgent:
logger.debug("Honcho prefetch failed (non-fatal): %s", e) logger.debug("Honcho prefetch failed (non-fatal): %s", e)
return "" return ""
def _honcho_save_user_observation(self, content: str) -> str:
"""Route a memory tool target=user add to Honcho.
Sends the content as a user peer message so Honcho's reasoning
model can incorporate it into the user representation.
"""
if not content or not content.strip():
return json.dumps({"success": False, "error": "Content cannot be empty."})
try:
session = self._honcho.get_or_create(self._honcho_session_key)
session.add_message("user", f"[observation] {content.strip()}")
self._honcho.save(session)
return json.dumps({
"success": True,
"target": "user",
"message": "Saved to Honcho user model.",
})
except Exception as e:
logger.debug("Honcho user observation failed: %s", e)
return json.dumps({"success": False, "error": f"Honcho save failed: {e}"})
def _honcho_sync(self, user_content: str, assistant_content: str) -> None: def _honcho_sync(self, user_content: str, assistant_content: str) -> None:
"""Sync the user/assistant message pair to Honcho.""" """Sync the user/assistant message pair to Honcho."""
if not self._honcho or not self._honcho_session_key: if not self._honcho or not self._honcho_session_key:
@ -1177,7 +1199,9 @@ class AIAgent:
mem_block = self._memory_store.format_for_system_prompt("memory") mem_block = self._memory_store.format_for_system_prompt("memory")
if mem_block: if mem_block:
prompt_parts.append(mem_block) prompt_parts.append(mem_block)
if self._user_profile_enabled: # When Honcho is active, it handles the user profile via prefetch.
# USER.md is skipped to avoid duplicate/conflicting user context.
if self._user_profile_enabled and not self._honcho:
user_block = self._memory_store.format_for_system_prompt("user") user_block = self._memory_store.format_for_system_prompt("user")
if user_block: if user_block:
prompt_parts.append(user_block) prompt_parts.append(user_block)
@ -1418,14 +1442,18 @@ class AIAgent:
if tc.function.name == "memory": if tc.function.name == "memory":
try: try:
args = json.loads(tc.function.arguments) args = json.loads(tc.function.arguments)
from tools.memory_tool import memory_tool as _memory_tool flush_target = args.get("target", "memory")
result = _memory_tool( if self._honcho and flush_target == "user" and args.get("action") == "add":
action=args.get("action"), result = self._honcho_save_user_observation(args.get("content", ""))
target=args.get("target", "memory"), else:
content=args.get("content"), from tools.memory_tool import memory_tool as _memory_tool
old_text=args.get("old_text"), result = _memory_tool(
store=self._memory_store, action=args.get("action"),
) target=flush_target,
content=args.get("content"),
old_text=args.get("old_text"),
store=self._memory_store,
)
if not self.quiet_mode: if not self.quiet_mode:
print(f" 🧠 Memory flush: saved to {args.get('target', 'memory')}") print(f" 🧠 Memory flush: saved to {args.get('target', 'memory')}")
except Exception as e: except Exception as e:
@ -1545,14 +1573,20 @@ class AIAgent:
if self.quiet_mode: if self.quiet_mode:
print(f" {_get_cute_tool_message_impl('session_search', function_args, tool_duration, result=function_result)}") print(f" {_get_cute_tool_message_impl('session_search', function_args, tool_duration, result=function_result)}")
elif function_name == "memory": elif function_name == "memory":
from tools.memory_tool import memory_tool as _memory_tool target = function_args.get("target", "memory")
function_result = _memory_tool( # When Honcho is active, route user profile writes to Honcho
action=function_args.get("action"), if self._honcho and target == "user" and function_args.get("action") == "add":
target=function_args.get("target", "memory"), content = function_args.get("content", "")
content=function_args.get("content"), function_result = self._honcho_save_user_observation(content)
old_text=function_args.get("old_text"), else:
store=self._memory_store, from tools.memory_tool import memory_tool as _memory_tool
) function_result = _memory_tool(
action=function_args.get("action"),
target=target,
content=function_args.get("content"),
old_text=function_args.get("old_text"),
store=self._memory_store,
)
tool_duration = time.time() - tool_start_time tool_duration = time.time() - tool_start_time
if self.quiet_mode: if self.quiet_mode:
print(f" {_get_cute_tool_message_impl('memory', function_args, tool_duration, result=function_result)}") print(f" {_get_cute_tool_message_impl('memory', function_args, tool_duration, result=function_result)}")