fix(gateway): escalate the session-hygiene compaction cooldown on repeat failures - #79741
Merged
kshitijk4poor merged 1 commit intoAug 5, 2026
Conversation
kshitijk4poor
force-pushed
the
fix/79624-preflight-cooldown-respect
branch
from
August 5, 2026 23:12
9770c6f to
ea87d0e
Compare
…eat failures A gateway session whose summary model keeps timing out no longer retries compaction on the same fixed interval forever. The in-agent compressor already escalates repeat summary timeouts 60 -> 300 -> 900s (ContextCompressor.record_timeout_failure), but that ladder reads the in-memory _consecutive_timeout_failures counter and bind_session_state() zeroes it (context_compressor.py:1645). Session hygiene constructs a FRESH AIAgent for every run (gateway/run.py:16820) and re-binds state each time, so from the gateway that streak is structurally always 0 -- only the flat hygiene_failure_cooldown_seconds (300s) could ever be recorded. Issue NousResearch#79624 reported exactly that steady state: an oversized session (1053 messages, ~119.5k tokens) whose aux model always timed out, re-attempting compaction every 300s across five days until the reporter deleted the session by hand. Track the streak on PersistentState instead, which outlives the per-run agent and is not cleared by turn/boundary resets, so consecutive hygiene failures climb 300 -> 900 -> 2700s and then saturate. Both failure sites (progress timeout and aborted compression) feed it; a real compression resets it, so a session that recovers starts from the first rung again. The ladder multiplies the configured base, so operators who tuned hygiene_failure_cooldown_seconds keep their first rung. Per-session, so one wedged chat cannot penalize other conversations. Deliberately NOT changed, since each is a maintainer policy call rather than a defect (all three are written up on NousResearch#79624): - no durable failure-streak column, so escalation still resets on restart - the gateway 30s / in-agent 120s / aux-client 300s-floor timeout mismatch - no `hermes doctor` check or `hermes sessions list` marker for a session stuck in a compression-failure cooldown Note the reported exit(1) is NOT a crash: it is the deliberate _signal_initiated_shutdown path (gateway/run.py:26746-26751, NousResearch#5646) that lets systemd Restart=on-failure revive the gateway after a bare SIGTERM, and it fires on every `systemctl restart` independently of compaction. The compaction log lines appear after the shutdown line because the gateway-owned executor is torn down with shutdown(wait=False, cancel_futures=True) (run.py:21164), so an in-flight turn keeps logging during teardown. Full analysis on the issue. Post-review hardening (Phase 2c + /simplify-code found five real defects in the first cut): - the recovery gate hand-rolled `_new_tokens < _approx_tokens` when a canonical predicate already existed: `compression_made_progress` (agent/turn_context.py, NousResearch#39548). They disagree on 3 of 5 cases -- the hand-rolled form misses a row-count win when the summary keeps the token estimate flat, misses one where the summary is slightly MORE verbose (so a genuinely recovered session would keep escalating forever), and counts a sub-5% wobble as recovery. Now reuses the shared predicate, promoted from `_compression_made_progress` to a public name with the old private name kept as a back-compat alias so the existing importer (tests/agent/test_protected_tail_pressure_61932.py) and any patcher of that symbol keep working. - the reset was gated on "not aborted", but the degenerate "did not rotate or compact in place" branch (NousResearch#21301) is NOT aborted and yields zero reduction, so a session wedged there reset its streak every run and could never escalate -- silently defeating the fix. Now gated on real progress. - no absolute ceiling: base * 9 reaches 9h at an operator base of 3600s, indistinguishable from "compaction switched off". Added _HYGIENE_COOLDOWN_MAX_SECONDS = 3600, mirroring the in-file _RECONNECT_BACKOFF_CAP precedent. - the reset used the get-or-create accessor to write a 0 that was already 0, materialising a _sessions entry (never evicted). Now peeks. - the abort verdict was probed twice, leaving the reset/record mutual exclusion implicit; a future await between the probes would have broken it silently. Computed once into _hyg_aborted. Tests: 19 new in tests/gateway/test_hygiene_failure_cooldown_ladder.py -- ladder escalation, saturation, the absolute cap, per-session isolation, reset-on-recovery, custom/zero base, PersistentState scoping (a mutation moving the field to TurnState fails), degraded runners, the progress gate, the exact progress-predicate semantics the gate depends on, and end-to-end that the escalated value is what reaches the state DB. All 12 mutations caught, including ones that restore the flat cooldown (the original bug), ungate the reset, swap the canonical predicate back for the hand-rolled comparison, remove the cap, and share the streak globally; the harness hard-errors when a mutation cannot be applied, since a silently no-op mutation check is worse than none -- an earlier version of it WAS silently no-opping after a refactor. The gate's contract test slices by AST node span rather than a fixed character count, which had already truncated once as the block grew. gateway hygiene + session-state + the three touched agent compression suites: 50 passed; ruff clean. E2E with real imports demonstrates the premise rather than asserting it: bind_session_state zeroes the in-agent counter, and the recorded deadlines go 300 -> 900 -> 2700 -> 2700 -> 2700s where they were previously a flat 300s. Reported by @yucezerey (NousResearch#79624), whose state.db column dump and "deleting the session fixed it" datapoint made the real mechanism findable.
kshitijk4poor
force-pushed
the
fix/79624-preflight-cooldown-respect
branch
from
August 5, 2026 23:27
ea87d0e to
6f037cf
Compare
kshitijk4poor
enabled auto-merge (rebase)
August 5, 2026 23:27
kshitijk4poor
added a commit
to kshitijk4poor/hermes-agent
that referenced
this pull request
Aug 6, 2026
…failure reason Follow-up to c0d974b (NousResearch#79741). Three review findings against that commit, none of which change the escalation behaviour it shipped. 1. The recovery decision lived inline in `_handle_message_with_agent`, a ~2000-line async method, so the only way to pin it was a test that read `inspect.getsource(...)` and asserted on substrings. AGENTS.md bans reading source in tests outright and names this exact situation: "if the logic lives inline in a god-file (gateway/run.py) and extracting it feels disruptive: that's the actual signal to do the extraction, not to regex around it." Those tests were not merely stylistically wrong, they were actively harmful. One asserted the substring `_new_tokens < _approx_tokens` was PRESENT -- so it passed while the gate had the bug that substring represents, and had to be edited when the gate was fixed. It failed on correct code and passed on broken code, in one assertion. Extracted `hygiene_compaction_recovered()` as a module-level pure predicate and replaced the three source-reading tests with eight direct unit tests. The extraction immediately earned itself: the new tests caught a `NameError` (the predicate called `compression_made_progress` while the module bound it under an alias) that a source-text assertion cannot see, because the symbol is spelled correctly in the source and only fails at runtime. 2. The gate inferred "did the transcript actually get rewritten" from a numeric side effect -- the degenerate "did not rotate or compact in place" path (NousResearch#21301) reuses the pre-compression counts -- when the booleans `_hyg_rotated` / `_hyg_in_place` were already in scope and explicitly set False on that path. The predicate now takes them directly, so a future edit that re-estimates instead of reusing the old counts cannot silently defeat the escalation. 3. `_record_hygiene_cooldown` passed no `error` to `record_compression_failure_cooldown`, which writes `compression_failure_error` unconditionally -- so a hygiene failure clobbered to NULL whatever reason the in-conversation path had recorded, and readers then show the user "unknown error" (agent/manual_compression_feedback.py, gateway/slash_commands.py). The reason was already in hand at both call sites. Pre-existing from NousResearch#74136 but amplified by escalation: a blank reason on a 45-minute cooldown is far more user-visible than on a 5-minute one. Also: the ladder docstring described the compressor's absolute 60/300/900s ladder while the constant is multipliers (1, 3, 9); the config docs still described `hygiene_failure_cooldown_seconds` as a flat interval rather than the first rung of a capped ladder; and `PersistentState.hygiene_failure_streak` now documents that it is process-local by design -- keying on `session_key` is what survives compaction rotation, which the persisted `compression_*_streak` columns cannot express since they key on the rotating `session_id`. Making it durable is a schema change, tracked on NousResearch#79624 rather than smuggled in here. Also replaces the file's hand-written `_Runner` stub with `object.__new__(GatewayRunner)` (already the idiom elsewhere in the same file). The stub reimplemented `_session_state` and `_peek_session_state`, so the tests exercised copies that could drift from production; using the real class immediately made one assertion stronger -- on a fresh runner `_sessions` does not exist at all until something materialises it, so the reset provably did not even create the map. A review pass on this follow-up then caught that the CALL SITE was still unbound: deleting the whole `if not _hyg_aborted: if hygiene_compaction_recovered(...)` block left every ladder test green, because the unit tests prove the predicate correct without proving it is wired in. The merged commit had the same gap and its only cover was the banned source-reading test. `test_session_hygiene_forces_in_place_compaction_with_bound_session_db` now spies the reset on a genuine in-place compaction, so deleting the wiring fails. Two earlier attempts at this test did NOT close the gap -- asserting on streak VALUES passes either way, since the streak is 0 whether or not the gate ran; only a positive spy assertion on a recovering run detects the deletion. Same pass also corrected an overstatement: point (2) is hardening, not a live bug. The degenerate path also sets `_new_count = _msg_count` and `_new_tokens = _approx_tokens`, and `compression_made_progress(n, n, t, t)` is always False, so the merged code already declined to reset there. A 200k-trial fuzz over the reachable state space found zero behavioural disagreements between the merged gate and this one. The guard's value is surviving a future edit that stops reusing those counts. Tests: 28 in tests/gateway/test_hygiene_failure_cooldown_ladder.py (8 new unit tests for the predicate, 3 for reason forwarding, 3 source-reading tests deleted). All 5 mutations caught -- including one that restores the hand-rolled comparison and one that removes the rotated/in_place guard. Two mutations initially SURVIVED and exposed vacuous tests of my own: the no-rewrite test used counts the progress predicate already rejects, so it passed without binding the guard at all; it now passes counts that read as progress on their own, proving the guard is what rejects them. gateway hygiene + session-state + agent compression-progress suites: 54 passed; ruff clean. Refs NousResearch#79624
kshitijk4poor
added a commit
that referenced
this pull request
Aug 6, 2026
…failure reason Follow-up to c0d974b (#79741). Three review findings against that commit, none of which change the escalation behaviour it shipped. 1. The recovery decision lived inline in `_handle_message_with_agent`, a ~2000-line async method, so the only way to pin it was a test that read `inspect.getsource(...)` and asserted on substrings. AGENTS.md bans reading source in tests outright and names this exact situation: "if the logic lives inline in a god-file (gateway/run.py) and extracting it feels disruptive: that's the actual signal to do the extraction, not to regex around it." Those tests were not merely stylistically wrong, they were actively harmful. One asserted the substring `_new_tokens < _approx_tokens` was PRESENT -- so it passed while the gate had the bug that substring represents, and had to be edited when the gate was fixed. It failed on correct code and passed on broken code, in one assertion. Extracted `hygiene_compaction_recovered()` as a module-level pure predicate and replaced the three source-reading tests with eight direct unit tests. The extraction immediately earned itself: the new tests caught a `NameError` (the predicate called `compression_made_progress` while the module bound it under an alias) that a source-text assertion cannot see, because the symbol is spelled correctly in the source and only fails at runtime. 2. The gate inferred "did the transcript actually get rewritten" from a numeric side effect -- the degenerate "did not rotate or compact in place" path (#21301) reuses the pre-compression counts -- when the booleans `_hyg_rotated` / `_hyg_in_place` were already in scope and explicitly set False on that path. The predicate now takes them directly, so a future edit that re-estimates instead of reusing the old counts cannot silently defeat the escalation. 3. `_record_hygiene_cooldown` passed no `error` to `record_compression_failure_cooldown`, which writes `compression_failure_error` unconditionally -- so a hygiene failure clobbered to NULL whatever reason the in-conversation path had recorded, and readers then show the user "unknown error" (agent/manual_compression_feedback.py, gateway/slash_commands.py). The reason was already in hand at both call sites. Pre-existing from #74136 but amplified by escalation: a blank reason on a 45-minute cooldown is far more user-visible than on a 5-minute one. Also: the ladder docstring described the compressor's absolute 60/300/900s ladder while the constant is multipliers (1, 3, 9); the config docs still described `hygiene_failure_cooldown_seconds` as a flat interval rather than the first rung of a capped ladder; and `PersistentState.hygiene_failure_streak` now documents that it is process-local by design -- keying on `session_key` is what survives compaction rotation, which the persisted `compression_*_streak` columns cannot express since they key on the rotating `session_id`. Making it durable is a schema change, tracked on #79624 rather than smuggled in here. Also replaces the file's hand-written `_Runner` stub with `object.__new__(GatewayRunner)` (already the idiom elsewhere in the same file). The stub reimplemented `_session_state` and `_peek_session_state`, so the tests exercised copies that could drift from production; using the real class immediately made one assertion stronger -- on a fresh runner `_sessions` does not exist at all until something materialises it, so the reset provably did not even create the map. A review pass on this follow-up then caught that the CALL SITE was still unbound: deleting the whole `if not _hyg_aborted: if hygiene_compaction_recovered(...)` block left every ladder test green, because the unit tests prove the predicate correct without proving it is wired in. The merged commit had the same gap and its only cover was the banned source-reading test. `test_session_hygiene_forces_in_place_compaction_with_bound_session_db` now spies the reset on a genuine in-place compaction, so deleting the wiring fails. Two earlier attempts at this test did NOT close the gap -- asserting on streak VALUES passes either way, since the streak is 0 whether or not the gate ran; only a positive spy assertion on a recovering run detects the deletion. Same pass also corrected an overstatement: point (2) is hardening, not a live bug. The degenerate path also sets `_new_count = _msg_count` and `_new_tokens = _approx_tokens`, and `compression_made_progress(n, n, t, t)` is always False, so the merged code already declined to reset there. A 200k-trial fuzz over the reachable state space found zero behavioural disagreements between the merged gate and this one. The guard's value is surviving a future edit that stops reusing those counts. Tests: 28 in tests/gateway/test_hygiene_failure_cooldown_ladder.py (8 new unit tests for the predicate, 3 for reason forwarding, 3 source-reading tests deleted). All 5 mutations caught -- including one that restores the hand-rolled comparison and one that removes the rotated/in_place guard. Two mutations initially SURVIVED and exposed vacuous tests of my own: the no-rewrite test used counts the progress predicate already rejects, so it passed without binding the guard at all; it now passes counts that read as progress on their own, proving the guard is what rejects them. gateway hygiene + session-state + agent compression-progress suites: 54 passed; ruff clean. Refs #79624
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…failure reason Follow-up to ad3be29 (NousResearch#79741). Three review findings against that commit, none of which change the escalation behaviour it shipped. 1. The recovery decision lived inline in `_handle_message_with_agent`, a ~2000-line async method, so the only way to pin it was a test that read `inspect.getsource(...)` and asserted on substrings. AGENTS.md bans reading source in tests outright and names this exact situation: "if the logic lives inline in a god-file (gateway/run.py) and extracting it feels disruptive: that's the actual signal to do the extraction, not to regex around it." Those tests were not merely stylistically wrong, they were actively harmful. One asserted the substring `_new_tokens < _approx_tokens` was PRESENT -- so it passed while the gate had the bug that substring represents, and had to be edited when the gate was fixed. It failed on correct code and passed on broken code, in one assertion. Extracted `hygiene_compaction_recovered()` as a module-level pure predicate and replaced the three source-reading tests with eight direct unit tests. The extraction immediately earned itself: the new tests caught a `NameError` (the predicate called `compression_made_progress` while the module bound it under an alias) that a source-text assertion cannot see, because the symbol is spelled correctly in the source and only fails at runtime. 2. The gate inferred "did the transcript actually get rewritten" from a numeric side effect -- the degenerate "did not rotate or compact in place" path (NousResearch#21301) reuses the pre-compression counts -- when the booleans `_hyg_rotated` / `_hyg_in_place` were already in scope and explicitly set False on that path. The predicate now takes them directly, so a future edit that re-estimates instead of reusing the old counts cannot silently defeat the escalation. 3. `_record_hygiene_cooldown` passed no `error` to `record_compression_failure_cooldown`, which writes `compression_failure_error` unconditionally -- so a hygiene failure clobbered to NULL whatever reason the in-conversation path had recorded, and readers then show the user "unknown error" (agent/manual_compression_feedback.py, gateway/slash_commands.py). The reason was already in hand at both call sites. Pre-existing from NousResearch#74136 but amplified by escalation: a blank reason on a 45-minute cooldown is far more user-visible than on a 5-minute one. Also: the ladder docstring described the compressor's absolute 60/300/900s ladder while the constant is multipliers (1, 3, 9); the config docs still described `hygiene_failure_cooldown_seconds` as a flat interval rather than the first rung of a capped ladder; and `PersistentState.hygiene_failure_streak` now documents that it is process-local by design -- keying on `session_key` is what survives compaction rotation, which the persisted `compression_*_streak` columns cannot express since they key on the rotating `session_id`. Making it durable is a schema change, tracked on NousResearch#79624 rather than smuggled in here. Also replaces the file's hand-written `_Runner` stub with `object.__new__(GatewayRunner)` (already the idiom elsewhere in the same file). The stub reimplemented `_session_state` and `_peek_session_state`, so the tests exercised copies that could drift from production; using the real class immediately made one assertion stronger -- on a fresh runner `_sessions` does not exist at all until something materialises it, so the reset provably did not even create the map. A review pass on this follow-up then caught that the CALL SITE was still unbound: deleting the whole `if not _hyg_aborted: if hygiene_compaction_recovered(...)` block left every ladder test green, because the unit tests prove the predicate correct without proving it is wired in. The merged commit had the same gap and its only cover was the banned source-reading test. `test_session_hygiene_forces_in_place_compaction_with_bound_session_db` now spies the reset on a genuine in-place compaction, so deleting the wiring fails. Two earlier attempts at this test did NOT close the gap -- asserting on streak VALUES passes either way, since the streak is 0 whether or not the gate ran; only a positive spy assertion on a recovering run detects the deletion. Same pass also corrected an overstatement: point (2) is hardening, not a live bug. The degenerate path also sets `_new_count = _msg_count` and `_new_tokens = _approx_tokens`, and `compression_made_progress(n, n, t, t)` is always False, so the merged code already declined to reset there. A 200k-trial fuzz over the reachable state space found zero behavioural disagreements between the merged gate and this one. The guard's value is surviving a future edit that stops reusing those counts. Tests: 28 in tests/gateway/test_hygiene_failure_cooldown_ladder.py (8 new unit tests for the predicate, 3 for reason forwarding, 3 source-reading tests deleted). All 5 mutations caught -- including one that restores the hand-rolled comparison and one that removes the rotated/in_place guard. Two mutations initially SURVIVED and exposed vacuous tests of my own: the no-rewrite test used counts the progress predicate already rejects, so it passed without binding the guard at all; it now passes counts that read as progress on their own, proving the guard is what rejects them. gateway hygiene + session-state + agent compression-progress suites: 54 passed; ruff clean. Refs NousResearch#79624
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
…failure reason Follow-up to c0d974b (NousResearch#79741). Three review findings against that commit, none of which change the escalation behaviour it shipped. 1. The recovery decision lived inline in `_handle_message_with_agent`, a ~2000-line async method, so the only way to pin it was a test that read `inspect.getsource(...)` and asserted on substrings. AGENTS.md bans reading source in tests outright and names this exact situation: "if the logic lives inline in a god-file (gateway/run.py) and extracting it feels disruptive: that's the actual signal to do the extraction, not to regex around it." Those tests were not merely stylistically wrong, they were actively harmful. One asserted the substring `_new_tokens < _approx_tokens` was PRESENT -- so it passed while the gate had the bug that substring represents, and had to be edited when the gate was fixed. It failed on correct code and passed on broken code, in one assertion. Extracted `hygiene_compaction_recovered()` as a module-level pure predicate and replaced the three source-reading tests with eight direct unit tests. The extraction immediately earned itself: the new tests caught a `NameError` (the predicate called `compression_made_progress` while the module bound it under an alias) that a source-text assertion cannot see, because the symbol is spelled correctly in the source and only fails at runtime. 2. The gate inferred "did the transcript actually get rewritten" from a numeric side effect -- the degenerate "did not rotate or compact in place" path (NousResearch#21301) reuses the pre-compression counts -- when the booleans `_hyg_rotated` / `_hyg_in_place` were already in scope and explicitly set False on that path. The predicate now takes them directly, so a future edit that re-estimates instead of reusing the old counts cannot silently defeat the escalation. 3. `_record_hygiene_cooldown` passed no `error` to `record_compression_failure_cooldown`, which writes `compression_failure_error` unconditionally -- so a hygiene failure clobbered to NULL whatever reason the in-conversation path had recorded, and readers then show the user "unknown error" (agent/manual_compression_feedback.py, gateway/slash_commands.py). The reason was already in hand at both call sites. Pre-existing from NousResearch#74136 but amplified by escalation: a blank reason on a 45-minute cooldown is far more user-visible than on a 5-minute one. Also: the ladder docstring described the compressor's absolute 60/300/900s ladder while the constant is multipliers (1, 3, 9); the config docs still described `hygiene_failure_cooldown_seconds` as a flat interval rather than the first rung of a capped ladder; and `PersistentState.hygiene_failure_streak` now documents that it is process-local by design -- keying on `session_key` is what survives compaction rotation, which the persisted `compression_*_streak` columns cannot express since they key on the rotating `session_id`. Making it durable is a schema change, tracked on NousResearch#79624 rather than smuggled in here. Also replaces the file's hand-written `_Runner` stub with `object.__new__(GatewayRunner)` (already the idiom elsewhere in the same file). The stub reimplemented `_session_state` and `_peek_session_state`, so the tests exercised copies that could drift from production; using the real class immediately made one assertion stronger -- on a fresh runner `_sessions` does not exist at all until something materialises it, so the reset provably did not even create the map. A review pass on this follow-up then caught that the CALL SITE was still unbound: deleting the whole `if not _hyg_aborted: if hygiene_compaction_recovered(...)` block left every ladder test green, because the unit tests prove the predicate correct without proving it is wired in. The merged commit had the same gap and its only cover was the banned source-reading test. `test_session_hygiene_forces_in_place_compaction_with_bound_session_db` now spies the reset on a genuine in-place compaction, so deleting the wiring fails. Two earlier attempts at this test did NOT close the gap -- asserting on streak VALUES passes either way, since the streak is 0 whether or not the gate ran; only a positive spy assertion on a recovering run detects the deletion. Same pass also corrected an overstatement: point (2) is hardening, not a live bug. The degenerate path also sets `_new_count = _msg_count` and `_new_tokens = _approx_tokens`, and `compression_made_progress(n, n, t, t)` is always False, so the merged code already declined to reset there. A 200k-trial fuzz over the reachable state space found zero behavioural disagreements between the merged gate and this one. The guard's value is surviving a future edit that stops reusing those counts. Tests: 28 in tests/gateway/test_hygiene_failure_cooldown_ladder.py (8 new unit tests for the predicate, 3 for reason forwarding, 3 source-reading tests deleted). All 5 mutations caught -- including one that restores the hand-rolled comparison and one that removes the rotated/in_place guard. Two mutations initially SURVIVED and exposed vacuous tests of my own: the no-rewrite test used counts the progress predicate already rejects, so it passed without binding the guard at all; it now passes counts that read as progress on their own, proving the guard is what rejects them. gateway hygiene + session-state + agent compression-progress suites: 54 passed; ruff clean. Refs NousResearch#79624
1 task
gabrielcosi
pushed a commit
to gabrielcosi/home-ops
that referenced
this pull request
Sep 1, 2026
…8.27 ➔ v2026.8.31) (#606) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/gabrielcosi/hermes-agent](https://github.com/NousResearch/hermes-agent) | patch | `v2026.8.27` → `v2026.8.31` | --- ### Release Notes <details> <summary>NousResearch/hermes-agent (ghcr.io/gabrielcosi/hermes-agent)</summary> ### [`v2026.8.31`](https://github.com/NousResearch/hermes-agent/releases/tag/v2026.8.31): Hermes Agent v0.21.0 (v2026.8.31) [Compare Source](https://github.com/NousResearch/hermes-agent/compare/v2026.8.27...v2026.8.31) ##### Hermes Agent v0.21.0 (v2026.8.31) **Release Date:** August 31, 2026 **Since v0.20.0:** \~5,800 commits · \~2,475 merged PRs · \~5,680 files changed · \~869,000 insertions · \~135,000 deletions · **\~2,100 issues closed** · 760+ contributors > **The Pantheon Release.** v0.20.0 made Hermes the herald — he spoke, and he carried word to other agents. In v0.21.0 the gods assemble. Bot Mode ships built into the desktop app: a society of named agents with their own faces and group chats, where your bots talk to each other — and to you — like a team, not a toolbox. Around that spine: cron jobs gained memory and continuity so scheduled agents actually learn between runs, subagents can be steered live mid-flight, the MCP surface became a real command center, and the agent can now drive the desktop's own browser. This release rolls up everything from the v0.20.1–v0.20.6 infrastructure patch tags — those windows are fully documented here. *** ##### ✨ Highlights - **Bot Mode — your agents become a society, built in** — Bot Mode is now a bundled, default-on part of the desktop app: every agent profile gets a name, a deterministic avatar face (with randomize/lock controls), and a place in a shared roster. Create Discord-style group chats where multiple bots and you talk in one room, @​-mention any bot from the composer, and give rooms names and pictures. Before, "multi-agent" meant plumbing; now it looks like a chat app full of coworkers. ([#​87886](https://github.com/NousResearch/hermes-agent/pull/87886), [#​88243](https://github.com/NousResearch/hermes-agent/pull/88243), [#​89386](https://github.com/NousResearch/hermes-agent/pull/89386), [#​96726](https://github.com/NousResearch/hermes-agent/pull/96726) — [@​teknium1](https://github.com/teknium1), [@​OutThisLife](https://github.com/OutThisLife), [@​dokterdok](https://github.com/dokterdok)) - **`hermes peer` — bot-to-bot DMs between your agents** — Any Hermes agent can now message any other by handle, across profiles and gateways, from the CLI or from inside a conversation. Ask your research bot to hand findings to your coding bot and get the reply back where you can read it. Replies land in each agent's canonical Bot Chat, so conversations between agents are durable and inspectable, not fire-and-forget. ([#​88725](https://github.com/NousResearch/hermes-agent/pull/88725), [#​88178](https://github.com/NousResearch/hermes-agent/pull/88178), [#​91487](https://github.com/NousResearch/hermes-agent/pull/91487) — [@​teknium1](https://github.com/teknium1)) - **Cron jobs that remember** — Scheduled jobs stopped being goldfish. Cron agents now load and update persistent memory like every other agent, `continuity=true` carries each run's output into the next (so a monitor can dedupe against what it already reported), every job gets a durable notepad scratchpad, monitor-mode jobs skip the LLM entirely when nothing changed, and cron output can land in a bot's canonical Bot Chat — where the bot actually responds. Your 9am briefing job now knows what it told you yesterday. ([#​91447](https://github.com/NousResearch/hermes-agent/pull/91447), [#​80774](https://github.com/NousResearch/hermes-agent/pull/80774), [#​81139](https://github.com/NousResearch/hermes-agent/pull/81139), [#​81138](https://github.com/NousResearch/hermes-agent/pull/81138), [#​91487](https://github.com/NousResearch/hermes-agent/pull/91487) — [@​teknium1](https://github.com/teknium1), [@​smwbev](https://github.com/smwbev)) - **Steer your subagents while they run** — `delegate_task` gained live orchestration: list running children, steer one mid-flight with a course correction, or stop it early and keep the partial result. Add optional JSON-schema validation on child outputs, per-delegation cost surfaced in results, and raised defaults (250 iterations, 10 concurrent children) — delegation went from fire-and-pray to actually managed parallel work. ([#​85232](https://github.com/NousResearch/hermes-agent/pull/85232), [#​81144](https://github.com/NousResearch/hermes-agent/pull/81144), [#​81142](https://github.com/NousResearch/hermes-agent/pull/81142), [#​86506](https://github.com/NousResearch/hermes-agent/pull/86506), [#​86745](https://github.com/NousResearch/hermes-agent/pull/86745) — [@​teknium1](https://github.com/teknium1)) - **The MCP command center** — MCP servers and the catalog merged into one coherent desktop page with drag-in "paste anything" import, background health checks that nudge you to re-auth before a tool call fails, a fleet cost/usage overlay showing schema token estimates and 30-day usage per server, and `hermes://` deep links that install an MCP server with explicit confirmation. Managing twenty MCP servers used to be config-file archaeology; now it's a dashboard. ([#​87525](https://github.com/NousResearch/hermes-agent/pull/87525), [#​87572](https://github.com/NousResearch/hermes-agent/pull/87572), [#​87576](https://github.com/NousResearch/hermes-agent/pull/87576), [#​87579](https://github.com/NousResearch/hermes-agent/pull/87579), [#​87581](https://github.com/NousResearch/hermes-agent/pull/87581) — [@​teknium1](https://github.com/teknium1)) - **A CLI power wave** — Ctrl+P opens a fuzzy command palette, the `/model` picker filters as you type, `/status` shows reasoning mode, pending approvals, and context usage, and the status bar can display live cache-hit %, latency, and tokens/sec with per-field toggles. Plus: a global emergency stop, session pin/unpin, rotating task-oriented composer placeholders — and Ghostty-level **terminal pets**, because a companion should have a companion. ([#​90730](https://github.com/NousResearch/hermes-agent/pull/90730), [#​90717](https://github.com/NousResearch/hermes-agent/pull/90717), [#​90745](https://github.com/NousResearch/hermes-agent/pull/90745), [#​98250](https://github.com/NousResearch/hermes-agent/pull/98250), [#​98282](https://github.com/NousResearch/hermes-agent/pull/98282), [#​97666](https://github.com/NousResearch/hermes-agent/pull/97666) — [@​teknium1](https://github.com/teknium1), and salvaged community work) - **The agent drives the desktop's browser** — The in-app browser stopped being a window the agent could only look at: Hermes now navigates, clicks, and reads it directly, and pages can be popped out to your system browser with full link context menus. Ask it to walk a docs site or debug a web app and watch it happen inside your own app. ([#​90197](https://github.com/NousResearch/hermes-agent/pull/90197), [#​89366](https://github.com/NousResearch/hermes-agent/pull/89366) — [@​OutThisLife](https://github.com/OutThisLife), [@​ethernet8023](https://github.com/ethernet8023)) - **Six new providers and a model catalog wave** — Meta Model API (Muse Spark) arrives as a built-in provider, alongside CommandCode, Tencent TokenPlan, Nebius Token Factory, Ramp Router, and Actual Computer. The catalogs picked up GLM-5.3-Flash, qwen3.8-max/flash, Gemini 3.7 Flash, MiniMax M3 free, and Nemotron 3.5 Lightning — and `model_overrides` lets you patch any model's context window or pricing yourself without waiting on a release. ([#​88565](https://github.com/NousResearch/hermes-agent/pull/88565), [#​88308](https://github.com/NousResearch/hermes-agent/pull/88308), [#​97917](https://github.com/NousResearch/hermes-agent/pull/97917), [#​97916](https://github.com/NousResearch/hermes-agent/pull/97916), [#​97915](https://github.com/NousResearch/hermes-agent/pull/97915), [#​85560](https://github.com/NousResearch/hermes-agent/pull/85560) — [@​teknium1](https://github.com/teknium1) and community) - **Security hardening across the board** — Protected agent-instruction files (AGENTS.md, skills, memory stores) now always require write approval so a prompt-injected agent can't quietly rewrite its own standing orders. A deep redaction sweep closed secret-leak gaps across terminal errors, `.env` file reads, checkpoints, and ACP logs; the approval system learned Windows destructive commands; and macOS permission grants finally survive updates via a stable TCC signing identity. ([#​81152](https://github.com/NousResearch/hermes-agent/pull/81152), [#​80965](https://github.com/NousResearch/hermes-agent/pull/80965), [#​84428](https://github.com/NousResearch/hermes-agent/pull/84428), [#​95091](https://github.com/NousResearch/hermes-agent/pull/95091) — [@​teknium1](https://github.com/teknium1) and community) *** ##### 🏗️ Core Agent & Architecture ##### Providers & Models - **New providers:** Actual Computer inference ([#​79644](https://github.com/NousResearch/hermes-agent/pull/79644), salvage of [#​26491](https://github.com/NousResearch/hermes-agent/issues/26491)), CommandCode with GOAT/Pro/Max plans ([#​88308](https://github.com/NousResearch/hermes-agent/pull/88308), salvage of [#​32909](https://github.com/NousResearch/hermes-agent/issues/32909)), Meta Model API (Muse Spark) as a built-in provider plugin ([#​88565](https://github.com/NousResearch/hermes-agent/pull/88565)), Tencent TokenPlan ([#​97917](https://github.com/NousResearch/hermes-agent/pull/97917)), Nebius Token Factory ([#​97916](https://github.com/NousResearch/hermes-agent/pull/97916)), and Ramp Router ([#​97915](https://github.com/NousResearch/hermes-agent/pull/97915)). - **Model catalog wave:** qwen3.8-max and qwen3.8-flash ([#​78024](https://github.com/NousResearch/hermes-agent/pull/78024), [#​96979](https://github.com/NousResearch/hermes-agent/pull/96979)), Gemini 3.7 Flash ([#​85526](https://github.com/NousResearch/hermes-agent/pull/85526)), GLM-5.3-Flash across OpenRouter/Nous/z.ai/OpenCode ([#​95621](https://github.com/NousResearch/hermes-agent/pull/95621), [#​96293](https://github.com/NousResearch/hermes-agent/pull/96293)), MiniMax M3 + Inkling free SKUs ([#​96264](https://github.com/NousResearch/hermes-agent/pull/96264)), Nemotron 3.5 Lightning ([#​84645](https://github.com/NousResearch/hermes-agent/pull/84645)), Meta Muse Spark 1.2 ([#​88600](https://github.com/NousResearch/hermes-agent/pull/88600)). - **Per-model metadata overrides** via `model_overrides` config — patch context windows, pricing, or capabilities for any model without waiting on a release ([#​85560](https://github.com/NousResearch/hermes-agent/pull/85560)). - **Data-training-tier warnings** — a unified selection-guard registry warns you across every picker surface when a model trains on your data ([#​85917](https://github.com/NousResearch/hermes-agent/pull/85917)). - **Pip-installed model providers** discovered via entry points — third parties can ship providers as packages ([#​85504](https://github.com/NousResearch/hermes-agent/pull/85504), salvage [#​81419](https://github.com/NousResearch/hermes-agent/issues/81419)). - **Prompt caching engaged for LiteLLM Claude** on the OpenAI wire ([#​87517](https://github.com/NousResearch/hermes-agent/pull/87517) — [@​kshitijk4poor](https://github.com/kshitijk4poor)) and api.meta.ai routed through the Responses API for caching ([#​88601](https://github.com/NousResearch/hermes-agent/pull/88601)). - **Codex GPT context defaults** back to the verified 272K, with explicit -900k picker variants for the large window ([#​92797](https://github.com/NousResearch/hermes-agent/pull/92797)). ##### Agent Loop & Reliability - **Stalled-provider recovery** — a stalled stream no longer leaves Desktop silent or burns the retry budget ([#​87236](https://github.com/NousResearch/hermes-agent/pull/87236) — [@​fangliquanflq](https://github.com/fangliquanflq)). - **MoA hardening cluster** — the Mixture-of-Agents facade now survives client rebuilds, stream retries, provider rotation, and fallback/restore without leaking prepared requests to native clients ([#​90212](https://github.com/NousResearch/hermes-agent/pull/90212) and siblings). - **Structured-output resilience** — auxiliary calls translate `response_format` for Anthropic wires and retry when a provider rejects structured output ([#​89589](https://github.com/NousResearch/hermes-agent/pull/89589) — [@​ethernet8023](https://github.com/ethernet8023)). - **Compression: lean tail mode** + a compaction recall eval harness to measure what compression actually preserves ([#​87326](https://github.com/NousResearch/hermes-agent/pull/87326)). - **Oversized tool results spill to cache** instead of being truncated in sandbox-less sessions ([#​89028](https://github.com/NousResearch/hermes-agent/pull/89028)). - **`clarify` asks multiple independent questions in one call** — one form instead of a chain of round-trips ([#​89467](https://github.com/NousResearch/hermes-agent/pull/89467) — [@​ethernet8023](https://github.com/ethernet8023)). - **Verify subsystem** (port from superagent-ai/grok-cli): run-recipe detection + environment manifest so "it works" claims are backed by detected build/test commands ([#​80686](https://github.com/NousResearch/hermes-agent/pull/80686)). ##### Sessions & State - **Resumed transcripts never re-append** — `_db_persisted` stamped at row load time ([#​92539](https://github.com/NousResearch/hermes-agent/pull/92539) — [@​kshitijk4poor](https://github.com/kshitijk4poor)). - **SessionDB context-manager protocol** for leak-free DB lifecycles ([#​88048](https://github.com/NousResearch/hermes-agent/pull/88048) — [@​jackulau](https://github.com/jackulau)). - **Instant session naming** from the opening message, with sticky titles across resume ([#​81985](https://github.com/NousResearch/hermes-agent/pull/81985) — [@​OutThisLife](https://github.com/OutThisLife)). - **Session pin/unpin from the CLI** — durable keep flags ([#​80761](https://github.com/NousResearch/hermes-agent/pull/80761)). ##### 📱 Messaging Platforms (Gateway) - **Slack native live cards** — real streaming replies via chat.startStream plus opt-in plan/task cards for tool progress ([#​85476](https://github.com/NousResearch/hermes-agent/pull/85476)); outbound link-preview suppression across native and relay planes ([#​95142](https://github.com/NousResearch/hermes-agent/pull/95142) — [@​benbarclay](https://github.com/benbarclay)). - **Telegram inline picker** — every command and skill searchable via [@​botname](https://github.com/botname), bypassing Telegram's command-menu cap ([#​98317](https://github.com/NousResearch/hermes-agent/pull/98317)). - **Relay matured** — native static/dynamic plugin initialization ([#​77915](https://github.com/NousResearch/hermes-agent/pull/77915) — [@​bbednarski9](https://github.com/bbednarski9)), live-card ops with draft streaming + task cards ([#​85796](https://github.com/NousResearch/hermes-agent/pull/85796) — [@​victor-kyriazakos](https://github.com/victor-kyriazakos)), session-span segmentation ([#​85467](https://github.com/NousResearch/hermes-agent/pull/85467)), voice-note STT restored over the relay lane ([#​95274](https://github.com/NousResearch/hermes-agent/pull/95274) — [@​benbarclay](https://github.com/benbarclay)), transport dedupe/fail-fast fixes ([#​89584](https://github.com/NousResearch/hermes-agent/pull/89584)). - **Gateway control socket** — fleet consumers query the gateway itself (identify/status), and updaters pause gateways gracefully instead of tree-killing them ([#​92447](https://github.com/NousResearch/hermes-agent/pull/92447), [#​95695](https://github.com/NousResearch/hermes-agent/pull/95695)). - **Selective multiplex profile serving** ([#​83400](https://github.com/NousResearch/hermes-agent/pull/83400)), per-profile MCP lifecycle RPCs ([#​86473](https://github.com/NousResearch/hermes-agent/pull/86473)), roster previews showing the latest message ([#​85905](https://github.com/NousResearch/hermes-agent/pull/85905)), concise background-process notifications by default ([#​85877](https://github.com/NousResearch/hermes-agent/pull/85877)). - **Turn-reaper stack dumps** — when the watchdog fires, wedged worker stacks are captured for diagnosis ([#​86248](https://github.com/NousResearch/hermes-agent/pull/86248)), with loop-watchdog tuning knobs wired end-to-end ([#​92317](https://github.com/NousResearch/hermes-agent/pull/92317)). - **Split-delivery bug class closed** — swallowed finals and split deliveries fixed as a class ([#​79669](https://github.com/NousResearch/hermes-agent/pull/79669) — [@​kshitijk4poor](https://github.com/kshitijk4poor), salvages [#​78558](https://github.com/NousResearch/hermes-agent/issues/78558)). ##### 🖥️ Desktop App - **Bot Mode built in** — see Highlights. Full sub-catalog: attributed agent-to-agent message cards ([#​85855](https://github.com/NousResearch/hermes-agent/pull/85855)), sender-side delivery notices ([#​85888](https://github.com/NousResearch/hermes-agent/pull/85888)), paint-first hydration for instant wakes ([#​89510](https://github.com/NousResearch/hermes-agent/pull/89510)), editable group names and room pictures ([#​89371](https://github.com/NousResearch/hermes-agent/pull/89371)), Routines pane ([#​88731](https://github.com/NousResearch/hermes-agent/pull/88731)), rebuilt on the app design system ([#​96726](https://github.com/NousResearch/hermes-agent/pull/96726) — [@​OutThisLife](https://github.com/OutThisLife)). - **In-app browser the agent can drive** — the agent uses the desktop's browser tab, not just looks at it ([#​90197](https://github.com/NousResearch/hermes-agent/pull/90197) — [@​OutThisLife](https://github.com/OutThisLife)), with external-open + link context menus ([#​89366](https://github.com/NousResearch/hermes-agent/pull/89366)). - **Detached update hand-off on every OS** — quit → update → reopen with a single shim window; Windows desktop updates no longer park on "Updating Hermes" ([#​83634](https://github.com/NousResearch/hermes-agent/pull/83634), [#​90937](https://github.com/NousResearch/hermes-agent/pull/90937), [#​95897](https://github.com/NousResearch/hermes-agent/pull/95897)). - **Quality-of-life wave** — HUD snaps to cursor on ⌘⇧G ([#​83130](https://github.com/NousResearch/hermes-agent/pull/83130)), paste into the composer without focusing it ([#​84955](https://github.com/NousResearch/hermes-agent/pull/84955)), per-turn duration badges ([#​87245](https://github.com/NousResearch/hermes-agent/pull/87245)), readable macOS window translucency ([#​88744](https://github.com/NousResearch/hermes-agent/pull/88744)), Linux launcher entry ([#​76456](https://github.com/NousResearch/hermes-agent/pull/76456)), Linux keychain auto-detection ([#​84903](https://github.com/NousResearch/hermes-agent/pull/84903)), cron + blueprint recipes in the sidebar ([#​85162](https://github.com/NousResearch/hermes-agent/pull/85162)). - **Send Diagnostics** — error cards can upload a redacted debug bundle with a support handoff ([#​92020](https://github.com/NousResearch/hermes-agent/pull/92020)). - **Seq-stamped event replay** — lossless desktop reconnect over WebSocket ([#​94219](https://github.com/NousResearch/hermes-agent/pull/94219) — [@​kshitijk4poor](https://github.com/kshitijk4poor)). - **God-file decomposition** — the desktop codebase refactored into atomic modules ([#​89611](https://github.com/NousResearch/hermes-agent/pull/89611) — [@​OutThisLife](https://github.com/OutThisLife)). ##### 🖥️ CLI & TUI - See Highlights for the power wave (command palette, fuzzy /model, richer /status, live status-bar metrics, pets). - **`hermes approval-check`** — dry-run approval verdicts for a command without running it ([#​81137](https://github.com/NousResearch/hermes-agent/pull/81137)). - **Session titles in status bars** ([#​81947](https://github.com/NousResearch/hermes-agent/pull/81947)); subagent model exposed in the auxiliary-models picker ([#​80463](https://github.com/NousResearch/hermes-agent/pull/80463)). - **Ctrl+C fixed** — stopped pushing the Kitty keyboard protocol that broke interrupts ([#​87074](https://github.com/NousResearch/hermes-agent/pull/87074)). ##### 🔧 Tool System & MCP - **MCP command center** — see Highlights ([#​87525](https://github.com/NousResearch/hermes-agent/pull/87525) et al.). - **Interrupted commands no longer poison cwd** — the terminal won't adopt a stale working directory after an interrupt ([#​85654](https://github.com/NousResearch/hermes-agent/pull/85654) — [@​ethernet8023](https://github.com/ethernet8023)). - **Windows rename contention recovery** ([#​84852](https://github.com/NousResearch/hermes-agent/pull/84852) — [@​OutThisLife](https://github.com/OutThisLife)); colon-bearing session IDs no longer break sandbox mounts ([#​93488](https://github.com/NousResearch/hermes-agent/pull/93488), consolidated 6 PRs). - **Vision output uncapped** — aux vision calls stop clamping max\_tokens ([#​75253](https://github.com/NousResearch/hermes-agent/pull/75253) — [@​adikpb](https://github.com/adikpb)). - **Subagent work protected** — delegate no longer deletes a child's uncommitted work when git inspection fails ([#​88419](https://github.com/NousResearch/hermes-agent/pull/88419) — [@​kshitijk4poor](https://github.com/kshitijk4poor)). ##### 🧩 Skills Ecosystem - **Productivity skill wave** (salvaged from [@​community](https://github.com/community) proposals): document-to-action-items, meeting-action-items, email-inbox-triage, github-issue-to-pr, weekly-review-planning, competitor-news-monitor, product-price-monitor, social-media-content-calendar ([#​81185](https://github.com/NousResearch/hermes-agent/pull/81185), [#​81749](https://github.com/NousResearch/hermes-agent/pull/81749), [#​81672](https://github.com/NousResearch/hermes-agent/pull/81672), [#​81708](https://github.com/NousResearch/hermes-agent/pull/81708), [#​81936](https://github.com/NousResearch/hermes-agent/pull/81936), [#​81945](https://github.com/NousResearch/hermes-agent/pull/81945), [#​81906](https://github.com/NousResearch/hermes-agent/pull/81906), [#​81942](https://github.com/NousResearch/hermes-agent/pull/81942)). - **HAR-derived API clients** — watch a website once, then call its hidden API directly with no browser ([#​70823](https://github.com/NousResearch/hermes-agent/pull/70823)). - **publish-site** — versioned website publishing to GitHub/Cloudflare/Netlify Pages ([#​72189](https://github.com/NousResearch/hermes-agent/pull/72189)); **session-librarian** for prompt-driven session management ([#​84917](https://github.com/NousResearch/hermes-agent/pull/84917)); **blocked-page-recovery** fallback ladder ([#​84906](https://github.com/NousResearch/hermes-agent/pull/84906)); **merge-reconciler** for neutral multi-agent conflict resolution ([#​83063](https://github.com/NousResearch/hermes-agent/pull/83063)); Box productivity skill ([#​85767](https://github.com/NousResearch/hermes-agent/pull/85767)); ast-grep codemods ([#​80892](https://github.com/NousResearch/hermes-agent/pull/80892)); draw-your-font + simple-english optional skills ([#​80814](https://github.com/NousResearch/hermes-agent/pull/80814), [#​80811](https://github.com/NousResearch/hermes-agent/pull/80811)); plan-interrogation (evolved grill-me) ([#​97831](https://github.com/NousResearch/hermes-agent/pull/97831)). - **Advisory SKILL.md linter on create** ([#​81896](https://github.com/NousResearch/hermes-agent/pull/81896)); skills-hub live-repo fallback for missing optional skills ([#​82780](https://github.com/NousResearch/hermes-agent/pull/82780)). ##### ⏰ Cron, Delegation & Multi-Agent - **Cron memory + continuity** — see Highlights ([#​91447](https://github.com/NousResearch/hermes-agent/pull/91447), [#​80774](https://github.com/NousResearch/hermes-agent/pull/80774)). - **Monitor-mode jobs** with hash-suppressed change detection ([#​81138](https://github.com/NousResearch/hermes-agent/pull/81138)); per-job durable notepads ([#​81139](https://github.com/NousResearch/hermes-agent/pull/81139)); pre-dispatch config validation ([#​81140](https://github.com/NousResearch/hermes-agent/pull/81140)); acked failure signatures stop re-pinging ([#​95017](https://github.com/NousResearch/hermes-agent/pull/95017)); per-job reasoning-effort pinning ([#​91244](https://github.com/NousResearch/hermes-agent/pull/91244)); "Trigger now" executes immediately and safely ([#​70638](https://github.com/NousResearch/hermes-agent/pull/70638) — [@​smwbev](https://github.com/smwbev)); model-drift impact surfaced in Desktop ([#​83266](https://github.com/NousResearch/hermes-agent/pull/83266) — [@​ctaylor86](https://github.com/ctaylor86)). - **Live subagent orchestration** — see Highlights ([#​85232](https://github.com/NousResearch/hermes-agent/pull/85232)); structured-output schemas on delegate\_task ([#​81144](https://github.com/NousResearch/hermes-agent/pull/81144)); batch-quality validation before spawning ([#​81141](https://github.com/NousResearch/hermes-agent/pull/81141)); truncation markers on capped children ([#​86641](https://github.com/NousResearch/hermes-agent/pull/86641)). - **Kanban hardening** — collision-hotspot flagging ([#​83428](https://github.com/NousResearch/hermes-agent/pull/83428)), split-brain decision-ownership contract ([#​83424](https://github.com/NousResearch/hermes-agent/pull/83424)), memory-aware dispatch guard ([#​88115](https://github.com/NousResearch/hermes-agent/pull/88115)), notify/wake delivery modes ([#​85487](https://github.com/NousResearch/hermes-agent/pull/85487)). ##### 📊 Observability - **Model/provider usage reporting** and bounded tool-metrics aggregation land as an observability subsystem ([#​68881](https://github.com/NousResearch/hermes-agent/pull/68881), [#​68882](https://github.com/NousResearch/hermes-agent/pull/68882) — [@​afourniernv](https://github.com/afourniernv)), with lifecycle ops bounded so a wedged pipeline can never block the agent ([#​83514](https://github.com/NousResearch/hermes-agent/pull/83514) — [@​victor-kyriazakos](https://github.com/victor-kyriazakos)). ##### 🔒 Security & Reliability - **Protected instruction files** — writes to AGENTS.md/skills/memory always require approval ([#​81152](https://github.com/NousResearch/hermes-agent/pull/81152)). - **Redaction sweep** — emission gaps closed across env-name variants, control-splits, process(list), checkpoints ([#​80965](https://github.com/NousResearch/hermes-agent/pull/80965)); `.env` reads redacted via file-read detection ([#​80964](https://github.com/NousResearch/hermes-agent/pull/80964)); terminal exception results + ACP stderr ([#​81675](https://github.com/NousResearch/hermes-agent/pull/81675), [#​81686](https://github.com/NousResearch/hermes-agent/pull/81686)); SSH target logging ([#​88232](https://github.com/NousResearch/hermes-agent/pull/88232)). - **Windows approval coverage** — destructive Windows commands and paths now trip the approval system ([#​84428](https://github.com/NousResearch/hermes-agent/pull/84428)). - **Supply-chain response** — the Blender MCP catalog entry and skill were removed after an upstream compromise ([#​83404](https://github.com/NousResearch/hermes-agent/pull/83404)); plugin installs get Tier-1 security scanning ([#​80728](https://github.com/NousResearch/hermes-agent/pull/80728)). - **macOS TCC identity** — permission grants survive every update via `hermes desktop --setup-tcc-identity` ([#​95091](https://github.com/NousResearch/hermes-agent/pull/95091)). - **PKCE cookie fix** — SameSite=None over HTTPS with a matching clear path ([#​83536](https://github.com/NousResearch/hermes-agent/pull/83536) — [@​benbarclay](https://github.com/benbarclay)). ##### 🏗️ Infrastructure, CI & Packaging - **Real-OS CI** — macOS and Windows CI lanes with tests gated by real host OS ([#​77992](https://github.com/NousResearch/hermes-agent/pull/77992) — [@​ethernet8023](https://github.com/ethernet8023)); work lanes on 32-core runners ([#​92009](https://github.com/NousResearch/hermes-agent/pull/92009)); review comments + image builds moved out of the hot CI path ([#​82668](https://github.com/NousResearch/hermes-agent/pull/82668)). - **Bootstrap installer** no longer waits on pipe EOF ([#​90941](https://github.com/NousResearch/hermes-agent/pull/90941) — [@​OutThisLife](https://github.com/OutThisLife)). ##### 🐛 Notable Bug Fixes - Desktop session races (Stop→edit/resend, submit ownership) fixed as a cluster ([#​86594](https://github.com/NousResearch/hermes-agent/pull/86594)); workspace-pane idle flicker ([#​97513](https://github.com/NousResearch/hermes-agent/pull/97513)); assistant-ui boundary recovery ([#​81232](https://github.com/NousResearch/hermes-agent/pull/81232)). - Kanban schema re-creation when a cached DB path loses it ([#​83619](https://github.com/NousResearch/hermes-agent/pull/83619) — [@​JoaoMarcos44](https://github.com/JoaoMarcos44)). - Cron jobs no longer inherit a kanban worker's dispatcher identity ([#​79657](https://github.com/NousResearch/hermes-agent/pull/79657) — [@​kshitijk4poor](https://github.com/kshitijk4poor)); relay-fronted Slack cron delivery ([#​85340](https://github.com/NousResearch/hermes-agent/pull/85340) — [@​victor-kyriazakos](https://github.com/victor-kyriazakos)). - Model-picker serves cached custom-provider catalogs on no-probe opens ([#​81973](https://github.com/NousResearch/hermes-agent/pull/81973) — [@​austinpickett](https://github.com/austinpickett)). - Docker sandbox MEDIA attachments no longer silently drop ([#​94509](https://github.com/NousResearch/hermes-agent/pull/94509)); desktop binary attachments delivered into sandbox backends ([#​81717](https://github.com/NousResearch/hermes-agent/pull/81717)). - Session-hygiene compaction cooldown escalates on repeat failures ([#​79741](https://github.com/NousResearch/hermes-agent/pull/79741) — [@​kshitijk4poor](https://github.com/kshitijk4poor)). - Provider-injected parameter 400s retried instead of aborting the turn ([#​91643](https://github.com/NousResearch/hermes-agent/pull/91643)). - …and roughly two thousand more closed issues' worth — this window averaged \~85 merged PRs per day. ##### ↩️ Reverted in this window (not shipping) - **Model Council mode (/council)** — landed then reverted; not in this release. - **DCP context engine** — landed then reverted; not in this release. - **WS-only gateway server ([#​94245](https://github.com/NousResearch/hermes-agent/issues/94245))** — merged then reverted ([#​96118](https://github.com/NousResearch/hermes-agent/issues/96118)); FastAPI remains on the desktop boot path. The seq-stamped event replay ([#​94219](https://github.com/NousResearch/hermes-agent/issues/94219)) DID ship. - Electron rolled back to 40.10.2; TCC interpreter anchor removed (superseded by the signing-identity approach that shipped). ##### 👥 Contributors ##### Core Team (Nous Research) - **[@​teknium1](https://github.com/teknium1)** — release direction, Bot Mode, MCP command center, cron memory, and \~1,340 merged PRs - **[@​kshitijk4poor](https://github.com/kshitijk4poor)** (374 PRs) — gateway delivery classes, session persistence, event replay, MoA/caching fixes - **[@​OutThisLife](https://github.com/OutThisLife)** (209 PRs) — desktop architecture, Bot Mode UI, in-app browser, update hand-off - **[@​ethernet8023](https://github.com/ethernet8023)** (38 PRs) — OS-gated CI lanes, clarify multi-question, tool cwd safety, aux structured-output resilience - **[@​benbarclay](https://github.com/benbarclay)** (17 PRs) — relay voice STT, Slack link previews, PKCE - **[@​victor-kyriazakos](https://github.com/victor-kyriazakos)** (15 PRs) — relay live cards, observability bounding - **[@​rob-maron](https://github.com/rob-maron)**, **[@​austinpickett](https://github.com/austinpickett)**, **[@​jquesnelle](https://github.com/jquesnelle)**, **[@​shannonsands](https://github.com/shannonsands)** — providers, dashboard, model catalogs, and more ##### Top Community Contributors (by merged PR count) - **[@​helix4u](https://github.com/helix4u)** (31 PRs) — wake-word capture, session UX, fix wave across desktop and gateway - **[@​fangliquanflq](https://github.com/fangliquanflq)** (17 PRs) — stalled-provider recovery and agent-loop resilience - **[@​bbednarski9](https://github.com/bbednarski9)** — relay native plugin integration; **[@​HexLab98](https://github.com/HexLab98)**, **[@​xxxigm](https://github.com/xxxigm)**, **[@​afourniernv](https://github.com/afourniernv)** (observability), **[@​liuhao1024](https://github.com/liuhao1024)**, **[@​Christopher-Schulze](https://github.com/Christopher-Schulze)**, **[@​Adolanium](https://github.com/Adolanium)**, **[@​webtecnica](https://github.com/webtecnica)**, **[@​smwbev](https://github.com/smwbev)** (cron Trigger-now), **[@​ctaylor86](https://github.com/ctaylor86)** (cron drift in Desktop), **[@​JoaoMarcos44](https://github.com/JoaoMarcos44)** (kanban schema recovery), **[@​jackulau](https://github.com/jackulau)** (SessionDB context manager), **[@​adikpb](https://github.com/adikpb)** (vision output caps), **[@​dokterdok](https://github.com/dokterdok)** (Bot Mode unification), and many more. ##### All Contributors Huge thanks to every one of the 760+ people who contributed code, co-authored fixes, or had their work salvaged into this release: [@​03farren](https://github.com/03farren), [@​0xarkstar](https://github.com/0xarkstar), [@​0xGr1mm](https://github.com/0xGr1mm), [@​0xWhiteMage](https://github.com/0xWhiteMage), [@​100yenadmin](https://github.com/100yenadmin), [@​1052326311](https://github.com/1052326311), [@​29206394](https://github.com/29206394), [@​2ndNatureAI](https://github.com/2ndNatureAI), [@​3Nya3](https://github.com/3Nya3), [@​3x3xX3N0N](https://github.com/3x3xX3N0N), [@​404Dealer](https://github.com/404Dealer), [@​4adwentures](https://github.com/4adwentures), [@​5Hyeons](https://github.com/5Hyeons), [@​686f6c61](https://github.com/686f6c61), [@​69k4xmdfm2-blip](https://github.com/69k4xmdfm2-blip), [@​6ylqq](https://github.com/6ylqq), [@​75day](https://github.com/75day), [@​a-espinoza](https://github.com/a-espinoza), [@​A-Snegin](https://github.com/A-Snegin), [@​a-yeyang](https://github.com/a-yeyang), [@​a0000001](https://github.com/a0000001), [@​A2chitect](https://github.com/A2chitect), [@​abitme](https://github.com/abitme), [@​abundantbeing](https://github.com/abundantbeing), [@​acewong7](https://github.com/acewong7), [@​acgh213](https://github.com/acgh213), [@​adamcap926](https://github.com/adamcap926), [@​addelh](https://github.com/addelh), [@​adikpb](https://github.com/adikpb), [@​Adolanium](https://github.com/Adolanium), [@​adurham](https://github.com/adurham), [@​adybag14-cyber](https://github.com/adybag14-cyber), [@​afourniernv](https://github.com/afourniernv), [@​ag9920](https://github.com/ag9920), [@​ahmadalzaro1](https://github.com/ahmadalzaro1), [@​Ahmett101](https://github.com/Ahmett101), [@​ai-ag2026](https://github.com/ai-ag2026), [@​AIalliAI](https://github.com/AIalliAI), [@​aider4ryder](https://github.com/aider4ryder), [@​Ailirag](https://github.com/Ailirag), [@​Aintworth](https://github.com/Aintworth), [@​airo7](https://github.com/airo7), [@​AiwendilInTheWoods](https://github.com/AiwendilInTheWoods), [@​akivavh](https://github.com/akivavh), [@​AL-ZiLLA](https://github.com/AL-ZiLLA), [@​Al3xand3r1987](https://github.com/Al3xand3r1987), [@​albertodepaola](https://github.com/albertodepaola), [@​aldoeliacim](https://github.com/aldoeliacim), [@​aleksclark](https://github.com/aleksclark), [@​AlexanderPrendota](https://github.com/AlexanderPrendota), [@​alexdev03](https://github.com/alexdev03), [@​AlexFucuson9](https://github.com/AlexFucuson9), [@​AlexGabbia](https://github.com/AlexGabbia), [@​AlexMnrs](https://github.com/AlexMnrs), [@​algf](https://github.com/algf), [@​Allecpu](https://github.com/Allecpu), [@​allin2](https://github.com/allin2), [@​alt-glitch](https://github.com/alt-glitch), [@​amanning3390](https://github.com/amanning3390), [@​andrexibiza](https://github.com/andrexibiza), [@​andyst-dev](https://github.com/andyst-dev), [@​angel12](https://github.com/angel12), [@​angeon922-collab](https://github.com/angeon922-collab), [@​Angriff36](https://github.com/Angriff36), [@​aniruddhaadak80](https://github.com/aniruddhaadak80), [@​annguyenNous](https://github.com/annguyenNous), [@​anuvratrastogi](https://github.com/anuvratrastogi), [@​Aoshi-Dev](https://github.com/Aoshi-Dev), [@​appletechie](https://github.com/appletechie), [@​arccat-114](https://github.com/arccat-114), [@​arcimun](https://github.com/arcimun), [@​ArthurFranckPat](https://github.com/ArthurFranckPat), [@​aryaxo](https://github.com/aryaxo), [@​asdasdadhj](https://github.com/asdasdadhj), [@​ashah360](https://github.com/ashah360), [@​AshuJoshi](https://github.com/AshuJoshi), [@​asimons81](https://github.com/asimons81), [@​assimovt](https://github.com/assimovt), [@​austinpickett](https://github.com/austinpickett), [@​AVW7](https://github.com/AVW7), [@​Axmr1](https://github.com/Axmr1), [@​aydnOktay](https://github.com/aydnOktay), [@​ayushnangia](https://github.com/ayushnangia), [@​baihemax](https://github.com/baihemax), [@​bananawalnut](https://github.com/bananawalnut), [@​Bartok9](https://github.com/Bartok9), [@​bashrusakh](https://github.com/bashrusakh), [@​bbednarski9](https://github.com/bbednarski9), [@​BearHuddleston](https://github.com/BearHuddleston), [@​benbarclay](https://github.com/benbarclay), [@​benfrank241](https://github.com/benfrank241), [@​beplee](https://github.com/beplee), [@​bgrablin](https://github.com/bgrablin), [@​bka9](https://github.com/bka9), [@​BkashJEE](https://github.com/BkashJEE), [@​BlackishGreen33](https://github.com/BlackishGreen33), [@​BlakeB254](https://github.com/BlakeB254), [@​blunkjamie-dev](https://github.com/blunkjamie-dev), [@​BobClawblaw](https://github.com/BobClawblaw), [@​bradmarshall987](https://github.com/bradmarshall987), [@​brian717](https://github.com/brian717), [@​briandevans](https://github.com/briandevans), [@​BrianFranco](https://github.com/BrianFranco), [@​BrinShadewater](https://github.com/BrinShadewater), [@​brucexu-eth](https://github.com/brucexu-eth), [@​BrunoBza](https://github.com/BrunoBza), [@​buffpesos](https://github.com/buffpesos), [@​burak33bb](https://github.com/burak33bb), [@​C-EXCITE-STUDIO](https://github.com/C-EXCITE-STUDIO), [@​c-pompa](https://github.com/c-pompa), [@​cadezhou](https://github.com/cadezhou), [@​calvinnwq](https://github.com/calvinnwq), [@​capt-marbles](https://github.com/capt-marbles), [@​carbongotfound](https://github.com/carbongotfound), [@​carlotestor](https://github.com/carlotestor), [@​Carry00](https://github.com/Carry00), [@​cation98](https://github.com/cation98), [@​ccowan93](https://github.com/ccowan93), [@​cedricziel](https://github.com/cedricziel), [@​cervantesh](https://github.com/cervantesh), [@​cfdude](https://github.com/cfdude), [@​Chadmc9889](https://github.com/Chadmc9889), [@​CharZhou](https://github.com/CharZhou), [@​chelsealong](https://github.com/chelsealong), [@​chesterXalan](https://github.com/chesterXalan), [@​chillerno1](https://github.com/chillerno1), [@​Christopher-Schulze](https://github.com/Christopher-Schulze), [@​chukirk-svg](https://github.com/chukirk-svg), [@​cicav](https://github.com/cicav), [@​citizendev9c](https://github.com/citizendev9c), [@​cj52973](https://github.com/cj52973), [@​clarkvines](https://github.com/clarkvines), [@​ClintonEmok](https://github.com/ClintonEmok), [@​clyu168](https://github.com/clyu168), [@​cmoiccool](https://github.com/cmoiccool), [@​codexbt](https://github.com/codexbt), [@​coe0718](https://github.com/coe0718), [@​Cossackx](https://github.com/Cossackx), [@​coygeek](https://github.com/coygeek), [@​crazyhulk](https://github.com/crazyhulk), [@​CrazyWillBear](https://github.com/CrazyWillBear), [@​criptogus](https://github.com/criptogus), [@​cryptoyasenka](https://github.com/cryptoyasenka), [@​ctaylor86](https://github.com/ctaylor86), [@​cvillarroel2](https://github.com/cvillarroel2), [@​cxxCoolStar](https://github.com/cxxCoolStar), [@​cycorld](https://github.com/cycorld), [@​dagbs](https://github.com/dagbs), [@​damadorPL](https://github.com/damadorPL), [@​DanBennettUK](https://github.com/DanBennettUK), [@​DanDo385](https://github.com/DanDo385), [@​DannyFengTianYu](https://github.com/DannyFengTianYu), [@​Dannyzen](https://github.com/Dannyzen), [@​danspicytaco](https://github.com/danspicytaco), [@​dante32683](https://github.com/dante32683), [@​darko-mesaros](https://github.com/darko-mesaros), [@​daromaj](https://github.com/daromaj), [@​david-bartos-phrase](https://github.com/david-bartos-phrase), [@​davidgut1982](https://github.com/davidgut1982), [@​DavidMetcalfe](https://github.com/DavidMetcalfe), [@​davidneyravyr](https://github.com/davidneyravyr), [@​daxro](https://github.com/daxro), [@​dcdexhome](https://github.com/dcdexhome), [@​deacon-botdoctor](https://github.com/deacon-botdoctor), [@​deadczarvc](https://github.com/deadczarvc), [@​deaneeth](https://github.com/deaneeth), [@​DECRUX9812](https://github.com/DECRUX9812), [@​deepeet-git](https://github.com/deepeet-git), [@​deniqlab](https://github.com/deniqlab), [@​DeseretSaint](https://github.com/DeseretSaint), [@​dhanesh](https://github.com/dhanesh), [@​Dhruv7201](https://github.com/Dhruv7201), [@​dhruvkej9](https://github.com/dhruvkej9), [@​digitalbase](https://github.com/digitalbase), [@​DmytroVolodymyrson](https://github.com/DmytroVolodymyrson), [@​dokterdok](https://github.com/dokterdok), [@​Dolverin](https://github.com/Dolverin), [@​dombejar](https://github.com/dombejar), [@​doncazper](https://github.com/doncazper), [@​dougatbuck](https://github.com/dougatbuck), [@​douxy1994](https://github.com/douxy1994), [@​dpersek](https://github.com/dpersek), [@​dplush](https://github.com/dplush), [@​Drexuxux](https://github.com/Drexuxux), [@​drissman](https://github.com/drissman), [@​dromai](https://github.com/dromai), [@​dtownsel](https://github.com/dtownsel), [@​duclucky](https://github.com/duclucky), [@​Dudeman456](https://github.com/Dudeman456), [@​Dusk1e](https://github.com/Dusk1e), [@​dvbaecker](https://github.com/dvbaecker), [@​e-macgregor](https://github.com/e-macgregor), [@​EAbaracus](https://github.com/EAbaracus), [@​eaglezzz0522-cloud](https://github.com/eaglezzz0522-cloud), [@​Eapwrk](https://github.com/Eapwrk), [@​easyvibecoding](https://github.com/easyvibecoding), [@​echoes666](https://github.com/echoes666), [@​egilewski](https://github.com/egilewski), [@​ehz0ah](https://github.com/ehz0ah), [@​ekinnee](https://github.com/ekinnee), [@​ekzhang](https://github.com/ekzhang), [@​elbukott1](https://github.com/elbukott1), [@​elisam0](https://github.com/elisam0), [@​elphamale](https://github.com/elphamale), [@​ElSnacko](https://github.com/ElSnacko), [@​embwl0x](https://github.com/embwl0x), [@​emozilla](https://github.com/emozilla), [@​EMT5320](https://github.com/EMT5320), [@​EndeavorYen](https://github.com/EndeavorYen), [@​Enough1122](https://github.com/Enough1122), [@​enwaiax](https://github.com/enwaiax), [@​epeterpanz](https://github.com/epeterpanz), [@​EpicIsTheOne](https://github.com/EpicIsTheOne), [@​eric-senyao](https://github.com/eric-senyao), [@​ernst-bablick](https://github.com/ernst-bablick), [@​Eros-ITA](https://github.com/Eros-ITA), [@​erosika](https://github.com/erosika), [@​ethernet8023](https://github.com/ethernet8023), [@​etzelvon](https://github.com/etzelvon), [@​EvanProgramming](https://github.com/EvanProgramming), [@​EvenXieWF](https://github.com/EvenXieWF), [@​explainanalyze](https://github.com/explainanalyze), [@​f-trycua](https://github.com/f-trycua), [@​fanfan343](https://github.com/fanfan343), [@​fangliquanflq](https://github.com/fangliquanflq), [@​fattchris](https://github.com/fattchris), [@​felix-windsor](https://github.com/felix-windsor), [@​Finn763](https://github.com/Finn763), [@​flaviovargasbrandao](https://github.com/flaviovargasbrandao), [@​Fly-onlyone](https://github.com/Fly-onlyone), [@​flyingdoubleG](https://github.com/flyingdoubleG), [@​forkercat](https://github.com/forkercat), [@​francialisomlimoeiro](https://github.com/francialisomlimoeiro), [@​francip](https://github.com/francip), [@​frankmendes1979](https://github.com/frankmendes1979), [@​fred0m](https://github.com/fred0m), [@​frendo](https://github.com/frendo), [@​frizikk](https://github.com/frizikk), [@​frohsinnllc](https://github.com/frohsinnllc), [@​Frowtek](https://github.com/Frowtek), [@​fukutake1207](https://github.com/fukutake1207), [@​fyzanshaik](https://github.com/fyzanshaik), [@​GarlicGo](https://github.com/GarlicGo), [@​Gateton](https://github.com/Gateton), [@​georgell-ceo](https://github.com/georgell-ceo), [@​gfriesen1](https://github.com/gfriesen1), [@​gigabyte22](https://github.com/gigabyte22), [@​gigashad](https://github.com/gigashad), [@​gijoby](https://github.com/gijoby), [@​gitong](https://github.com/gitong), [@​gkd2323c](https://github.com/gkd2323c), [@​gnanirahulnutakki](https://github.com/gnanirahulnutakki), [@​GodsBoy](https://github.com/GodsBoy), [@​gokay-ai](https://github.com/gokay-ai), [@​goktugvatandas](https://github.com/goktugvatandas), [@​golldyck](https://github.com/golldyck), [@​goodchang77](https://github.com/goodchang77), [@​GottZ](https://github.com/GottZ), [@​grahfmusic](https://github.com/grahfmusic), [@​gregorustar-maker](https://github.com/gregorustar-maker), [@​greyvito](https://github.com/greyvito), [@​gsy324](https://github.com/gsy324), [@​guanla-zz](https://github.com/guanla-zz), [@​guilhermeartileshubsai](https://github.com/guilhermeartileshubsai), [@​guilhermeraiuga](https://github.com/guilhermeraiuga), [@​h-mascot](https://github.com/h-mascot), [@​h8hawk](https://github.com/h8hawk), [@​Haakam21](https://github.com/Haakam21), [@​Haik-G](https://github.com/Haik-G), [@​halaprix](https://github.com/halaprix), [@​Halldrix](https://github.com/Halldrix), [@​handnewb](https://github.com/handnewb), [@​Hangzian](https://github.com/Hangzian), [@​hanhvs](https://github.com/hanhvs), [@​hansai-art](https://github.com/hansai-art), [@​HAOWANG116](https://github.com/HAOWANG116), [@​HarishDarko](https://github.com/HarishDarko), [@​hbentel](https://github.com/hbentel), [@​helix4u](https://github.com/helix4u), [@​HenryGHJ](https://github.com/HenryGHJ), [@​hermias1](https://github.com/hermias1), [@​HexLab98](https://github.com/HexLab98), [@​hillimited](https://github.com/hillimited), [@​Hjharris1](https://github.com/Hjharris1), [@​hkfiberlaser-svg](https://github.com/hkfiberlaser-svg), [@​honor2030](https://github.com/honor2030), [@​howdeploy](https://github.com/howdeploy), [@​hsearcy](https://github.com/hsearcy), [@​huklaa](https://github.com/huklaa), [@​hustwkr](https://github.com/hustwkr), [@​hutao562](https://github.com/hutao562), [@​hxwvaa](https://github.com/hxwvaa), [@​IAvecilla](https://github.com/IAvecilla), [@​icemeng](https://github.com/icemeng), [@​infinitycrew39](https://github.com/infinitycrew39), [@​infocentr](https://github.com/infocentr), [@​InphinitiZ](https://github.com/InphinitiZ), [@​insane66613](https://github.com/insane66613), [@​Inspired-by-Atmosphere](https://github.com/Inspired-by-Atmosphere), [@​intelac](https://github.com/intelac), [@​intellectronica](https://github.com/intellectronica), [@​isak-ialogics](https://github.com/isak-ialogics), [@​ishanparihar](https://github.com/ishanparihar), [@​isheng-eqi](https://github.com/isheng-eqi), [@​Ishman82](https://github.com/Ishman82), [@​iskysun96](https://github.com/iskysun96), [@​iso2kx](https://github.com/iso2kx), [@​itsflownium](https://github.com/itsflownium), [@​izumi0uu](https://github.com/izumi0uu), [@​Jaaneek](https://github.com/Jaaneek), [@​Jackal991](https://github.com/Jackal991), [@​jackijianxa](https://github.com/jackijianxa), [@​jackoconner45](https://github.com/jackoconner45), [@​jackulau](https://github.com/jackulau), [@​jaimedhenriques](https://github.com/jaimedhenriques), [@​james47kjv](https://github.com/james47kjv), [@​jbagdonas](https://github.com/jbagdonas), [@​jcjc81](https://github.com/jcjc81), [@​jdgg777](https://github.com/jdgg777), [@​jeeves-assistant](https://github.com/jeeves-assistant), [@​jeff-mettel](https://github.com/jeff-mettel), [@​Jefftree](https://github.com/Jefftree), [@​JElfferich](https://github.com/JElfferich), [@​jeremyrandria-debug](https://github.com/jeremyrandria-debug), [@​JinUltimate1995](https://github.com/JinUltimate1995), [@​jirathip-k](https://github.com/jirathip-k), [@​jmmaloney4](https://github.com/jmmaloney4), [@​JoaoMarcos44](https://github.com/JoaoMarcos44), [@​joe-rodgers](https://github.com/joe-rodgers), [@​joe050860](https://github.com/joe050860), [@​John-Lussier](https://github.com/John-Lussier), [@​Johnny-xuan](https://github.com/Johnny-xuan), [@​johnrazmus](https://github.com/johnrazmus), [@​johnsonAyo](https://github.com/johnsonAyo), [@​jonpol01](https://github.com/jonpol01), [@​JonthanaHanh](https://github.com/JonthanaHanh), [@​JoshPaulie](https://github.com/JoshPaulie), [@​Jreevo](https://github.com/Jreevo), [@​jtstothard](https://github.com/jtstothard), [@​Julientalbot](https://github.com/Julientalbot), [@​justcarlosm](https://github.com/justcarlosm), [@​justinbowes](https://github.com/justinbowes), [@​justinjohnson25600](https://github.com/justinjohnson25600), [@​jwtor7](https://github.com/jwtor7), [@​k0rnacki](https://github.com/k0rnacki), [@​kadiratesdev](https://github.com/kadiratesdev), [@​Kailigithub](https://github.com/Kailigithub), [@​kaishi00](https://github.com/kaishi00), [@​KarateWilly](https://github.com/KarateWilly), [@​kas-cor](https://github.com/kas-cor), [@​katie-lpd](https://github.com/katie-lpd), [@​Kavyrocom](https://github.com/Kavyrocom), [@​KBANTH](https://github.com/KBANTH), [@​kchernev](https://github.com/kchernev), [@​KeaneYan](https://github.com/KeaneYan), [@​kernel-t1](https://github.com/kernel-t1), [@​KeroZelvin](https://github.com/KeroZelvin), [@​kerpopule](https://github.com/kerpopule), [@​KHALIDagara](https://github.com/KHALIDagara), [@​KhanCold](https://github.com/KhanCold), [@​khanhngoo](https://github.com/khanhngoo), [@​KIAgent01](https://github.com/KIAgent01), [@​kim-miram](https://github.com/kim-miram), [@​kimyxx](https://github.com/kimyxx), [@​Kinkoolino-Hermes](https://github.com/Kinkoolino-Hermes), [@​kinsolee](https://github.com/kinsolee), [@​kitsonk](https://github.com/kitsonk), [@​klaus765](https://github.com/klaus765), [@​koltyj](https://github.com/koltyj), [@​konsisumer](https://github.com/konsisumer), [@​kronexoi](https://github.com/kronexoi), [@​krunkosaurus](https://github.com/krunkosaurus), [@​kshitijk4poor](https://github.com/kshitijk4poor), [@​kudapara](https://github.com/kudapara), [@​kvnloo](https://github.com/kvnloo), [@​kweiner](https://github.com/kweiner), [@​kyssta-exe](https://github.com/kyssta-exe), [@​Kyzcreig](https://github.com/Kyzcreig), [@​laithrw](https://github.com/laithrw), [@​lakshyaag-tavily](https://github.com/lakshyaag-tavily), [@​landaun](https://github.com/landaun), [@​laulopezreal](https://github.com/laulopezreal), [@​laviesony](https://github.com/laviesony), [@​lazy-idler](https://github.com/lazy-idler), [@​LBeghini](https://github.com/LBeghini), [@​leeclouddragon](https://github.com/leeclouddragon), [@​Leegenux](https://github.com/Leegenux), [@​LemonSchneid](https://github.com/LemonSchneid), [@​LeonardoLGDS](https://github.com/LeonardoLGDS), [@​leonphull](https://github.com/leonphull), [@​LeonSGP43](https://github.com/LeonSGP43), [@​lepetitprince716-prog](https://github.com/lepetitprince716-prog), [@​Lesnak1](https://github.com/Lesnak1), [@​lesterlxt](https://github.com/lesterlxt), [@​lesyuk](https://github.com/lesyuk), [@​lEWFkRAD](https://github.com/lEWFkRAD), [@​lextiz](https://github.com/lextiz), [@​lgy1027](https://github.com/lgy1027), [@​Lidang-Jiang](https://github.com/Lidang-Jiang), [@​lilShawtty-byte](https://github.com/lilShawtty-byte), [@​lin-hongkuan](https://github.com/lin-hongkuan), [@​liuhao1024](https://github.com/liuhao1024), [@​liusencomic-cyber](https://github.com/liusencomic-cyber), [@​lkz-de](https://github.com/lkz-de), [@​loafoe](https://github.com/loafoe), [@​locker95](https://github.com/locker95), [@​LordMelkor](https://github.com/LordMelkor), [@​lorzl](https://github.com/lorzl), [@​lost9999](https://github.com/lost9999), [@​loulanyue](https://github.com/loulanyue), [@​LovePlayCode](https://github.com/LovePlayCode), [@​luckygreen](https://github.com/luckygreen), [@​lukeroberts](https://github.com/lukeroberts), [@​luoxiao6645](https://github.com/luoxiao6645), [@​luxles](https://github.com/luxles), [@​m1k3s0](https://github.com/m1k3s0), [@​Mabolla](https://github.com/Mabolla), [@​MagMueller](https://github.com/MagMueller), [@​magnus919](https://github.com/magnus919), [@​magnuslundstedt](https://github.com/magnuslundstedt), [@​MaheshBhushan](https://github.com/MaheshBhushan), [@​MarcoFernstaedt](https://github.com/MarcoFernstaedt), [@​mariobgsp](https://github.com/mariobgsp), [@​marketing2981](https://github.com/marketing2981), [@​markmcd](https://github.com/markmcd), [@​markmnl](https://github.com/markmnl), [@​MarkVLK](https://github.com/MarkVLK), [@​marzukia](https://github.com/marzukia), [@​Mat-London](https://github.com/Mat-London), [@​matsvarn](https://github.com/matsvarn), [@​mattprusak](https://github.com/mattprusak), [@​MaximCrabbe](https://github.com/MaximCrabbe), [@​maxmilian](https://github.com/maxmilian), [@​mehmetkr-31](https://github.com/mehmetkr-31), [@​Mengchee118](https://github.com/Mengchee118), [@​menhguin](https://github.com/menhguin), [@​metamindedu](https://github.com/metamindedu), [@​michaelHMK](https://github.com/michaelHMK), [@​michaelsam94](https://github.com/michaelsam94), [@​milnerrad](https://github.com/milnerrad), [@​MindDragonLabs](https://github.com/MindDragonLabs), [@​misterdas](https://github.com/misterdas), [@​mjolley9](https://github.com/mjolley9), [@​mmcallister8](https://github.com/mmcallister8), [@​mmcclean-aws](https://github.com/mmcclean-aws), [@​mmchuangyt-ai](https://github.com/mmchuangyt-ai), [@​moisesvalero](https://github.com/moisesvalero), [@​mollusk](https://github.com/mollusk), [@​monerostar](https://github.com/monerostar), [@​Morad37](https://github.com/Morad37), [@​mrkillbob](https://github.com/mrkillbob), [@​mrmixx-max](https://github.com/mrmixx-max), [@​mrsucesso](https://github.com/mrsucesso), [@​MrTheSoulz](https://github.com/MrTheSoulz), [@​MustafaK99](https://github.com/MustafaK99), [@​myk0la-b](https://github.com/myk0la-b), [@​mzkarami](https://github.com/mzkarami), [@​n-leezy](https://github.com/n-leezy), [@​n1majne3](https://github.com/n1majne3), [@​NaMinhyeok](https://github.com/NaMinhyeok), [@​nanami7777777](https://github.com/nanami7777777), [@​nankingjing](https://github.com/nankingjing), [@​natebransc](https://github.com/natebransc), [@​nateEc](https://github.com/nateEc), [@​Navlem](https://github.com/Navlem), [@​nbxuhk](https://github.com/nbxuhk), [@​nductien](https://github.com/nductien), [@​Ne0teric](https://github.com/Ne0teric), [@​NealZhouPanda](https://github.com/NealZhouPanda), [@​necoweb3](https://github.com/necoweb3), [@​negroni334](https://github.com/negroni334), [@​nftpoetrist](https://github.com/nftpoetrist), [@​nicochase](https://github.com/nicochase), [@​Nicolas-Formenton](https://github.com/Nicolas-Formenton), [@​nicolasdmolina](https://github.com/nicolasdmolina), [@​nikitaBarkov](https://github.com/nikitaBarkov), [@​NikolaRHristov](https://github.com/NikolaRHristov), [@​noahingh](https://github.com/noahingh), [@​nolanchic](https://github.com/nolanchic), [@​NorethSea](https://github.com/NorethSea), [@​notkisk](https://github.com/notkisk), [@​notwitcheer](https://github.com/notwitcheer), [@​NousResearch](https://github.com/NousResearch), [@​null-runner](https://github.com/null-runner), [@​ojassharma7](https://github.com/ojassharma7), [@​oliver-mee](https://github.com/oliver-mee), [@​olympusbuildz](https://github.com/olympusbuildz), [@​OmarB97](https://github.com/OmarB97), [@​ooiuuii](https://github.com/ooiuuii), [@​openclaww-xz](https://github.com/openclaww-xz), [@​opti-josh-holt](https://github.com/opti-josh-holt), [@​osgeek90](https://github.com/osgeek90), [@​ousiaresearch](https://github.com/ousiaresearch), [@​OutThisLife](https://github.com/OutThisLife), [@​Owen-narcissus](https://github.com/Owen-narcissus), [@​paoloantinori](https://github.com/paoloantinori), [@​Parker-Fawcett](https://github.com/Parker-Fawcett), [@​pasevin](https://github.com/pasevin), [@​pasmud](https://github.com/pasmud), [@​PaulBlackSwan](https://github.com/PaulBlackSwan), [@​paultaki](https://github.com/paultaki), [@​peetteerr](https://github.com/peetteerr), [@​pefontana](https://github.com/pefontana), [@​Per0-1](https://github.com/Per0-1), [@​pierrenode](https://github.com/pierrenode), [@​PINKIIILQWQ](https://github.com/PINKIIILQWQ), [@​pittosporum-seu](https://github.com/pittosporum-seu), [@​pju-hoge](https://github.com/pju-hoge), [@​plcunha](https://github.com/plcunha), [@​Pluviobyte](https://github.com/Pluviobyte), [@​pmos69](https://github.com/pmos69), [@​pnascimento9596](https://github.com/pnascimento9596), [@​poisdahl](https://github.com/poisdahl), [@​potatosalad](https://github.com/potatosalad), [@​prashantjain25](https://github.com/prashantjain25), [@​PRATHAMESH75](https://github.com/PRATHAMESH75), [@​professorpalmer](https://github.com/professorpalmer), [@​projetsjsl](https://github.com/projetsjsl), [@​protas-box](https://github.com/protas-box), [@​qazasdsdqaza](https://github.com/qazasdsdqaza), [@​QDung210](https://github.com/QDung210), [@​qixuancao](https://github.com/qixuancao), [@​QuarkAssistant](https://github.com/QuarkAssistant), [@​quocanh261997](https://github.com/quocanh261997), [@​ragingbulld](https://github.com/ragingbulld), [@​rainbowgore](https://github.com/rainbowgore), [@​rapsealk](https://github.com/rapsealk), [@​Raven26-VooDoo](https://github.com/Raven26-VooDoo), [@​RaviTharuma](https://github.com/RaviTharuma), [@​RayCharlizard](https://github.com/RayCharlizard), [@​razultull](https://github.com/razultull), [@​re-ITRT](https://github.com/re-ITRT), [@​Reksely](https://github.com/Reksely), [@​RelaxJonh](https://github.com/RelaxJonh), [@​repfigit](https://github.com/repfigit), [@​RGerrish](https://github.com/RGerrish), [@​RibatTRW](https://github.com/RibatTRW), [@​RichardGuan1](https://github.com/RichardGuan1), [@​RichardHojunJang](https://github.com/RichardHojunJang), [@​richardhowes](https://github.com/richardhowes), [@​RickyYii](https://github.com/RickyYii), [@​rikkarth](https://github.com/rikkarth), [@​rille111](https://github.com/rille111), [@​Ringo6107](https://github.com/Ringo6107), [@​rjhilgefort](https://github.com/rjhilgefort), [@​rjvandeve](https://github.com/rjvandeve), [@​rkfshakti](https://github.com/rkfshakti), [@​rlaope](https://github.com/rlaope), [@​Rmohid](https://github.com/Rmohid), [@​rob-maron](https://github.com/rob-maron), [@​rodrigogs](https://github.com/rodrigogs), [@​royalaid](https://github.com/royalaid), [@​royzhrxy-glitch](https://github.com/royzhrxy-glitch), [@​rroverin](https://github.com/rroverin), [@​rshi0212](https://github.com/rshi0212), [@​rsk-731](https://github.com/rsk-731), [@​rtcopenclawgh](https://github.com/rtcopenclawgh), [@​ruangraung](https://github.com/ruangraung), [@​rudrakshchahal](https://github.com/rudrakshchahal), [@​ruochu88s](https://github.com/ruochu88s), [@​rweddle](https://github.com/rweddle), [@​ryankhart](https://github.com/ryankhart), [@​S-Claw](https://github.com/S-Claw), [@​Sahil-SS9](https://github.com/Sahil-SS9), [@​salch-cred](https://github.com/salch-cred), [@​sam7894604](https://github.com/sam7894604), [@​samclams](https://github.com/samclams), [@​saralilyb](https://github.com/saralilyb), [@​sashmatash](https://github.com/sashmatash), [@​sasquatch9818](https://github.com/sasquatch9818), [@​satotakumi](https://github.com/satotakumi), [@​ScaleLeanChris](https://github.com/ScaleLeanChris), [@​SeashoreShi](https://github.com/SeashoreShi), [@​serefyarar](https://github.com…
melon-xf
added a commit
to melon-xf/hermes-agent
that referenced
this pull request
Sep 3, 2026
…failure reason Follow-up to c0d974b (NousResearch#79741). Three review findings against that commit, none of which change the escalation behaviour it shipped. 1. The recovery decision lived inline in `_handle_message_with_agent`, a ~2000-line async method, so the only way to pin it was a test that read `inspect.getsource(...)` and asserted on substrings. AGENTS.md bans reading source in tests outright and names this exact situation: "if the logic lives inline in a god-file (gateway/run.py) and extracting it feels disruptive: that's the actual signal to do the extraction, not to regex around it." Those tests were not merely stylistically wrong, they were actively harmful. One asserted the substring `_new_tokens < _approx_tokens` was PRESENT -- so it passed while the gate had the bug that substring represents, and had to be edited when the gate was fixed. It failed on correct code and passed on broken code, in one assertion. Extracted `hygiene_compaction_recovered()` as a module-level pure predicate and replaced the three source-reading tests with eight direct unit tests. The extraction immediately earned itself: the new tests caught a `NameError` (the predicate called `compression_made_progress` while the module bound it under an alias) that a source-text assertion cannot see, because the symbol is spelled correctly in the source and only fails at runtime. 2. The gate inferred "did the transcript actually get rewritten" from a numeric side effect -- the degenerate "did not rotate or compact in place" path (NousResearch#21301) reuses the pre-compression counts -- when the booleans `_hyg_rotated` / `_hyg_in_place` were already in scope and explicitly set False on that path. The predicate now takes them directly, so a future edit that re-estimates instead of reusing the old counts cannot silently defeat the escalation. 3. `_record_hygiene_cooldown` passed no `error` to `record_compression_failure_cooldown`, which writes `compression_failure_error` unconditionally -- so a hygiene failure clobbered to NULL whatever reason the in-conversation path had recorded, and readers then show the user "unknown error" (agent/manual_compression_feedback.py, gateway/slash_commands.py). The reason was already in hand at both call sites. Pre-existing from NousResearch#74136 but amplified by escalation: a blank reason on a 45-minute cooldown is far more user-visible than on a 5-minute one. Also: the ladder docstring described the compressor's absolute 60/300/900s ladder while the constant is multipliers (1, 3, 9); the config docs still described `hygiene_failure_cooldown_seconds` as a flat interval rather than the first rung of a capped ladder; and `PersistentState.hygiene_failure_streak` now documents that it is process-local by design -- keying on `session_key` is what survives compaction rotation, which the persisted `compression_*_streak` columns cannot express since they key on the rotating `session_id`. Making it durable is a schema change, tracked on NousResearch#79624 rather than smuggled in here. Also replaces the file's hand-written `_Runner` stub with `object.__new__(GatewayRunner)` (already the idiom elsewhere in the same file). The stub reimplemented `_session_state` and `_peek_session_state`, so the tests exercised copies that could drift from production; using the real class immediately made one assertion stronger -- on a fresh runner `_sessions` does not exist at all until something materialises it, so the reset provably did not even create the map. A review pass on this follow-up then caught that the CALL SITE was still unbound: deleting the whole `if not _hyg_aborted: if hygiene_compaction_recovered(...)` block left every ladder test green, because the unit tests prove the predicate correct without proving it is wired in. The merged commit had the same gap and its only cover was the banned source-reading test. `test_session_hygiene_forces_in_place_compaction_with_bound_session_db` now spies the reset on a genuine in-place compaction, so deleting the wiring fails. Two earlier attempts at this test did NOT close the gap -- asserting on streak VALUES passes either way, since the streak is 0 whether or not the gate ran; only a positive spy assertion on a recovering run detects the deletion. Same pass also corrected an overstatement: point (2) is hardening, not a live bug. The degenerate path also sets `_new_count = _msg_count` and `_new_tokens = _approx_tokens`, and `compression_made_progress(n, n, t, t)` is always False, so the merged code already declined to reset there. A 200k-trial fuzz over the reachable state space found zero behavioural disagreements between the merged gate and this one. The guard's value is surviving a future edit that stops reusing those counts. Tests: 28 in tests/gateway/test_hygiene_failure_cooldown_ladder.py (8 new unit tests for the predicate, 3 for reason forwarding, 3 source-reading tests deleted). All 5 mutations caught -- including one that restores the hand-rolled comparison and one that removes the rotated/in_place guard. Two mutations initially SURVIVED and exposed vacuous tests of my own: the no-rewrite test used counts the progress predicate already rejects, so it passed without binding the guard at all; it now passes counts that read as progress on their own, proving the guard is what rejects them. gateway hygiene + session-state + agent compression-progress suites: 54 passed; ruff clean. Refs NousResearch#79624
smfworks
pushed a commit
to smfworks/hermes-agent
that referenced
this pull request
Sep 7, 2026
…failure reason Follow-up to c0d974b (NousResearch#79741). Three review findings against that commit, none of which change the escalation behaviour it shipped. 1. The recovery decision lived inline in `_handle_message_with_agent`, a ~2000-line async method, so the only way to pin it was a test that read `inspect.getsource(...)` and asserted on substrings. AGENTS.md bans reading source in tests outright and names this exact situation: "if the logic lives inline in a god-file (gateway/run.py) and extracting it feels disruptive: that's the actual signal to do the extraction, not to regex around it." Those tests were not merely stylistically wrong, they were actively harmful. One asserted the substring `_new_tokens < _approx_tokens` was PRESENT -- so it passed while the gate had the bug that substring represents, and had to be edited when the gate was fixed. It failed on correct code and passed on broken code, in one assertion. Extracted `hygiene_compaction_recovered()` as a module-level pure predicate and replaced the three source-reading tests with eight direct unit tests. The extraction immediately earned itself: the new tests caught a `NameError` (the predicate called `compression_made_progress` while the module bound it under an alias) that a source-text assertion cannot see, because the symbol is spelled correctly in the source and only fails at runtime. 2. The gate inferred "did the transcript actually get rewritten" from a numeric side effect -- the degenerate "did not rotate or compact in place" path (NousResearch#21301) reuses the pre-compression counts -- when the booleans `_hyg_rotated` / `_hyg_in_place` were already in scope and explicitly set False on that path. The predicate now takes them directly, so a future edit that re-estimates instead of reusing the old counts cannot silently defeat the escalation. 3. `_record_hygiene_cooldown` passed no `error` to `record_compression_failure_cooldown`, which writes `compression_failure_error` unconditionally -- so a hygiene failure clobbered to NULL whatever reason the in-conversation path had recorded, and readers then show the user "unknown error" (agent/manual_compression_feedback.py, gateway/slash_commands.py). The reason was already in hand at both call sites. Pre-existing from NousResearch#74136 but amplified by escalation: a blank reason on a 45-minute cooldown is far more user-visible than on a 5-minute one. Also: the ladder docstring described the compressor's absolute 60/300/900s ladder while the constant is multipliers (1, 3, 9); the config docs still described `hygiene_failure_cooldown_seconds` as a flat interval rather than the first rung of a capped ladder; and `PersistentState.hygiene_failure_streak` now documents that it is process-local by design -- keying on `session_key` is what survives compaction rotation, which the persisted `compression_*_streak` columns cannot express since they key on the rotating `session_id`. Making it durable is a schema change, tracked on NousResearch#79624 rather than smuggled in here. Also replaces the file's hand-written `_Runner` stub with `object.__new__(GatewayRunner)` (already the idiom elsewhere in the same file). The stub reimplemented `_session_state` and `_peek_session_state`, so the tests exercised copies that could drift from production; using the real class immediately made one assertion stronger -- on a fresh runner `_sessions` does not exist at all until something materialises it, so the reset provably did not even create the map. A review pass on this follow-up then caught that the CALL SITE was still unbound: deleting the whole `if not _hyg_aborted: if hygiene_compaction_recovered(...)` block left every ladder test green, because the unit tests prove the predicate correct without proving it is wired in. The merged commit had the same gap and its only cover was the banned source-reading test. `test_session_hygiene_forces_in_place_compaction_with_bound_session_db` now spies the reset on a genuine in-place compaction, so deleting the wiring fails. Two earlier attempts at this test did NOT close the gap -- asserting on streak VALUES passes either way, since the streak is 0 whether or not the gate ran; only a positive spy assertion on a recovering run detects the deletion. Same pass also corrected an overstatement: point (2) is hardening, not a live bug. The degenerate path also sets `_new_count = _msg_count` and `_new_tokens = _approx_tokens`, and `compression_made_progress(n, n, t, t)` is always False, so the merged code already declined to reset there. A 200k-trial fuzz over the reachable state space found zero behavioural disagreements between the merged gate and this one. The guard's value is surviving a future edit that stops reusing those counts. Tests: 28 in tests/gateway/test_hygiene_failure_cooldown_ladder.py (8 new unit tests for the predicate, 3 for reason forwarding, 3 source-reading tests deleted). All 5 mutations caught -- including one that restores the hand-rolled comparison and one that removes the rotated/in_place guard. Two mutations initially SURVIVED and exposed vacuous tests of my own: the no-rewrite test used counts the progress predicate already rejects, so it passed without binding the guard at all; it now passes counts that read as progress on their own, proving the guard is what rejects them. gateway hygiene + session-state + agent compression-progress suites: 54 passed; ruff clean. Refs NousResearch#79624
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A gateway session whose summary model keeps timing out no longer retries compaction on the same fixed interval forever.
Fixes the one real defect behind #79624. Note the reported
exit(1)is not a crash — see Context.Context — what actually happens, concretely
Who is affected: anyone running the gateway with a session large enough to trigger hygiene compaction whose summary (auxiliary) model is slow or failing. The reporter hit it on a 1GB droplet with a 1053-message / ~119.5k-token session.
The bug: the in-agent compressor escalates repeat summary timeouts 60 → 300 → 900s (
agent/context_compressor.py:1983-1991), but that ladder reads the in-memory_consecutive_timeout_failurescounter andbind_session_state()zeroes it (context_compressor.py:1645). Session hygiene constructs a freshAIAgentfor every run (gateway/run.py:16820) and re-binds state each time — so from the gateway the streak is structurally always 0, and only the flathygiene_failure_cooldown_secondscould ever be recorded.Result: an un-compactable session re-attempted compaction every 300s indefinitely, which matches the reporter's "observed 5 times over 5 days" exactly. Each attempt burns the full
hygiene_timeout_secondsbefore giving up.Recorded cooldown deadlines, before vs after (E2E, real imports, config default base of 300s):
Changes
gateway/session_state.py:PersistentState.hygiene_failure_streak. This scope outlives the per-run agent and is not cleared by turn/boundary resets, which is the whole point —TurnStatewould be wiped every turn and reproduce the original bug.gateway/run.py:_hygiene_cooldown_for_failure()bumps the streak and returns the escalated cooldown (base × 1/3/9, saturating);_reset_hygiene_failure_streak()clears it. Wired at both failure sites (progress timeout, aborted compression) and reset on a real compression.The ladder multiplies the configured base, so operators who tuned
hygiene_failure_cooldown_secondskeep their first rung. Per-session, so one wedged chat cannot penalize other conversations.Post-review hardening
The first cut passed its own tests and mutation check, but
hermes-pr-reviewPhase 2c and/simplify-codebetween them found five real defects, all now fixed and pinned by tests:The recovery gate hand-rolled a predicate that already existed. It compared
_new_tokens < _approx_tokens; the canonicalcompression_made_progress(agent/turn_context.py, [Bug]: Compression token savings ignored when message count is unchanged, causing false context exhaustion #39548) disagrees on 3 of 5 cases:The two "missed" rows are the dangerous ones: a genuinely recovered session would keep escalating forever. Now reuses the shared predicate, promoted to a public name with the old private name kept as a back-compat alias so the existing importer and any patcher of that symbol keep working.
The reset could never let the cooldown escalate. It was gated on "not aborted", but the degenerate "did not rotate or compact in place" branch ([bug] /compress and gateway hygiene compression destroy original transcript — tmp_agent missing session_db kwarg #21301) is not aborted and yields zero reduction — so a session wedged there cleared its streak on every run, silently defeating the entire fix. Now gated on real progress.
No absolute ceiling.
base × 9reaches 9h at an operator base of 3600s, indistinguishable from "compaction switched off". Added_HYGIENE_COOLDOWN_MAX_SECONDS = 3600, mirroring the in-file_RECONNECT_BACKOFF_CAPprecedent.The reset materialised a
_sessionsentry (never evicted) to write a 0 that was already 0. Now peeks.The abort verdict was probed twice, leaving the reset/record mutual exclusion implicit — a future
awaitbetween the probes would have broken it silently. Computed once into_hyg_aborted.Deliberately not in scope
Three further gaps found while investigating are maintainer policy calls, not defects, and are written up on #79624 rather than changed here:
config_defaults.py:641) vs in-agent 120s (:650) vs the aux client's 300s floor (agent/auxiliary_client.py:7514). Retuning this changes behavior for every gateway user.hermes doctorcheck orhermes sessions listmarker for a session stuck in a compression-failure cooldown; prune/archive deliberately preserve active sessions, so an oversized actively-used session is untouched by every retention mechanism.On the reported
exit(1)Not a crash, and not caused by compaction. It is the deliberate
_signal_initiated_shutdownpath (gateway/run.py:26746-26751, added in #5646) that letssystemd Restart=on-failurerevive the gateway after a bare SIGTERM, and it fires on everysystemctl restartbecause the installed unit has noExecStopand onlyhermes gateway stop/restartwrite the planned-stop marker. The compaction log lines appear after the shutdown line because the gateway-owned executor is torn down withshutdown(wait=False, cancel_futures=True)(run.py:21164, no join), so an in-flight turn keeps logging during teardown. OOM is ruled out — a kernel OOM kill reportsstatus=9/KILL, notexited/1. The reporter's other two asks (recoverable timeout wrap,max_attemptscap) are already implemented on main; full analysis on the issue.Validation
os.environ/ schema / serializationtests/gateway/test_hygiene_failure_cooldown_ladder.py: escalation, saturation, the absolute cap, per-session isolation, reset-on-recovery, custom/zero base,PersistentStatescoping, degraded runners, the reduction gate, and that the escalated value is what reaches the state DB.TurnState, and share the streak globally. The harness hard-errors if a mutation cannot be applied — a silently no-op mutation check is worse than none, and an earlier version of it was silently no-opping after a refactor.PersistentState.__dict__allocation), creates no new_sessionsentries, and escalation makes state.db writes less frequent, not more.tests/gateway/4859 passed; the 14 failures are byte-identical to cleanupstream/main(verified by diffing sorted failure lists from two worktrees) across 8 unrelated files — zero regressions.raiseso it cannot also reach the abort site (exactly-once increment), and the reset is the exact logical complement of the abort branch (no reset-then-re-increment pinning at rung 1).ruffclean on all three files.Reported by @yucezerey (#79624), whose
state.dbcolumn dump and "deleting the session fixed it" datapoint made the real mechanism findable.Refs #79624