awo Orchestrator
Overview
This is the meta-loop above the awo binary. awo itself drives the inner loop for a single task (implementer ⇄ reviewer, up to --max-rework-rounds); this skill drives the outer loop across a whole implementation plan: per step, generate task files; per task, run awo; bookmark, log, repeat.
Its substantive job beyond bookkeeping is escalation handling. When the inner loop terminates ESCALATED because the task cannot be satisfied as written, the correct repair is to the specification and task file, not the code. This skill authors that repair as a separate commit interposed below the task's implementation, then restarts awo seeded with an injected review so the implementer reconciles its own work against the corrected task. The result is an auditable spec commit and a separate reconciliation commit — instead of code that quietly diverges from its spec.
Repository interaction is jj-only. Never use Git commands.
Parameters
- plan_file (required): Path to the implementation plan, e.g.
.agents/planning/{project_name}/implementation/plan.md. The planning slug is the{project_name}path component; it names the task directories and bookmarks. - work_log (optional, default:
.agents/scratchpad/orchestration-report.{planning_slug}.md): Your durable orchestration record. Read it first; create it if absent; keep it updated after every step and task. - generate_tasks_cmd (optional, default:
.agents/scratchpad/awo-generate-tasks.sh): Invoked as{generate_tasks_cmd} {plan_file} {step_number}. Drives an agent that runsplan-to-tasksand commits the resulting task files. - run_task_cmd (optional, default:
.agents/scratchpad/awo-run-task.sh): Invoked as{run_task_cmd} {task_file}. Wrapsawo runwith the harness/model/effort flags for this project. - rework_task_cmd (optional, default:
.agents/scratchpad/awo-rework-task.sh): Invoked to resume a task in rework mode. Wrapsawo reworkwith the same harness/model/effort flags asrun_task_cmd, and passes through the resume arguments (seed review, base, produced changes). See §Escalation Handling for its inputs.
Constraints for parameter acquisition:
- You MUST resolve
plan_filebefore starting; everything else has a derivable default. - You MUST read
work_log(or create it) before touching the repository, and derive the next step number fromplan_file's progress checklist andwork_logtogether. The plan may be partially complete — never assume you start at step 1. - You MUST verify
generate_tasks_cmdandrun_task_cmdexist and are executable before the first step; a missing wrapper is a stop-and-ask condition, not something to improvise.
Operating Constraints
- Long-running commands. The wrapper scripts drive agent harnesses and can run for hours, producing little output. You MUST run them as background tasks and wait for completion notification. If the inner agent exhausts its quota it will block and appear to hang — you MUST NOT cancel it.
- Never destroy completed work. No
jj abandon, nojj undo, no amending or squashing changes produced by a task loop. Every recovery must be additive. - jj only. Inspect and mutate the repository with jj.
- Create bookmarks, never move them. Always use
jj bookmark create— neverjj bookmark set.createfails if the name already exists, surfacing a name collision or a re-run you did not intend;setwould silently move an existing bookmark off the change it was protecting. Acreatefailure is a stop-and-investigate signal, not something to switch tosetfor. - When in doubt, stop and ask the user. The user is often away from keyboard; a clean stop with a clear question in the
work_logbeats a guess. - If you get lost, re-read
work_logand the plan to reorient before acting.
Steps
Loop over plan steps from the resolved next step number until every step in plan_file is checked off, or you hit a block you cannot clear.
1. Verify jj Working State
Constraints:
- You MUST confirm you are in an empty jj working copy on top of the stack (
jj st). - If
@contains stray files, you MUST investigate their origin. If they are leftovers from the previous step,jj committhem with a descriptive conventional-commit message; if their origin is unclear, stop and ask the user.
2. Generate Task Files for the Step
Constraints:
- You MUST run
{generate_tasks_cmd} {plan_file} {step_number}as a background task and wait for it. - After it returns, you MUST verify you are again in an empty working copy with
@-holding the task files. If the agent left the files uncommitted in@, you MUST commit them yourself. - You MUST create a bookmark on that change named
pr/awo-generate-task-{planning_slug}-step-{step_number}(withjj bookmark create). The{planning_slug}guarantees the name is unique across concurrently-open plans. - You MUST enumerate the step's task files (
jj log -son the change). They live in.agents/tasks/{planning_slug}/step{NN}/and are namedtask-{MM}-{task_slug}.code-task.md. - You MUST record the generated task files in
work_logbefore implementing any of them.
3. Implement Each Task
For each task file in order:
3.1 Ensure a Clean Starting State
Constraints:
- You MUST verify
@is empty; commit stray files if present. - You MUST note the change ID of
@-— the tip left by the previous task or by task generation. This is the task's base. - You MUST verify
@-carries a bookmark. If not, create one (withjj bookmark create) namedawo-loop-checkpoint-step{NN}-task{MM}-{task_slug}so the base is recoverable. The step/task/slug components MUST make the name unique — a bareawo-loop-checkpointreused across tasks would collide, andcreatewould (correctly) reject it.
3.2 Run the awo Task Loop
Constraints:
- You MUST run
{run_task_cmd} {task_file}as a background task and wait for it. - awo writes a run directory at
.agents/runs/{timestamp}-task-{MM}-{task_slug}/. Note that these directory names carry no step number, so identify the current run by timestamp. It contains:
task-state.json — the canonical per-task record: base, ordered
produced changes, checkpoints, terminal outcome
(incl. outcome.error / outcome.error_code)
orchestrator.log — terse JSONL progress
implementer/results/round-{N}.yaml — each round's archived result.yaml
reviewer/reviews/round-{N}.yaml — each round's archived review.yaml
- You MUST read the printed terminal status and route on it:
| Status | Meaning | Action |
|---|---|---|
APPROVED |
Reviewer approved the change series, and awo finalized it | Proceed to §3.3 |
ESCALATED |
Implementer or reviewer stopped the loop deliberately | Go to §Escalation Handling |
REWORK_CAP_HIT |
Rounds exhausted without approval | Read the last reviewer/reviews/round-{N}.yaml. If the outstanding findings are genuinely deferrable, record them in work_log and proceed; otherwise treat as a block and stop |
ORCH_ABORTED |
awo itself stopped: a preflight, topology, or finalization invariant did not hold. Never a verdict about the code | Go to §3.2a |
BLOCKED |
Deprecated reviewer verdict, from an older build | Read the report and treat it as ESCALATED |
FAILED / other |
Transient or harness failure | Investigate; a quick, non-destructive fix and re-run is fine. Otherwise stop and ask |
- You MUST NOT edit the produced code yourself to make a task pass. Route it back through awo, or escalate.
3.2a Handling ORCH_ABORTED
An abort is a statement about the repository or the run, not about the work. Changes awo
already committed are intact — recovery is diagnosis, never jj abandon.
Constraints:
- You MUST read
task-state.jsonin the run directory first:outcome.error_codeandoutcome.errorname the failed invariant, andproduced_changestells you exactly which changes exist. Cross-check withjj log. - You MUST classify before acting:
- Aborted at preflight (
bookmark_collision, a preflight topology code, no produced changes): nothing ran. Clear the stated condition — most often a PR bookmark left by an earlier approval of the same task — and re-run §3.2 unchanged. - Aborted mid-loop, with produced changes: the work exists but was never carried to a verdict. Do not treat it as approved. Resume it through awo with a seeded review per §E.4 and §E.5, whose finding states plainly that the prior round aborted before review and asks for verification against every acceptance criterion.
- Aborted at finalization, after an approval: the reviewer approved and the changes are committed; only the bookmark/description step failed. Record it, fix the stated cause, and bookmark the tip yourself per §3.3.
- Aborted at preflight (
- You MUST record the abort, its
error_code, and your classification inwork_log. - You MUST NOT edit the produced code to clear an abort, and MUST NOT re-run a task on top of its own completed work — the implementer would find nothing to do and awo would abort again on an empty result.
3.3 Confirm the Produced Changes Are Bookmarked
The loop leaves one change per round — the initial implementation plus one per rework round.
On approval awo finalizes the stack itself: it describes the oldest produced change with the
review's merge-request title and body, and creates the task bookmark on the newest one. The name
it derives is the task file's path under .agents/tasks/, prefixed with pr/ and stripped of
.md — i.e. exactly pr/{planning_slug}/step{NN}/task-{MM}-{task_slug}.code-task. Creating that
bookmark yourself after an APPROVED run collides with awo's.
Constraints:
- You MUST verify
@is empty; commit stray files if present. - You MUST note the change ID of
@-, the last change the loop produced. - You MUST verify that
@-carriespr/{planning_slug}/step{NN}/task-{MM}-{task_slug}.code-task(jj log -r @- -T 'self.local_bookmarks()'). After anAPPROVEDrun it will. These bookmarks are what PR generation consumes, so the name must match exactly. - You MUST create it yourself, with
jj bookmark create, only when awo did not finalize — i.e. when you are proceeding past anORCH_ABORTEDper §3.2a on work you have decided to accept. Record that provenance inwork_log: a hand-made bookmark means awo never verified the finalized stack. - If awo's own
createfailed because the name was taken, do not switch tojj bookmark set. Find what holds the name (jj log -r 'bookmarks(<name>)') and stop and ask, unless the holder is a superseded tip of this same task's series — in which case say so explicitly inwork_logbefore moving the label onto the true tip.
3.4 Update the Work Log
Constraints:
- You MUST append a summary of the task execution to
work_log: task file, terminal status, round count, produced change IDs, and the bookmark you created. Record any escalation and its resolution here too —work_logis the human-readable audit trail.
4. Close Out the Step
Constraints:
- You MUST verify every task in the step is
APPROVED(or explicitly recorded as deferred) before advancing. - You MUST mark the step's checklist item complete in
plan_file(- [ ]→- [x]) yourself, and commit that edit as its own change containing only the checklist edit.task-to-codeno longer does this: plan progress is owned by the orchestrator, so that a task change contains only that task's work (keeping@-identical to theresult.change_idthe producer reports), and so that plan progress can later move to an external tracker without changing any producer skill. - If the item is already ticked, stop and investigate — nothing else should be ticking it.
- Then continue the loop at §1 with the next step number.
Escalation Handling
An ESCALATED terminal status means a producer decided that another rework round would be wasted. Read the escalation before doing anything else.
E.1 Read the Escalation
Constraints:
- You MUST locate the escalation in the run directory: the reviewer's
review.yaml(review.verdict: escalated, top-levelescalation) or the implementer'sresult.yaml(result.status: escalated, top-levelescalation). Both carryreasonanddetails. - You MUST read
detailsin full and read the task file it names. Route onreason:
reason |
Meaning | Handling |
|---|---|---|
spec_defect |
The task cannot be satisfied as written | Candidate for repair — apply the §E.2 boundary test |
spec_ambiguity |
The task admits materially different implementations | Candidate for repair — apply the §E.2 boundary test |
unrecoverable_state |
The produced change cannot be trusted as a base (e.g. tests deleted wholesale to force green) | Not a spec repair. Stop and surface to the user with the evidence; the produced changes need human judgement before anything builds on them |
blocked_dependency |
A prerequisite outside this task's scope is missing | Usually a plan-ordering problem — stop and surface to the user |
E.2 Decide: Repair Inline, or Escalate to the User
You may repair a specification inline only when the correction is contained to this task's requirements plus the shared spec/plan document.
Constraints:
- You MUST stop and escalate to the user, without editing anything, when the correction would:
- change sibling or downstream task files in this or any other step,
- alter the plan's step structure (adding, removing, splitting, or reordering steps),
- constitute an architectural or design decision rather than a correction of a clear defect, or
- leave you genuinely unsure which of several readings the user intended.
- You MAY repair inline when the defect is a self-contained inconsistency in this task's requirements or its shared spec doc, and the correct reading is unambiguous from the surrounding design documents.
- When you stop, you MUST write the escalation, your boundary reasoning, and the change IDs involved into
work_log, and leave the repository in a clean, non-destructive state.
E.3 Author the Spec Repair as a Separate Commit
The repair commit S contains only the specification-document edit and its trickle-down into the task file. No code. No tests.
Constraints:
- You MUST edit only spec/design/plan documents and the affected
.code-task.mdfile. If you find yourself wanting to touch code, you are past the §E.2 boundary — stop. - You MUST make the task file and the spec document consistent with each other; a task edit that leaves the design doc stating the defective requirement just relocates the divergence.
- You MUST commit with a conventional-commit message that names the defect and cites the escalation, e.g.
fix(spec): resolve contradictory quota criteria in task-03.
Then place S correctly, in one of two shapes:
Shape A — produced changes exist (the usual case: the loop got at least one implementation change out before escalating). Interpose S below the first produced change, so the implementation series never appears as commits authored against a spec that was already known to be wrong:
jj rebase -r <S> --insert-before <I1> # I1 = first produced change of this task
jj bookmark create pr/<planning_slug>-spec-fix-step<NN>-task<MM> -r <S>
Resulting topology — B is the task base, I1…In the produced series:
B ─ S (spec+task fix, bookmarked) ─ I1′ ─ [I2′ …] ─ @ (empty)
Shape B — no produced changes yet (round-0: the implementer escalated before committing anything). Author S directly on the base B; it simply becomes the new base. Bookmark it the same way, then re-run the task from scratch with {run_task_cmd} per §3.2 — there is nothing to reconcile, so no seeded review is needed.
Constraints:
- The rebase MUST leave
I1's content untouched — it reparents, it does not merge. Change IDs stay stable; commit IDs are rewritten, which awo tolerates. - You MUST verify the resulting topology with
jj logbefore proceeding, and confirm@is still empty. - You MUST NOT squash
Sinto any produced change. Its separateness is the audit trail.
E.4 Write the Injected Review
For Shape A, awo resumes via an injected review.yaml that tells the implementer what changed and what to reconcile. Write it to the task's scratchpad (e.g. {scratchpad}/injected-review.yaml) conforming to code-task-review/report-schema.md.
Constraints:
review.verdictMUST bechanges_requested— this is the verdict that drives another implementer round.review.change_idMUST be the current tip of the produced series;review.task_fileMUST point at the corrected task file.acceptance_criteriaMUST reflect the corrected task, not the defective one.- You MUST include at least one
criticalfinding, categoryacceptance_criteria, that states the spec was corrected, cites the repair commit, and gives specific reconciliation steps insuggested_action. - You MUST NOT include an
escalationblock — the verdict is notescalated.
review:
task_file: .agents/tasks/<planning_slug>/step<NN>/task-<MM>-<slug>.code-task.md
change_id: <current tip of the produced series>
reviewed_at: <now, ISO 8601 UTC>
verdict: changes_requested
schema_version: 2
lsp_coverage: unavailable
merge_request:
title: "fix(<scope>): reconcile implementation with corrected task [<Topic>: Step NN/Task MM]"
body: |
Reconciles the implementation series with the task specification corrected
in the interposed spec commit.
summary: |
Specification corrected (see bookmark pr/<planning_slug>-spec-fix-step<NN>-task<MM>).
Re-implement against the updated task file.
acceptance_criteria:
- text: "<criterion, per the corrected task>"
status: fail
evidence: |
Implemented against the previous, defective wording of this criterion.
findings:
- severity: critical
category: acceptance_criteria
file: <primary implementation file>
line: null
title: Spec corrected; reconcile the implementation
details: |
The task's requirement <X> was logically inconsistent (<the defect>), and
has been corrected in the interposed change <S>. The existing
implementation reflects the old wording.
suggested_action: |
<specific reconciliation steps against the corrected requirement>
source: built-in
ecosystem_reviews: []
E.5 Resume with awo rework
awo rework identifies the task from the seed review's review.task_file; no positional task-file
argument is needed (one is accepted as an override, and must then name the same file). It needs the
seed review plus the change series to resume from, in one of two equivalent forms:
# Explicit form: base + produced changes, oldest-to-newest
{rework_task_cmd} --seed-review <path to injected review> \
--base <S> --produced-change <I1′> [--produced-change <I2′> …]
# Prior-run form: reuse the earlier run's task-state.json
{rework_task_cmd} --seed-review <path to injected review> \
--task-state .agents/runs/<timestamp>-task-<MM>-<slug>/task-state.json
Constraints:
- You MUST resume with
{rework_task_cmd}, passing--seed-review <path to the injected review>plus either--base <S>with the ordered--produced-changelist (oldest-to-newest), or--task-state <path>. The wrapper supplies the project's harness/model/effort flags; you supply the resume arguments. - The
--baseMUST be the interposed spec commitSfrom §E.3, so the reworked round descends from the corrected spec. Pass the produced changesI1′ … In′in oldest-to-newest order. - Change IDs may be given in jj's abbreviated form; awo resolves them and records the full IDs. The seed review's
change_idand the newest--produced-changeneed only denote the same change, not be spelled the same length. - awo pre-flights the resume:
@must be empty and childless,@-must be described/bookmarked, the produced changes must descend from the given base, and the task's PR bookmark must still be free. A pre-flight error means your topology from §E.3 is wrong — re-inspect withjj log; do not force it. - The seeded round is a rework round: the implementer reads the injected review and produces a fresh child change. Subsequent rounds are the normal loop under
--max-rework-rounds, which budgets the rounds this invocation may run: the carried series does not consume it, so a long series never caps a resume before it starts. - When it returns, you MUST route on its terminal status exactly as in §3.2. A second escalation on the same task after a repair is a stop-and-ask condition — do not repair twice in a row.
- On approval, continue at §3.3. The task's bookmark goes on the final tip; the spec commit keeps its own bookmark, and both appear in the log as separate, reviewable changes.
- You MUST record the whole sequence in
work_log: the escalation reason, your boundary judgement, the spec commit and its bookmark, and the rework outcome.
Examples
Example: normal step
Step 03, 4 tasks generated → bookmark pr/awo-generate-task-2026-07-18-escalation-step-3
task-01 → APPROVED, 1 round → pr/2026-07-18-escalation/step03/task-01-….code-task
task-02 → APPROVED, 3 rounds → pr/2026-07-18-escalation/step03/task-02-….code-task
…
Step 03 checklist item marked complete → continue at step 04.
Example: spec-defect escalation, repaired
task-03 → ESCALATED, reviewer, reason: spec_defect
details: AC2 mandates rejection above quota; AC4 forbids rejecting trial
tenants, whose quota is 0. No implementation satisfies both.
E.2 boundary: contained — the contradiction is between two criteria of this
task, and design/detailed-design.md §4 states trial tenants are exempt
from quota accounting. No sibling task or step structure is affected.
→ repair inline.
E.3 Author S: correct AC4's wording in the task file and the corresponding
sentence in detailed-design.md §4. Commit; then
jj rebase -r S --insert-before I1
jj bookmark create pr/2026-07-18-escalation-spec-fix-step03-task03 -r S
Topology: B ─ S ─ I1′ ─ I2′ ─ @(empty) ✓
E.4 Injected review: changes_requested, one critical finding pointing at
src/quota.py, suggested_action = exempt trial tenants from the quota
check rather than granting them a zero quota.
E.5 awo rework --seed-review … → APPROVED after 1 seeded round.
Bookmark the tip; log both the spec commit and the reconciliation.
Example: escalation that must go to the user
task-02 → ESCALATED, implementer, reason: blocked_dependency
details: The task imports lib/validators, which does not exist. The design
references it as a prerequisite from an earlier step.
E.2 boundary: NOT contained — supplying the prerequisite means adding a task
to an earlier step, i.e. changing the plan's step structure.
→ do not repair. Stop, write the finding to work_log, surface to user.
Troubleshooting
Stray files in @ at loop boundaries
Commit them with a descriptive message if their origin is clear (usually an agent that finished work without committing). If not, stop and ask — never abandon them.
The run directory for this task is ambiguous
Run directories carry no step number. Identify the current one by timestamp, and cross-check orchestrator.log's task file against the one you launched.
awo rework pre-flight fails
The resume contract requires an empty @ and a produced series descending from the given base. Re-inspect with jj log and fix the topology (§E.3); do not work around the pre-flight. A base mismatch usually means the interpose targeted the wrong change.
ORCH_ABORTED with error_code: bookmark_collision
The task's PR bookmark already exists, so the run could never be finalized; awo says so before spending a round. Almost always a superseded tip from an earlier approval of the same task. Find the holder with jj log -r 'bookmarks(<name>)', resolve it per §3.3, then re-run.
A rework resume ends immediately at REWORK_CAP_HIT
Not expected: --max-rework-rounds budgets this invocation's rounds and carried changes do not consume it. If you see it with zero rounds run, the wrapper is pinned to an awo build that predates that fix — pass a larger --max-rework-rounds after the wrapper's own flag (the last value wins) and record it in work_log.
A task escalates again after a spec repair
Stop. Either the repair was wrong or the defect is deeper than §E.2's boundary allows. Surface both escalations and your repair to the user.
Artifacts
{work_log} — durable orchestration record (you maintain this)
.agents/runs/{timestamp}-task-{MM}-{slug}/ — per-task awo run directory (awo writes this)
task-state.json — canonical record: base, produced changes, outcome
orchestrator.log — JSONL progress
implementer/results/round-{N}.yaml — archived per-round result.yaml
reviewer/reviews/round-{N}.yaml — archived per-round review.yaml
{scratchpad}/injected-review.yaml — seed review for a rework resume (you write this)
Bookmarks:
pr/awo-generate-task-{planning_slug}-step-{N} — the step's task-generation change (you create)
awo-loop-checkpoint-step{NN}-task{MM}-{slug} — recovery checkpoint on an unbookmarked base (you create)
pr/{planning_slug}/step{NN}/task-{MM}-{slug}.code-task — the task's final tip, consumed by PR generation
(awo creates on approval; see §3.3)
pr/{planning_slug}-spec-fix-step{NN}-task{MM} — an interposed spec repair commit (you create)