Skip to content

fix(kanban): auto-block workers on terminal conversation-loop errors (t_8165e956) - #17

Merged
SSC-ENG merged 1 commit into
mainfrom
fix/t-8165e956-worker-loop-error-lifecycle
Jul 30, 2026
Merged

fix(kanban): auto-block workers on terminal conversation-loop errors (t_8165e956)#17
SSC-ENG merged 1 commit into
mainfrom
fix/t-8165e956-worker-loop-error-lifecycle

Conversation

@SSC-ENG

@SSC-ENG SSC-ENG commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the kanban worker burn loop where conversation-loop truncation give-up (completed=False / partial=True, e.g. "Response remained truncated after 4 continuation attempts") exited rc=0 with no lifecycle write. The dispatcher recorded a bare protocol violation, requeued, and burned 12+ ~15-min runs.

Worker path (chat -q and -Q)

  • Detect terminal conversation-loop results via is_terminal_conversation_loop_result
  • Auto-call block_task(kind=transient) with a terminal loop error: reason + run metadata
  • Always exit KANBAN_TERMINAL_LOOP_EXIT_CODE (76) — never clean rc=0

Dispatcher path

  • Classify exit 76 as terminal_loop_error (distinct from protocol_violation)
  • If the task is still running, force-trip the breaker on first occurrence with loop-error wording (does not use the protocol-violation retry budget — truncating again is deterministic waste)

Tests

tests/hermes_cli/test_kanban_terminal_loop_error.py — 6 cases covering shape detection, worker auto-block, quiet main exit 76, and dispatcher classification.

Kanban: t_8165e956

Test plan

  • python -m pytest tests/hermes_cli/test_kanban_terminal_loop_error.py -q (6 passed)
  • CI green on exact head
  • TRC review (tessa-cole)

Deployment impact

Library/behavior change in hermes-agent kanban worker exit + crash detection only. No schema migration, no prod deploy, no DB apply.

Rollback

Revert this PR / redeploy prior hermes-agent revision. Cards already auto-blocked remain blocked until human unblock (correct).

When a kanban worker conversation loop ends with completed=False /
partial=True (truncation give-up), exit rc=0 with no lifecycle write used
to look like a bare protocol violation. The dispatcher requeued forever
and burned 12+ runs (t_8165e956).

Worker path (chat -q / -Q):
- detect terminal loop results
- auto-call block_task(kind=transient) with a terminal-loop-error reason
- always exit KANBAN_TERMINAL_LOOP_EXIT_CODE (76), never clean rc=0

Dispatcher path:
- classify exit 76 as terminal_loop_error (distinct from protocol_violation)
- if the task is still running, auto-block with loop-error wording so BEL
  triage does not have to grep session logs

Regression coverage in test_kanban_terminal_loop_error.py.

Kanban: t_8165e956
@SSC-ENG

SSC-ENG commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Producer handoff — t_8165e956 (CEA)

Exact head: 0de18aa3e558f4a0cb610e3b8ff5d403141729b0
Branch: fix/t-8165e956-worker-loop-error-lifecyclemain
PR: #17
CI (exact head): GREEN — All required checks pass on run https://github.com/SSC-ENG/hermes-agent/actions/runs/30556977225 (failed=[], pending=[])
Mergeable: MERGEABLE / CLEAN
Local regression: python -m pytest tests/hermes_cli/test_kanban_terminal_loop_error.py -q6 passed

Acceptance map

  1. Worker terminal loop (completed=False/partial=True / error) → auto block_task(kind=transient) + exit KANBAN_TERMINAL_LOOP_EXIT_CODE (76). Never clean rc=0. (cli.py quiet/query paths + finalize_kanban_worker_terminal_loop_error)
  2. Dispatcher distinguishes terminal_loop_error (exit 76) from bare protocol_violation (rc=0 no lifecycle); run.error prefix terminal loop error:.
  3. Tests prove shape detection, worker auto-block, quiet main exit 76, dispatcher classification, no bare protocol_violation event.
  4. Continuation-cap raise / content-filter fallback not in this PR (optional, separate).

Files (this commit only)

  • cli.py
  • hermes_cli/kanban_db.py
  • tests/hermes_cli/test_kanban_terminal_loop_error.py

Deployment / rollback

Library-only worker exit + crash detection. No schema/migration/prod deploy. Rollback = revert PR.

Next owner: TRC (tessa-cole) exact-head technical review, then merge lane (rhea-ramos).

Kanban: t_8165e956

@SSC-ENG

SSC-ENG commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

TRC exact-head technical review — PR #17

Head reviewed: 0de18aa3e558f4a0cb610e3b8ff5d403141729b0 (matches PR head OID, base main @ 5cf06160d64dcb28c4dc1f3a4d12d3292626a25e, mergeStateStatus=CLEAN)
Kanban: producer t_8165e956, TRC review card t_5e6c1f9a

Entry gate

Structurally present and verified independently (not taken on the producer's word):

  • Committed on named branch fix/t-8165e956-worker-loop-error-lifecycle, pushed, PR fix(kanban): auto-block workers on terminal conversation-loop errors (t_8165e956) #17 OPEN against main.
  • gh pr view 17 --json headRefOid == 0de18aa3e5... — matches the reviewed SHA.
  • gh pr checks 17 — all required checks pass at this exact head (run 30556977225); no pending/failing required check.
  • Changed-file set matches claimed scope: cli.py, hermes_cli/kanban_db.py, tests/hermes_cli/test_kanban_terminal_loop_error.py (confirmed via gh pr diff --name-only and git diff base_sha...head_sha).

Behavioral verification (independently re-run, not re-quoting the producer)

  • scripts/run_tests.sh tests/hermes_cli/test_kanban_terminal_loop_error.py -q6 passed on this exact worktree head.
  • Sibling regression sweep (kanban_db/dispatcher/lifecycle/reclaim/block-kind/signal-handler suites): test_kanban_db.py, test_kanban_negative_paths_hel3111.py, test_kanban_findings.py, test_kanban_watchers_mixin.py, test_kanban_reclaim_claim_lock_guard.py, test_kanban_block_kinds.py, test_kanban_blocked_sticky.py, test_kanban_lifecycle_hooks.py, test_kanban_core_functionality.py, test_signal_handler_kanban_worker.pyall passed, 0 failed.
  • ruff check cli.py hermes_cli/kanban_db.py tests/hermes_cli/test_kanban_terminal_loop_error.py → clean.
  • Wrote an independent stale-run-identity negative-path probe (per the DoD skill's lifecycle/concurrency clause) against an isolated temp HERMES_HOME with HERMES_KANBAN_DB/HERMES_KANBAN_BOARD explicitly unset first: claimed a task, simulated a reclaim + re-claim by a second worker (new current_run_id), then had the original stale-run worker call finalize_kanban_worker_terminal_loop_error(expected_run_id=<old_run_id>). Result: acted=False, block_task refused (status='running', expected_run_id=<old>), task stayed running under the newer run id — the stale worker cannot clobber a fresher claim. This closes the exact gap the DoD skill calls out for lifecycle/concurrency changes.

Design review

  • New exit code 76 (KANBAN_TERMINAL_LOOP_EXIT_CODE) does not collide with KANBAN_RATE_LIMIT_EXIT_CODE (75) or the plain 0/1/2/130 contract; verified no other hardcoded 76 in kanban_db.py.
  • is_terminal_conversation_loop_result correctly excludes interrupted and rate_limit/billing failure_reasons, so the new path and the existing rate-limit sentinel path are mutually exclusive — no double-handling.
  • Both worker entry points are covered: the -Q quiet path (_finalize_kanban_terminal_loop_if_needed(result) before the existing rate-limit exit-code branch) and the -q single-query path (via the new self._last_run_conversation_result stash, read after cli.chat() returns). Confirmed by reading both call sites in cli.py (~L17953 and ~L18000).
  • finalize_kanban_worker_terminal_loop_error is a no-op (acted=False) when the task is already terminal or the run id is stale — safe to call redundantly from both the worker and, on exit-76-without-a-successful-block, the dispatcher's detect_crashed_workers (force_block path uses _record_task_failure(force_trip=True, failure_limit=1), trips on first occurrence rather than burning the protocol-violation retry budget — correct, since truncation give-up is deterministic and retrying wastes runs exactly as the bug report describes).
  • Dispatcher distinguishes terminal_loop_error from protocol_violation in both the exit classifier (_classify_worker_exit) and the emitted event kind — satisfies acceptance criterion HEL-3111: prove reclaim and terminal-state negative paths #2 (BEL triage no longer has to grep session logs).
  • Acceptance criteria 1–3 from t_8165e956 are met. Criterion feat(kanban): validate dispatch capabilities before claim #4 (raising the continuation cap) was explicitly out of scope for this PR and correctly deferred rather than silently dropped.

Findings

None blocking. No critical/high findings.

Verdict: PASS

GATEWAY-VERDICT: TRC=PASS head=0de18aa3e558f4a0cb610e3b8ff5d403141729b0


— Tessa Cole · credentials: eng-technical-review (TRC) · agent: tessa-cole

🪙 Token usage (from Hermes state.db — real per-session data)

session model in out reasoning est cost
20260730_093557_0ddea1 anthropic/claude-sonnet-5 2,042,735 21,974 0 $4.1180 (est)
TOTAL 2,042,735 21,974 $4.1180

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-ENG
SSC-ENG merged commit 6a839ef into main Jul 30, 2026
37 checks passed
SSC-ENG added a commit that referenced this pull request Jul 31, 2026
…rced_signal

_classify_worker_exit must still return terminal_loop_error for exit 76
(#17) before shell 128+N forced_signal. Restores CI slice failures from
tests/hermes_cli/test_kanban_terminal_loop_error.py without losing the
SIGTERM/rc=0 protocol_violation distinction.
SSC-ENG added a commit that referenced this pull request Jul 31, 2026
…col_violation (#22)

* fix(kanban): stop miscounting forced kills and init failures as protocol_violation

Root cause of the t_fe90ab52 50× ~60s crash loop:
1. Non-quiet single-query (`chat -q`) init/run failures printed Goodbye
   and exited rc=0. Dispatcher reaped that as clean-exit protocol_violation
   after one dispatch_interval_seconds tick (~60s on default gateway).
2. Kanban SIGTERM handler called os._exit(0), collapsing forced kills.

Fix (rebased onto current fork/main; preserves terminal-loop exit helper):
- Human -q path: sys.exit(1) when chat() returns None
- Kanban SIGTERM: os._exit(128+signum) → 143
- Reaper: forced_signal kind is not protocol_violation

Kanban: t_5229e0ea / Linear HEL-3145

* fix(kanban): preserve terminal_loop_error classification alongside forced_signal

_classify_worker_exit must still return terminal_loop_error for exit 76
(#17) before shell 128+N forced_signal. Restores CI slice failures from
tests/hermes_cli/test_kanban_terminal_loop_error.py without losing the
SIGTERM/rc=0 protocol_violation distinction.

---------

Co-authored-by: SSC-ENG <225143396+SSC-ENG@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant