feat(kanban): add --task flag to dispatch for targeted task dispatch - #53956
scottbontrager wants to merge 1 commit into
Conversation
Competing with open #30457 ( |
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Clean CLI addition: adds --task flag to kanban dispatch for targeted task dispatch by ID. The only_task_ids parameter threads through dispatch_once -> _dispatch_once_locked correctly, bypassing max_spawn and max_in_progress caps when targeting specific tasks. The Run.from_row NULL id guard is a nice defensive touch.
Reviewed by Hermes Agent
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused CLI path; current main still lacks a dispatch-specific --task option (hermes_cli/kanban.py:636-649), so the use case remains valid.
Problems
- In the PR diff,
hermes_cli/kanban_db.py:7000clearsmax_spawn, while the new filter applies only to ready rows. The dispatcher also has a separate review dispatch loop (hermes_cli/kanban_db.py:7559-7575) that remains unfiltered, sodispatch --task ...can spawn unrelated review tasks. - The PR changes no tests, despite changing task selection and concurrency-cap behavior.
Suggested changes
- Port the selected-ID parameter through current main's
dispatch_once()lock wrapper and_dispatch_once_locked(), filtering both ready and review queries. - Ensure cap bypass cannot permit an unselected task to spawn, and add regression coverage for selected/unselected ready and review tasks plus default dispatch behavior.
Automated hermes-sweeper review.
| # externally — bypass max_spawn and max_in_progress caps. | ||
| if only_task_ids: | ||
| max_spawn = None | ||
| max_in_progress = None |
There was a problem hiding this comment.
Clearing max_spawn here also removes the cap for the later review-task loop, but this PR only filters ready_rows. A dispatch --task ... invocation can therefore spawn every unselected review task. Apply the selected-ID filter to review rows too, or otherwise ensure the cap bypass is confined to selected tasks.
There was a problem hiding this comment.
Good catch — this was a real bug, and it is fixed in 53c8ce3e2.
You are right that clearing max_spawn also unbounded the review loop: its only guard is if max_spawn is not None and running_count + spawned >= max_spawn: break, so with the cap cleared and the review query unfiltered, a single dispatch --task X would spawn every review task on the board.
Rather than filter the two queries independently and risk them drifting apart again, both now go through one _dispatchable_rows(conn, status, only_task_ids) helper, so the ready and review paths cannot diverge. Status is a bound parameter rather than interpolated, and an empty only_task_ids list is treated as "no selection" to match the if only_task_ids: truthiness checks the callers already use.
Regression coverage added in tests/hermes_cli/test_kanban_db.py for selected vs. unselected tasks in both queues, targeting a review task directly, the cap bypass staying confined to the selection, and unchanged default dispatch. I verified these actually catch the bug: with the review-queue filter reverted, three of them fail with an unselected review task in the spawn list.
The branch is also rebased onto current main as you suggested.
63842f3 to
53c8ce3
Compare
…d dispatch Adds `--task <id>` (repeatable) to `hermes kanban dispatch`, dispatching specific tasks through the full lifecycle (claim + workspace + spawn + PID recording) instead of the priority-ordered sweep. This lets an external scheduler choose *which* tasks run — e.g. balancing across several inference endpoints — while Hermes still does the actual dispatch work. `hermes kanban claim` was the only prior option, and it marks a task running without spawning a worker, producing zombie tasks with no PID or heartbeat that then block the dispatcher via max_in_progress. Both the ready and review queues are filtered through a shared `_dispatchable_rows()` helper. This matters: a targeted dispatch clears max_spawn/max_in_progress (the caller owns concurrency), so the filter is the only thing bounding the spawn loop. Filtering just the ready query — as the first revision did — let `dispatch --task X` spawn every review task on the board. Regression tests cover selected/unselected tasks in both queues, the confined cap bypass, and unchanged default dispatch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53c8ce3 to
10a354e
Compare
|
Rebased onto current What conflicted, and how it was resolvedOnly The cause was the two test-pruning waves on I kept Re-verified against current
|
Summary
--task <id>(repeatable) tohermes kanban dispatchfor dispatching specific tasks through the full lifecycle (claim + workspace + spawn + PID recording)max_in_progress/max_spawncaps when--taskis used, since the caller manages concurrency externallyprofile_exists, per-profile cap, respawn guard, failure limit)Motivation
The built-in dispatcher selects tasks by priority order, which can stack work onto a single LLM endpoint when multiple inference endpoints are available (e.g. local GPU, remote API, second machine). An external scheduler can make smarter selections — for example, balancing across endpoints — but the only available CLI command was
hermes kanban claim, which marks a task as running without spawning a worker process. This creates zombie tasks with no PID tracking or heartbeat support, which then block the dispatcher due tomax_in_progress.With
--task, an external scheduler calls:Each task gets the full dispatcher treatment — identical to what the gateway's automatic dispatch loop does — while the scheduler controls which tasks to dispatch.
Changes
hermes_cli/kanban_db.pyonly_task_ids: Optional[list]parameter todispatch_once()and_dispatch_once_locked()_dispatchable_rows(conn, status, only_task_ids)helper that both the ready and review queues now go through, restricting rows to the selected IDs when a selection is givenonly_task_idsis set, bypassesmax_in_progressandmax_spawncaps (caller manages concurrency)Run.from_rowraises on a NULL id, andlist_runsskips such rows, rather than constructing a corruptRunhermes_cli/kanban.py--taskargument (action=append) to thedispatchsubcommanddispatch_once()asonly_task_idsReview feedback addressed
Thanks — the review caught a real bug, and it is fixed here.
Clearing
max_spawnfor a targeted dispatch also removed the bound on the review-column loop, whose only guard isif max_spawn is not None and running_count + spawned >= max_spawn: break. With the cap cleared and the review query unfiltered, a singledispatch --task Xwould spawn every review task on the board.Rather than filter the two queries separately and risk them drifting apart again, both now share one
_dispatchable_rows()helper. The task status is a bound parameter rather than interpolated, and an emptyonly_task_idslist is treated as "no selection" to stay consistent with theif only_task_ids:truthiness checks the callers already use.The branch has also been rebased onto current
main, as requested.Test plan
Regression coverage added in
tests/hermes_cli/test_kanban_db.py:test_dispatch_only_task_ids_spawns_selected_ready_only— targeted dispatch spawns the named ready task and no othertest_dispatch_only_task_ids_does_not_spawn_unselected_review— the reported bug; unselected review tasks are never spawnedtest_dispatch_only_task_ids_can_target_a_review_task— a review task named explicitly still dispatchestest_dispatch_only_task_ids_bypasses_caps_for_selected_only— cap bypass admits the selected task without freeing unselected ready or review taskstest_dispatch_without_only_task_ids_is_unchanged— default dispatch still sweeps both queuestest_dispatch_empty_only_task_ids_falls_back_to_full_sweep— empty list means "no selection"Each of these was confirmed to actually catch the bug: with the review-queue filter reverted, three of them fail with an unselected review task appearing in the spawn list. They pass with the fix in place.
Full kanban suite green — 747 passed, 1 skipped across
test_kanban_db.py,test_kanban_core_functionality.py,test_kanban_cli.py,test_kanban_dashboard_plugin.py,test_kanban_tools.py,test_kanban_notifier.py,test_kanban_decompose.py, andtest_kanban_diagnostics.py.Also exercised against a live board of 448 tasks (140 dispatchable ready, plus review tasks):
hermes kanban dispatch --task <id> --dry-run --jsonreports exactly the one named task.🤖 Generated with Claude Code