Hermes Agent Weekly #42 — Desktop Rendering Overhaul, CLI Hard-Exit Safety, and 40+ Bug Fixes (Jul 20–26)
- Commits summarized
- 1,515
- Period
- Jul 20 - Jul 26, 2026
- Published
- Jul 27, 2026
Hermes Agent Weekly #42
Week of Jul 20–26, 2026 · 1,515 commits in period · Highlights from 200 included summaries
If you maintain a custom MCP server, run Hermes on Windows, or use multi-pane desktop layouts, this issue has fixes you'll want to read about.
🎯 Desktop: Streaming & Rendering Overhaul
The biggest theme this week was eliminating unnecessary re-renders across the desktop app — every commit below targets the time-to-first-token (TTFT) or frame drop you might have felt during streaming sessions.
Per-token re-render fixes
- Sidebar & tool rows no longer re-render on every stream token (#67742). ToolEntry subscriptions to
$activeSessionId+$currentCwdwere causing a full re-render of every mounted tool row on any session or cwd change — now read at fire time only. - Memoized
AnsiTextandCompactMarkdown(#67844) so child re-renders are skipped when their text props haven't changed. total_charsestimate no longer serializes base64 images on every API iteration (#67788). The old codestr()-serialized the full history — including large tool results and images — just to measure its length. Now derived from a singleestimate_messages_tokens_roughcall.
File tree & review pane
- Targeted file-tree revalidation (#67824). Before, any file mutation bumped a contentless tick and the tree re-read every loaded directory. Now mutations carry their path and only the changed subtree is rescanned. Opaque mutations (terminal, unresolvable paths) still fall back to a full reconcile.
- Review-pane diff is now virtualized (#67818). Selecting a large changed file in review used to freeze the app — a full Shiki highlight over every line. Windowing now kicks in regardless of
showLineNumbers, so only visible rows mount. - File tree no longer goes sticky during agent edit bursts.
clearProjectDirCache()was wiping gitroot + gitignore caches on every workspace tick — but listings were read fresh every time anyway, so the wipe forced a full re-read of every ancestor.gitignorefor every loaded dir. Dropped. - Parallel child-dir reconciliation:
reconcileawaited each child serially; now usesPromise.allover siblings.
Other desktop wins
- Pane & console sash resizes are rAF-coalesced (#67838). Both drag handlers wrote to nanostores on every
pointermove— now stashed and applied once per frame. - Eager
JSON.stringifyof every tool's args + result is gone (#67842).prettyJsonwas running for every tool row on every rebuild, even for tools likeread_filewhere the stringified result is never read. Moved to a memoized, web_search-only computation. - Idle-mount boot-hidden panes (#67857). Files, preview, review, and logs were mounting their real content during first paint even though none are visible at launch. Wrapped in
<IdleMount>(fires afterrequestIdleCallback, 2s timeout fallback) — zero UX change, instant-toggle contract intact. - Collapsible provider groups in the model picker (#64690). Click a provider header to collapse/expand its model list. State persists via
localStorage, stale slugs are never pruned (so profile switches don't lose your collapsed set).
Model UI fixes
- Multi-pane model UI scoped per tile (#67855). Composer model controls were keyed off primary session globals, so every tile showed the same model and a busy primary blocked switches in idle panes. Now bound to
SessionViewper tile, with persisted strip order so add/remove/switch stops scrambling adjacent panes. - Canvas/pixels driven from signal-based stores (#67569) — re-renders cut from per-frame to per-changed-property.
🔧 CLI: Oneshot Hard-Exit Safety
A significant rework of the oneshot (non-interactive) CLI teardown path to prevent SIGABRT during Python finalization:
- Expanded cleanup to cover all process-global resources (#67768). The initial salvage only shut down MCP servers. Now also closes terminal environments, browser sessions, and interrupts async delegations — all sites that can hold native-extension-backed resources (aiohttp connectors, websocket clients) that crash during
Py_FinalizeEx. - Guaranteed hard exit after cleanup interruption — if cleanup itself is interrupted (e.g. a stuck websocket close), the process now forces exit instead of hanging.
- Idempotency guard (
_oneshot_cleanup_done) added to_cleanup_oneshot_runtime, matching the interactive CLI's pattern. - Test isolation:
_cleanup_oneshot_runtimeis now mocked in all_run_and_exittests so real cleanup doesn't run in the pytest worker.
🌐 Gateway & Networking
- Don't spend a redelivery attempt when the platform is down (#67843). The delivery ledger increments
attemptsbefore the caller knows whether it can send. When the platform failed to connect this boot, the attempt was spent without ever sending — after three such boots the message was abandoned forever. Now the caller declares which platforms it can send on; rows for unreachable platforms are skipped. - Stop slow restart redelivery loops (#67747).
- OAuth cookie read retry on cold-start jar race (#67769). A
persist:partition's cookie store hydrates lazily, so the firstcookies.get()on a fresh launch can return empty for a signed-in user. Now retries once with a ~180ms backoff before trusting the negative. - Spurious stdin EOF recovery (#67639). When a child process sets
O_NONBLOCKon shared fd 0, the gateway's nextread()can returnEAGAIN→ CPython seesb''→ false EOF. Now detectsO_NONBLOCK, restores blocking mode, and continues. Windows guard, fd leak fix, andSO_RCVTIMEOcleanup included.
🧠 Agent Core
Compression fix: silent tool output loss
A critical bug in the compression floor: _find_tail_cut_by_tokens aligns cut_idx away from tool-call/result boundaries, but the progress floor (head_end + 1) discarded that alignment. When the floor landed inside a tool group, the parent assistant(tool_calls) fell in the summarised region while its tool results started the tail — and _sanitize_tool_pairs dropped those orphans outright. The tool output vanished.
- Reproduced: sweeping every well-formed block layout up to length 6, 26% (5,623 of 21,840 transcripts) split a call/result pair.
- Fix: re-align forward after applying the floor (never backward, which would reopen the no-op loop). The same sweep reports 0 violations after the change. (commit)
Model metadata preservation
_save_discovered_models_to_configno longer destroys per-model metadata (#67841). Whencustom_providers[].modelsuses the dict form (e.g.{context_length: ...}), the auto-save helper was replacing it with a flat list of strings. Regression introduced by PR #65652. Guard extended to the list-of-dicts form as well.- Qwen model context lengths updated:
qwen3.7-plusadded with 1M context (commit). Without this entry it fell back to 128K, causing premature compression at 64K instead of 500K.
⚙️ MCP & Config
- Opt-out for automatic MCP reload on config change (#67642). Every config rewrite triggered a full MCP reconnect + prompt cache invalidation. New
mcp.auto_reload_on_config_changetoggle (default: true, backward compatible) lets users opt out. Config changes are still detected and reported; manual/reload-mcpis unaffected. - Config path fixed: the opt-out default was declared in
auxiliary.mcpbut the watcher read top-levelcfg["mcp"]— the default was never observed. Moved to a new top-levelmcp:section. - Env-var expansion in MCP config watcher (#55701).
mcp_serversentries with${VAR}templates caused false-positive reloads on every unrelatedsave_config_value(). Now both sides use the same expanded representation. - Doctor allowlist widened to all gateway-bridged top-level keys (
stt_echo_transcripts,reset_triggers,always_log_local,filter_silence_narration,known_plugin_toolsets,group_sessions_per_user,thread_sessions_per_user). - Shipped template no longer enables session auto-reset (#67772). The example config still shipped
session_reset.mode: both, overriding the code default ofnone. Fresh installs hit 24h-idle resets they never asked for.
🧪 Testing & DX
- Wider Testing Library async deadline (#67849) —
asyncUtilTimeoutbumped to 5000ms to de-flake UI panel tests under xdist CPU contention. - CLI oneshot tests now mock cleanup — 3 tests that called
_run_and_exit_oneshotwithout mocking were running real cleanup in the pytest worker. - Compression tests extended to cover the floor-alignment fix.
🤝 Community Contributions
This week includes salvaged commits from multiple contributors:
| Contributor | Area |
|---|---|
| kshitijk4poor | Windows guard, config allowlist, CLI cleanup |
| harjoth | Oneshot hard-exit safety, termux support |
| OYLFLMH | MCP env-var expansion fix |
| TurgutKural | MCP auto-reload opt-out |
| Drexuxux | Gateway redelivery fix, compression fix |
| Dhravya Shah | Supermemory self-hosted endpoint |
| RenoMG | Supermemory routing fixes |
| YAMAGUCHI Seiji | X Search reasoning effort |
| ksy | Model metadata preservation |
| joezhang | Alibaba coding plan model list |
| asscan | Qwen 3.7-plus context length |
Full commit logs available at github.com/NousResearch/hermes-agent. Next issue: Jul 27 – Aug 2.