plan-then-architecture
How to plan non-trivial work in this repo — write a plan, get it torn apart by the opposite provider through Orca until you converge, then write an ASCII architecture doc a human can read in one pass.
SKILL.md.agents/skills/plan-then-architecture/SKILL.md
GitHubPlan, then architecture
Two artifacts, in this order, before any implementation code:
plan.md ──► adversarial review ──► converge ──► architecture.md
(the reasoning) (opposite provider) (rev N) (the shape)
plan.md is for the decision — options, rejections, verification, risk.
architecture.md is for the reader — diagrams, anchors, build order.
They are not the same document and neither replaces the other.
Both go in the agent folder that CLAUDE.md already mandates:
docs/plans/<topic-in-kebab-case>/<claude|codex|cursor|human>/plan.md
docs/plans/<topic-in-kebab-case>/<claude|codex|cursor|human>/architecture.md
Scope. This skill covers the pre-implementation phase. The post-commit
opposite-provider code review is a different process —
docs/development/agent-workflow.md.
Run this one first; nothing here commits code.
Phase 1 — write the plan
Read the code before you write a word of it. karpathy-guidelines applies: the
ladder shortens the solution, never the reading.
A plan is done when it has all of these:
- What is actually broken, split into numbered defects, each anchored to real
file.py:line. Grep the anchors fresh immediately before writing — anchors drift, and a wrong line number is worse than no line number because it looks verified. - The fix, as one sentence of intent plus one named change per defect.
- Deliberately not building — every item with the reason AND what would change my mind. A plan without this section will get rejected for scope.
- Steps and verification — every step carries the check that fails if the step is wrong. Name the actual test file. Name the existing tests your change breaks; there are almost always some.
- Risk register — one row per way this goes wrong, each with its mitigation and the step that proves it.
- Review rounds — empty at first; Phase 3 fills it.
Mark the status at the top: PLAN / IN PROGRESS / BUILT, plus rev N.
Claims that will get you rejected
- "No code path can do X" — check every path first, including the
-yflag on the ffmpeg call and the fallback branch nobody uses. - "No skill/pipeline depends on this" — grep
skills/pipelines/**, not just.agents/skills/*/SKILL.md. Pipeline directors pass tool arguments you are about to delete. - "This is atomic" — say which lock, held from where to where, and in what order relative to every other lock.
- "Duration == sum of parts" — not once a transition, xfade, or speed change exists.
Phase 2 — adversarial review by the opposite provider
Claude-authored plan → Codex reviews. Codex-authored → Claude reviews. Never review your own plan.
This is supervised orchestration, not a handoff: you wait for findings and you iterate. Load the current command surface first — never guess flags:
orca skills get orchestration
Then the loop. Substitute your resolved Orca executable for orca:
orca orchestration run-create --objective "<topic>: converge plan with reviewer" --json
orca orchestration task-create --spec "<the review brief, see below>" --json
# Custom model/effort needs the low-level path: worker-start cannot pass -m/-c.
orca terminal create --worktree current --title "PLAN REVIEW" \
--command "codex --model gpt-5.6-sol -c model_reasoning_effort=high \
-s workspace-write -c sandbox_workspace_write.network_access=true -a never" --json
orca terminal wait --terminal <handle> --for tui-idle --timeout-ms 120000 --json
orca orchestration dispatch --task <task_id> --to <handle> --inject --json
orca orchestration check --wait --types worker_done,escalation,question,status \
--timeout-ms 540000 --json
orca orchestration check --ack <delivery_id> --json
Three gotchas that cost real time
1. Never launch the reviewer with -s read-only. Its sandbox blocks the local
socket the Orca CLI needs, so worker_done fails with "Could not connect to the
running Orca app" after the review is finished and the findings are stranded in a
session you then have to resume. Use workspace-write + network_access=true and
enforce read-only by instruction ("change no repo file"). Verify afterwards:
git -c core.bare=false --work-tree=. --git-dir=.git status --porcelain
Plain git status fails in these worktrees (core.bare = true in the shared config).
2. Tell the reviewer to pass the body as a literal. Codex will otherwise write
--body "$ORCA_BODY", the variable is unset, and you receive a worker_done with a
subject and an empty body. Put this in the brief verbatim:
Pass the worker_done body as a LITERAL single-quoted string, never a shell variable. If it is too long for one command, send the findings as two or three
--type statusmessages first, then a finalworker_donewith the verdict.
3. A check --wait timeout is a checkpoint, not a failure. Reviews run 6–15
minutes at high effort. Keep rolling the wait. Read progress with
orca terminal read --terminal <handle> --json (the tail is spinner-polluted; strip
it) — but only to check liveness, never as a substitute for the reported findings.
The review brief
Ask for findings you can act on, not an opinion:
- verify every
file:linein the plan against the code; report drift - take a position on each named design decision: ACCEPT or REJECT with a concrete failure case (inputs/state → wrong outcome), not a preference
- hunt what the plan missed — name the areas: other writers of the same artifact,
other readers, concurrency between two entry points, cache keys, resume/retry
paths, contracts in
skills/pipelines/**, tests that break - judge scope against
karpathy-guidelines: what is over-built, and is anything in "Deliberately not building" actually required to close the ticket - output:
SEVERITY (BLOCKER|MAJOR|MINOR|NIT) - claim - file:line - failure case - suggested change, then aVERDICTline - read-only; change no repo files; expect follow-up rounds
Phase 3 — converge
Verify every finding against the code before you accept it. Reviewers are sometimes right about the defect and wrong about the cause. Two rounds of this on one plan produced: five confirmed blockers, one correct-but-misattributed diagnosis, and one reversal of a change the same reviewer had asked for.
For each round:
- Re-read the cited code. Confirm or refute.
- Accept, partially accept, or push back with a reason — a judgement call is not a defect, and saying so is allowed. Reviewers will concede.
- Apply the change to the plan, bump
rev N. - Record it in
## Review rounds— a table of finding → resolution. This is the audit trail a human reads to see the plan got harder, not just longer. - Send the revision back with an explicit map of which change went where, and
name anything you did not do and why. New task, new
dispatch --injectto the same terminal so the reviewer keeps its context.
Converged means the reviewer says APPROVE, or APPROVE WITH CHANGES where every remaining item is a wording nit you then apply. Do not implement before that.
Expect 3–5 rounds on anything touching concurrency or a data contract. A plan approved on round 1 usually means the brief was too soft.
Phase 4 — architecture.md
Now write the doc a human reads in one pass. Load explain-with-ascii and follow it.
The rules that matter most here:
78 columns, hard cap, inside fenced
```textblocks. Check it, don't eyeball it:python3 - <<'PY' p='docs/plans/<topic>/<agent>/architecture.md' inb=False for i,l in enumerate(open(p),1): if l.startswith('```'): inb = not inb; continue s=l.rstrip('\n') if inb and len(s)>78: print(f"{i}: {len(s)} cols") PYBox-drawing characters are multi-byte, so
awk 'length>78'lies. Count characters. Also check that every line of a box shares one width.Two diagrams carry the whole doc: today, with
★ DIVERGENCEcallouts naming the exact lines where paths split, and after, with the entries funnelling into one component and an explicit convergence bar:═════════ ALL THREE ROUTES CONVERGE HERE — one publisher ═════════The funnel is the claim. If your fix has no funnel, say what shape it does have.
Show resolved values, not just calls —
→ true,→ renders/final.mp4.Failure edges inline —
invalid → success=False, NO job. A happy-path diagram hides what the implementer will actually hit.A timeline diagram wherever ordering is the design (crash windows, lock windows, commit markers). Ordering arguments are unreadable as prose.
Then, for the agent who implements it:
- a surface table:
file:line→ what changes, one row each, plus a short list of what is deliberately untouched - a build order, dependency-first, ending in the end-to-end check
- two or three observations the diagrams make obvious and prose would bury. Name a consequence ("the whole bug is upstream of the renderer"), never a summary.
Link back to plan.md for reasoning; do not restate it.
Done
-
plan.mdhas all six sections; every anchor grepped fresh - reviewer is the opposite provider and reported through Orca (
task-listshows the taskscompleted) - every finding verified against code, then accepted or refuted in writing
-
## Review roundsrecords each round and its resolutions - reviewer's verdict is APPROVE
-
architecture.mdpasses the 78-column and box-width checks - reviewer terminal closed;
git status(with thecore.bareoverride) shows onlydocs/plans/touched - no implementation code written
Implementation starts after this, and goes through
docs/development/agent-workflow.md
for the commit-time review.
Worked example
docs/plans/opn-30-edit-decisions-render-desync/claude/ — five review rounds, three
REJECTs, one reversal, plan.md §6 has the full round table and architecture.md
has the before/after funnel.