Files
agent-skills/skills/blitz/AOE-WORKERS.md
T
Miguel Palhas c9e1c68ea2
ci / nix (push) Successful in 8s
ci / lint (push) Failing after 11s
feat(blitz): account for provider usage pressure
2026-08-25 14:08:07 +01:00

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 send races with --launch and fails silently. Never trust it. Deliver the prompt with tmux send-keys -l "<prompt>" followed by a separate Enter a second later, then verify with tmux capture-pane -p that 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-branch is 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:

  1. 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.
  2. 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. (aoe leaves removed worktrees locked — git worktree unlock before a manual git 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.