Skip to content

fix(kanban): add per-task --max-iterations override for worker budget - #19

Merged
cwest merged 1 commit into
cwest/integrationfrom
topic/dispatcher-lifecycle-bug3-bug4
Jun 29, 2026
Merged

fix(kanban): add per-task --max-iterations override for worker budget#19
cwest merged 1 commit into
cwest/integrationfrom
topic/dispatcher-lifecycle-bug3-bug4

Conversation

@cwest

@cwest cwest commented Jun 29, 2026

Copy link
Copy Markdown
Owner

What

Adds a per-task inner-iteration budget override so a large kanban card can raise the worker's agent.max_turns ceiling instead of timing out at the global default (90).

Why

The dispatcher's worker-spawn env block set the goal-loop turn budget and terminal timeouts but never set HERMES_MAX_ITERATIONS, so every spawned worker fell back to the global agent.max_turns default (90) regardless of task size. A large card exhausts 90 iterations and times out, forcing manual recovery. (This very fix's first implementation run timed out at the 90-iteration ceiling — the bug fixing itself.)

The existing --goal-max-turns / goal_max_turns knob bounds the outer goal-loop budget and does not feed the inner agent.max_turns.

How

  • New nullable max_iterations INTEGER column on tasks (schema + additive migration mirroring goal_max_turns).
  • New --max-iterations N flag on hermes kanban create.
  • Surfaced in hermes kanban show and --json.
  • In the worker-spawn env block: HERMES_MAX_ITERATIONS=str(value) only when the card carries one; a plain card leaves a clean env so the global default (90) is preserved.

No new env var

HERMES_MAX_ITERATIONS already exists and is already honored in cli.py's budget fallback chain (config.yaml agent.max_turns > HERMES_MAX_ITERATIONS > 90). This wires the existing internal var from a per-task config column — the user-facing surface is the DB column + CLI flag, not an env var.

Tests

  • DB persist / default / legacy-migration
  • Spawn-env set-when-present / clean-when-absent (behavior contract against the real spawn-env builder)
  • CLI flag round-trip with --json surface
  • E2E against a temp HERMES_HOME exercising the real hermes kanban create / show path

241 kanban tests pass; ruff clean. Carried in PATCHES.md as upstream-pending.

The dispatcher's worker-spawn env set the goal-loop turn budget and
terminal timeouts but never set HERMES_MAX_ITERATIONS, so every spawned
worker fell back to the global agent.max_turns default (90) regardless of
task size. A large card (e.g. a multi-thousand-line reconcile) exhausts
90 iterations and times out, forcing manual recovery. The existing
--goal-max-turns / goal_max_turns knob bounds the OUTER goal-loop budget
and does not feed the inner agent.max_turns.

Add a per-task inner-iteration budget that maps to agent.max_turns:

- New nullable max_iterations INTEGER column on tasks (schema + additive
  migration mirroring goal_max_turns).
- New --max-iterations N flag on hermes kanban create.
- Surface it in hermes kanban show and --json output.
- In the worker-spawn env block, set HERMES_MAX_ITERATIONS=str(value)
  only when the card carries one; a plain card leaves a clean env so the
  global default (90) is preserved.

No new user-facing env var is introduced: HERMES_MAX_ITERATIONS already
exists and is already honored in cli.py's budget fallback chain
(config.yaml agent.max_turns > HERMES_MAX_ITERATIONS > 90); this wires
the existing internal var from a per-task config column.

Tests cover DB persist/default/legacy-migration, spawn-env
set-when-present / clean-when-absent, a CLI flag round-trip with --json
surface, and an E2E against a temp HERMES_HOME.
@cwest
cwest marked this pull request as draft June 29, 2026 19:30
@cwest
cwest marked this pull request as ready for review June 29, 2026 19:45

@cwest cwest left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

No changes needed.

The override is wired correctly. cli.py reads HERMES_MAX_ITERATIONS in its budget fallback chain (config agent.max_turns, then this env var, then 90), and the spawn block exports it only when the card carries a value, so a plain task keeps a clean env and the default holds. No new user-facing env var; the surface is the DB column and the flag.

The column, the create flag, the show line, and the --json field all mirror the existing goal_max_turns plumbing. edit is left alone, which is right: that subcommand only backfills result/summary/metadata on a done task, so a config flag there would have no home. The additive migration follows the established pattern and a legacy table without the column upgrades cleanly.

Verified by exercising the real path against a temp HERMES_HOME, not just the unit suite: create --max-iterations 250 round-trips through the DB to --json and show, and _default_spawn exports HERMES_MAX_ITERATIONS=250 for that task while a plain task (clean parent env) exports nothing. The seven new tests pass at the head SHA. Ran the kanban suite at the head SHA and at the base commit: the 27 failures are identical on both sides, all pre-existing and unrelated to this change (notify, decompose, signal-handler, stale-claim-with-live-pid), so this PR introduces none. No secrets, injection, eval, or unsafe deserialization in the added lines.

@cwest
cwest merged commit 73b3145 into cwest/integration Jun 29, 2026
8 checks passed
@cwest
cwest deleted the topic/dispatcher-lifecycle-bug3-bug4 branch June 29, 2026 20:00
cwest added a commit that referenced this pull request Jul 1, 2026
Rebased the fork stack onto upstream main@9be292f1e and dropped the
per-task --max-iterations override (former P16, upstream #19). The knob
cut against the board's decompose-first design: a card that exhausts the
global 90-turn ceiling is almost always a sizing failure (too big, should
be split into smaller cards), not a budget failure. The escape hatch for
a genuinely atomic-large task already exists natively — upstream bridges
HERMES_MAX_ITERATIONS from agent.max_turns in config — so the per-task DB
column was redundant carry with no upstream home. Stack is now 17 patches.
cwest added a commit that referenced this pull request Jul 26, 2026
Rebased the fork stack onto upstream main@9be292f1e and dropped the
per-task --max-iterations override (former P16, upstream #19). The knob
cut against the board's decompose-first design: a card that exhausts the
global 90-turn ceiling is almost always a sizing failure (too big, should
be split into smaller cards), not a budget failure. The escape hatch for
a genuinely atomic-large task already exists natively — upstream bridges
HERMES_MAX_ITERATIONS from agent.max_turns in config — so the per-task DB
column was redundant carry with no upstream home. Stack is now 17 patches.

(cherry picked from commit 94d1076)
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