8.9 KiB
Blitz workers: aoe sessions
How blitz fans out: each ready issue gets an external aoe session — its
own tmux pane, worktree, tool (claude / codex / opencode) and model. Sessions
survive the orchestrator restarting, and routing across providers keeps one
provider's outage or blind spots from shaping the whole run.
Everything else in blitz (DAG, integration, review cadence, readiness gate, ship, notify) is defined in SKILL.md. This file covers spawning, prompting, babysitting and cleanup.
Model routing
Two inputs decide who runs an issue: the roster (what this box may spawn) and the notes (what previous blitzes learned about them).
The roster
~/.config/agent-skills/config.json — the same file the PR daemon reads, so a
model added once is available to both. Query it rather than parsing it:
~/.claude/scripts/roster.sh --role blitz --tier execution --format aoe
# --tool claude --extra-args "--model sonnet"
Entries carry roles (blitz entries are yours; review-only ones are not)
and tiers. Exit 3 means no config on this box and exit 4 means the tier is
empty — in both cases fall back to the defaults below and say so in the run
summary, since an empty tier is usually a config gap worth fixing.
Picking a tier
Read ~/.local/state/agent-skills/models.md first — it carries what
previous blitzes learned (see "Model notes" below). A note that contradicts
the tier definitions here wins, because it was written against real runs.
Blitz is conservative by default: start with the least expensive capable
entry in the selected tier and escalate only when the issue or a failed pass
justifies it. In particular, use medium/standard models for execution and
ordinary design work; reserve high-effort models for genuinely subtle
issues or escalation. Codex medium is therefore a normal choice for execution
and design, while Codex high is not a default for every design issue. This
keeps subscription usage bounded without removing stronger models from the
roster or from review rotation.
Before each wave, the orchestrator runs
python3 <skills-root>/blitz/usage-budget.py --json. When its
preferMedium flag is true, choose medium/standard entries even when a high
entry is available. A park verdict means do not refill the wave; finish and
record the current state. The probe is intentionally conservative when a
provider exposes no machine-readable short-window percentage.
Assess each issue at spawn time. You have just read its body to write the prompt — use that read. The question is not "how big is this" but how much judgment does the session still have to exercise:
execution— body settles the approach (root cause named, fix shape decided, numbers suggested, files pointed at); the thinking happened at filing time. Regardless of size: a large mechanical CRUD issue is still execution.design— body states the goal, but the session must design the interface, choose the data model, or amend the design doc.subtle— the design doc is thin or contradictory where this issue lives, correctness is subtle, or the change is cross-cutting with unclear blast radius.
Defaults if the roster is unreachable: claude --model sonnet, --model opus,
--model fable for the three tiers in that order. Apply the same conservative
rule to those fallbacks: use the lower tier first and escalate only on
evidence.
A difficulty/ label is one input — a filing-time guess that cannot see how
much the body scaffolds. Trust your read of the body over it; the label is a
tie-breaker. When in doubt between two tiers take the lower one: escalation
on failure is cheap, and a failed cheap run teaches something a successful
expensive run does not.
The operator overrides any of this by just saying so in the invocation ("run these on codex", "use ox alpha for the easy ones") — no config edit needed. When a tier holds several entries, alternate rather than draining one first — a bad run should be visible early. Escalate sideways (a peer in the same tier) before escalating up, and never de-escalate mid-issue.
Model notes: ~/.local/state/agent-skills/models.md
The shared memory across blitzes. Read it at spawn time, update it before the run ends — including a run that ends blocked, since a model failing is the evidence that's hardest to come by.
It is a compiled summary, not a log. Four sections, fixed: difficulty tiers, task fit, cost effectiveness, caveats. Rules:
- Under ~60 lines, always. If an edit pushes past that, something in there has stopped earning its line — cut it in the same edit.
- Rewrite in place. Merge new evidence into the claim that already exists; never append a dated entry or a per-run section. The next blitz should read the current belief, not reconstruct it from history.
- Record what would change a routing decision. Something that repeated
across sessions, or that was decisive once (a model that couldn't finish an
issue the tier below finished). Mark a single-run claim
(1 run). - Caveats are for annoyances that don't veto a model — "the free 0x endpoint times out often, retry and it lands" belongs there; it is not a reason to route around it.
- Contradicted by a newer run? Replace the line, don't stack a qualifier on it.
- If the file is missing, create it with those four sections and note that the tiers are seeded from this doc rather than measured.
Bounce anything with wider reach than blitz — an entry that should leave the
roster, change tiers, or drop a role — to week-review, which owns config
changes. These notes stay advisory; the roster is the config.
Spawning
aoe add <repo-root> \
--title "<repo>-<n>-<slug>" \
--worktree issue/<n>-<slug> --new-branch \
--tool <tool> --extra-args "<model args>" \
--launch
Known traps, all confirmed the hard way:
aoe sendraces with--launchand fails silently. Never trust it. Deliver the prompt withtmux send-keys -l "<prompt>"followed by a separateEntera second later, then verify withtmux capture-pane -pthat it landed.- tmux truncates session names. Look panes up by prefix
(
tmux list-sessions -F '#{session_name}' | grep '^aoe_<title-prefix>'), never by exact title. A failed exact lookup can dump the prompt into your own pane. --new-branchis required for a branch that doesn't exist. The worktree path comes from--title, not the branch — distinct titles or the second session collides.
The prompt
Two phrasings matter, learned from models that do the work and then stop:
- Exit criteria beat autonomy language. "Work fully autonomously" does not stop a model from ending its turn after a big tool result. What does: "Do not end your final reply until ALL of these are true: … If you catch yourself summarizing progress before those are true, you have stopped too early — keep going." Fill the criteria with the terminal state you actually assigned: under blitz that is gate-green + branch pushed (the orchestrator merges and closes); when driving direct-to-main it is merged + pushed + issue closed with a comment naming the commit.
- Fence parallel sessions off each other's files. When two issues run at once, each prompt names what the other owns: "Do not touch X — issue #M owns it and runs in parallel." Merge conflicts are cheaper to prevent in the prompt than to resolve after.
Also include: read CLAUDE.md and the design doc first; fetch the issue body via the tracker API; merge (never rebase) the base branch if it moved; and never wait for input.
Babysitting
Some models stall — idle turn-end after absorbing a large tool result, work half done. Don't hand-poll; arm a self-nudging Monitor per session:
- Poll every ~90s. Idle means the pane shows no in-progress marker and the context/size indicator is frozen across two consecutive checks — one check is not enough, models legitimately pause.
- On stall: send the nudge yourself via
tmux send-keys— restate the exit criteria and where it stopped — capped at ~6 nudges before escalating to a human. - Exit (and notify the orchestrator) on: issue closed, session gone, nudge cap, or timeout. Silence must not look like success — every terminal state emits a line.
Collect, integrate, clean up
- Detect completion by tracker state (issue closed) or the integration branch moving — never by grepping commit messages; fuzzy matches fire on the wrong branch's commits.
- After a session's work is merged:
aoe remove <title> --delete-worktree. Sweep every couple of waves; stale sessions pile up. (aoeleaves removed worktrees locked —git worktree unlockbefore a manualgit worktree remove.) - Verify the landed result yourself against the live system when one exists (deploy health, a smoke request against the changed endpoint). A session reporting success is a claim, not a verification.