Repository navigation
Tracking: one gateway owns every local session (#106742) — post-merge fixes #134889
Description
Tracking issue for #106742 (feat/unified-gateway-runtime). #106742 merged to main on Oct 14 (merge commit eb75627f, branch head 2c01eb59). This issue now tracks the post-merge fixes. Goal: every surface that talks to a local backend (CLI, TUI, locally-spawned Desktop, API/Responses, ACP, bots, cron, Kanban) attaches to one gateway-owned session with one FIFO, one durable queue and one writer, instead of each running its own agent on the same state.db.
Status (Oct 19, 21:30 UTC): 329 rows tracked. 273 fixed on main and 32 not reproducible or verified unchanged on main, all listed in Fixed on main and its continuations (1, package 12, package 13). 24 open, below: package 1 PRs are in review on 13 of them, #141573 on TUI-8, RT-18 and TUI-19, and #140778 on GW-19. Package 13 is merged (#141481, #141484, #141488, #141490, #141492, #141493, #141495, #141501, #141508) and its 14 rows have moved to its own comment; its review rounds added GW-25, GW-26, CLI-41, CLI-42, TUI-27, RT-47 and CI-2. Open work is grouped into packages #139158, #139161 and #140762.
Scope: the local/gateway cutover only. The browser dashboard and Desktop URL / Cloud / SSH modes still talk to standalone hermes serve; that cutover is a staged fast-follow and is out of scope here.
Every finding reported in the comments below is collected in Open findings. The most recent live pass ran on b5112db885. The 49 commits between that head and the merge only bring in main and touch no gateway code, so its results apply to main as merged.
Found a problem on main? Report here
Report anything you hit with the unified gateway as a comment on this issue, not as a new issue. Each report gets triaged into the tables below. The fix PR is linked back on your comment so you can retest.
Include, where you can:
- Build:
mainSHA (git rev-parse --short HEAD), plus Desktop version/commit from Settings → About if Desktop is involved - Surface and topology: CLI / TUI / Desktop (local) / API / ACP / bot / cron; OS; native, container or WSL; single profile or multiplexed
- Steps, expected, actual
- Evidence: the relevant lines from
~/.hermes/logs/gateway.log,errors.log, and Desktop'sdesktop.log; for turn-state bugs, thesession_admissionsrow (intent,status,outcome,owner_epoch) - Redact tokens, cookies, tickets and session URLs before posting
Fixing an item? Name its ID (e.g. CLI-15, N1) in the PR so it can be ticked off here.
Contract under test
- Queued admissions survive owner restart and run FIFO exactly once.
- A started admission that loses its owner becomes unknown/interrupted and is never replayed automatically.
- Retry reuses the original admission identity and committed payload.
- Clients are readers/submitters; the gateway is the only canonical writer.
- Profile, provider/issuer, generation and revision boundaries hold across every transport.
Deliberate limits (not bugs): the served profile set is a start-time snapshot (adding a profile needs hermes gateway restart); hosted-room execution across two profiles on one multiplexer is not live-certified.
Verified working
| Test | Head | Result |
|---|---|---|
hermes gateway restart during a 40 s tool call |
86e81c4 |
✅ admission started → terminal/interrupted in 7 s; tool killed, not replayed; lease released; owner epoch 2 → 3; follow-up completes |
| Container network down 45 s mid-turn | 86e81c4 |
✅ model call waits with the lease held; completes 33 s after reconnect; no replay; follow-up completes |
| Stop right after submit, and on the first turn after a restart | b5112db885 |
✅ via Ctrl+C: row interrupted, follow-up completes, same on owner epoch 2 (/stop does not work, see N4) |
| Two viewers sending identical text | b5112db885 |
✅ 8 ms apart → 2 admissions run FIFO; one submission id sent 3× → 1 admission |
Cron fire → /compress → human input |
b5112db885 |
✅ order kept; all 9 admissions terminal once; each prompt reached the model once |
SSE approvals over /v1/chat/completions |
b5112db885 |
✅ approval.request on the stream; /v1/runs/{id} reports waiting_for_approval; approval POST completes the run (cosmetic: last_event stays approval.request) |
| Cron paused → resumed → restart | b5112db885 |
✅ job stays enabled and fires once after a gateway comes back (depends on N1) |
| Long-session storage growth | b5112db885 |
✅ ~0.46 MB per 100 turns, down from 8.4 MB (still linear for a live session, see LC-9) |
| API contract | b5112db885 |
✅ X-Hermes-Session-Id on a CLI-owned session → 409; Idempotency-Key /v1/runs retry after restart replays without re-running; a different body → idempotency_key_conflict |
| Desktop marks a running turn ended after 45 s silence (RT-1) | b5112db885 |
✅ fixed by c3ecea58a8; a second viewer sees working through a 60 s tool |
| Restart and reconnect (N1, N2), live in a container | 6930bb0c |
✅ gateway restart on a CLI-started gateway starts a new one (twice in a row); the idle CLI's next prompt reconnects and runs once on the new owner; /sessions and /bg work before any prompt, with no local fallback; /new stamps end_reason='new_session' |
Fix commits that claimed to close a review finding before the merge (cb8d692 → 86e81c4): dashboard token 9112cb59, legacy WS scope bf7ea215, /compress memory hooks 4279636b, Kanban worker exit codes fffab163/43cbb0e4, hosted admission reauth 835aac9c, headless cron cdf36315, replay gaps / ACP cancel 37662b16, delete takes the compression chain a942a715/c5ea1989, runs waiting_for_approval e6cc0fa5, Windows local attach 145d58b7/996d9beb/2e6d4fe0. Claimed, not re-verified except where a row above covers them.
Open findings
Open work packages: #139158 (package 1, classic CLI; @kshitijk4poor) · #139161 (package 3, Ink TUI and Desktop; @yoniebans) · #140762 (package 11, session ids after a prune or delete, Windows clients and an elevated gateway, and stable-channel updates; @unsupportedpastels). Packages 2, 4 to 10, 12 and 13 are closed. Rows added by package 13's reviews (GW-25, GW-26, CLI-41, CLI-42, TUI-27, RT-47, CI-2) aren't in a package yet.
Status: 🔴 confirmed live · 🟡 static read, not reproduced yet · ⚪ refuted or not reproduced · 🔵 needs a maintainer decision · ⬜ not tested yet · 🟠 fix in progress (no PR yet) · 🟣 fix in review (a member's PR linked) · 🟢 fixed on main (PR linked)
Maintainer decisions: rows marked "decided Oct 16" follow this comment.
Severity: broken = fails or gives a wrong result · degraded = silently does less · security · cosmetic
Fixed rows move to the first comment once their PR lands on main, to keep this body under GitHub's size limit. That comment is full; later rows are in its continuation.
Outside PRs: a row is 🟣 only while a Nous member's PR is open on it. A PR from an outside contributor does not cover a row: it is listed as "outside PR to salvage", and a member still has to pick the row up, review the PR, and salvage it with credit or fix the row fresh.
Added Oct 15, afternoon, from @teknium1's triage at 13:57, 14:43, 15:29, 16:16, 17:47, 18:42 and 19:29.
Added Oct 15, evening, and early Oct 16, from @teknium1's triage at 21:00, 21:46, 23:17, 00:03, 01:34 and 02:20.
Added Oct 17, after package 9 opened: from @teknium1's triage at 07:01, 07:44, 07:47, 08:34, 10:06, 10:53, 11:40, 12:26, 13:12, 13:58, 14:44, 15:31, 17:02, 17:49 and 18:35 (GW-14 to GW-18, CLI-34 to CLI-39, CMD-2, ACP-19, RT-37 to RT-44, MP-16, PLG-12, LC-22 to LC-24, CFG-11, MSG-21 to MSG-27, KB-6, DB-5 to DB-7), plus reports not triaged yet: PLG-13, KB-7 and RT-45. Windows session.active_list evidence by @Yzz2023 belongs to TUI-21. #140400 was fixed in package 9 (#140472, rows CFG-8 and TUI-18).
Added Oct 19, from package 12's (#141321) review rounds: GW-20 to GW-23, TUI-26, MP-17, KB-8; and, already fixed, GW-24, RT-46, MP-18, CLI-40 and KB-9 from @dokterdok's package 10 review (#141555 to #141559, fixed by #141578 to #141582, listed in the continuation).
Added Oct 19, evening, from package 13's review rounds (independent review, Greptile, @unsupportedpastels and @dokterdok): GW-25, GW-26, CLI-41, CLI-42, TUI-27, RT-47, CI-2.
Sources: each section links the comment with the full write-up (steps, code locations, evidence). IDs are the ones used in those comments. T1–T24 are the 24 items in the Classic CLI / command-layer comment. M1–M4 are the four GatewayAgent missing-method items. W1–W2 are the Windows items. Rows that open with an issue number were triaged by @teknium1; the section sources link those comments.
Gateway lifecycle and restart
Sources: live E2E pass, crash-recovery scan, triage 08:01, 10:35
| ID | Finding | Severity | Status |
|---|---|---|---|
| GW-19 | #140729: a stable-channel install on a post-merge main commit (v0.22.1+163.g046094d) can never update. The channel's release, v0.22.2 (d923a5eb487a), has main in its history but no gateway runtime in its tree, so update_downgrade_guard.target_predates_runtime refuses: "Refusing to move this install to d923a5e… Downgrading is unsupported". The user didn't ask to downgrade, and gets the refusal from both the CLI and Desktop on every attempt. Fix direction (@teknium1): when the channel's release is older than the runtime the stores already use, keep the install where it is, say so (ahead of the stable release; nothing to update until stable ships the gateway runtime, or switch to main), and exit 0. |
broken | 🟣 fix in review: #140778; from report, triage 20:11; package 11 (#140762); outside PR to salvage: #140741 (draft, @arcabotai) |
| GW-25 | Found in package 13 review of GW-21: the transcript mark that lets a restart notice replace the agent's own failed-turn notice lives only on the in-memory claim. A normal restart settles before the old process exits, so that case is covered, but after a hard crash the next owner settles without the mark and the two notices can still stack (as on main). |
cosmetic | open; static read |
| GW-26 | Found in package 13 review of GW-23: the unscoped-thread fail-closed covers the live CDP endpoint only. On a multiplexed host an unbound thread still resolves the launch profile's browser.cdp_url from config (tools/browser_tool_cdp.py::_get_cdp_override_raw). No browser path runs unbound today. |
cosmetic | open; static read |
Classic CLI: GatewayAgent missing methods
Sources: missing-method audit, Windows report, live E2E pass
| ID | Finding | Severity | Status |
|---|
Classic CLI: flags, commands and input
Sources: parity round 1, parity round 2, Classic CLI / command-layer list, live E2E pass Also: W1 post-merge review, #138748 triage, Oct 15 triage 04:55, 10:35, 11:36.
| ID | Finding | Severity | Status |
|---|---|---|---|
| CLI-10 | /compress discards the gateway's result, so a failed summary reads as success; compression_count increments on a no-op. |
degraded | 🟣 fix in review: #141091 |
| CLI-11 | Status bar and /usage show the previous session's numbers after /new, /resume or /branch. |
degraded | 🟣 fix in review: #141090 (🔴 confirmed live) |
| CLI-14 | Todo state lost after /new, /resume or /branch. |
cosmetic | 🟣 fix in review: #141090 |
| CLI-15 | /worktree new says tools moved, but the gateway keeps running them in the original checkout. |
broken | 🟣 fix in review: #141110 (🔴 confirmed live) |
| CLI-16 | /snapshot restore always fails in a chat and nulls the CLI's session store; afterwards /new gives permission_denied on every turn. |
broken | 🟣 fix in review: #140676; outside PR to salvage: #139019 |
| CLI-19 | /save silently writes a degraded export that can't round-trip through hermes sessions import. |
degraded | 🟣 fix in review: #141092 (🔴 confirmed live) |
| CLI-22 | /clear banner and /toolsets come from the CLI's own registry (no MCP; long local-… id). |
cosmetic | 🟣 fix in review: #141093 |
| T1 | Still running on the CLI's own code instead of the gateway handler: /voice on|off|tts, /cron add|pause|remove, /debug, /reasoning show|hide and --global. |
degraded | 🟣 fix in review: #141105 |
| CLI-25 | @file attached on a classic CLI voice turn, or on the first turn after /model or /reload-skills, reaches the model once; the stored row keeps the bare @file: token, so later turns lose the content (turn_references expands only api_text; persist_override is the typed text). Left by #138665. |
degraded | 🟣 fix in review: #141096; deterministic probe, not live |
| CLI-26 | /diff session on a large, escape-heavy diff drops the gateway connection and prints "Gateway disconnected; turn outcome is unknown". The 2 Mi-character cap exceeds the client's 8 MiB WebSocket frame once JSON-escaped. Introduced by #138668. |
broken | 🟣 fix in review: #141094 (🔴 confirmed live) |
| CLI-27 | /stop typed mid-turn interrupts the turn and kills background processes but prints nothing (plain print() is buried by the prompt_toolkit redraw; /steer uses _cprint). Introduced by #138668. |
cosmetic | 🟣 fix in review: #141095 (🔴 confirmed live) |
| CLI-30 | #138831: the in-chat /sessions list is always empty. Gateway rows are source='local', and the token-accounting placeholder write stamps created_source='unknown', which the CLI's surface filter drops (hermes sessions list works). |
broken | 🟣 fix in review: #140678; partly fixed on main by #139351 (3b2f9711, @kshitijk4poor): CLI rows hidden behind newer Desktop and TUI rows (RV-19). Still open: the created_source='unknown' stamp; outside PRs to salvage: #138838 (draft), #139721 |
| CLI-31 | #139049: hermes -c / --resume latest resumes an empty launch stub. Every launch creates a local-<hex> row; one that sends nothing leaves a 0-message row that is never discarded, and each failed resume adds another. |
broken | 🟣 fix in review: #140678; partly fixed on main by #139351 (3b2f9711, @kshitijk4poor; the RV-19 cause). Still open: the empty launch stub; outside PRs to salvage: #139058 (draft), #139721 |
| CLI-41 | Found in package 13 review of N5: /queue edit ignores the row's controls allowlist, and /queue clear also cancels automation rows on the session's shared queue (the owner's cancel_queued has no per-row principal check). |
degraded | open; static read |
| CLI-42 | Found in package 13 review of N5: queued rows admitted by another client, or before a relaunch, still run without being painted in the classic CLI; after QueueOutcomeUnknown the CLI never paints that prompt's turn live. |
cosmetic | open; static read |
Command layer (one gateway handler per command)
Source: Classic CLI / command-layer list. Also: Oct 15 triage. Per-surface coverage is in website/docs/developer-guide/gateway-command-parity.md; 56 registry commands were still refused for local sessions at 2c6a8f05574 (8 messaging-only by design).
| ID | Finding | Severity | Status |
|---|
ACP (Zed / VS Code / JetBrains)
Source: parity round 1, live results in the E2E pass. Production uses GatewayACPAgent; the old in-process server is only exercised by tests, which stay green while the shipped path regresses. Also: W1 post-merge review, Oct 15 triage.
| ID | Finding | Severity | Status |
|---|
Ink TUI and local Desktop
Sources: parity round 1, parity round 2, Classic CLI / command-layer list, live E2E pass. Desktop itself was not covered by the live pass. Also: W1 post-merge review, macOS case triage, Desktop triage, Oct 15 triage 04:09, 05:42, 06:28, 07:14, 09:49, 11:36, 12:23; Oct 16 triage 22:25 (RT-32), 23:57 (RT-33).
| ID | Finding | Severity | Status |
|---|---|---|---|
| TUI-8 | Ink /resume hides stored messaging and cron sessions; resuming one → not_found. |
degraded | 🟣 fix in review: #141573 (consolidated; #141491 closed as superseded, its compressed-lane listing in 8427419cdc5) |
| TUI-19 | A refused delete in the Ink picker (stale revision, read-only grant) reopens the closed session: the CAS-read session.resume attaches a viewer and nothing detaches it, so it stays in the live list until Ink exits. Classic CLI /sessions delete has the same gap. Introduced by #138667. |
degraded | 🟣 fix in review: #141573 (consolidated; #141498 closed as superseded, its resolved-id release and viewed-session guard in 7d7a2b9b83d) (🔴 confirmed live) |
| RT-18 | #139017: Desktop treats messaging-channel sessions (WeChat) as local: their completions raise unread badges, and opening one fails with Session controls unavailable: stale_generation. |
degraded | 🟣 fix in review: #141573 (consolidated; #141491 closed as superseded, its local-session route check in 038197ff377) |
| TUI-27 | Found in package 13 review of N8: quit's liveness check and git worktree remove aren't atomic with the gateway. A submit from another viewer that lands in between is admitted, then refused workspace_unavailable because the clean tree is gone (no work is lost). Needs an owner call that marks the tree as retiring and refuses new work in it until the launcher finishes: a new contract method plus refusal. |
degraded | open; static read |
| RT-47 | Found in package 13 review of RT-40: a retained Desktop rewind is keyed by session, not command. After a lost /undo, a following /retry replays the undo's receipt (matching Ink); no test pins that behaviour yet. |
cosmetic | open; static read |
Model and reasoning picks
Source: @teknium1's triage 04:09, 04:55, 05:42, 08:01, 09:49, 11:36. One cluster: a model or reasoning pick on a canonical session must land for the next turn or show a named refusal, never fail silently. RT-12 (#138771, #138790) is the first report. #114853 fixed only config set model.provider, not MP-1 or MP-2.
| ID | Finding | Severity | Status |
|---|
Plugins
Sources: parity round 1, parity round 2 Also: W1 post-merge review.
No open rows. Fixed rows are in the continuation.
Session lifecycle and storage
Sources: parity round 1, live E2E pass, Oct 15 triage 04:55, 06:28
| ID | Finding | Severity | Status |
|---|
Config, credential and skill freshness
Sources: parity round 2, live E2E pass Also: #138670 triage.
No open rows. Fixed rows are in the continuation.
Hosted rooms (Group Chat)
Sources: member_id triage, attachments triage, Oct 15 triage 08:01, 09:49
| ID | Finding | Severity | Status |
|---|
Messaging and bots
Source: @teknium1's triage 06:28, 07:14, 10:35, 11:12, 12:23.
| ID | Finding | Severity | Status |
|---|
Cron, Kanban and managed workers
Source: @teknium1's triage 04:09, 08:47, 09:49, 12:23.
| ID | Finding | Severity | Status |
|---|
Windows
Source: Windows report, Oct 15 triage 09:49, 12:23
No open rows. Fixed Windows rows are in the continuation.
Dashboard and remote Desktop mutations
The browser dashboard and remote Desktop are outside this cutover's scope, but these fail in the revision-fenced mutation path #106742 added. Source: @teknium1's 06:28 triage.
| ID | Finding | Severity | Status |
|---|
CI and test harnesses
Source: Classic CLI / command-layer list
| ID | Finding | Severity | Status |
|---|---|---|---|
| CI-2 | Found in package 13 review of RT-21: the Windows Desktop attach E2E builds with electron-builder --dir and no HERMES_DESKTOP_VARIANT=bundled|store, so no agent payload is staged and the Store ticket path is never reached on Windows CI. The electron unit tests for it run only on Linux. |
degraded | open |
Fixed rows are in the continuation.
Not tested yet
hermes updateover a live turn- Mixed-version pass: current gateway with an older Desktop, and the reverse
- A real half-open network link (for F3)
--tui -s
Done when
- Every 🔴 and 🟡 item is fixed on
mainor explicitly accepted by a maintainer, with a retest comment here. - Every 🔵 decision is made and recorded here.
- The ⬜ items have at least one test run.
Activity
476 remaining items
dokterdok commentedon Oct 18, 2026
@FengKan880 thanks, I reproduced this on
main(9e01b44dd23) with a test, not live.The activation event you couldn't trace is the gateway's embedded dashboard.
GatewayDashboardMountmounts the dashboard inside the gateway process. The first time it enters another profile's scope,_config_profile_scopecallsactivate_multi_profile_hosting()(web_server_profiles.py:309).?profile=<other>, andPOST /api/profileswriting a new profile's model (_write_profile_model). So Desktop's New Bot or New Profile against that gateway is enough.GatewayRunner.__init__turns the guard off for a standalone gateway, and nothing arms it at boot. So the gateway works at first and breaks after one of those requests.owner_scope, as you said.The fix, in your direction, for cherry-pick:
b3d1a9ba5fonfix/standalone-owner-scope, onmain, crediting you. I have three ready PRs open, so it's a branch for now.owner_scopereturnslaunch_profile_scope_if_multiplexed(). That stays a literalnullcontextuntil the guard is armed, and becomes the launch profile's scope after.test_standalone_session_create_after_the_dashboard_served_another_profilearms the guard through the real_config_profile_scope("beta"), then sends a realsession.createthroughtui_gateway.ws._dispatch_request.mainit fails with your chain, ending atsecret_scope.py:238UnscopedSecretError..envkey and an env-only injected key resolve; an unscoped read outside it still raises.nullcontext.owner_scope, launch-profile policy, secret scope, bypass policy and multi-profile hosting suites pass (72 tests), and so doesscripts/check.Two points for the maintainers, not in the commit:
--safe-mode/ignore_user_configsession then reads the launch profile's.envandterminal:settings, so a malformedconfig.yamlrefuses it (TerminalPolicyUnavailable).mainthat session fails anyway, and a configured multiplex gateway's bypass already reads its profile through_profile_runtime_scope. So nothing gets worse than either today.session_inventoryread, the socket-wide scope atrun_api.py:370, and later RPCs. That applies on both branches. A first attempt showed how far that reaches, so I left it out.main.owner_scopedecides once at handler entry, andsession.createawaits skill preparation beforebuild_policy. This happens once per process, and a retry works. Two ways to close it:hermes servedoes withactivate_multi_profile_hosting_eagerly;The same-shaped standalone gates (
_routed_scope,_execution_scope, plugin injection, media delivery) showed no failure. Each already runs inside a profile scope, or reads no secret.An Opus 5.5 verify round and GPT-6 Astra reviews (Codex CLI, reasoning
xhigh) checked the fix; they raised the two points above.dokterdok commentedon Oct 18, 2026
@unsupportedpastels @teknium1 thanks for landing package 10. To keep this tracker readable, here's everything I still have open after it, in one place. I'll keep this comment up to date instead of adding new ones. Everything below was found in code review and reproduced with tests; the serial-reader fix also ran live against Signal. Each finding has a probe that fails on
mainat54809183676, and still fails at045b4fd437, after package 12. The probes are inreview/pkg10-probes(r2-lane*/).1. Fixes ready to cherry-pick
Thanks @unsupportedpastels for landing the four P1s and #141559 today (#141578–#141582), and @teknium1 for landing CLI-34 (#140348) with its review follow-ups in #141481. The three remaining fixes now have their own issues. Each fix is rebased onto
mainat493208879aand checked again there: its tests fail onmainand pass with the fix, andscripts/checkpasses.maindocker_persist_across_processes: false, idle containers are stopped and never removed2. Open findings without a fix from me
These need a design choice, so I left them to you. The direction for each is in the linked review, already checked once for side effects.
16 P2 findings, one line each
profile_routeschange runs on the profile the route no longer names./model --once(review):/model <current> --once --reasoning highdrops the level;/reasoningfollowed by a--oncepick is never committed./skillthat finishes during/stopor/new's first read still runs;/skillsent before/newruns in the fresh conversation.Smaller, from static reading:
DIRECT_ALIASESis one process-wide dict, so two profiles' turns can interleave insideswitch_model._cached_profile_read(hermes_cli/profiles.py:726) caches a failed read of the gateway's ownprofile.yaml.profile.yamlnow logs a warning on every 5 s poll.3. Other fixes waiting for a home
owner_scopereport (above): fixed onfix/standalone-owner-scope, with a test that reproduces it onmain, crediting them.list_cron_job_runsas a known limitation, so they ended up in no package. Both apply cleanly onmain.invalid_params, and keeps a client's secret out of every refusal: field, detail, log line and CLI. Rebased ontomaintoday and checked again; it merges cleanly.If you'd rather take any of the above another way, or drop some, tell me and I'll close them.
teknium1 commentedon Oct 18, 2026
Package 10 (#140719) is merged: #140967, #140968, #140969, #140970, #140971, #140972 and #140973, plus the fixes to
main#141112, #141168 and #141213. Every row those PRs claimed is now fixed onmain, including CLI-36, LC-20, LC-22, LC-23, RT-37, RT-38, RT-39, RT-42, RT-45, MP-16, CMD-2, CFG-11 (--ignore-user-config), KB-6, CLI-35/37, LC-24 and #140805. Still open: KB-7's boot-time delay, F5 (RPCs past the frame cap) and CFG-11 for ordinary managed workers.rickhuizinga commentedon Oct 18, 2026
[KB candidate] Kanban heartbeat backstop reclaims live workers during long non-streaming LLM calls — contradicts the agent-side local-endpoint trust policy
Reported by: Rick (5-profile multiplexed gateway on macOS, workers on
local engines via LiteLLM at an RFC-1918 address)
Related: #23025 (spawn-then-reclaim loop), #31752 (heartbeat bridge), KB-7 / #106742 tracker
Summary
Two layers of the same product make opposite policy decisions about the same call — a
worker's iteration-budget summary, which is a single non-streaming LLM request:
Agent side (
run_agent.py:_compute_non_stream_stale_timeout): with an implicit defaultand a local endpoint (
is_local_endpoint→ loopback / RFC-1918 / Tailscale CGNAT), thestale timeout is
float("inf")— trust the endpoint, wait indefinitely, no watchdog.Our LiteLLM proxy is at 172.16.x.x, so this path applies.
Dispatcher side (
hermes_cli/kanban_db.py:DEFAULT_CLAIM_HEARTBEAT_MAX_STALE_SECONDS,hardcoded 3600): during the same call the worker emits no heartbeats — non-streaming
responses surface no chunks, and the
_touch_activity→ kanban-heartbeat bridge (Kanban worker runtime activity does not update board heartbeat, causing stale reclaim of active workers #31752)only fires on observable progress, which a non-stream call doesn't produce until it
completes. So
last_heartbeat_atgoes stale, andrelease_stale_claims' backstop reclaimsthe claim of a live, healthy worker mid-summary.
The agent says "wait forever"; the dispatcher kills at 60 minutes. Every worker on a local
engine that needs >60 min for a budget summary is reclaimed while healthy.
Observed instances
t_79b1cf7e,t_ef8fdb20: live workers reclaimed mid-summary (the reclaim loop therelease_stale_claimsdocstring itself describes via recurring stall loop: kanban workers repeatedly reclaimed with stale_lock, zero output across respawns #23025: "no tool calls means nokanban_heartbeat, even though the subprocess is healthy").slow-engine summary times. There is no env/config override for this constant, so a source
patch is the only mechanism — it must be re-applied after every update.
Suggested directions (maintainer's choice)
last_activity_descindicates anin-flight API call ("waiting for provider response…"), extend the claim instead of
reclaiming. That signal already distinguishes "hung LLM call" from the logic-loop case the
backstop was built to catch (kanban: three gaps in blocked / iteration-exhausted handling that permit infinite "unblock → re-stuck" loops #29747 gap 3).
handle_max_iterationsalready builds kwargs withthe main loop's builder (
_chat_summary_attempt); streaming the summary would feed_touch_activityper chunk → heartbeats stay fresh → the race self-resolves on bothlayers, with no new policy needed.
Happy to provide claim-log evidence or test a candidate fix against our multiplexed setup.
RahmanShukri commentedon Oct 19, 2026
Windows finding: a relaunched client can't start the gateway when its host's job forbids breakaway.
On Windows,
hermes_bootstraprelaunches onto the store interpreter with a baresubprocess.call. Every borrowing data root does this, including each Multica per-taskHERMES_HOME. The child silently breaks away from hermes.exe'sKILL_ON_JOB_CLOSE | SILENT_BREAKAWAY_OKjob into the enclosing one. When that job lacksBREAKAWAY_OK,spawn_unmanaged_gateway'sCREATE_BREAKAWAY_FROM_JOBPopen fails with WinError 5,and ACP
session/newreturnsGateway inaccessible: windows_breakaway_unavailableon every task.That is the Windows face of the #138949 consumer pattern.
Fix PR: #141458. It keeps the relaunched interpreter in a launcher-equivalent job, and names the OS
refusal in the verdict.
Separate note, config only, for anyone running Multica on the same machine as their messaging
gateway: once the per-task gateway can start, it loads the bot tokens of the profile the overlay was
seeded from. If the default gateway already serves that bot, the token lock refuses it, the per-task
gateway exits at startup, and
session/newfails withGateway draining: not_ready. A managed-scopeconfig.yaml(viaHERMES_MANAGED_DIRin the Multica agent's environment only) withplatforms.<telegram|slack|discord>.enabled: falseavoids it, since an explicit disable beats envtokens. With both in place, a Multica task on the Hermes runtime completes end to end on Windows. It
may be worth having overlay homes never start messaging adapters at all.
teknium1 commentedon Oct 19, 2026
Fixed on
main: package 12 (#141321)Rows fixed or closed by package 12, moved here from the tracker. Every lane PR passed two independent review rounds plus Greptile and @unsupportedpastels' reviews, with each finding fixed before merge, and CI was green on the merged head (
77b446b6e14). A 🟢 is merge state plus each PR's tests and probes; the live retests still owed are named per row. Findings from review that were out of scope are new rows on the tracker (GW-20 to GW-23, TUI-26, MP-17, KB-8).Gateway lifecycle and restart
hermes pausenow blocks CLI, TUI and Desktop turns (⏸️ Hermes is paused);mainruns them.main: #141357 (eea83bb603c). Kept as decided: every surface shows the sameestop.paused_reply()refusal, turns already running finish,hermes pause --helpand the CLI docs say interactive turns are held. Live probe on a real gateway passed.agent.restart_drain_timeout: 0, same asmain), so restart and update interrupt in-flight turns. With 60 s the turn completed. Keep the default?0bd184f3af3). The default stays 0. A turn the restart interrupts carries "Gateway restarting…" taken from its own recorded cause (in-process and managed-worker turns), never from runner flags, so a user Stop or a pending correction is not relabelled. The key and its systemd caveat are documented.unknown, which pauses the session's whole input queue, and no surface explains it or offers a way out. A multiplexed Bot Chat stopped answering for about ten hours behind a cron delivery fencedunknown(paused: unknown_execution), while the Bot panel said the gateway was disconnected. Desktop, TUI and messaging must show why a session is paused and offer resolve or discard in place. Open: should anunknownautomation delivery pause the queue at all, or settle as interrupted?main: #141357 (eea83bb603c), salvages #139541 (@KoNit-K). Automation deliveries (cron,automation:*,local_automation_v1, webhook) fencedunknownsettle interrupted with a transcript notice and the FIFO moves on, including at a lost managed worker's exit. A person's turn still pauses, with the reason and discard in place on messaging (/discard;/hermes discardon Slack), TUI, ACP and Desktop. Live retest pending:/discardon a real messaging platform; a multiplexed Bot Chat behind a lost cron delivery. Kanban cards: KB-8.Classic CLI: flags, commands and input
/browser connectonly changes the CLI process; browser tools run in the gateway.main: #141357 (eea83bb603c). Classic/browsersendsbrowser.manage. Live connect/disconnect now work on every gateway, scoped to the session's profile: per-profile live CDP override, per-profile reap of sessions, supervisors and Browser Use daemons (ownBH_RUNTIME_DIR), and the env fallback reads the profile's own.env. Refused only from a classic CLI attached to a remote gateway. Live retest pending: two live Chromes on a multiplexed gateway; macOS. Edges: GW-23.mainshows the~estimate.main: #141335 (d2d3fa1c1d2). Live retest pending: Ctrl+C during the first request inhermesshows~N.Command layer (one gateway handler per command)
main's CLI disagree, the gateway behaviour now ships everywhere:/goal gate addis admin-only;/debug localuploads instead of rendering locally;/voice ttsenables auto-TTS with voice off; bare/voiceswitches to voice-only replies;/reasoning show|hideand/footersave the per-platform key;/moadrops the cached agent so/refineafter it finds nothing.50de19c13b6). Local sessions: the owner may add a/goalgate;/debug localnever uploads (refused by name from a chat);/voice ttsstays refused;/reasoning show|hideand/footersavedisplay.platforms.cli.*, which every local surface reads first, and Desktop Settings writes both keys; a chat's/footerkeeps the global key;/refineafter/moareads the session, with one review per session that the next turn cancels./voiceplayback: CLI/TUI don't read the gateway's TTS state; Ink's/voice on|off|statusstill uses the old sidecar call.main: #141354 (18b79b2ba8e). Ink/voice on|off|status, wake, spoken and typed stops run on the gateway's per-session voice mode; a second session can't take over a live capture. Speech output stays off per CMD-2. Live retest pending: a real microphone. Typed stop on the shared gateway: TUI-26.ACP (Zed / VS Code / JetBrains)
set_session_modeandset_config_optionraise.90b20222658). Gateway-owned per-session edit policy (ask/workspace_session/session) set throughsession.mutate, kept with the session, enforced by the gateway; sensitive paths always ask; forks start atask; every snapshot (attach, reconnect, gap,/new,/undo) repaints the editor's mode. Live retest pending: Zed.Ink TUI and local Desktop
/browser connect|disconnectrefused in Ink and Desktop.eea83bb603c) scopes/browser connect|disconnectto the session's profile (CLI-9); Desktop'sbrowser.managewire is pinned by a test in #141336 (0bd184f3af3).!cmdrefused on non-local terminal backends.0bd184f3af3). The refusal stays and names the backend (field=terminal.backend); an older gateway gets a generic translated refusal./config /export /import /snapshot /handoff /hatch /palette /timestamps /worktree /reload-mcp;/subscription,/topupand the/skillshub still use the old sidecar.main: #141354 (18b79b2ba8e)./reload-mcp,/config,/timestampsand/save(viasession.export, written in the user's cwd) run on the gateway;/export /import /snapshot /handoff /hatch /worktree /paletterefuse with what to use instead./subscription,/topupand the/skillshub already reach grant-checked gateway handlers, so nothing remains on a client sidecar. Live retest pending:/reload-mcpagainst a real MCP server./config /export /import /snapshot /worktree /timestamps /palette /history /agents /rollback /save. File-writing commands need Electron-side writes because the gateway may be remote.main: #141355 (b406d1860b9)./rollback,/agents,/config(owner projection only),/historyrun on gateway verbs;/savewrites through Electron's dialog fromsession.export;/exportand/importuse the profile dialogs locally and refuse on a remote gateway;/timestampsand/paletteare display toggles;/worktreeand/snapshotrefuse by name; all notices translated. Live retest pending:/saveagainst a remote gateway; macOS and Windows dialogs./compresson a short session: token estimate lower thanmain's when the agent isn't resident.main: #141335 (d2d3fa1c1d2). A cold no-op/compressprices the stored prompt and the tools[] pin, legacy name-list pins included.LOCALeven from a remote client. Rename (e.g.CLIENT) touches stored rows.LOCAL; pinned by a test in #141336 (0bd184f3af3).Model and reasoning picks
/modelswitch in a non-default profile never commits, in the classic CLI and Desktop: the note appears, turns run on the config default,sessions.modelnever moves, andhermes -p X chat -m … --provider …loses its launch override. Default-profile switches commit.main20acba03769(package 12, #141321): a real multiplexed gateway with profilexcommitted/modelthrough the classic CLI andsession.mutate, and kept-m … --provider …; #139694 closed.providers.<slug>endpoint always failsmodel_resolution_failed, while CLI and TUI/modelto the same endpoint work. The session's frozen config used bysession.mutate operation=modeldoesn't include theproviders:entry when it was added after the session was created. Predates #106742, per @teknium1.main: #141337 (77b446b6e14), salvages #139746 (@KoNit-K). A pick resolves against the owning profile's currentproviders:(its current.envtoo), freezes only that entry and itskey_envpointer, and replaces that entry's secrets whole; bypass sessions never import profile providers. Live retest pending: a real remote endpoint; a multiplexed secondary profile. Reorder after a pick: MP-17.Messaging and bots
/topicmode a root-DM message no longer opens a fresh topic (v0.21 → v0.22, via the lobby-recovery rule).6a67be16f9e), salvages #139188 (@KoNit-K). Opt-intelegram.extra.dm_topic_auto_transfer(off by default, read per bot): a root-DM message opens a fresh topic and the reply lands there unquoted; a failed topic create falls back to lobby recovery. Live retest pending: a real bot with Threaded Mode.Cron, Kanban and managed workers
WorkerPersistenceError: synchronous_rpc_on_event_loop; a worker path reaches the synchronous persistence RPC from the event loop. The guard is correct; the caller moves toto_thread.main: #141334 (7e0966ace4f). Activity, label, aux-usage and async-delegation ledger writes reached from Relay callbacks run off its event loop; the guard is unchanged. Live retest pending: a Kanban managed worker on a Relay profile with a non-streaming provider.Dashboard and remote Desktop mutations
session.events.sinceis refused withreplay_epoch: Extra inputs are not permittedby the standalonehermes servecontract.main: #141331 (fa2836d1ee9), salvages #140456 (@dokterdok). Live-retested by @dokterdok against a realhermes servewith the shared client in all four old/new pairs.teknium1 commentedon Oct 19, 2026
Package 13: the remaining open rows
There are 34 open rows on the tracker. 14 of them are already covered by @kshitijk4poor's package 1 salvages:
/resume//sessionsswitch subscribe (package 1 lane 1; no tracker row)We are taking 17 of the other 20. GW-19, T12 and TUI-15 stay with their open contributor PRs (#140778 by @unsupportedpastels, #140382 by @yoniebans) and are not part of this package. Each lane reproduces its rows on
mainfirst. Each outside PR listed is reviewed and salvaged withcherry-pick -xif it's right, so its author keeps the commit./queue/undo//retryafter a lost replyUpdate, Oct 19 evening: package 13 is merged. The 14 rows it fixed are listed in Fixed on
main: package 13. TUI-8, RT-18 and TUI-19 moved to #141573, and GW-19, T12 and TUI-15 stayed with their contributor PRs.yoniebans commentedon Oct 19, 2026
T12 and TUI-15 fixed on
main(package #139161, lane 5): #140382 (b4170e73b0), reviewed by @teknium1 and @kshitijk4poor. Rows for the Fixed onmaintable:Ink TUI and local Desktop
session.historyrefused).main: #140382 (b4170e73b0); the shared gateway servessession.historywith row ids/undoand/retryduring a running turn show the busy refusal instead of interrupting first.main: #140382 (b4170e73b0); still refused while a reply runs, as before #106742, now with the stop-first text on Desktop and the Ink TUI, or the wait-for-compress text when another writer holds the compression lockThe row text "instead of interrupting first" describes a behaviour
mainnever had before #106742: the old backend also refused, with guidance. The fix restores that guidance rather than adding an interrupt.yuzilongleif-collab commentedon Oct 19, 2026
Managed Kanban construction failure: preserve the cause without replaying an unknown admission
Reporting on this tracker as requested, with a small independent candidate rather than another overlapping log-boundary implementation.
Fixed source and observed behavior
Reproduced on official
mainatd4c6414728fda599e5fe886b89edf6b79ea0af84, Linux, Python 3.14.7, pytest 9.1.1, against isolated homes and a temporary SQLite board. All provider names and credential strings used in these tests are synthetic.The relevant chain is:
agent/managed_worker.py:426–437catches an agent-construction/resolution exception and callsrecord_start_failure.gateway/session_kanban.py:275–282records the credential exit code but an emptylast_output, so the board result loses the cause.hermes_cli/kanban_worker_client.py:75–85raises on anunknownreceipt before reading the exact attempt's result. A synthetic construction exception therefore never reaches that submitter's output.This is source-bound construction/receipt-path evidence, not a claim that every pre-first-turn failure shares this cause. In particular, a missing optional card skill is resolved after construction, in
_run_task_turns; this patch does not repair that failure path.Minimal candidate
unknown, best-effort print onlyworker_resultfor the same task/run/claim; then raise the unchangedKanban execution unknown; retained by ownererror.kanban.run, replay, automatic unblock, receipt settlement, state-machine change, credential/provider rerouting or retry-budget change. Existing exits 75 / 78 / 1 remain intact.The formatter's catch-all is intentional at this error-reporting boundary: a formatter failure must neither replace the startup failure nor log raw exception text. A fallback emits the exception class. The patch carries the repository's narrowly explained BLE001 comment waiver.
Scope relative to current work
unknown._run_task_turnsdelta is separate and is not duplicated here.Exact-symbol and semantic searches covered open and closed issues/PRs. No exact indexed implementation match for this bounded construction/result-reader delta was found; this is not proof that no unindexed branch exists. The tracker remains the appropriate reporting owner. The independent review checked later main
b4170e73b0bbd74b47dc567c36166d749c069699, whose relevant inputs had not changed. The subsequent publication check observed493208879a3248c6a4b844981d2c31c78ae00929:gateway/session_kanban.pynow also has a separate Docker workspace/profile-binding change in_worker_workspace, outside this patch's construction-error/result-recording functions. That later complete head was not executed; the behavioral test results below remain bound to the fixedd4c64147base.Actual tests
Final identical tests on the pristine fixed base and candidate:
test_kanban_start_failure_diagnostic.pytest_kanban_start_failure_boundaries.pyThe real construction wrapper/provider resolver, temporary SQLite persistence and real GatewayClient loop were exercised; transport was a deterministic in-memory fixture, not a live owner/model. Cases cover forced redaction, a synthetic secret crossing the output cut, long CJK first lines, multiline exclusion, exact task/run/claim mismatches, unknown with/without readable results, no replay and unchanged terminal exits.
The native
scripts/run_tests.shentry was used per file with an explicitly disclosed infrastructure-only adaptation to retain isolated HOME/PM/cache/tmp roots through its environment reset, avoid whole-tree bytecode writes, and place its per-file scratch under the task root. This is not unmodified-runner or CI certification. The full reproduction note and separate harness delta accompany the candidate.After export, the final patch was applied to its pristine preimage and all four payload files matched byte-for-byte; its core diagnostic file then passed 4/4 again. Between the 40-case green and export, only the BLE001 explanatory comment changed; AST identity was verified. The 40-case run and final-artifact four-case rerun are separate evidence, not a full-PR/CI claim.
Limits and proposed next step
This does not fix old log attribution or promise that the dispatcher's existing 400-character tail preserves every long leading cause. Existing redaction is not universal PII removal. No production data/credentials, live provider requests, native Desktop render, installed-runtime change, hosted CI or full suite was used.
The implementation runs above are author self-tests. A subsequent independent review fetched the same fixed official base and tested the final exported patch: the identical 17 new regression cases produced 12 failures / 5 passes on pristine production code and 17 passes with the patch; with 23 adjacent controls the exported candidate produced 40 passes, no failures/errors/skips. That review also checked patch replay and public-artifact identity; it reused the explicitly disclosed test-infrastructure adaptation and is not hosted-CI certification. Please consider the bounded patch below for maintainer pickup in this tracker. No competing PR or installed-runtime adoption is requested by this report.
Complete independent patch (MIT)
DarkoKuzmanovic commentedon Oct 19, 2026
Ink TUI process dock: every background process shows
0s · startingon the shared gatewayChecked against
mainat6e9b3e3b88(running5fc490e2b3). Surface: Ink TUI on the local shared gateway, Linux, single profile. I searched the open issues, PRs and this thread forstarted_epoch,uptime_secondsand "0s · starting" and found no existing report. #135062 covers the same dock but a different problem (ANSI inoutput_preview).Symptom: each row in the Processes dock reads
⚙ <command> · 0s · startingfor the whole run. In my case a heartbeat had been running for over 4 hours (checked withps) and still showed 0s. Rows of exited processes also show the wrong runtime.Cause: the client and the gateway disagree on field names.
gateway/session_ancillary.pyprocess_snapshot()(theprocess.listhandler on the shared gateway since bf94644) sendsstarted_epoch,exited_atandoutput_tail.ui-tui/src/app/processRoster.tsbuildProcessRows()still readsuptime_secondsandoutput_preview, the fields the oldtui_gateway_session_processes()/list_sessions()row carried. On the new payload both are undefined, soelapsedSecondscomes out as0, andprocessVerdict()falls back to'starting'because no output line is found.ProcessEntrycontract intui_gateway/contracts/tools_commands.pylists both sets of fields, so neither side's types catch the mismatch. The classic CLI dock reads the new fields and is not affected.Fix (running locally, about 4 lines): in
buildProcessRows(), compute the runtime fromstarted_epochwhen present, as(exited_at || now) - started_epoch; this also keeps it ticking between polls. Fall back tooutput_tailfor the last-line detail. Then addstarted_epochandoutput_tailto theProcessEntryinterface. Test:The other option is for
process_snapshot()to also senduptime_secondsandoutput_preview. The client-side fix seems better because the runtime then counts up between polls, but either one works.686f6c61 commentedon Oct 19, 2026
Diagnosis matches
mainat6e9b3e3b88:gateway/session_ancillary.py::process_snapshot()(process.liston the shared gateway) sendsstarted_epoch,exited_atandoutput_tail, whileui-tui/src/app/processRoster.ts::buildProcessRows()still readsuptime_secondsandoutput_preview. Both come outundefinedon the new payload, soelapsedSecondsis0andprocessVerdict()falls back tostarting.Fix pushed as
fix/tui-process-dock-elapsed(98267ccb), four lines inbuildProcessRows()plus a test:elapsedSecondscomes fromstarted_epochwhen it is present:(exited_at || now) - started_epoch, floored; the olduptime_seconds - sinceExitSecondsstays as the fallback for the session-local registry row, which is what the existing dock tests pin.output_tailfirst and falls back tooutput_preview.ProcessEntrydeclares both new fields.tui_gateway/contracts/tools_commands.pyalready lists them on both sides, so the contract file does not change.Client-side over the server-side alternative for the reason you gave: recomputing from
started_epochkeeps the runtime advancing between the 1.5 sprocess.listpolls instead of freezing at a value the server computed at poll time.Verification on that head:
npx vitest run src/__tests__/processDock.test.tsx→ 3 passed, and the new case fails without the source change (1 failed | 2 passed).npx tsc --noEmit -p tsconfig.jsonclean. The neighbouring dock tests (agentsDock,agentsCompact,agentsHydration,agentRoster) → 8 passed.Branch: https://github.com/686f6c61/hermes-agent/tree/fix/tui-process-dock-elapsed —
gh pr create --base main --head 686f6c61:fix/tui-process-dock-elapsed --title "fix(tui): read the shared gateway's process row in the processes dock". My account is at the open-PR ceiling, soCreatePullRequestis refused even with--draft; the branch is the deliverable until there is a slot.Sahilvishnaliya commentedon Oct 19, 2026
F5 (RPCs past the frame cap) — candidate fix in #141707 (hermes_cli/gateway_client.py).
Mechanism, for the record: the client delivers replies from its single reader loop, so an answer is only reachable while the reader reads. _read_on() stopped that reader at EVENT_OVERFLOW_FRAMES (16,384) / EVENT_OVERFLOW_BYTES (8 MiB) with an RPC pending — and the pending RPC is usually the one awaited by the task that drains events (session.resume after a gap, prompt.receipt). Because the owner attaches the re-attached subscription before it answers, a live turn's frames sit ahead of the answer; past the cap the reader parked for the consumer that was waiting on that answer, the 30 s stall budget expired, and the RPC failed as
Gateway disconnected; turn outcome is unknown— a healthy socket and a still-running turn.#141707 keeps the reader reading toward the answer and declares what it can no longer hold: one session.replay_gap per session behind the held frames (the same notice the fanout sends on overflow, already handled by CLI and ACP by re-attaching and replaying from the store). Bounded at cap + one notice; a consumer that stalls with nothing awaited is still failed and bounded, unchanged.
Distinct from #140388/CLI-35 (fixed by #140968 — the owner retiring a slow viewer's subscription, server → client), #132546 (LONG-handler pool saturation) and #141422 (sync compaction inside session.resume), and no open PR touches EVENT_OVERFLOW_FRAMES / EVENT_OVERFLOW_BYTES.
Note on severity: this fails an in-flight RPC with a misleading reason and a resume workaround; the turn keeps running and persists, so I'd call it P1 degraded-RPC rather than P0-class, even though it was listed under this P0 tracker.
teknium1 commentedon Oct 19, 2026
Fixed on
main: package 13The rows package 13 took (plan) and merged. Each PR went through an independent review round, Greptile, and the round-2 reviews from @unsupportedpastels and @dokterdok before merge. Every fix has a test that fails on the merge base and passes on the PR. Live retests that are still owed are listed in each PR body.
53a32851071)53a32851071)@reference expansion, is latched: the worker never starts and the interrupted result carries the restart reason. A user Stop leaves no cause.53a32851071)hbh-<hash>dir, measured on the resolved path. Each profile gets its ownBH_TMP_DIR. A/browser connectduring session creation or duringbrowser_execrouting reroutes or reloads, so the old browser is never kept or handed out.650cbf30142)admission_conflict/permission_denied. A typed--api-keyis held to the startup resume only. A cancelled reconnect cancels itssession.resume(no 25 s exit hang). Salvages #140348 plus two fix commits by @dokterdok.3ea5a08fa19)/queuewhile a turn runs, and a text-only queue-mode Enter, go into the gateway's durable FIFO. The prompt is adopted by admission id, survives an owner restart, runs once, and is painted after a reconnect. A slash command typed after it waits (bounded) until it starts.list/rm/edit/clearact on the session's shared queue;moveexplains it can't reorder owner-held rows.c0015464f0f)custom_providersentry is recovered on cold start by its canonical name, so a reorder survives. An in-place rename (same index and URL) runs as onmain. A deleted entry still refuses and never borrows another entry's key.7df8c462b8e)unknownacross owner epochs settles interrupted and is never replayed. Clients can't set the marker, and rows minted without it keep today's pause.e3277f17f30)hermes --tui -wremoves a clean worktree on quit again. A tree is kept when it is dirty, has unpushed commits, has a pending turn, has active/loop, heartbeat, goal or bound cron work, or another viewer still holds the session. A resume (--tui --resume,-c, classic--resume) recreates a missing launcher-recorded tree at the same path, so its turns run.f4ac0bea1ea)prompt.submitand through busy steer/redirect, and a retry replays the stop. The new{voice_stopped: true}reply is handled by Ink, Desktop (both paths), ACP, the classic CLI,chat --attachand the sidecar forward. Messaging lanes are untouched.f4ac0bea1ea)f4ac0bea1ea)/undoor/retryafter a lost reply replays the same rewind, including the full cut of a lost/undo Nand on named profiles after a redial. A refused rewind is dropped, not resent. Salvages #140465 (@rodricksz4h5).c9dce2c26f8)hermes.exeshim, and RT-10's remaining spaced.cmdoverride is quoted. The Windows attach E2E is green, but it doesn't stage a bundled payload (see CI-2, added below). Salvages #139174 (@KoNit-K, @austinpickett).afe27560b14)mainalready answersnot_foundfor a live entry whose row is gone (0bf1ed99ce3, @dokterdok). #138817's test file passes onmainand fails with the reportedTypeErrorwithout that guard. The 30 ssession.resumetimeouts in the same Windows report are a different symptom. #138817 is closed as superseded.0bf1ed99ce3Still open from package 13's scope: TUI-8, RT-18 and TUI-19, consolidated into @yoniebans's #141573. #141491 and #141498 are closed as superseded, and their closing notes list the commits to carry over. GW-19, T12 and TUI-15 stayed with their open contributor PRs (#140778, #140382).
sinisa222 commentedon Oct 19, 2026
Following up on the triage of #141163 (and its earlier, narrower duplicate #140913) into the "Gateway lifecycle and restart" section — it doesn't have a row ID there yet, so here are the build details and two further findings from the same machine.
Build
mainSHA:ccc6956350(hermes --version:v0.22.2+1545.gccc6956 · stable), git installapps/desktop/release/mac-arm64/Hermes.app, bundle version0.0.0)b11671(the lockfile pin)Core problem (#141163) is unchanged on this SHA: after an update the Desktop app doesn't respawn its
hermes servebackend, so the router it owns never starts. The workaround is stillnohup hermes serve --skip-build &from a terminal; it doesn't work from a launchd agent (the router gets SIGINT right after binding).New 1 —
hermes updatepins a new engine but never installs itAfter the update
pm/lock.jsonpinnedllamacpp-metal11671, butbinaries.installed_engine()kept returningb10964(allow_outdated=True), and the router kept running on it. Nothing told me. It only surfaced because a catalog model withmin_engine: b11370was greyed out in Local Models with "update the engine first". Expected: the update installs the pinned engine, or says plainly that the running engine is older than the pin.New 2 — catalog MTP flag applied to a GGUF that has no MTP layers → model fails to load on b11671
Qwen3.6-35B-A3B-UD-Q4_K_M.gguffromunsloth/Qwen3.6-35B-A3B-GGUF(22,134,528,992 bytes,qwen35moe.block_count = 40, nonextn_predict_layerskey) has the same filename as the catalog variant, which comes fromunsloth/Qwen3.6-35B-A3B-MTP-GGUF(22,663,387,424 bytes).find_entry_for_model()matches on the stem alone, so_runs_mtp()(presets.py:77) returns true and the preset getsspec-type = draft-mtp. Onb10964this was silently ignored; onb11671the model refuses to start:With the model as
fallback_providers[1], this means the fallback chain itself fails right after the engine update. Expected: confirm the MTP layers from the GGUF header (or match the catalog on size/hash, not filename) before addingdraft-mtp. Workaround: overridespec-type = noneafter every preset regeneration.Related, smaller: there is no supported, durable way to cap a local model's window.
window_overrides.jsoncan only raise it (context_policy.py:153,target > decision.window), andpresets.iniis rebuilt on every backend start. K2-Horizon-MoVA-36B-A4B was given its full 524,288-token training window and took 71.6 GiB RSS, which pushed this 96 GB machine to 7 % free and nearly full swap.yoniebans commentedon Oct 19, 2026
TUI-19, TUI-8 and RT-18 fixed on
main; TUI-21 already fixed (package #139161, lane 3): #141573 (cc6b1f299b), reviewed by @unsupportedpastels and @teknium1. Rows for the Fixed onmaintable:Ink TUI and local Desktop
/resumehides stored messaging and cron sessions; resuming one →not_found.main: #141573 (cc6b1f299b) lists them; resume landed earlier as79f8d1f86c(@Hamakaze1s, #139675)session.resumeattaches a viewer and nothing detaches it. Classic CLI/sessionsdelete has the same gap.main: #141573 (cc6b1f299b); both clients hand the viewer back on refusalhermes sessions deleteremoves a session that is still in the live registry,session.listraisesTypeErrorinsession_authority._handle.main: #140971 (0bf1ed99ce); diagnosis and regression probe by @686f6c61 (#138817)Session controls unavailable: stale_generation.main: #141573 (cc6b1f299b), with @KoNit-K's Desktop commits from #139031TUI-21 does not reproduce at
8f5693af41: delete a live TUI session withhermes sessions delete <id> --yes, thensession.listandsession.active_listboth answer anderrors.loghas noTypeError.RT-18 restores the control read as
mainhad it before #106742: a messaging session's goal, loop and heartbeat stay readable after its chat moves on. A message sent from Desktop into a messaging session still lights the unread dot; replies from the chat itself do not.