You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Read-only fleet sub-agents (Scout/Reviewer/Planner) stall and burn tokens on today's defaults — no per-user [subagents] edit should be required to fix this.
Rôles recap: 8 roles (role.rs:64-98): Worker (full), Scout/Planner/Reviewer (read-only, role.rs:712-720 — spawn narrows them to a read-only shell), Builder (full), Verifier (dedicated Run surface), Consultant (shell-less even among read-only, role.rs:88, executor.rs:245), Custom (inherits parent). Agent surface maps through a separate alias layer (identity.rs:355-362: explore→scout, implement→builder, general→worker). Per-role deny lists exist (role.rs:14-320); inherit_disallowed_tools: false can never drop a posture denial (SUBAGENTS.md:100, pinned by fleet/exact.rs test) — read-only stays read-only through delegation.
Rejection isn't treated as no-progress. The agent retries bash variants; stall not detected.
Model-step defaults unbounded.[subagents] default_max_steps = 0 = unbounded (SUBAGENTS.md:423-433; FLEET.md:502-503 "must not synthesize"); DEFAULT_MAX_MODEL_STEPS=200 exists only for the exec path (turn_budget.rs:40), fleet workers get None (executor.rs:202). Wall-clock does have a 1800s default — model steps do not.
Allow-vs-reject contradiction. Read-only role surface permits exec_shell (executor.rs:325 allow/deny lists empty by default), the grammar then rejects it → model confused, keeps trying.
"bugs" lens, same task → 30 steps, ~1.4M, via read_file, clean report.
Why 6M: quadratic growth — each step resends the full growing transcript (169 msgs), compactions re-enter as fresh context; provider input_tokens sums all resends. Real generation was ~35k tokens — the cost is stall + resend, not "the model thought a lot".
Proposed solutions (options for maintainers)
A. Adaptive anti-stall detector (core). Same action / same rejection N× consecutively (same command shape or error signature) → stalled → force strategy-switch, or stop-with-partial-report / ask operator. No fixed budget → doesn't chop agents making real progress. Needs precise "same action" definition; false positives route to strategy-switch, not kill.
B. Widen read-only grammar. Add sed -n (never -i), awk (no write), find, sort, uniq, diff; relax | only for allowlisted read-only pipelines; keep banning ; && $() > < and globs. Every addition must be proven mutation-proof.
C. Rejection-aware role prompt. Read-only roles: "grammar rejection = hard no → use read_file/grep_files/fd". Cheapest; behavior-only.
E. Fallback budgets — NOTE: conflicts with a stated invariant.FLEET.md:502-503 ("must not synthesize a default step budget"). Alert+checkpoint defaults would change that invariant deliberately; flag for explicit decision. Only if A is rejected.
F. Combination. A+B strongest (mechanism + tooling); C+D cheap first wave; E only if A rejected (and the invariant consciously changed).
G. Text-review path. Text review (issues, docs, design proposals) is a different job from code review; the fleet reviewer role is code-oriented and read-only-shell-constrained. Route document/issue reviews to a general/custom sub-agent with explicit bounds ("no bash, no code verification", step/output limits, strict output template), or add a dedicated text-review role. Removes the same stall causes from document work.
Use case
Bug-hunt reviewers, same task: 85 steps / 6M / no report (retrying rejected bash) vs 30 steps / 1.4M via read_file. Defaults should catch the first case for everyone — without per-user config, and without chopping agents that are doing real work.
Alternatives considered
Per-user [subagents] limits: local patch; asks everyone to fix defaults; hard budgets chop working agents.
Grammar-only (B): removes tool stalls, not "re-reading same files" stalls.
Prompt-only (C): not a mechanism.
Do nothing: ~6M tokens per stall, no report.
Impact
Anyone running read-only fleet review/exploration (bug-hunt, security-review, research). Today: unbounded cost per stall (observed 6M tokens, no report). Compatibility: explicit positive budgets must keep winning; the stall detector stops on no-progress, not on budget — preserving "omitted = unbounded".
Problem
Read-only fleet sub-agents (Scout/Reviewer/Planner) stall and burn tokens on today's defaults — no per-user
[subagents]edit should be required to fix this.Rôles recap: 8 roles (
role.rs:64-98): Worker (full), Scout/Planner/Reviewer (read-only,role.rs:712-720— spawn narrows them to a read-only shell), Builder (full), Verifier (dedicated Run surface), Consultant (shell-less even among read-only,role.rs:88,executor.rs:245), Custom (inherits parent). Agent surface maps through a separate alias layer (identity.rs:355-362:explore→scout,implement→builder,general→worker). Per-role deny lists exist (role.rs:14-320);inherit_disallowed_tools: falsecan never drop a posture denial (SUBAGENTS.md:100, pinned byfleet/exact.rstest) — read-only stays read-only through delegation.Four root problems:
command_safety.rs:361allowlists 21 prefixes +gh … view/list; charset gate (:465+) rejects any command containing; & | > <$ * ? [ ] { }or newline.sed,awk,find, pipes,cd &&impossible.sed -n '300,400p' f` → rejection → retries → stalls.[subagents] default_max_steps= 0 = unbounded (SUBAGENTS.md:423-433;FLEET.md:502-503"must not synthesize");DEFAULT_MAX_MODEL_STEPS=200exists only for the exec path (turn_budget.rs:40), fleet workers getNone(executor.rs:202). Wall-clock does have a 1800s default — model steps do not.exec_shell(executor.rs:325allow/deny lists empty by default), the grammar then rejects it → model confused, keeps trying.Real case (2026-09-08, bug-hunt):
read_file, clean report.input_tokenssums all resends. Real generation was ~35k tokens — the cost is stall + resend, not "the model thought a lot".Proposed solutions (options for maintainers)
sed -n(never-i),awk(no write),find,sort,uniq,diff; relax|only for allowlisted read-only pipelines; keep banning; && $() > <and globs. Every addition must be proven mutation-proof.read_file/grep_files/fd". Cheapest; behavior-only.exec_shellfor these roles (a posture denial — cannot be dropped by inheritance) → removes contradiction Remove Normal mode and consolidate to Agent #4.FLEET.md:502-503("must not synthesize a default step budget"). Alert+checkpoint defaults would change that invariant deliberately; flag for explicit decision. Only if A is rejected.reviewerrole is code-oriented and read-only-shell-constrained. Route document/issue reviews to ageneral/customsub-agent with explicit bounds ("no bash, no code verification", step/output limits, strict output template), or add a dedicated text-review role. Removes the same stall causes from document work.Use case
Bug-hunt reviewers, same task: 85 steps / 6M / no report (retrying rejected bash) vs 30 steps / 1.4M via
read_file. Defaults should catch the first case for everyone — without per-user config, and without chopping agents that are doing real work.Alternatives considered
[subagents]limits: local patch; asks everyone to fix defaults; hard budgets chop working agents.Impact
Anyone running read-only fleet review/exploration (bug-hunt, security-review, research). Today: unbounded cost per stall (observed 6M tokens, no report). Compatibility: explicit positive budgets must keep winning; the stall detector stops on no-progress, not on budget — preserving "omitted = unbounded".
Open questions
|only when both sides are allowlisted read-only?$(...)in read-only context (e.g.cat $(git ls-files))?exec_shellentirely vs keep the classifier-bounded shell (executor.rs:245design)?FLEET.md:502("must not synthesize"), is changing that invariant acceptable — and what are the alert/kill semantics?general/customwith explicit "no bash/no code" bounds — which default?References (verified in fork, upstream/main)
crates/tui/src/command_safety.rs:361(allowlist),:465+(charset gate)crates/tui/src/fleet/role.rs:64-98(roles),:88(Consultant shell-less),:712-720(read-only),:14-320(deny lists)crates/tui/src/fleet/identity.rs:355-362(surface aliases)crates/tui/src/fleet/executor.rs:202(max_turns None),:245(classifier-bounded shell; consultants shell-less),:325(allow/deny)crates/tui/src/core/engine/turn_budget.rs:40(DEFAULT_MAX_MODEL_STEPS=200, exec only)docs/SUBAGENTS.md:100(posture denial not droppable),:423-433(zero = unbounded; 1800s wall)docs/FLEET.md:502-503("must not synthesize a default step budget")