Skip to content

feat(kanban): add --task flag to dispatch for targeted task dispatch - #53956

Open
scottbontrager wants to merge 1 commit into
NousResearch:mainfrom
scottbontrager:feat/kanban-dispatch-task-flag
Open

scottbontrager wants to merge 1 commit into
NousResearch:mainfrom
scottbontrager:feat/kanban-dispatch-task-flag

Conversation

@scottbontrager

@scottbontrager scottbontrager commented Jun 28, 2026

Copy link
Copy Markdown

Summary

  • Adds --task <id> (repeatable) to hermes kanban dispatch for dispatching specific tasks through the full lifecycle (claim + workspace + spawn + PID recording)
  • Bypasses max_in_progress / max_spawn caps when --task is used, since the caller manages concurrency externally
  • Filters both the ready and review queues to the selected IDs, so the cap bypass can never reach a task the caller did not name
  • All per-task safety guards still apply (profile_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 to max_in_progress.

With --task, an external scheduler calls:

hermes kanban dispatch --task t_abc123 --task t_def456 --json

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.py

  • Added only_task_ids: Optional[list] parameter to dispatch_once() and _dispatch_once_locked()
  • Added a _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 given
  • When only_task_ids is set, bypasses max_in_progress and max_spawn caps (caller manages concurrency)
  • Run.from_row raises on a NULL id, and list_runs skips such rows, rather than constructing a corrupt Run

hermes_cli/kanban.py

  • Added --task argument (action=append) to the dispatch subcommand
  • Passes collected task IDs to dispatch_once() as only_task_ids

Review feedback addressed

Thanks — the review caught a real bug, and it is fixed here.

Clearing max_spawn for a targeted dispatch also removed the bound on the review-column loop, whose only guard is if max_spawn is not None and running_count + spawned >= max_spawn: break. 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 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 empty only_task_ids list is treated as "no selection" to stay consistent with the if 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 other
  • test_dispatch_only_task_ids_does_not_spawn_unselected_review — the reported bug; unselected review tasks are never spawned
  • test_dispatch_only_task_ids_can_target_a_review_task — a review task named explicitly still dispatches
  • test_dispatch_only_task_ids_bypasses_caps_for_selected_only — cap bypass admits the selected task without freeing unselected ready or review tasks
  • test_dispatch_without_only_task_ids_is_unchanged — default dispatch still sweeps both queues
  • test_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, and test_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 --json reports exactly the one named task.

🤖 Generated with Claude Code

@alt-glitch alt-glitch added type/feature New feature or request comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have labels Jun 28, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Competing with open #30457 (feat: allow forced kanban dispatch), which adds --task/--task-id + --force to the same hermes kanban dispatch command and the same files (kanban.py, kanban_db.py). This PR is a cleaner --task-only variant. Cross-linked as related_to so a maintainer can pick within the cluster; not marking either a duplicate.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:7000 clears max_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, so dispatch --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.

Comment thread hermes_cli/kanban_db.py
# externally — bypass max_spawn and max_in_progress caps.
if only_task_ids:
max_spawn = None
max_in_progress = None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@scottbontrager
scottbontrager force-pushed the feat/kanban-dispatch-task-flag branch from 63842f3 to 53c8ce3 Compare July 19, 2026 22:24
…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>
@scottbontrager
scottbontrager force-pushed the feat/kanban-dispatch-task-flag branch from 53c8ce3 to 10a354e Compare August 7, 2026 04:55
@scottbontrager

Copy link
Copy Markdown
Author

Rebased onto current main (55505be15) — the merge conflict is resolved and the PR is mergeable again. The change itself is unchanged: same 3 files, +169/−12.

What conflicted, and how it was resolved

Only tests/hermes_cli/test_kanban_db.py conflicted; both source files merged cleanly.

The cause was the two test-pruning waves on main (6b81590c5, 39975613b), which cut this file from 4,961 to 1,585 lines and removed the review-dispatch tests that this PR's new tests were written next to — so the conflict was "upstream deleted this neighbourhood, the PR added to it".

I kept main's pruning and re-applied only the 6 tests this PR adds. None of the ~3,600 pruned lines were resurrected — this PR shouldn't be quietly reverting that cleanup.

Re-verified against current main

The earlier review found that clearing max_spawn left the review loop unbounded. Since main has moved a long way since this branch was cut, I re-checked that specific class of problem rather than trusting the clean auto-merge:

  • _dispatch_once_locked() still has exactly two task-selection sites (ready and review); no third queue has appeared. Both go through _dispatchable_rows().
  • The two queries the merge replaced were textually identical to what the shared helper produces, so no filtering conditions were dropped.
  • The per-profile cap (max_in_progress_per_profile) is evaluated independently and is unaffected by the targeted-dispatch bypass.

Test results (local)

  • The 6 new tests pass. test_kanban_db.py collects 36 tests on this branch vs 30 on main — exactly the 6 added.
  • Full tests/hermes_cli/ suite: the set of failing tests is byte-identical on this branch and on clean main (157 failures, all pre-existing and unrelated — webhook_cli, web_ui_build, model_validation, etc.). No regressions introduced.
  • To confirm the regression coverage is real rather than decorative, I reverted the review-queue filter locally to reproduce the originally-reported bug: 3 of the 6 new tests fail, including test_dispatch_only_task_ids_does_not_spawn_unselected_review. Restored afterwards.

CLI parsing spot-check: dispatch --task a --task b['a', 'b'], and omitting --task leaves task_ids as None, so default dispatch behaviour is untouched.

Note there are no CI checks configured on this branch, so the above is local-only; happy to adjust if there's a suite I've missed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants