Hermes Agent

Hermes finds MCP results bypassing its compression budget

A Hermes report says MCP tool results skip per-tool summarizers, size caps and cross-turn deduplication. Related fixes reap orphaned MCP watchdog children and rescan skill commands when profiles change, exposing three ways dynamic runtime state can outlive its owner.

← Back to homeOriginal source ↗

NousResearch/hermes-agent issue #88042 is the inspected primary source: “MCP tool results bypass compression's per-tool summarizers — no size cap, no cross-turn dedup.” The related records below were inspected as supporting context rather than independent confirmation.

The facts

- NousResearch/hermes-agent issue #88042 was created 2026-08-17T01:03:45Z and was open at inspection; its title is “MCP tool results bypass compression's per-tool summarizers — no size cap, no cross-turn dedup.” - NousResearch/hermes-agent PR #88041 was created 2026-08-17T01:00:48Z and was open at inspection; its title is “fix(gateway): reap orphaned MCP watchdog children on manual restart.” - NousResearch/hermes-agent PR #88043 was created 2026-08-17T01:04:07Z and was open at inspection; its title is “fix(skills): rescan skill commands cache when active profile changes.” - The repository reported 231519 stars, 32472 open issues, default branch main, and last push 2026-08-17T01:07:24Z. - The latest tagged-release baseline checked was v2026.8.16 published 2026-08-16T17:33:22Z.

What changed

NousResearch/hermes-agent issue #88042 reports: Problem context_compressor.py's tool-result summarizer (_summarize_tool_result_unguarded) has explicit, informative branches for built-in tools (terminal, read_file, search_files, skill_view, etc.) but no branch for any mcp__*-prefixed tool. Every MCP tool call falls through to the generic fallback: This discards the entire result body the moment the message ages out of the protected tail (protect_last_n), leaving only a byte count. For MCP servers that return large, uncapped payloads (a document store, a vault reader, a search index — anything that returns full file/document content rather than a snippet), this creates a duplicate-fetch loop: 1. A turn calls the MCP tool for several documents at once (nothing stops multiple large results landing in a single turn). 2. Total tokens exceed threshold_tokens, compression runs, and — since MCP tools have no dedicated summarizer — the results collapse to one-line stubs with zero retrievable content. 3. The model still needs that content for the task at hand and re-issues the identical tool call, paying the full token cost again. 4. Repeated across a session, this trips compression multiple times in quick succession and can end up forcing a large rolling-summary pass that loses more than it needs to. Evidence (real session) Observed with a document-vault MCP server (Obsidian's Local REST API via an MCP bridge), on qwen-27b,

NousResearch/hermes-agent PR #88041 reports: Fixes #87906 Bug hermes gateway restart on hosts without a registered systemd/launchd service falls back to stop_profile_gateway() (hermes_cli/gateway.py) followed by run_gateway(). That stop path only sent SIGTERM to the recorded gateway PID and waited for it to exit — it never looked at the old gateway's children. gateway/run.py's start_gateway(..., replace=True) codepath already has to deal with exactly this: on POSIX, once a parent process exits its children are reparented (to init) and can no longer be discovered by a parent walk, so any snapshot has to happen *before* the kill signal. That path snapshots descendants via _snapshot_gateway_children() first, then reaps survivors via reap_gateway_children() once the old PID is confirmed dead. stop_profile_gateway() was missing both steps. Concretely: a Hermes gateway with an mcp_servers entry (stdio transport, e.g. arr-mcp) spawns a watchdog (mcp_stdio_watchdog.py) + MCP server pair as children. gateway restart killed the parent gateway PID but left that pair running as orphans; the new gateway then spawned its own pair, and the two MCP server instances competed on stdio, producing intermittent "unreachable" errors — exactly the repro in 87906. Fix stop_profile_gateway() now mirrors the --replace pattern: snapshot the gateway's live children before sending SIGTERM, then reap any that are still alive (SIGTERM, bounded wait, SIGKILL for survivors) once the PID is

NousResearch/hermes-agent PR #88043 reports: What does this PR do? get_skill_commands() (agent/skill_commands.py) caches the scanned skill slash-command map in a process-global dict, and only rescans when the platform* scope changes (_resolve_skill_commands_platform(), added for 14536). It never checked whether the active **profile** changed. Hermes Desktop can run multiple profiles in one process, each with its own HERMES_HOME (via set_hermes_home_override) and its own skills.external_dirs. Switching profiles mid-session changes HERMES_HOME but not the platform, so get_skill_commands() kept serving the previous profile's skill list. That cache-primitive fix alone isn't enough for slash.exec (tui_gateway/methods_tools.py), the caller named in #88023 as the one that falls through to the slash_worker "dead path" (the worker parks the expanded skill body in a queue nobody reads and Desktop shows Loading skill with no agent turn). slash.exec is a _LONG_HANDLER — dispatch() schedules it on the pool with a copied context (server.py's dispatch()), and neither stdio (entry.py) nor WS (ws.py) binds _HERMES_HOME_OVERRIDE around that call. So even with the cache keyed on the active home, get_skill_commands() at that call site always resolved against the process-level HERMES_HOME — never the requesting session's profile — and a profile-B-only skill was still invisible there. This PR now also binds the session's own profile_home around the get_skill_commands() check inside slash.exec, mirroring the same bind/reset-in-finally pattern used at every

Why it matters

MCP output is part of the context budget whether or not it arrived through a familiar built-in tool. Unbounded results can turn one integration response into recurring latency and cost. 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-17. The tagged-release baseline was v2026.8.16 published 2026-08-16T17:33:22Z. 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/issues/88042. Supporting records are https://github.com/NousResearch/hermes-agent/pull/88041 and https://github.com/NousResearch/hermes-agent/pull/88043. 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

Route MCP results through the same bounded summarization contract as built-in tools, deduplicate stable payloads across turns, and record watchdog ownership in shutdown receipts. 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.