Skip to content

CLI keeps the api_content sidecar when a turn was persisted early — prompt cache survives (#102194, salvage #102411) - #105842

Merged
kshitijk4poor merged 4 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/p0-102194
Sep 9, 2026
Merged

kshitijk4poor merged 4 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/p0-102194

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Contributor

The CLI no longer loses the exact bytes it sent to the provider when a user turn is persisted before the prologue composes them — the next turn replays the same request prefix and the prompt cache stays warm.

Salvage of #102411 by @JoaoMarcos44 (cherry-picked, authorship preserved), for #102194. Sibling carriers #103565 (@itsflownium) and #103721 (@salch-cred) implement the same row-addressed design; the persist-lock commit here is the same shape as @salch-cred's follow-up on #103721 and is co-credited.

Root cause

_stamp_api_content_sidecar stamps the wire bytes (<memory-context>, plugin context) onto the live user dict as api_content; the turn-start persist normally writes them with the row. When another writer already materialised that row — a close/early flush on the CLI path, or in-place preflight compaction — the persist marker-skips the message and the row keeps api_content = NULL. Replay then sends clean content and the prefix diverges at that message.

Changes

  • hermes_state_messages.py: SessionDB.set_message_api_content(session_id, row_id, content, api_content) — row-addressed UPDATE guarded on role='user', active=1, content IS ?. The positional set_latest_user_api_content gets a docstring warning: repeated identical turns ("ok", "y") make an older row compare equal, so it is only safe when the caller knows the newest row is its own.
  • agent/turn_context.py: backfill by _row_id when the live dict carries one (both early writers stamp it); positional fallback only for in-place compaction; the API-only pre-flushed turn (voice prefix / model-switch note, no injection) now stamps the live bytes too.
  • Commit 2 (ours): the _row_id read and the backfill run under _session_persist_lock, re-checking after acquiring — a close flush holding that lock could commit the row and only afterwards write _row_id back, so an unlocked read saw no id and skipped forever.
  • Commit 3 (ours): the durable-row rule (persist override = clean transcript, live content = wire bytes) is one helper, durable_user_row_content, used by both _db_flush_row and the stamp; the lock uses the existing _persist_lock; tests trimmed from 18 to 7 invariants.

Validation

Check Result
tests/agent/test_api_content_row_addressed_backfill.py + test_api_content_sidecar.py + test_turn_context.py 54 passed
Real-import probe: older identical "ok" row + pre-flushed current "ok" row + plugin injection main: row api_content=None; branch: row carries the sent bytes, older row untouched
Mutation: swap agent/turn_context.py for main's copy 3 red / 4 green in the new file
Lock-wrap: _session_persist_lock is an RLock; nothing on the stamp path holds it deadlock-free by construction
ruff, windows-footguns, compat pointers, git diff --check clean

Closes #102194. Supersedes #102411, #103565, #103721 (credited above).

@alt-glitch alt-glitch added type/bug Something isn't working P0 Critical — data loss, security, crash loop comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard area/sessions Session lifecycle, resume, persistence, history sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 8, 2026
@kshitijk4poor kshitijk4poor reopened this Sep 8, 2026
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) September 8, 2026 15:47
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

PR 105842 — CLI keeps the api_content sidecar when a turn was persisted early

Well-reasoned race fix (#102194): when another writer materializes the turn's user row before the sidecar exists (in-place preflight compaction or a close/early flush racing the prologue), the stamp is now backfilled onto the exact durable row instead of being lost to the crash-persist marker-skip. The docstrings show the failure modes were thought through, not just patched.

Strengths (verified in diff)

  • Row-addressed backfill (set_message_api_content in hermes_state_messages.py) with triple guards: explicit row_id, active = 1, and content match. Cannot drift onto a neighbour row.
  • The identical-turns hazard ("ok"/"y"/"continue" matching an older row) is explicitly documented, and the positional set_latest_user_api_content path is deliberately narrowed to the compaction case where the caller knows the newest row IS the stamped message. Good.
  • row_id validation rejects bools (isinstance(row_id, bool)) and non-positive ints — the True == 1 footgun is covered.
  • Shared durable_user_row_content helper (agent/session_persistence.py) guarantees the flush and the stamp agree on override-vs-live bytes; the previous inline duplication is removed, not copied.
  • The TOCTOU on _row_id (stamp reads outside the lock while the flush commits + marks under it) is fixed by reading under _persist_lock. The comment explains the interleaving precisely.

Findings (all Non-blocking)

  1. Non-blocking — _stamp_api_content_sidecar now acquires _persist_lock(agent) while performing a DB write. If any caller in the prologue path already holds that lock and it is a non-reentrant Lock, this deadlocks. Please confirm the lock is reentrant (RLock) or that no caller holds it at this point; a brief assertion or comment at the call site would lock the invariant in.
  2. Non-blocking — cross-module import of a private name (from agent.session_persistence import _persist_lock). Works, but consider exporting a small public helper (e.g. persist_locked(agent)) so the underscore boundary doesn't rot as the two modules evolve.
  3. Non-blocking — on the early-return path (_db is None or no row id and not compacted) the live dict keeps api_content while the DB row has NULL. That in-memory/DB skew is pre-existing behavior, but a debug-level log on the skip would make future divergence hunts easier.

No blocking issues.

@andrexibiza andrexibiza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 98ed9e58a7275eaaf2cbb8c3e3303b2e4f1f3aa4, including the four-file diff, discussion, flush/marker synchronization, compaction writer, and sidecar replay. The comparison for the existing compaction backfill is main b1f003e18633298d549668b8e186af84cca45b76.

One P2 correctness finding remains, attached inline: the clean-content projection is correct for _db_flush_row, but not for the in-place compaction writer. With a surviving API-only user variant plus injected context, this change makes the content guard reject the very row compaction just inserted. The live request contains the injection, SQLite retains api_content = NULL, and reload loses those bytes. Keep the row-addressed update and the persist-lock fix; correct the writer/guard representation mismatch rather than weakening the guard or falling back to a neighbouring row.

Verification

I ran an isolated real-SQLite/threading probe using the fetched stamp/helper logic and UPDATE statements, with minimal storage scaffolding and ASCII inputs. This was not a repository checkout or a full SessionDB/pytest run; GitHub DNS resolution failed in the execution environment. I am not claiming the PR body's 54-test result as independently reproduced.

Observed controls and counterexample:

  • Early flush with plugin injection, API-only override without injection, and override plus injection all retained the sidecar. An unpersisted repeated ok did not change its predecessor; eight invalid-ID/session/content/archive guard cases refused the write.
  • A deterministic pause after the close-flush INSERT but before marker/row-ID publication retained the sidecar with the lock. Removing the lock in that probe reproduced the lost sidecar.
  • Compaction without an override retained the injection. Compaction with live [voice] hello, clean override hello, and PLUGIN-CTX updated zero rows on this head. The same isolated case retained the sidecar with main's previous stamp logic. The no-row-ID compaction fallback also rejected the mismatched content.

Repository regression to add

This is the corresponding production-import test to add/run in the suite; that full-import execution was not available here:

def test_compacted_api_only_override_keeps_injection(tmp_path):
    import threading
    from types import SimpleNamespace
    from agent.context_compressor import stamp_db_persisted_markers
    from agent.turn_context import _stamp_api_content_sidecar
    from hermes_state import SessionDB

    db = SessionDB(db_path=tmp_path / "state.db")
    try:
        db.create_session("s1", source="cli")
        messages = [{"role": "user", "content": "[voice] hello"}]
        db.archive_and_compact("s1", messages)
        stamp_db_persisted_markers(messages)
        agent = SimpleNamespace(
            session_id="s1", _session_db=db,
            _session_persist_lock=threading.RLock(),
            _persist_user_message_override="hello",
            _last_compaction_in_place=True,
        )
        _stamp_api_content_sidecar(
            agent, messages, 0, "", "PLUGIN-CTX",
            preflight_compressed=True,
        )
        expected = "[voice] hello\n\nPLUGIN-CTX"
        assert messages[0]["api_content"] == expected
        assert db.get_messages("s1")[-1]["api_content"] == expected
    finally:
        db.close()

Also retain a deterministic overlapping-flush regression using a real RLock: the current new tests finish flushing before stamping, and their _FakeAgent base does not install _session_persist_lock, so they do not exercise the new synchronization boundary.

Landing state

At the final read, the PR is open/non-draft and GitHub reports mergeable=false. On the exact reviewed head, CI run 34246417134 is failure, while Docker 34246417251 and Nix 34246417247 are success. The failed CI run returns zero jobs; its underlying failure cause is not established by the available job data. This is not an all-green or merge-ready head.

The preserved JoaoMarcos44 implementation from #102411 and the kshitijk4poor/salch-cred lock work should remain intact while closing this combined-path regression. Recheck the corrected implementation and focused/adjacent tests after resolving the main integration, with fresh CI evidence bound to the resulting head.

Comment thread agent/turn_context.py
Comment on lines +733 to 737
# Match the row the flush wrote (persist override = clean transcript), not the live bytes.
durable_content, _api_content = durable_user_row_content(
agent, _turn_user_msg, live_content,
compose_user_api_content(live_content or "", ext_prefetch_cache, plugin_user_context),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Match the representation written by compaction, not always the flush override

durable_user_row_content() computes the content that _db_flush_row would write, but an in-place preflight compaction does not go through that writer. _commit_compaction passes compressed directly to archive_and_compact; _message_row_params inserts msg['content'] without the agent's clean override.

For a surviving current user message with live content [voice] hello, _persist_user_message_override='hello', and plugin context PLUGIN-CTX, compaction therefore inserts [voice] hello and stamps its _row_id. This helper selects hello as durable_content, so the subsequent row-addressed UPDATE ... AND content IS ? affects 0 rows. api_content exists on the live dict, but remains NULL in SQLite; the compaction persistence marker then makes the ordinary flush skip it. A later reload drops the injection and breaks the request-prefix invariant this PR is repairing. The previous compaction backfill matched the live content and did not have this failure. The positional compaction fallback receives the same wrong guard value here as well.

The isolated SQLite counterexample and a production-import regression test are in the review body. Preserve row addressing and all SQL guards, but distinguish the compaction writer's committed representation from the ordinary flush projection, or make those writers genuinely share the same projection. Cover compaction + differing persist override + injection together, while retaining the passing clean early-flush cases. Do not repair the zero-row result by removing the content guard or trying an unrelated newest row.

JoaoMarcos44 and others added 4 commits September 9, 2026 10:25
… turns (NousResearch#102194)

The api_content sidecar ('persist what you send') preserves prompt-cache
stability across turn boundaries by persisting the exact API-bound bytes
(including memory-manager prefetch, plugin injections, and API-only notes)
and substituting them on replay.

When a user turn was already materialized in the database before the
sidecar could be composed (in-place preflight compaction or a close/early
flush racing the prologue on the CLI path), the turn-start crash persist
marker-skips that message. Previously, the backfill was gated strictly on
in-place compaction (_preflight_compressed and _last_compaction_in_place),
so racing CLI flushes left api_content = NULL in SQLite and broke prompt
caching on subsequent turns (NousResearch#102194).

Positional approaches (such as NousResearch#102239 and NousResearch#102286) using LIMIT 1 on the
newest active user row are unsafe: repeated common inputs ('ok', 'yes',
'continue') cause the backfill to match and overwrite the PREVIOUS turn's
row with the new turn's sidecar, corrupting history and breaking cache parity.

Resolve all landing blockers and review feedback from NousResearch#102411:

1. Bounded state owner (Sahilvishnaliya):
   Add SessionDB.set_message_api_content(session_id, row_id, content, api_content)
   to SessionMessagesMixin in hermes_state_messages.py instead of growing
   hermes_state.py. Update set_latest_user_api_content docstring with durable
   warning on the positional hazard.

2. API-only turns & durable content selection (ehz0ah):
   When a pre-flushed clean input has an API-only difference (e.g. voice
   prefix or model-switch note):
   - Retain the differing API-facing bytes as api_content even when no
     new memory or plugin context was injected.
   - Derive the durable content guard using _override_replaces_content so
     the SQL 'content IS ?' guard matches the clean override text stored
     in the DB row rather than the restored wire text.

3. Turn prologue gating (_row_id) & fail-closed store duck-typing (ehz0ah):
   In agent/turn_context.py::_stamp_api_content_sidecar: check _row_id on
   the live user dict (stamped by _insert_message_rows and synced by
   sync_flushed_message_markers). If valid (positive int, not bool), address
   by exact ID. Do NOT fall back to positional matching when a row ID is
   present: if an external or custom wrapper lacks set_message_api_content,
   fail closed and skip rather than corrupting a neighbouring row. If absent
   but in-place compacted, fall back to positional update. On normal turns,
   skip the backfill entirely (single atomic INSERT).

4. Real lifecycle test coverage (salch-cred, ehz0ah):
   Comprehensive tests in tests/agent/test_api_content_row_addressed_backfill.py
   covering store guards, surrogate scrubbing, gate non-arming, older identical
   row protection, real close-flush row_id synchronization, API-only clean
   override preservation with exact wire replay, and duck-typed store fail-closed
   verification when set_message_api_content is absent.

Fixes NousResearch#102194.
Closes NousResearch#102411.
_stamp_api_content_sidecar read _row_id without holding
_session_persist_lock. A close/early flush holds that lock while it
commits the row and only afterwards writes _row_id back onto the live
dict; a stamp that ran in between saw no id and skipped the backfill,
the flush finished with api_content = NULL and marked the message
persisted, and the turn-start persist skipped it — the row kept the
wrong bytes with no writer left to fix it.

Run the _row_id read and the DB backfill under the (re-entrant) lock,
re-checking _row_id after acquiring it. Race reported by @ehz0ah on

Co-authored-by: sal <141555468+salch-cred@users.noreply.github.com>
NousResearch#102411; same fix shape as @salch-cred's follow-up on NousResearch#103721.
…tamp; trim tests

The turn-start stamp had grown its own copy of the "what does the current
user row hold" rule (persist override = clean transcript, live content =
wire bytes = sidecar when they differ) that _db_flush_row already
implements. Two copies drift; extract durable_user_row_content() in
session_persistence and call it from both.

Also: reuse _persist_lock() instead of a third open-coded lock/nullcontext
ladder; drop the hasattr guard on set_latest_user_api_content (it predates
this fix and exists on every SessionDB); cut the comment to the WHY;
trim the new test file from 18 cases to the 7 invariants (real close
flush E2E, repeated-"ok" positional protection, API-only pre-flushed
turn, normal path writes nothing, compaction keeps positional, store
guards). Still 3 red / 4 green when agent/turn_context.py is swapped
for main's copy.
…plicated row-id predicate; tests 7→6

_session_db is always a SessionDB (agent_init / delegate_tool), so the
"fail closed on a store wrapper" hasattr was defense around code that
cannot fail; the store's own guard binds the value into SQL, so the
prologue only needs the sibling idiom isinstance(_row_id, int) that
session_persistence and transcript_repair already use. The positional
hazard is explained once, on set_latest_user_api_content. The in-place
compaction test duplicated test_api_content_sidecar's
test_inplace_compaction_backfills_sidecar_into_db verbatim (its row_id
parameter was never varied); dropped, as was the positional-helper tail
of test_older_identical_row_is_untouched already covered there.
@kshitijk4poor
kshitijk4poor merged commit 63c6f9b into NousResearch:main Sep 9, 2026
37 checks passed
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/sessions Session lifecycle, resume, persistence, history comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P0 Critical — data loss, security, crash loop sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

5 participants