Hermes Agent
Hermes makes routing identity survive a missing state database
Hermes proposes transcript recovery that keeps session routing identity when state.db is unavailable. Nearby work prevents MCP keepalives racing active calls and withholds sensitive context from memory prefetch providers that cannot be cancelled.
NousResearch/hermes-agent PR #88804 is the inspected primary source: “fix(gateway): persist session routing identity and recover transcripts when state.db is unavailable.” The related records below were inspected as supporting context rather than independent confirmation.
The facts
- NousResearch/hermes-agent PR #88804 was created 2026-08-18T00:59:15Z and was open at inspection; its title is “fix(gateway): persist session routing identity and recover transcripts when state.db is unavailable.” - NousResearch/hermes-agent PR #88802 was created 2026-08-18T00:57:01Z and was open at inspection; its title is “fix(mcp): keepalive prober must not race an in-flight tool call.” - NousResearch/hermes-agent PR #88796 was created 2026-08-18T00:52:54Z and was open at inspection; its title is “fix(security): withhold sensitive context from non-cancellable memory prefetch providers.” - The repository reported 232039 stars, 32747 open issues, default branch main, and last push 2026-08-18T00:53:58Z. - The latest tagged-release baseline checked was v2026.8.16.2 published 2026-08-17T18:43:27Z.
What changed
NousResearch/hermes-agent PR #88804 reports: Problem Live incident (2026-08-17): Telegram chats stopped remembering prior turns. User replies to an agent response and the agent lacks context. Root cause chain (verified against the running multiplexed gateway): 1. Default/global state.db was corrupt (file is not a database) at gateway startup → SessionStore._db = None (JSONL routing fallback) and GatewayRunner._session_db = None. 2. Under multiplexed profile routes (_profile_runtime_scope), the AIAgent lazily opens the **profile's** state.db via _get_session_db_for_recall() (observed open fds on profiles/orion/state.db). 3. AIAgent._ensure_db_session() created the row with user_id=None and NO session_key/chat_id/chat_type/thread_id/origin_json/display_name — the identity-less row observed in the DB (chat_id=NULL, session_key=NULL, 67 messages). 4. The gateway's record_gateway_session_peer self-heal never fired because SessionStore._db was None. 5. Next message: routing still resolved the same session (JSONL), but load_transcript() returned [] because _db was None — even though the transcript existed in the profile DB. → agent amnesia. Fix **run_agent.py**: _ensure_db_session now persists the agent's full gateway routing identity (session_key, chat_id, chat_type, thread_id, user_id, display_name, origin_json) at first row creation — so lazily-created rows are never identity-less, including multiplexed profile routes. COALESCE in _insert_session_row means this only fills NULLs and never overwrites the gateway's richer origin_json in the healthy path. **gateway/session.py**: SessionStore.load_transcript falls back to a scoped SessionDB() (resolved through the current HERMES_HOME override) when _db is None, recovering the
NousResearch/hermes-agent PR #88802 reports: What does this PR do? Fixes the root cause behind #88661: a long-running MCP tool call timing out is followed, with no operator action, by the *entire* toolset from that server vanishing from the live session with no automatic recovery (only a gateway restart brings it back). MCPServerTask._wait_for_lifecycle_event's keepalive loop probes the session (send_ping, falling back to list_tools) directly, without acquiring server._rpc_lock — the lock every other call that touches self.session (call_tool, list_resources, list_prompts, get_prompt, read_resource, _refresh_tools) serializes on. The codebase already has a matching precedent for this exact race: _stdio_recycle_reason() / _next_stdio_recycle_deadline() both skip their own maintenance action when self._rpc_lock.locked() (see tools/mcp_tool.py:2532 and:2549) — the keepalive prober was the one caller that didn't follow that rule. When a real tool call runs long (the reporter's timeout was 300s) and the remote server processes requests sequentially (true of many simple MCP servers, and plausible for the "verified healthy from outside" remote HTTP+OAuth servers in the report), the keepalive probe fired concurrently gets no answer until the in-flight call finishes. The probe has its own 30s budget (asyncio.wait_for(..., timeout=30.0) in _keepalive_probe), so it times out well before the tool call's own (longer) timeout. The keepalive-failure path then does: which tears down and rebuilds the transport — out from under the
NousResearch/hermes-agent PR #88796 reports: What changed and why MemoryManager prefetch runs each external provider's prefetch in a daemon thread and join(timeout)s it. On timeout the thread stays **detached and uncancellable** (daemon — cannot be aborted), while the sensitive query had **already been sent** to the external provider. On subsequent turns the same provider was called again with *new* sensitive context — widening the exposure each time (#84263). This PR marks a provider **non-cancellable** when its prefetch times out, and **withholds sensitive query context** from it on future turns (with a warning so the operator knows context is being withheld). A prefetch that completes within the timeout does not mark the provider, so normal providers are unaffected. How to test New regression test: a provider whose prefetch blocks forever → _prefetch_provider times out → provider added to _non_cancellable_providers → a second call returns "" without invoking the provider again (and logs the withholding warning). Platforms tested Windows 11 (git-bash), Python 3.11 — memory test suites green (8 passed across the async/sync + session-switch files). git diff --check clean. Why this matters to users When a memory provider (e.g. an external vector store or an LLM-backed memory backend) hangs on prefetch, the query — which can contain sensitive conversation context — was already sent to it
Why it matters
Fallback state paths are where ownership labels usually disappear. Preserving routing identity while refusing unsafe speculative context keeps recovery from widening authority. The operator test is whether the system remains bounded and its receipts still describe the action after failure, retry or restart.
Current
The primary record was open when captured on 2026-08-18. The tagged-release baseline was v2026.8.16.2 published 2026-08-17T18:43:27Z. Repository metadata, full source bodies, current pull requests and issues, releases, Google News, Hacker News, Lobsters, Metamesh and the rendered ClawCharts row were inspected. ClawCharts selected the subject; it did not prove the claim.
Evidence
The primary URL is https://github.com/NousResearch/hermes-agent/pull/88804. Supporting records are https://github.com/NousResearch/hermes-agent/pull/88802 and https://github.com/NousResearch/hermes-agent/pull/88796. Source bodies, timestamps and states are preserved in the daily evidence bundle. Test counts and reproductions remain attributed to their authors unless identified as independently rerun.
Source boundary
Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Search residue, package mirrors and historically published source spines were excluded.
Operator take
Treat transcript identity as durable state, serialize keepalive probes with tool calls, and never send sensitive context to work that cannot be cancelled. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently.
Caveat
Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.
Source inspected; source state, environment and release boundary remain explicit.