diff --git a/agent/agent_init.py b/agent/agent_init.py index bf90925a061e..80437cca27fe 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -1771,6 +1771,17 @@ def init_agent( # Memory provider plugin (external — one at a time, alongside built-in) # Reads memory.provider from config to select which plugin to activate. + # memory.recall_indicator gates the deterministic recall status line + # ("👁️ Hindsight — recalled 3 memories") emitted when external memory is + # auto-recalled before a turn. Default true preserves current behaviour; + # false keeps recall internal (context still injected, status suppressed). + agent._recall_indicator_enabled = True + try: + agent._recall_indicator_enabled = bool( + ((_agent_cfg or {}).get("memory") or {}).get("recall_indicator", True) + ) + except Exception: + agent._recall_indicator_enabled = True agent._memory_manager = None if not skip_memory: try: diff --git a/agent/turn_context.py b/agent/turn_context.py index a90bee9a1a11..3a12ebf01892 100644 --- a/agent/turn_context.py +++ b/agent/turn_context.py @@ -1287,11 +1287,16 @@ def build_turn_context( # Deterministic, model-independent recall indicator: when memory was # actually injected this turn, tell the user — don't rely on the model # to surface it. Rendered by Hermes (via _emit_status), so it always - # shows and can't be silently dropped by the model. + # shows and can't be silently dropped by the model. Gate: the + # memory.recall_indicator config flag (agent._recall_indicator_enabled, + # set at init, default true) lets users keep recall internal — memory + # still injects into context, but the status line is suppressed. if ext_prefetch_cache: try: _recall_indicator = agent._memory_manager.describe_recall() - if _recall_indicator: + if _recall_indicator and getattr( + agent, "_recall_indicator_enabled", True + ): agent._emit_status(_recall_indicator) except Exception: pass diff --git a/cli-config.yaml.example b/cli-config.yaml.example index cd2f40499d46..b0fe47eda0c1 100644 --- a/cli-config.yaml.example +++ b/cli-config.yaml.example @@ -827,6 +827,13 @@ memory: # For exit/reset, only fires if the session had at least this many user turns. flush_min_turns: 6 # Min user turns to trigger flush on exit/reset (0 = disabled) + # Deterministic recall indicator: when an external memory provider + # (memory.provider) auto-recalls memory before a turn, Hermes shows a status + # line (e.g. "👁️ Hindsight — recalled 3 memories") on the CLI and messaging + # platforms so you SEE memory was used. Set false to keep recall internal — + # memory still injects into context, but the status line is suppressed. + recall_indicator: true + # ============================================================================= # Session Reset Policy (Messaging Platforms) # ============================================================================= diff --git a/hermes_cli/config_defaults.py b/hermes_cli/config_defaults.py index c2813aecfd67..12ef72b3de8c 100644 --- a/hermes_cli/config_defaults.py +++ b/hermes_cli/config_defaults.py @@ -1778,6 +1778,12 @@ # "hindsight", "holographic", "retaindb", "byterover". # Only ONE external provider is allowed at a time. "provider": "", + # Deterministic recall indicator: when an external provider auto-recalls + # memory before a turn, Hermes shows a status line (e.g. + # "👁️ Hindsight — recalled 3 memories") so you SEE memory was used. + # Set false to keep recall internal: memory still injects into context, + # but the status line is suppressed on every surface (CLI + gateway). + "recall_indicator": True, }, # Subagent delegation — override the provider:model used by delegate_task diff --git a/tests/agent/test_turn_context.py b/tests/agent/test_turn_context.py index 9c087111ed04..7385e645cc7a 100644 --- a/tests/agent/test_turn_context.py +++ b/tests/agent/test_turn_context.py @@ -365,6 +365,27 @@ def test_recall_indicator_skipped_when_nothing_injected(): assert "👁️" not in str(call) +def test_recall_indicator_suppressed_when_config_disabled(): + """memory.recall_indicator: false keeps recall internal — no status line. + + Memory still injects into context (prefetch_all runs and returns content), + but the deterministic indicator must not be emitted on any surface. + """ + agent = _FakeAgent() + agent._emit_status = MagicMock() + agent._recall_indicator_enabled = False + mm = MagicMock() + mm.prefetch_all.return_value = "- recalled fact" + mm.describe_recall.return_value = "👁️ Hindsight — recalled 2 memories" + agent._memory_manager = mm + + _build(agent, user_message="what did we decide about the deploy pipeline?") + + mm.describe_recall.assert_called_once() + for call in agent._emit_status.call_args_list: + assert "👁️" not in str(call) + + def test_ensure_db_session_runs_after_system_prompt_restore(): """Regression for #45499.