feat: raise the subagent timeout default and turn-budget ceiling - #8891
Conversation
UX Review (Fable 5) — ✅ PASSUX-level review of Reconciliation complete. The blind read didn't run and no screenshots exist, but I verified independently (two-dot diff against the base tree) that this PR's entire frontend delta is one numeral inside an existing tooltip ("1–200" → "1–1000", updated consistently across all 13 locales) and the matching UX-Verdict: PASS Only rendered delta is a bound numeral in an existing tooltip, consistent across all locales and true to the backend clamp; nothing new to comprehend. Suggestions
[UX-REVIEWED] 206794a |
Design Review (Fable 5) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS Sound single-owner consolidation, but the wedge ceiling is sized against a premise the PR's own docs contradict, and one described fix has no code in the diff. Watch
[DESIGN-REVIEWED] 206794a |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All verification is complete. The single First-Principles-Verdict: CONCERNS The What this change shipsIntent: let subagents run long enough to finish real work without being reaped mid-progress — a limits ADDITION carrying one genuine FIX (the hard-wedge coupling).
Watch
[FIRST-PRINCIPLES-REVIEWED] 206794a |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe change is internally consistent: the manager normalizes the No findings. [OPUS-REVIEWED] 206794a Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsFINDING -- src/kiro_crew/config/loader.py:1683 -- False positive or not applicable? A repository writer can comment: |
82ac999 to
c082db3
Compare
c082db3 to
7ef0917
Compare
|
Both GPT 5.6 findings on F1 (blocking) — the
Pinned by F2 (non-blocking) — I was wrong last round, you were right. I rebutted this by arguing that Corrected in both places it appeared. The setting help and Local gates on |
7ef0917 to
6f49d32
Compare
|
GPT 5.6 F1 on Traced it end to end and the mismatch is real on stock config: a subagent's outer deadline is Fix, and why it is not the plumbing the finding suggested. Passing a per-call timeout down through Three tests added, and the invariant is pinned against the constant rather than a literal so raising either default cannot silently reopen it:
The existing turn-ceiling cases keep measuring the ceiling term alone: the fixture's subagent value defaults to the loader's own 60s floor, which never binds. Same class as the two already fixed on this branch (the MCP gateway hard-wedge ceiling, and the blocking |
6f49d32 to
24b4556
Compare
|
Pinned it from the shard's own blob artifact (the job log is not readable while the run is in flight, but Reproduced the ratchet's own scan on three trees to place the blame:
The extra key is Fixed by deletion, not by raising the baseline — the gate's own doctrine is ratchet DOWN, never up, and 30 -> 29 lands exactly on the existing Order mattered. The key is still LIVE on the pre-rebase branch (25 commits behind, so Verified on |
24b4556 to
ac51f1e
Compare
ac51f1e to
319d2d7
Compare
A subagent was cut at 30 minutes and could never be granted more than 200 tool calls, so a long refactor, a full-suite run, or a wide investigation was reaped mid-work with its findings only on disk. The wall-clock default moves to 3 hours and SUBAGENT_MAX_TURNS_CEILING to 1000, which raises the load-time clamp on agent.subagent_max_turns and the per-spawn max_turns override that spawn_run and spawn_continue accept. Both spawn schemas now read the ceiling from its owning module instead of restating 200, and the timeout default has one owner in constants rather than three restated literals. Three limits sized against the OLD 30-minute deadline move with it. The MCP gateway's hard-wedge ceiling now sits above the subagent deadline, and the shared ACP prompt wait bounds against the larger of the turn ceiling and the subagent deadline: a blocking spawn_sub_agents is in flight for as long as its slowest member runs, and a subagent's prompt shares one transport wait, so both cut a healthy subagent early and reported a transport failure instead of the configured deadline. agent.subagent_timeout_secs also joins the load-time clamp table, with its documented 0 sentinel preserved so the manager still resolves it. Neither limit is removed: the reaper still force-kills at the deadline, and the concurrency cap and memory gate are untouched.
319d2d7 to
206794a
Compare
Problem
Two subagent limits cut work that was still making progress:
_TIMEOUT_SECSwas 30 minutes, so a long refactor, a full test-suite run, or a wide investigation was reaped mid-work. The partial result survives on disk, but the parent gets a timeout instead of an answer.SUBAGENT_MAX_TURNS_CEILINGwas 200, and it bounds both the load-time clamp onagent.subagent_max_turnsand the per-spawnmax_turnsoverride. An operator who wanted more could not have it at any layer, config or per-call.Change
agent.subagent_timeout_secsdefault1800→10800(3 hours), with one owner:constants.SUBAGENT_TIMEOUT_SECS. The dataclass default, the loader fallback andsubagent.py's_TIMEOUT_SECSall read it instead of restating the number.SUBAGENT_MAX_TURNS_CEILING200→1000. The load-time clamp, thePUT /api/config/kirocrewgate and the dashboard number input follow it.SPAWN_RUN_SCHEMA/SPAWN_CONTINUE_SCHEMAnow boundmax_turnsby that constant instead of restating200.HARD_WEDGE_CEILING_SECSwas2100.0— literally "wait_max (1800s) + 5-min margin", tuned to the old subagent ceiling. A blockingspawn_sub_agentsis in flight for as long as its slowest member runs, so at 3 hours the old ceiling recycled the backend under a caller whose work was healthy: the parent gotbackend gonewhile the subagent kept running detached and its result was stranded. It is now derived (SUBAGENT_TIMEOUT_SECS + 300) and pinned by a test that asserts the relationship rather than a literal.agent.subagent_timeout_secsjoins_SECURITY_BOUNDED_FIELDS(60s..86400s, the max matchingCHAT_TURN_TIMEOUT_MAX). It governs how long one subagent holds a concurrency slot, so with a 6x larger default an inflated on-disk value — a directconfig.jsonedit by any same-uid process — is a resource-exhaustion vector rather than a preference. This closes the gap the first revision merely documented.subagents.mdname the remaining interaction: a blockingspawn_sub_agentscall is still bounded byagent.chat_turn_timeout_secs(2 h default, 24 h max), so that has to be raised too when a batch must be awaited inside one turn.spawn_runis fire-and-forget and unaffected.test_snapshot.py's fresh-gateway ordering test hooked "the firstO_CREATopen anywhere" and so fired on whatever file the host happened to create first, queueing its delivery before the copy had taken the executor. The trigger is now anchored to the notifications destination, verified with a decoy-file negative control.What is deliberately NOT changed
subagent_auto_max(64),max_subagentsandspawn_min_memory_gbare untouched._WAVE_STUCK_SECSstays at 1800s. It is a lost-submission backstop that only fires once every registered member is already terminal, so it never cuts a live member. The spec prose calling it "symmetric with the per-agent hard ceiling" was the only thing that broke, and it is corrected.DIGEST_HOLD_SECSstays at 120s. Clamped to_TIMEOUT_SECS, so raising the ceiling only widens what an operator may opt into; the 120s latency trigger is what bounds straggler silence, and it is now the only thing between a hung member and 3 hours of quiet.Review findings
Adjudicated rather than waved through:
backend.py's own comment named the 1800s sizing it was tuned to.chat_turn_timeout_secs' default;CHAT_TURN_TIMEOUT_MAXis 86400, and the loader clamps to that, so raising it is effective up to 24 hours. The help text is accurate.10800literals are now one owner,subagent.py's stale "(30 min)" comment is fixed, and the PR body claim that the dashboard input "already read the constant" was wrong and is gone.subagent_timeout_secsis in the clamp table.Why no screenshot: the only change under
website/src/outside the locale catalogs is themaxattribute on the existing "Max turns per subagent" number input (200→1000) and the i18n KEY name beside it. A number input'smaxis not rendered text. The visible string ("… (1–1000). Default: 100.") lives entirely inwebsite/src/i18n/locales/**, which this repo's ownux-review-requiredruleset excludes from UX review — so there is no non-excluded rendered delta to photograph. No control is added, moved, resized or state-swapped, which the UX reviewer independently confirmed from the diff.Verification
pytest: 229 failures, none of them in the modules this diff touches (no config, subagent, mcp_gateway, validation or constants test file appears in the set). They are this host's standing families —PlatformCompositionErrorfrom anenterpriseprofile with no companion installed, and host-uid isolation checks — reproduced on a stashed clean tree in the previous round (85 failures across four of the worst-hit files alone).test_snapshot.pynow passes in full.isort,flake8,mypy --platform linux(1301 files), the black / subprocess-encoding / sync-io-in-async diff-scoped gates,docs-lint.sh,scrub-lint.sh,check_harness_parity.py: clean.tsc -b,eslint,vitest run --coverage(29435 passed),npm run i18n:checkwith and withoutI18N_BASE_REF— all 14 checks ok,en-XA.jsonregenerated throughi18n:pseudo.config-baseline.jsonregenerated by its own script.Pattern harvest
A constant whose comment states what it was sized against has a dependency the type system cannot see.
HARD_WEDGE_CEILING_SECS = 2100.0 # wait_max (1800s) + 5-min marginwas correct when written and became a data-loss bug the moment another file's number moved, because nothing linked the two.Rule candidate: when a limit is derived from another limit, derive it in code from that limit's constant and pin the RELATIONSHIP in a test, rather than writing the arithmetic result with the derivation in a comment.