fix(kanban): keep initial_status='blocked' tasks sticky - #76718
fix(kanban): keep initial_status='blocked' tasks sticky#76718Jeffgithub0029 wants to merge 2 commits into
Conversation
Add `kanban update-body` to atomically replace a task's canonical body with an audited body_updated event (sha256 + length; repeat is a no-op). Make initial_status='blocked' a typed sticky block (block_kind 'needs_input') so dispatcher recompute_ready cannot silently promote a human parking decision. Cherry-picked from df778fd765 (original message was an accidental API-error dump); preserved as backup/kanban-sticky-body-update-20260802-175124.
Related: #34735 covers the initial-blocked event. This PR also adds the audited body-update operation and typed-gate/reclaim/legacy-repair behavior, so it is not a duplicate; maintainers should choose the desired sticky-gate scope. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused initial-blocked and audited body-update work. The initial-status premise is real on current main: create_task() records only created for an initially blocked task (hermes_cli/kanban_db.py:3254-3273), while _has_sticky_block() only recognizes blocked/unblocked events (hermes_cli/kanban_db.py:4126-4132).
Problems
hermes_cli/kanban_db.py:4253conflates historicalblock_kindwith an active block.unblock_task()intentionally preserves that field (hermes_cli/kanban_db.py:5942-5951) but sends tasks with unfinished parents totodo; the new earlycontinueprevents their normal later promotion.hermes_cli/kanban_db.py:4708has the same conflation. A task explicitly unblocked, claimed, then manually reclaimed becomes blocked again merely because its historicalblock_kindremains. Current reclaim semantics are explicitly reset-to-ready (hermes_cli/kanban_db.py:4607-4616).
Suggested changes
- Limit the sticky fix to emitting the initial
blockedevent, or introduce separate active-gate state thatunblock_task()clears while retaining loop history. - Add the unblock/parent-completion and unblock/claim/reclaim regressions; document
update-bodyinwebsite/docs/reference/cli-commands.md.
Automated hermes-sweeper review.
| for row in todo_rows: | ||
| task_id = row["id"] | ||
| cur_status = row["status"] | ||
| if row["block_kind"] in {"needs_input", "capability"}: |
There was a problem hiding this comment.
block_kind is intentionally preserved after unblock_task() as loop-history (hermes_cli/kanban_db.py:5942-5951). An explicitly unblocked task with an unfinished parent is therefore todo plus needs_input, and this guard prevents it from ever becoming ready after the parent completes. Gate on current sticky state, or store a separate active-gate marker that unblock clears.
| return False | ||
| if row["status"] != "running" and row["claim_lock"] is None: | ||
| # Nothing to reclaim — already ready / blocked / done. | ||
| return False |
There was a problem hiding this comment.
This treats retained loop-history as a current gate. After initial_status='blocked' -> unblock -> claim, block_kind remains needs_input by design, so a later operator reclaim changes an explicitly released task back to blocked. That contradicts reclaim's current reset-to-ready behavior; do not use historical block_kind alone here.
…vent create_task(initial_status='blocked') is an explicit human parking decision, not a recoverable circuit-breaker failure. Record a 'blocked' event (kind: needs_input) at creation so _has_sticky_block sees it and the dispatcher recompute_ready cannot silently promote the card after an assignment or dependency tick. unblock_task remains the only way out. Narrow fix: no block_kind mutation, no recompute_ready/reclaim changes, so unblock/parent-completion and unblock/claim/reclaim semantics are untouched. This converges the earlier combined PR (update-body command moved to NousResearch#76765).
|
Thanks for the triage note — acting on the scope guidance. Converged per your suggestion: this PR is now a deliberately minimal sticky-initial-block fix only. It records a typed The audited Reclaim/legacy-repair/typed-gate behavior dropped from this branch entirely. Since the sticky-gate space already has several open PRs (#34735, #71977, #35832, #46565, #71147, #69679, #34756), maintainers deciding the desired sticky-gate scope can use this PR as the minimal reference implementation; if #34735 or another is preferred, this one can be closed. |
|
Closing this in favor of #34735, which already has maintainer attention (Kayhusk commented) and covers the same minimal fix (emit a |
What
create_task(initial_status='blocked')is an explicit human parking decision, not a recoverable circuit-breaker failure. Until now it wrotetasks.status='blocked'without ablockedevent row, so_has_sticky_blocksaw no sticky signal and the next dispatcherrecompute_readysilently promoted the card toready— after which it was dispatched to a worker that had nothing left to do.This PR records a typed
blockedevent (kind: needs_input) at creation, making the initial parking decision sticky the same way a worker-initiatedkanban_blockis.unblock_taskremains the only way out.Scope (narrow by design)
create_taskgains an event emission. Noblock_kindmutation, norecompute_ready/reclaim_taskchanges.Relationship to other PRs
kanban update-bodycommand that previously lived in this branch is now its own focused PR: feat(kanban): add audited update-body command #76765.Tests