Skip to content

feat(memory): notify memory providers on in-chat /resume and /branch session switches #6672

@nicoloboschi

Description

@nicoloboschi

Context

#6654 fixes the Hindsight resume overwrite bug for the CLI path (hermes --resume <id> / hermes -c) by scoping document_id to the process lifecycle. Each new process gets a fresh document_id, so resuming via the terminal works correctly.

However, the fix does not cover the in-chat slash commands:

  • /resume <id> — swaps session in-process
  • /branch [name] — forks the current session into a new one

Both of these reassign self.session_id and self.agent.session_id mid-process without notifying memory providers. The Hindsight provider (and any other provider that caches per-session state in initialize()) keeps its old _session_id, accumulated state, and document tracking — so subsequent turns get written into the wrong session's memory document.

Why the plugin can't fix this on its own

run_agent.py calls self._memory_manager.sync_all(user, response) without passing session_id:

# run_agent.py
self._memory_manager.sync_all(original_user_message, final_response)
self._memory_manager.queue_prefetch_all(original_user_message)

The memory provider has no way to detect the session switch — it only sees what was set during initialize(). The agent's session_id attribute is updated, but the provider never gets the new value.

Proposed Solutions

Any one of the following would unblock plugins:

Option A: Pass session_id through the manager methods

run_agent.py already has self.session_id available. Pass it explicitly:

self._memory_manager.sync_all(
    original_user_message, final_response,
    session_id=self.session_id,
)
self._memory_manager.queue_prefetch_all(
    original_user_message,
    session_id=self.session_id,
)

MemoryManager.sync_all and queue_prefetch_all already accept session_id as a kwarg — they just need to be called with it. Providers can then detect a mismatch with their cached _session_id and reset internal state.

Pros: smallest change, no ABC modification, backward compatible (existing providers ignore the kwarg).

Option B: Add an on_session_switch hook to MemoryProvider

# agent/memory_provider.py
def on_session_switch(self, new_session_id: str, **kwargs) -> None:
    \"\"\"Called when the agent switches to a different session mid-process.

    Triggered by /resume and /branch slash commands. Providers should
    reset any per-session in-memory state (caches, counters, etc.).
    Default is no-op.
    \"\"\"
\`\`\`

Then `cli.py:_handle_resume_command` and `_handle_branch_command` would call:

```python
if self.agent and self.agent._memory_manager:
    for p in self.agent._memory_manager.providers:
        try:
            p.on_session_switch(new_session_id, parent_session_id=old_session_id)
        except Exception as e:
            logger.warning(\"on_session_switch failed: %s\", e)

Pros: explicit signal, providers don't have to detect drift on every turn.
Cons: new ABC method.

Option C: Re-call initialize() on session switch

Simplest signal — just have _handle_resume_command call provider.initialize(new_session_id, ...) again. Providers already need initialize() to be idempotent for cron flushes, so this should be safe in most cases.

Pros: zero new API surface.
Cons: providers may set up resources in initialize() they don't want to re-create (clients, threads, daemon connections). Hindsight specifically guards against re-creating its _client, but other providers may not.

Affected providers

Any provider that caches per-session state in initialize() is potentially affected. Confirmed:

  • Hindsight_session_id, _document_id, _session_turns, _turn_counter

Likely also affected (would need verification):

  • Any provider that uses session_id for scoping (per-user memory, per-session bank, etc.)

My preference

Option A seems cleanest: it's the smallest possible change, doesn't extend the ABC, is backward compatible (the kwarg is already accepted by MemoryManager.sync_all), and gives plugins enough information to do the right thing on every turn.

I'm happy to implement whichever approach the maintainers prefer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/agentCore agent loop, run_agent.py, prompt buildercomp/pluginsPlugin system and bundled pluginstool/memoryMemory tool and memory providerstype/featureNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions