Hermes Agent

Hermes closes restart, preview and approval state gaps

Hermes proposes counting final delivery as active work so restart cannot duplicate a reply, makes compression previews read-only across Desktop and TUI, and adds run-scoped approval events to streamed API sessions.

← Back to homeOriginal source ↗

NousResearch/hermes-agent PR #92589 is the inspected primary source: “fix(gateway): count in-flight final delivery as active work so restart can't duplicate a reply.” The related records below were inspected as supporting context rather than independent confirmation.

The facts

- NousResearch/hermes-agent PR #92589 was created 2026-08-23T00:23:56Z and was open at inspection; its title is “fix(gateway): count in-flight final delivery as active work so restart can't duplicate a reply.” - NousResearch/hermes-agent PR #92591 was created 2026-08-23T00:31:04Z and was open at inspection; its title is “fix(compression): keep Desktop and TUI previews from mutating sessions.” - NousResearch/hermes-agent PR #92592 was created 2026-08-23T00:33:25Z and was open at inspection; its title is “feat(api): support approvals in session chat streams.” - The repository reported 234393 stars, 34796 open issues, default branch main, and last push 2026-08-23T00:08:29Z. - The latest tagged-release baseline checked was v2026.8.19 published 2026-08-21T12:16:39Z.

What changed

NousResearch/hermes-agent PR #92589 reports: Summary A graceful restart can replace the gateway process while a final response is between mark_attempting and mark_delivered in the delivery ledger. The next boot sweeps the still-attempting row and redelivers a reply the user **already received**, prefixed: Recovered reply — the gateway restarted during delivery, so this may be a duplicate: The root cause is an **accounting gap**, not a race inside the ledger. _run_agent_inner releases the turn's _running_agents slot in its finally, and that finally runs *before* the final response is handed to the platform adapter. Everything after that point — _send_with_retry and the ledger settle — was invisible to _active_work_count(). The restart wait added in #77184 waits on exactly that count. So it reported *"active work drained"* while a reply was still in flight, and proceeded to stop(). Reproduction Observed **five times** on a production Slack gateway (2026-08-08, 08-15, and 3× on 08-23). Every occurrence has the same fingerprint — drain-complete lands ~0.4–0.5s *before the response is even generated*: The ledger row shows the settle happening 25s late, on the *next* process: The ordering is not coincidental — it is identical in all five occurrences, which is what distinguishes this from ordinary restart/send overlap. Fix BasePlatformAdapter gains a small in-flight counter that _process_message_background holds across the send+settle window, and _active_work_count() sums it over the adapters. The

NousResearch/hermes-agent PR #92591 reports: What does this PR do? Desktop and TUI users can now run /compress --preview or /compress --dry-run without invoking the compression model or changing the live transcript, session identity, history version, or compute-host metadata mirror. The dedicated RPC now accepts a canonical raw args field, preserves the legacy focus_topic field for older clients, and applies the same flag parser already used by the CLI and messaging gateway. Symptom In Desktop or TUI, /compress --preview and /compress --dry-run were forwarded through session.compress as a focus topic. The backend then performed and committed a real compression instead of returning a read-only report. Impact A user asking to inspect a proposed compression could unexpectedly replace hundreds of live context messages and rotate session state. The affected paths were the Desktop action, the TUI action, the live slash mirror, and turn-isolated compute-host forwarding. Bug Cause Trigger:** tui_gateway/methods_session.py in the session.compress handler and tui_gateway/server.py in _mirror_slash_side_effects. Causal chain:** 1. Desktop or TUI sent the complete text after /compress through the field named focus_topic. 2. The session RPC and live slash mirror passed that text directly to _compress_session_history without calling extract_compress_flags. 3. --preview and --dry-run became literal focus text, so the normal model-backed compression path committed a new history. Why it is wrong:** Preview flags are control arguments with a strict no-write contract, not focus content.

NousResearch/hermes-agent PR #92592 reports: Summary Adds native approval support to the /v1/runs session chat streams so external UIs (e.g. the Hermes dashboard) can surface and resolve tool approvals inline. Changes approval.request SSE event carrying run_id, request_id, redacted command/description, and the authoritative allowed choices; run status becomes waiting_for_approval POST /v1/runs/{run_id}/approval resolves exactly one request: validates request_id and allowed choice atomically, emits approval.responded with the matching request_id, keeps waiting_for_approval when more requests are pending Dedicated approval namespace ContextVar in tools/approval.py: approval queues/allowlists are isolated per run while tools' stable get_current_session_key() identity (terminal cwd, delegation, file/process/computer-use) is preserved Run-scoped approval state is cleared on success, error, cancel, stop, and orphan sweep via clear_session API egress force-redacts command, description, and exposed pattern text and whitelists payload fields Docs updated in website/docs/user-guide/features/api-server.md Test Plan 264 tests pass across test_api_server (111), test_api_server_runs (28), test_approval_prompt_redaction (7), test_approve_deny_commands (15), test_approval (103) New coverage: dedicated identity continuity + ContextVar restoration, exact/stale/disallowed request binding, sequential approvals, run-scoped cleanup, orphan sweep, behavioral command+description redaction

Why it matters

A user-visible receipt is only trustworthy when send settlement, preview semantics and approval identity survive interface boundaries without silently changing live state. The operator test is whether the system remains bounded and its receipts still describe the action after failure, retry or restart.

Current

The primary record was open when captured on 2026-08-23. The tagged-release baseline was v2026.8.19 published 2026-08-21T12:16:39Z. Repository metadata, full source bodies, current pull requests and issues, releases, Google News, Hacker News, Lobsters, Metamesh and the rendered ClawCharts row were inspected. ClawCharts selected the subject; it did not prove the claim.

Evidence

The primary URL is https://github.com/NousResearch/hermes-agent/pull/92589. Supporting records are https://github.com/NousResearch/hermes-agent/pull/92591 and https://github.com/NousResearch/hermes-agent/pull/92592. Source bodies, timestamps and states are preserved in the daily evidence bundle. Test counts and reproductions remain attributed to their authors unless identified as independently rerun.

Source boundary

Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Search residue, package mirrors and historically published source spines were excluded.

Operator take

Hold restart until obligation-backed sends settle, enforce no-write preview invariants, and bind each approval response atomically to the run and request that produced it. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently.

Caveat

Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.

Source inspected; source state, environment and release boundary remain explicit.