Skip to content

Desktop: open bot tiles no longer cold-start or pin pooled backends from the background (#103375, salvage #103399) - #107969

Merged
kshitijk4poor merged 8 commits into
NousResearch:mainfrom
kshitijk4poor:fix/103375-passive-tile-reads
Sep 11, 2026
Merged

kshitijk4poor merged 8 commits into
NousResearch:mainfrom
kshitijk4poor:fix/103375-passive-tile-reads

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented Sep 11, 2026 •

Copy link
Copy Markdown

Open bot tiles no longer cold-start or pin pooled backends from the background, so interactive bot opens stop timing out on "waiting for a free local slot" (#103375; salvage of #103399 by @kyssta-exe).

Fixes #103375. Supersedes #103399 (cherry-picked to preserve authorship — the original commit carried a hermes-pr@nousresearch.com identity that is not the contributor's, so it is re-authored to @kyssta-exe's GitHub noreply).

What changes

  • Passive read intent (from fix(desktop): prevent background tile reconcile from starving pool slots (#103375) #103399): HermesApiRequest.passive, getLatestSessionMessages(id, scope, { passive }); reconcileTileTranscripts reads every tile passively.
  • Passive is decided inside the resolvers, not the dispatchers. ensureBackend / ensureRegistryBackend take { passive }: an existing pool entry is served without refreshing lastActiveAt; no entry → throw before the eviction/spawn step. The original PR recomputed the pool key in the two REST dispatchers and missed forced-local (conn:local::<profile>) and primary-routed profiles, which would have made every bot-tile reconcile throw against a warm backend (silently — the reconcile catch is bare) and regressed background deliveries into open bot chats ([Bug]: Open Bot Chat stops receiving messages — no on-screen append/re-key path (background delivery AND mid-conversation model switch) #93942).
  • Keepalive sweep touches only open sockets. touchSecondaryGateways pinged every wantOpen secondary every 60 s, including ones stuck in reconnect backoff. A tile pins its owner scope wantOpen for its lifetime, so an unreachable backend stayed keepalive-fresh forever and neither LRU eviction nor the idle reaper could free its slot.
  • Bounded stalled redials. reconnectSecondary fail-stopped only on removed-connection / deleted-profile errors; a pool-slot wait timeout fell through to an unbounded backoff loop that re-issued a background spawn per tick per tile — the repeating same-order "waiting for a free local slot … timed out … will retry" cycle in the issue's log. After 3 consecutive stalled dials (slot-wait timeout or the 20 s dial timeout) the scope parks (wantOpen=false); fast failures (a restarting gateway) keep the ordinary unbounded backoff. Only a successful open refills the budget (an interleaved fast failure can't). Every explicit open of the scope — the viewed scope's Reconnect / request retry via ensureActiveGatewayOpen, and the online/focus/wake nudge (reconnectSecondaryGateways) — re-arms it with a fresh budget.
  • Passive reads stay out of backendDialClaims in the registry dispatcher: they never dial, and an interactive open coalescing onto an in-flight passive read would otherwise inherit its "no warm backend" rejection.

Root cause

Tile-pinned owner scopes are wantOpen for the tile's lifetime; every automatic path that dials them (reconnect backoff, keepalive touch, REST reads) had spawn/slot-holding side effects with no bound, so N tiles > pool size saturated the pool permanently.

Validation

Check Result
tsc -p tsconfig.electron.json / tsc -p . clean
eslint on touched files 0 errors
use-background-sync.test.ts (3 failing on #103399) 34/34 pass (assert passive intent)
backend-dial-claim.test.ts (1 failing on #103399), registry-primary-profile-scope.test.ts 14/14, 3/3 (both are source-grep tests pinned to exact call text; strings updated)
gateway-connection-lifecycle.test.ts (+6 invariant tests) 22/22; each guard test goes red when its guard is removed (touch on closed socket / infinite stall budget / budget refilled by a fast failure / no re-arm on the active scope / no re-arm on the wake nudge); the fast-failure test pins that ECONNREFUSED never parks
Full desktop vitest (--project electron + --project ui) 2149 + 7498 pass; 2 failures (managed-ssh-update, use-prompt-actions/utils) reproduce on unmodified origin/main — pre-existing
hermes-desktop-app-dev/scripts/main-boot-smoke.sh on the final branch main process boots
Live multi-profile repro not run — no 20-profile desktop install available here; mechanism traced against the issue's log

Not changed: resumeTile's non-passive transcript prefetch — it shares the main-process dial claim with the session.resume dial for the same scope, adds no spawn, and a cold tile needs it to paint (omit_messages resume).

Also relevant: #103401 (same symptom family), #105264, #105390 (open, priority threading — complementary).

okyssta and others added 4 commits September 11, 2026 11:37
…ots (NousResearch#103375)

Passive tile reconciles (reconcileTileTranscripts on every sessions.changed
tick) were cold-starting pooled backends for every open bot tile, including
hidden ones, and holding each spawned slot for its lifetime via the 10s touch
loop. With 15-20+ profiles this permanently saturated the Warm Bot Backends
pool so interactive opens timed out.

Add a passive read intent: getLatestSessionMessages now accepts a passive
flag forwarded as HermesApiRequest.passive. The Electron main process honors
it by serving only already-warm pool entries and failing fast otherwise,
without spawning or bumping lastActiveAt. reconcileTileTranscripts uses
passive reads so background refresh never consumes a pool slot; interactive
opens keep the normal spawn path.
The salvaged commit answered a passive read with its own pool lookup in the
two REST dispatchers. That lookup recomputed the pool key and got it wrong
for every route that is not the plain v1 pool: forced-local registry children
live at `conn:local::<profile>`, and primary-routed profiles (the active
profile, global-remote, registry-primary reuse) are served by startHermes()
and are never in backendPool. Bot tiles always carry an ownerRoute with a
connectionId, so their reconcile threw "no warm backend" on every tick for
a warm backend, and the bare catch in reconcileTileTranscripts hid it --
open bot chats silently stopped receiving background deliveries.

Thread `passive` into ensureBackend / ensureRegistryBackend instead and
decide at the one place each route already does its `backendPool.get`:
existing entry -> serve it without refreshing lastActiveAt; no entry ->
throw before evictLruPoolBackends/spawn. Primary routes never reach a
spawn site, so they stay served. The remote-descriptor revalidation stays
on the passive path.

A passive read never dials, so the registry dispatcher keeps it OUT of the
backendDialClaims coalescing: an interactive open joining an in-flight
passive read would otherwise inherit its "no warm backend" rejection
instead of spawning.

getLatestSessionMessages takes `{ passive }` (matching getSessionMessages)
instead of a positional boolean; the reconcile tests assert the passive
intent.
The 60s keepalive sweep pinged every wantOpen secondary, including ones
sitting in reconnect backoff. A bot tile pins its owner scope wantOpen for
the tile's whole lifetime, so a backend no window could reach stayed
keepalive-fresh forever: evictLruPoolBackends skipped it and the idle reaper
never fired, which is how open-but-unviewed tiles held pool slots
regardless of what the REST reads did (NousResearch#103375).

Ping only entries whose socket is actually open; openSecondary already
touches on a successful dial, so a reopened socket becomes fresh again.
reconnectSecondary fail-stopped only on "connection removed" and "profile
deleted"; every other dial failure fell through to scheduleReconnect with a
15s-capped jitter and no budget. For a tile-pinned owner scope that is
forever wantOpen, a pool-slot wait timeout therefore re-issued a background
spawn on every backoff tick, for every such tile, indefinitely -- the
"waiting for a free local slot ... timed out ... will retry" cycle in
NousResearch#103375's desktop.log, in the same profile order each pass.

Count only STALLED dials (a slot-wait timeout or the 20s dial timeout):
those are the ones that hold a queue position and re-queue a spawn. After
SECONDARY_STALLED_DIAL_BUDGET consecutive stalls the entry parks
(wantOpen=false) with a console warning. Fast failures (ECONNREFUSED while
a gateway restarts) keep the ordinary unbounded backoff, so a socket that
merely dropped still comes back on its own.

Parked is not evicted: every explicit open of the scope (requestGatewayForAgent,
openGatewayForAgent, ensureGatewayForAgent, and ensureActiveGatewayOpen for
the viewed scope -- the Reconnect action and request retries) goes through
rearmSecondary, which sets wantOpen and resets the budget.

Not changed: resumeTile's non-passive transcript prefetch. It shares the
main-process dial claim with the session.resume dial for the same scope, so
it never adds a spawn, and a cold tile needs it to paint (resume runs with
omit_messages).
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) area/sessions Session lifecycle, resume, persistence, history area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 11, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Supersedes #103399 (salvage with authorship preserved). Related: #103375 (issue), #102828 (pool cap), #93942 (background delivery regression the original approach would have re-introduced).

@kshitijk4poor
kshitijk4poor force-pushed the fix/103375-passive-tile-reads branch from fc21a4b to ec74e8d Compare September 11, 2026 06:35
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) September 11, 2026 06:37
@kshitijk4poor
kshitijk4poor merged commit dc90a75 into NousResearch:main Sep 11, 2026
29 checks passed
teknium1 added a commit that referenced this pull request Sep 11, 2026
…ered profile at launch

The first Bot Mode roster paint after launch ran pullServerAvatars over every
row, and for a source-scoped row (every row on a local-primary desktop once
host.agents annotates the roster) each profiles.get_asset / set_asset went
through requestForBot -> host.requestProfile -> requestGatewayForAgent, i.e. a
(connectionId, profile) secondary that spawns that profile's pooled backend.
With ~60 registered profiles and 3 warm slots this queued 56 background spawns
at boot; each queued dial then rode reconnectSecondary's backoff until the
stall budget parked it (#107969), so the pool never drained and desktop.log
filled with "waiting for a free local slot" (#102978).

The active gateway's own profiles.list already produced these rows by reading
every local profile directory; get_asset/set_asset are the same directory
reads addressed by name. Route both through host.request on the active socket
with the row's backend profile name (route.targetProfile, so managed aliases
still resolve). No secondary socket, no pool slot, no spawn.

Cross-connection (remoteSource) rows never reached this path: pullServerAvatars
is fed activeSourceRoster, which filters them out.
gabrielcosi pushed a commit to gabrielcosi/home-ops that referenced this pull request Sep 12, 2026
…9.7 ➔ v2026.9.11) (#754)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/gabrielcosi/hermes-agent](https://github.com/NousResearch/hermes-agent) | patch | `v2026.9.7` → `v2026.9.11` |

---

### Release Notes

<details>
<summary>NousResearch/hermes-agent (ghcr.io/gabrielcosi/hermes-agent)</summary>

### [`v2026.9.11`](https://github.com/NousResearch/hermes-agent/releases/tag/v2026.9.11): Hermes Agent v0.21.2 (v2026.9.11)

[Compare Source](NousResearch/hermes-agent@v2026.9.7...v2026.9.11)

##### Hermes Agent v0.21.2 (v2026.9.11) — The state.db Patch Release

**Release Date:** September 11, 2026

> Patch release. v0.21.0 shipped a large rewrite of the session store's connection handling, and for some installs it made `state.db` fragile: second writers cancelling each other's locks, healthy databases reported as corrupt, one bad row killing `sessions list`. This release closes that class and rolls up everything else that landed on `main` in the four days since v0.21.1.

##### About this release

Measured at commit `04dd80a977f40b05e5b2054111747af07a61886a`, the window since v0.21.1 contains **947 non-merge commits** across **1,869 changed files** (+182,504 / −15,564) and **312 merged PRs**. **140 contributors** appear in commits, co-author trailers, or salvage credits.

##### ✨ Highlights

##### state.db reliability campaign (six PRs, 44 issues closed)

If your `state.db` broke after 0.21.0, this is the release for you. Six PRs fix the root causes rather than the symptoms:

- **No more second writers.** Profile gateways wrote hosted-room state into the *root* `state.db` every 5 seconds; the dashboard opened a writable handle on startup; cron's lifecycle guard did a raw `open()` on a live database (which cancels the gateway's POSIX locks — the classic "how to corrupt SQLite" recipe); `doctor --fix` would checkpoint under a live holder. All four are gone: hosted rooms live in `shared-state.db`, the dashboard opens read-only first, the guard goes through the tracked connection registry, and `doctor --fix` refuses a checkpoint it can't prove is safe. ([#&#8203;108076](NousResearch/hermes-agent#108076) — salvage [#&#8203;103489](NousResearch/hermes-agent#103489) [@&#8203;RikETS](https://github.com/RikETS), [#&#8203;102682](NousResearch/hermes-agent#102682) [@&#8203;JoaoMarcos44](https://github.com/JoaoMarcos44), [#&#8203;108012](NousResearch/hermes-agent#108012) [@&#8203;Halldrix](https://github.com/Halldrix), [#&#8203;105428](NousResearch/hermes-agent#105428) [@&#8203;TaoMasterCoder](https://github.com/TaoMasterCoder))
- **Healthy WAL databases stop wedging.** OpenZFS `(deleted)` dentries and a `close()` racing an `append_message` both produced a sticky `DeletedWalGenerationError` on a perfectly good store; the read pool was handed out under an unconfirmed journal mode; a transient `disk I/O error` on WSL2 killed `get_session` on the first attempt; and a "state.db locked" banner was broadcast after the lock had already cleared. ([#&#8203;108082](NousResearch/hermes-agent#108082) — salvage [#&#8203;107411](NousResearch/hermes-agent#107411) [@&#8203;chelsealong](https://github.com/chelsealong), [#&#8203;105578](NousResearch/hermes-agent#105578) [@&#8203;ca-shrimp](https://github.com/ca-shrimp), [#&#8203;105711](NousResearch/hermes-agent#105711) [@&#8203;gaoanze888](https://github.com/gaoanze888), [#&#8203;106958](NousResearch/hermes-agent#106958) [@&#8203;nikkoxgonzales](https://github.com/nikkoxgonzales); co-authored [@&#8203;QDung210](https://github.com/QDung210), [@&#8203;fangliquanflq](https://github.com/fangliquanflq), [@&#8203;Sahilvishnaliya](https://github.com/Sahilvishnaliya))
- **FTS damage no longer kills your turn.** An error scoped to the full-text-search index was classified as whole-file corruption and fail-closed the conversation. It's now `fts_index`: search degrades, the index rebuilds later, the transcript store is untouched. Same PR: doctor names structural damage honestly instead of "FTS write corruption", the FTS write probe catches the stale-index shape that passed every check while every write failed, `.recover` output no longer fails startup on orphan FTS5 shadow tables, header-zeroed databases recover instead of being refused, and the dashboard analytics poller returns a 503 instead of 520K tracebacks a day. ([#&#8203;108130](NousResearch/hermes-agent#108130) — salvage [#&#8203;97843](NousResearch/hermes-agent#97843) [@&#8203;SulthanZahran1](https://github.com/SulthanZahran1) + [#&#8203;97841](NousResearch/hermes-agent#97841) [@&#8203;Finn763](https://github.com/Finn763), [#&#8203;88604](NousResearch/hermes-agent#88604) [#&#8203;56824](NousResearch/hermes-agent#56824) [#&#8203;103657](NousResearch/hermes-agent#103657) [@&#8203;liuhao1024](https://github.com/liuhao1024), [#&#8203;106890](NousResearch/hermes-agent#106890) [@&#8203;nftpoetrist](https://github.com/nftpoetrist), [#&#8203;103321](NousResearch/hermes-agent#103321) [@&#8203;jangomango76](https://github.com/jangomango76), [#&#8203;91413](NousResearch/hermes-agent#91413) [@&#8203;leegunwoo98](https://github.com/leegunwoo98), [#&#8203;102808](NousResearch/hermes-agent#102808) [@&#8203;TaoMasterCoder](https://github.com/TaoMasterCoder))
- **One corrupt row no longer kills `sessions list`, export, or insights.** A TEXT timestamp or a `1e30` epoch used to crash the whole listing; malformed marker JSON crashed `json_extract`; more than 999 ids crashed bulk delete/prune. One `coerce_epoch()` helper on every reader (bad rows render `?` with a WARNING naming the session), a `json_valid` guard, IN-list chunking, and batched export hydration. ([#&#8203;108086](NousResearch/hermes-agent#108086) — salvage [#&#8203;106071](NousResearch/hermes-agent#106071) [@&#8203;Xipong](https://github.com/Xipong), [#&#8203;101726](NousResearch/hermes-agent#101726) [@&#8203;efe-arv](https://github.com/efe-arv), [#&#8203;94701](NousResearch/hermes-agent#94701) [@&#8203;liuhao1024](https://github.com/liuhao1024), [#&#8203;102679](NousResearch/hermes-agent#102679) [@&#8203;mssteuer](https://github.com/mssteuer), [#&#8203;100658](NousResearch/hermes-agent#100658) [@&#8203;Mi55ed](https://github.com/Mi55ed))
- **Sessions never bind to or read another profile's database.** The Desktop launch backend could pin itself to the wrong profile's `state.db` under a HERMES\_HOME override race; `session_search` by bare ID silently scanned every profile and returned someone else's transcript; recovery guidance pointed at the wrong file; profile delete kept a handle open (WinError 32). ([#&#8203;108074](NousResearch/hermes-agent#108074) — salvage [#&#8203;102534](NousResearch/hermes-agent#102534) [@&#8203;HexLab98](https://github.com/HexLab98), [#&#8203;106975](NousResearch/hermes-agent#106975) [@&#8203;Sora-bluesky](https://github.com/Sora-bluesky))
- **Opening state.db no longer takes the write lock when nothing needs writing.** A one-shot `hermes` process opening the store behind a busy gateway stalled 4–20 s and then failed with "database is locked". Now 0.01 s. ([#&#8203;108067](NousResearch/hermes-agent#108067) — salvage [#&#8203;106751](NousResearch/hermes-agent#106751) [@&#8203;kshitijk4poor](https://github.com/kshitijk4poor), [#&#8203;101881](NousResearch/hermes-agent#101881) [@&#8203;jonpol01](https://github.com/jonpol01))

Also in the window from the same subsystem: a fresh `state.db` no longer publishes FTS tables before owning the rebuild lock ([#&#8203;106311](NousResearch/hermes-agent#106311)), a handle that lost its WAL generation no longer checkpoints stale frames at shutdown ([#&#8203;106315](NousResearch/hermes-agent#106315), [#&#8203;106840](NousResearch/hermes-agent#106840)), a clobbered first page is quarantined with its WAL instead of opened destructively ([#&#8203;106587](NousResearch/hermes-agent#106587)), WAL setup leaves an unverifiable database untouched ([#&#8203;106568](NousResearch/hermes-agent#106568)), and quarantined handles refuse VACUUM/FTS optimize ([#&#8203;106343](NousResearch/hermes-agent#106343), [#&#8203;106349](NousResearch/hermes-agent#106349)). Most of these salvaged community diagnoses by [@&#8203;kshitijk4poor](https://github.com/kshitijk4poor).

##### Multi-profile isolation hardening

A cluster of fixes for installs running several profiles under one gateway (multiplex): secondary-profile bots no longer inherit the default profile's allow-lists ([#&#8203;107616](NousResearch/hermes-agent#107616)), adapters no longer send credentials to the default profile's host ([#&#8203;107617](NousResearch/hermes-agent#107617)), stdio MCP servers no longer receive the default profile's vault secrets ([#&#8203;107630](NousResearch/hermes-agent#107630)), `MEDIA:` delivery can no longer attach another profile's `.env` / `auth.json` / `state.db` ([#&#8203;107609](NousResearch/hermes-agent#107609)), Feishu drive callbacks and `/p/<profile>/` webhook replies stay on the routed profile ([#&#8203;107620](NousResearch/hermes-agent#107620), [#&#8203;107626](NousResearch/hermes-agent#107626)), and secondary profiles no longer get a sibling's Nous bearer from per-process memos ([#&#8203;107611](NousResearch/hermes-agent#107611)).

##### Desktop backend spawn storms are over

Bot Mode used to spawn or dial one backend per profile on launch and on every roster tick, hovering the Bots roster spawned a backend per row, and profile switches could spawn a duplicate primary. ([#&#8203;108069](NousResearch/hermes-agent#108069), [#&#8203;108107](NousResearch/hermes-agent#108107), [#&#8203;108118](NousResearch/hermes-agent#108118), [#&#8203;108134](NousResearch/hermes-agent#108134), [#&#8203;107969](NousResearch/hermes-agent#107969), [#&#8203;108112](NousResearch/hermes-agent#108112) — salvage [#&#8203;102512](NousResearch/hermes-agent#102512), [#&#8203;103634](NousResearch/hermes-agent#103634), [#&#8203;103399](NousResearch/hermes-agent#103399), [#&#8203;107997](NousResearch/hermes-agent#107997) and others by [@&#8203;kshitijk4poor](https://github.com/kshitijk4poor))

##### Password-blind credential vault

The agent can now sign in, pay, and fill addresses from 1Password, Bitwarden, or the local Hermes vault without ever seeing a secret; two-factor codes come from a saved authenticator key or are asked for in the user's UI ([#&#8203;106480](NousResearch/hermes-agent#106480), [#&#8203;107585](NousResearch/hermes-agent#107585)). Private git plugins install with the user's stored credentials ([#&#8203;106981](NousResearch/hermes-agent#106981)).

##### Plugin catalog and one Plugins page

A curated, SHA-pinned plugin index with CLI, admission CI, docs and dashboard ([#&#8203;69446](NousResearch/hermes-agent#69446)); Desktop gets one Plugins page owning agent + desktop plugins, install, catalog and per-commit pinning ([#&#8203;107212](NousResearch/hermes-agent#107212), [#&#8203;107314](NousResearch/hermes-agent#107314), [#&#8203;107321](NousResearch/hermes-agent#107321)); Radio ships as an opt-in SDK plugin ([#&#8203;107072](NousResearch/hermes-agent#107072)).

##### Nous free tier and guided first launch

Free inference and connectors out of the box with one command to sign in ([#&#8203;105258](NousResearch/hermes-agent#105258), [#&#8203;105260](NousResearch/hermes-agent#105260)), `/login` from a chat ([#&#8203;105261](NousResearch/hermes-agent#105261)), connector tools (Gmail, Linear, Notion, ...) searchable through `tool_search` ([#&#8203;106842](NousResearch/hermes-agent#106842)), and a guided first launch behind `HERMES_GUEST_ONBOARDING=1` ([#&#8203;107697](NousResearch/hermes-agent#107697), [#&#8203;107958](NousResearch/hermes-agent#107958), [#&#8203;107985](NousResearch/hermes-agent#107985), [#&#8203;108211](NousResearch/hermes-agent#108211)).

##### 🐛 Notable Bug Fixes

**Gateway & platforms**

- A bare `display:` key in config.yaml no longer crashes every gateway turn ([#&#8203;106305](NousResearch/hermes-agent#106305)); a queued-lane final refused by the platform is recorded and redelivered ([#&#8203;106316](NousResearch/hermes-agent#106316)); a stalled WebSocket send no longer blocks every later event ([#&#8203;106581](NousResearch/hermes-agent#106581)); the first turn no longer waits on the Python toolchain probe ([#&#8203;106556](NousResearch/hermes-agent#106556)).
- Telegram bots must @&#8203;mention when `bots_require_mention` is on, breaking bot-to-bot loops ([#&#8203;106534](NousResearch/hermes-agent#106534)); Matrix renders LaTeX ([#&#8203;106515](NousResearch/hermes-agent#106515)); Signal renders markdown tables ([#&#8203;106538](NousResearch/hermes-agent#106538)); WhatsApp replies to view-once messages keep their quote ([#&#8203;106541](NousResearch/hermes-agent#106541)); media-only replies report SUCCESS everywhere ([#&#8203;106557](NousResearch/hermes-agent#106557)).

**Providers & routing**

- `/model` and auxiliary auto never bill a provider you didn't select ([#&#8203;107366](NousResearch/hermes-agent#107366)); never auto-switch to a provider you have no credentials for ([#&#8203;107281](NousResearch/hermes-agent#107281)); Bedrock Claude/Converse/Mantle models survive `/model`, fallback and restore ([#&#8203;107621](NousResearch/hermes-agent#107621), [#&#8203;107658](NousResearch/hermes-agent#107658)); Bedrock Guardrails enforced ([#&#8203;107815](NousResearch/hermes-agent#107815)).
- Codex: patch-budget image 400 shrinks and retries ([#&#8203;106525](NousResearch/hermes-agent#106525)); unentitled primary + fallback no longer oscillate ([#&#8203;106549](NousResearch/hermes-agent#106549)); Azure Foundry replayed-reasoning rejection classified and pruned ([#&#8203;106718](NousResearch/hermes-agent#106718) [@&#8203;erosika](https://github.com/erosika)). MCP OAuth refresh no longer erases the refresh token ([#&#8203;106185](NousResearch/hermes-agent#106185)). Anthropic clients send exactly one credential ([#&#8203;107978](NousResearch/hermes-agent#107978)).
- DeepSeek V4.1 Flash on Nous Portal and OpenRouter pickers ([#&#8203;107489](NousResearch/hermes-agent#107489)); GPT Image 2.5 via OpenAI and FAL ([#&#8203;105988](NousResearch/hermes-agent#105988)); Opus 5 / Fable 5.1 on the native Anthropic picker ([#&#8203;106636](NousResearch/hermes-agent#106636) [@&#8203;xxxigm](https://github.com/xxxigm)).

**Agent loop & compression**

- One blocked periodic callback no longer stalls lease refresh ([#&#8203;106308](NousResearch/hermes-agent#106308)); a mid-turn `/steer` is persisted as its own user row ([#&#8203;106317](NousResearch/hermes-agent#106317), [#&#8203;106344](NousResearch/hermes-agent#106344)); local-inference memory-ceiling rejections back off instead of compressing history ([#&#8203;106307](NousResearch/hermes-agent#106307)); context-overflow after partial streaming ends the turn ([#&#8203;106567](NousResearch/hermes-agent#106567)); length continuation stops when the prompt filled the window ([#&#8203;106571](NousResearch/hermes-agent#106571)); compression no longer times out silently on aux retries ([#&#8203;106866](NousResearch/hermes-agent#106866)); `model_thresholds` keys can be provider-scoped ([#&#8203;108061](NousResearch/hermes-agent#108061)).
- Surface switch (Desktop↔TUI) no longer rebuilds the system prompt and busts the prompt cache ([#&#8203;105844](NousResearch/hermes-agent#105844)); CLI keeps the `api_content` sidecar so the cache survives an early persist ([#&#8203;105842](NousResearch/hermes-agent#105842)).

**CLI, TUI & Desktop**

- `hermes -z --resume` continues the session ([#&#8203;106313](NousResearch/hermes-agent#106313)); Shift+letter and Cmd+Shift+Z work on extended-key terminals ([#&#8203;90674](NousResearch/hermes-agent#90674) [@&#8203;francip](https://github.com/francip), [#&#8203;105493](NousResearch/hermes-agent#105493)); `browser_exec` timeout kills the whole process tree ([#&#8203;106589](NousResearch/hermes-agent#106589)); update checks poll the GitHub API once a day instead of git-fetching every 30 min ([#&#8203;107648](NousResearch/hermes-agent#107648)); `hermes update` names the real cause and can't hang on a stalled fetch ([#&#8203;108053](NousResearch/hermes-agent#108053)).
- Desktop: UI language survives the update relaunch ([#&#8203;106476](NousResearch/hermes-agent#106476)), expired OAuth grants get a one-click re-sign-in ([#&#8203;106965](NousResearch/hermes-agent#106965)), HUD mode shows the transcript again and always gives the window back ([#&#8203;107491](NousResearch/hermes-agent#107491), [#&#8203;107423](NousResearch/hermes-agent#107423)), the backend exits when its Desktop parent dies ([#&#8203;107977](NousResearch/hermes-agent#107977)), Windows updates stop reporting false failures ([#&#8203;106175](NousResearch/hermes-agent#106175), [#&#8203;107183](NousResearch/hermes-agent#107183)), WSLg renders on the Windows GPU ([#&#8203;106528](NousResearch/hermes-agent#106528)), Telegram quick setup with QR ported from the dashboard ([#&#8203;107242](NousResearch/hermes-agent#107242)), and \~60 more Desktop fixes largely from [@&#8203;OutThisLife](https://github.com/OutThisLife) and [@&#8203;kshitijk4poor](https://github.com/kshitijk4poor).

**Cron & Kanban**

- An off-tick "run now" no longer cancels the next scheduled run ([#&#8203;106306](NousResearch/hermes-agent#106306)); a killed manual run no longer blocks the next one for 5 minutes ([#&#8203;106733](NousResearch/hermes-agent#106733)); a one-shot changed to recurring keeps firing ([#&#8203;106532](NousResearch/hermes-agent#106532)); unpinned jobs run on their creation-snapshot model ([#&#8203;106499](NousResearch/hermes-agent#106499)); `--clone-all` no longer copies cron jobs ([#&#8203;106478](NousResearch/hermes-agent#106478)); `kanban promote` refuses undone parents ([#&#8203;106550](NousResearch/hermes-agent#106550)); `kanban_request_review` rejects unknown reviewer profiles ([#&#8203;106547](NousResearch/hermes-agent#106547)).

**Tools & memory**

- A stdio MCP server dying mid-call no longer replays the tool call ([#&#8203;106546](NousResearch/hermes-agent#106546)); a skills-only background review can no longer delete memory entries ([#&#8203;106310](NousResearch/hermes-agent#106310)); mem0 memory no longer drops long turns ([#&#8203;106542](NousResearch/hermes-agent#106542)); `tool_search` returns nothing rather than five tools sharing one word ([#&#8203;106676](NousResearch/hermes-agent#106676)); remote NOPASSWD sudo no longer prompts ([#&#8203;107939](NousResearch/hermes-agent#107939)); RSS and Reddit reading no longer activate by default ([#&#8203;105873](NousResearch/hermes-agent#105873)).

**Housekeeping**

- `config.yaml` backups live in one bounded `backups/config/` dir ([#&#8203;106388](NousResearch/hermes-agent#106388)); `hermes backup` keeps the newest 3 zips ([#&#8203;106455](NousResearch/hermes-agent#106455)); `hermes setup --reset` backs up the real config ([#&#8203;106453](NousResearch/hermes-agent#106453)); `debug share` retention shrunk to 1 day on the dpaste fallback ([#&#8203;106531](NousResearch/hermes-agent#106531)).

##### 👥 Contributors

Thank you to the **140 contributors** whose commits, co-author trailers, and salvaged PRs landed in this window.

**state.db campaign — salvaged PR authors:** [@&#8203;RikETS](https://github.com/RikETS), [@&#8203;JoaoMarcos44](https://github.com/JoaoMarcos44), [@&#8203;Halldrix](https://github.com/Halldrix), [@&#8203;TaoMasterCoder](https://github.com/TaoMasterCoder), [@&#8203;chelsealong](https://github.com/chelsealong), [@&#8203;ca-shrimp](https://github.com/ca-shrimp), [@&#8203;gaoanze888](https://github.com/gaoanze888), [@&#8203;nikkoxgonzales](https://github.com/nikkoxgonzales), [@&#8203;QDung210](https://github.com/QDung210), [@&#8203;fangliquanflq](https://github.com/fangliquanflq), [@&#8203;Sahilvishnaliya](https://github.com/Sahilvishnaliya), [@&#8203;kshitijk4poor](https://github.com/kshitijk4poor), [@&#8203;jonpol01](https://github.com/jonpol01), [@&#8203;HexLab98](https://github.com/HexLab98), [@&#8203;Sora-bluesky](https://github.com/Sora-bluesky), [@&#8203;Xipong](https://github.com/Xipong), [@&#8203;efe-arv](https://github.com/efe-arv), [@&#8203;liuhao1024](https://github.com/liuhao1024), [@&#8203;mssteuer](https://github.com/mssteuer), [@&#8203;Mi55ed](https://github.com/Mi55ed), [@&#8203;SulthanZahran1](https://github.com/SulthanZahran1), [@&#8203;Finn763](https://github.com/Finn763), [@&#8203;nftpoetrist](https://github.com/nftpoetrist), [@&#8203;jangomango76](https://github.com/jangomango76), [@&#8203;leegunwoo98](https://github.com/leegunwoo98), [@&#8203;ggoldani](https://github.com/ggoldani).

**state.db campaign — issue reporters** (the forensics in these threads were often better than the fixes): [@&#8203;thedigitalcarpenterdad](https://github.com/thedigitalcarpenterdad), [@&#8203;Rroven](https://github.com/Rroven), [@&#8203;aoeman84](https://github.com/aoeman84), [@&#8203;StephanRosin](https://github.com/StephanRosin), [@&#8203;rubensandrade-sketch](https://github.com/rubensandrade-sketch), [@&#8203;wanliqin](https://github.com/wanliqin), [@&#8203;chenzheshushi-commits](https://github.com/chenzheshushi-commits), [@&#8203;CarlosReyesPena](https://github.com/CarlosReyesPena), [@&#8203;revazone](https://github.com/revazone), [@&#8203;reservassai-art](https://github.com/reservassai-art), [@&#8203;Cuttingwater](https://github.com/Cuttingwater), [@&#8203;soroush5](https://github.com/soroush5), [@&#8203;e-shizz](https://github.com/e-shizz), [@&#8203;shobhit-87labs](https://github.com/shobhit-87labs), [@&#8203;shivanathd](https://github.com/shivanathd), [@&#8203;hoelzl](https://github.com/hoelzl), [@&#8203;i8ei](https://github.com/i8ei), [@&#8203;Ace-Kelly](https://github.com/Ace-Kelly), [@&#8203;YinsenWANG](https://github.com/YinsenWANG), [@&#8203;zbabiarz](https://github.com/zbabiarz), [@&#8203;Sravanjangam](https://github.com/Sravanjangam), [@&#8203;0gl20shk0sbt36](https://github.com/0gl20shk0sbt36), [@&#8203;RChina](https://github.com/RChina), [@&#8203;bronder](https://github.com/bronder), [@&#8203;ccwssy](https://github.com/ccwssy), [@&#8203;bottenbenny](https://github.com/bottenbenny), and [@&#8203;Hitman117890](https://github.com/Hitman117890) whose Discord report kicked the campaign off.

**Everyone in the window (alphabetical):** [@&#8203;0genlab](https://github.com/0genlab), [@&#8203;0xalydev](https://github.com/0xalydev), [@&#8203;100yenadmin](https://github.com/100yenadmin), [@&#8203;1052326311](https://github.com/1052326311), [@&#8203;686f6c61](https://github.com/686f6c61), [@&#8203;69k4xmdfm2-blip](https://github.com/69k4xmdfm2-blip), [@&#8203;abundantbeing](https://github.com/abundantbeing), [@&#8203;Adolanium](https://github.com/Adolanium), [@&#8203;Ahmett101](https://github.com/Ahmett101), [@&#8203;albert748](https://github.com/albert748), [@&#8203;AlexxRussell](https://github.com/AlexxRussell), [@&#8203;alt-glitch](https://github.com/alt-glitch), [@&#8203;auroracapital](https://github.com/auroracapital), [@&#8203;austinpickett](https://github.com/austinpickett), [@&#8203;babatorik](https://github.com/babatorik), [@&#8203;Bartok9](https://github.com/Bartok9), [@&#8203;benbarclay](https://github.com/benbarclay), [@&#8203;bennybuoy](https://github.com/bennybuoy), [@&#8203;brian717](https://github.com/brian717), [@&#8203;briandevans](https://github.com/briandevans), [@&#8203;buihongduc132](https://github.com/buihongduc132), [@&#8203;ca-shrimp](https://github.com/ca-shrimp), [@&#8203;cervantesh](https://github.com/cervantesh), [@&#8203;Cesar-Azeredo](https://github.com/Cesar-Azeredo), [@&#8203;ChanPark03](https://github.com/ChanPark03), [@&#8203;chelsealong](https://github.com/chelsealong), [@&#8203;ckomma](https://github.com/ckomma), [@&#8203;ClintonEmok](https://github.com/ClintonEmok), [@&#8203;crazyief](https://github.com/crazyief), [@&#8203;ctaylor86](https://github.com/ctaylor86), [@&#8203;dalzio](https://github.com/dalzio), [@&#8203;DavidMetcalfe](https://github.com/DavidMetcalfe), [@&#8203;Drexuxux](https://github.com/Drexuxux), [@&#8203;edosulai](https://github.com/edosulai), [@&#8203;efe-arv](https://github.com/efe-arv), [@&#8203;emozilla](https://github.com/emozilla), [@&#8203;ericmaddox](https://github.com/ericmaddox), [@&#8203;erosika](https://github.com/erosika), [@&#8203;ethernet8023](https://github.com/ethernet8023), [@&#8203;everm1nd](https://github.com/everm1nd), [@&#8203;FalconOrtiz](https://github.com/FalconOrtiz), [@&#8203;fangliquanflq](https://github.com/fangliquanflq), [@&#8203;Finn763](https://github.com/Finn763), [@&#8203;FirmamentalSpring](https://github.com/FirmamentalSpring), [@&#8203;francip](https://github.com/francip), [@&#8203;g3org3yo](https://github.com/g3org3yo), [@&#8203;gaoanze888](https://github.com/gaoanze888), [@&#8203;ggoldani](https://github.com/ggoldani), [@&#8203;Halldrix](https://github.com/Halldrix), [@&#8203;haydster7](https://github.com/haydster7), [@&#8203;hbizi](https://github.com/hbizi), [@&#8203;helix4u](https://github.com/helix4u), [@&#8203;HexLab98](https://github.com/HexLab98), [@&#8203;huklaa](https://github.com/huklaa), [@&#8203;IAvecilla](https://github.com/IAvecilla), [@&#8203;infinitycrew39](https://github.com/infinitycrew39), [@&#8203;jahfaliabdulrahman-dev](https://github.com/jahfaliabdulrahman-dev), [@&#8203;jangomango76](https://github.com/jangomango76), [@&#8203;JoaoMarcos44](https://github.com/JoaoMarcos44), [@&#8203;jonpol01](https://github.com/jonpol01), [@&#8203;jwilson411](https://github.com/jwilson411), [@&#8203;KeyArgo](https://github.com/KeyArgo), [@&#8203;kokhlo](https://github.com/kokhlo), [@&#8203;KoNit-K](https://github.com/KoNit-K), [@&#8203;kshitijk4poor](https://github.com/kshitijk4poor), [@&#8203;kyssta-exe](https://github.com/kyssta-exe), [@&#8203;leegunwoo98](https://github.com/leegunwoo98), [@&#8203;lesterlxt](https://github.com/lesterlxt), [@&#8203;liuhao1024](https://github.com/liuhao1024), [@&#8203;Mabolla](https://github.com/Mabolla), [@&#8203;manuelschipper](https://github.com/manuelschipper), [@&#8203;MaxFreedomPollard](https://github.com/MaxFreedomPollard), [@&#8203;mearls0501](https://github.com/mearls0501), [@&#8203;mengyuyuan](https://github.com/mengyuyuan), [@&#8203;Mi55ed](https://github.com/Mi55ed), [@&#8203;MiseHinoha](https://github.com/MiseHinoha), [@&#8203;mjshorty](https://github.com/mjshorty), [@&#8203;mkrb84](https://github.com/mkrb84), [@&#8203;moisesvalero](https://github.com/moisesvalero), [@&#8203;moken627-hub](https://github.com/moken627-hub), [@&#8203;mssteuer](https://github.com/mssteuer), [@&#8203;nateEc](https://github.com/nateEc), [@&#8203;nftpoetrist](https://github.com/nftpoetrist), [@&#8203;nickseelert](https://github.com/nickseelert), [@&#8203;nikkoxgonzales](https://github.com/nikkoxgonzales), [@&#8203;notwitcheer](https://github.com/notwitcheer), [@&#8203;onuraycicek](https://github.com/onuraycicek), [@&#8203;outdog-hwh](https://github.com/outdog-hwh), [@&#8203;OutThisLife](https://github.com/OutThisLife), [@&#8203;philmossman](https://github.com/philmossman), [@&#8203;phuongvm](https://github.com/phuongvm), [@&#8203;pierrenode](https://github.com/pierrenode), [@&#8203;portavales](https://github.com/portavales), [@&#8203;PRATHAMESH75](https://github.com/PRATHAMESH75), [@&#8203;QDung210](https://github.com/QDung210), [@&#8203;rewbs](https://github.com/rewbs), [@&#8203;RikETS](https://github.com/RikETS), [@&#8203;romanovzky](https://github.com/romanovzky), [@&#8203;ryantuc](https://github.com/ryantuc), [@&#8203;Sahilvishnaliya](https://github.com/Sahilvishnaliya), [@&#8203;salch-cred](https://github.com/salch-cred), [@&#8203;sgarrand](https://github.com/sgarrand), [@&#8203;shannonsands](https://github.com/shannonsands), [@&#8203;simpolism](https://github.com/simpolism), [@&#8203;Solitud1nem](https://github.com/Solitud1nem), [@&#8203;somewheresy](https://github.com/somewheresy), [@&#8203;Sora-bluesky](https://github.com/Sora-bluesky), [@&#8203;sprmn24](https://github.com/sprmn24), [@&#8203;squevo](https://github.com/squevo), [@&#8203;StellarisW](https://github.com/StellarisW), [@&#8203;Stoltemberg](https://github.com/Stoltemberg), [@&#8203;SulthanZahran1](https://github.com/SulthanZahran1), [@&#8203;Svector-anu](https://github.com/Svector-anu), [@&#8203;szicely](https://github.com/szicely), [@&#8203;TaoMasterCoder](https://github.com/TaoMasterCoder), [@&#8203;teknium1](https://github.com/teknium1), [@&#8203;ten82e](https://github.com/ten82e), [@&#8203;thedavidweng](https://github.com/thedavidweng), [@&#8203;tkaufmann](https://github.com/tkaufmann), [@&#8203;Totoro-qaq](https://github.com/Totoro-qaq), [@&#8203;Tranquil-Flow](https://github.com/Tranquil-Flow), [@&#8203;tuancookiez-hub](https://github.com/tuancookiez-hub), [@&#8203;TurgutKural](https://github.com/TurgutKural), [@&#8203;ugoenyioha](https://github.com/ugoenyioha), [@&#8203;unsupportedpastels](https://github.com/unsupportedpastels), [@&#8203;victor-kyriazakos](https://github.com/victor-kyriazakos), [@&#8203;webtecnica](https://github.com/webtecnica), [@&#8203;wliu-dev](https://github.com/wliu-dev), [@&#8203;wukangcheng1994](https://github.com/wukangcheng1994), [@&#8203;Xipong](https://github.com/Xipong), [@&#8203;Xixiartemis](https://github.com/Xixiartemis), [@&#8203;xkam7ar](https://github.com/xkam7ar), [@&#8203;xxxigm](https://github.com/xxxigm), [@&#8203;yavarb](https://github.com/yavarb), [@&#8203;yoniebans](https://github.com/yoniebans), [@&#8203;Youssef](https://github.com/Youssef), [@&#8203;yoyodine-industries](https://github.com/yoyodine-industries), [@&#8203;yuanchenglu](https://github.com/yuanchenglu), [@&#8203;YuhGuan](https://github.com/YuhGuan), [@&#8203;Zeus-Deus](https://github.com/Zeus-Deus).

Also: Youssef.

##### Updating

- Existing install: `hermes update`
- Fresh install: `curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash`
- Managed deployments should update through their deployment tooling using the new tag.
- If your `state.db` was already damaged by 0.21.0/0.21.1: run `hermes doctor` first; it now names structural vs index damage correctly and points at `hermes sessions recover --inspect-only` (profile-pinned) when a rebuild isn't enough.

**Full Changelog:** [v2026.9.7...v2026.9.11](NousResearch/hermes-agent@v2026.9.7...v2026.9.11)

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/Berlin)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42NS4wIiwidXBkYXRlZEluVmVyIjoiNDQuNjUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUvY29udGFpbmVyIiwidHlwZS9wYXRjaCJdfQ==-->

Reviewed-on: https://git.xcd.dev/gabrielcosi/home-ops/pulls/754
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bot tiles auto-reconnect in an infinite loop, starving the local backend pool (Warm Bot Backends slots never free up for 20+ profile setups)

3 participants