mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
feat(honcho): context injection overhaul, 5-tool surface, cost safety, session isolation (#10619)
Salvaged from PR #9884 by erosika. Cherry-picked plugin changes onto current main with minimal core modifications. Plugin changes (plugins/memory/honcho/): - New honcho_reasoning tool (5th tool, splits LLM calls from honcho_context) - Two-layer context injection: base context (summary + representation + card) on contextCadence, dialectic supplement on dialecticCadence - Multi-pass dialectic depth (1-3 passes) with early bail-out on strong signal - Cold/warm prompt selection based on session state - dialecticCadence defaults to 3 (was 1) — ~66% fewer Honcho LLM calls - Session summary injection for conversational continuity - Bidirectional peer targeting on all 5 tools - Correctness fixes: peer param fallback, None guard on set_peer_card, schema validation, signal_sufficient anchored regex, mid->medium level fix Core changes (~20 lines across 3 files): - agent/memory_manager.py: Enhanced sanitize_context() to strip full <memory-context> blocks and system notes (prevents leak from saveMessages) - run_agent.py: gateway_session_key param for stable per-chat Honcho sessions, on_turn_start() call before prefetch_all() for cadence tracking, sanitize_context() on user messages to strip leaked memory blocks - gateway/run.py: skip_memory=True on 2 temp agents (prevents orphan sessions), gateway_session_key threading to main agent Tests: 509 passed (3 skipped — honcho SDK not installed locally) Docs: Updated honcho.md, memory-providers.md, tools-reference.md, SKILL.md Co-authored-by: erosika <erosika@users.noreply.github.com>
This commit is contained in:
parent
00ff9a26cd
commit
cc6e8941db
17 changed files with 2632 additions and 396 deletions
|
|
@ -939,3 +939,74 @@ class TestOnMemoryWriteBridge:
|
|||
mgr.on_memory_write("add", "user", "test")
|
||||
# Good provider still received the call despite bad provider crashing
|
||||
assert good.memory_writes == [("add", "user", "test")]
|
||||
|
||||
|
||||
class TestHonchoCadenceTracking:
|
||||
"""Verify Honcho provider cadence gating depends on on_turn_start().
|
||||
|
||||
Bug: _turn_count was never updated because on_turn_start() was not called
|
||||
from run_conversation(). This meant cadence checks always passed (every
|
||||
turn fired both context refresh and dialectic). Fixed by calling
|
||||
on_turn_start(self._user_turn_count, msg) before prefetch_all().
|
||||
"""
|
||||
|
||||
def test_turn_count_updates_on_turn_start(self):
|
||||
"""on_turn_start sets _turn_count, enabling cadence math."""
|
||||
from plugins.memory.honcho import HonchoMemoryProvider
|
||||
p = HonchoMemoryProvider()
|
||||
assert p._turn_count == 0
|
||||
p.on_turn_start(1, "hello")
|
||||
assert p._turn_count == 1
|
||||
p.on_turn_start(5, "world")
|
||||
assert p._turn_count == 5
|
||||
|
||||
def test_queue_prefetch_respects_dialectic_cadence(self):
|
||||
"""With dialecticCadence=3, dialectic should skip turns 2 and 3."""
|
||||
from plugins.memory.honcho import HonchoMemoryProvider
|
||||
p = HonchoMemoryProvider()
|
||||
p._dialectic_cadence = 3
|
||||
p._recall_mode = "context"
|
||||
p._session_key = "test-session"
|
||||
# Simulate a manager that records prefetch calls
|
||||
class FakeManager:
|
||||
def prefetch_context(self, key, query=None):
|
||||
pass
|
||||
def prefetch_dialectic(self, key, query):
|
||||
pass
|
||||
|
||||
p._manager = FakeManager()
|
||||
|
||||
# Simulate turn 1: last_dialectic_turn = -999, so (1 - (-999)) >= 3 -> fires
|
||||
p.on_turn_start(1, "turn 1")
|
||||
p._last_dialectic_turn = 1 # simulate it fired
|
||||
p._last_context_turn = 1
|
||||
|
||||
# Simulate turn 2: (2 - 1) = 1 < 3 -> should NOT fire dialectic
|
||||
p.on_turn_start(2, "turn 2")
|
||||
assert (p._turn_count - p._last_dialectic_turn) < p._dialectic_cadence
|
||||
|
||||
# Simulate turn 3: (3 - 1) = 2 < 3 -> should NOT fire dialectic
|
||||
p.on_turn_start(3, "turn 3")
|
||||
assert (p._turn_count - p._last_dialectic_turn) < p._dialectic_cadence
|
||||
|
||||
# Simulate turn 4: (4 - 1) = 3 >= 3 -> should fire dialectic
|
||||
p.on_turn_start(4, "turn 4")
|
||||
assert (p._turn_count - p._last_dialectic_turn) >= p._dialectic_cadence
|
||||
|
||||
def test_injection_frequency_first_turn_with_1indexed(self):
|
||||
"""injection_frequency='first-turn' must inject on turn 1 (1-indexed)."""
|
||||
from plugins.memory.honcho import HonchoMemoryProvider
|
||||
p = HonchoMemoryProvider()
|
||||
p._injection_frequency = "first-turn"
|
||||
|
||||
# Turn 1 should inject (not skip)
|
||||
p.on_turn_start(1, "first message")
|
||||
assert p._turn_count == 1
|
||||
# The guard is `_turn_count > 1`, so turn 1 passes through
|
||||
should_skip = p._injection_frequency == "first-turn" and p._turn_count > 1
|
||||
assert not should_skip, "First turn (turn 1) should NOT be skipped"
|
||||
|
||||
# Turn 2 should skip
|
||||
p.on_turn_start(2, "second message")
|
||||
should_skip = p._injection_frequency == "first-turn" and p._turn_count > 1
|
||||
assert should_skip, "Second turn (turn 2) SHOULD be skipped"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue