| name | tp-run-full-design |
| description | Autonomous full-design orchestrator. Drives the TDD pipeline unattended for a single task — pickup → design → worker → audits → PR — and produces a decision log for human review. |
| argument-hint | {slug} [--pickup-skill <name>] [--task-id <id>] [--skip-design] [--mode {full|design|plan|build}] [--max-tokens N] [--max-wall-clock SECS] [--max-attempts N=3] [--pr-reviewers <comma-list>] [--no-review] [--no-iterate] [--force-takeover] |
tp-run-full-design — Autonomous Orchestrator (Mode A0, single candidate)
This skill drives the entire three-pillars TDD pipeline unattended for a single task. It dispatches each tier into a dedicated subagent (## Dispatch loop): every delegated --auto skill runs inline inside its slot's subagent, which synthesizes a clipped return envelope (## Per-slot return contract). The orchestrator itself only ever reads those envelopes plus the running token total — never the tier work product — which is what keeps it under ~100k regardless of design size. Every decision is logged to decisions.md per skills/_shared/auto-mode.md. (This supersedes the earlier inline prose-orchestration pattern — modelled on skills/tp-spike-auto/SKILL.md — where the orchestrator read each delegated SKILL.md and followed its --auto instructions in its own context.)
Mode A0 — MVP scope: Tier 1 (pickup) → Tier 2 (design pipeline) → Tier 3 (a worker Agent per plan phase, each in an isolated worktree) → Tier 3.5 (validation gate on the worker's structured response) → Tier 5 (consolidation audits) → Tier 5.6 (closeout: fold candidate→tp/{slug}, learn-verify, archive) → Tier 6 (completion PR tp/{slug}→{default}, design already closed) → Tier 7 (PR-iterate: drive the review loop to a reviewed-stable state). Tier 7 is on by default (--no-iterate opts out; --no-review implies no iteration) so a run ends at a reviewed-stable PR, not a fresh un-reviewed one — the orchestrator owns the review loop instead of leaving it for a separate invocation. The merge-only gate is preserved: Tier 7 iterates and pushes fix commits, but never merges — a human still merges the PR. Tier 4 (council over multiple candidates) is out of scope for MVP per design.md §Behavior 4 — adding it requires /council to gain a code-candidate evaluation mode, tracked as a follow-on design.
This orchestrator is not itself an --auto skill — it is the orchestrator of --auto skills. It does not expose --auto in its argument-hint, and the --auto ↔ auto-mode.md linkage requirement deliberately does not apply to this file (it is the orchestrator, not an auto-mode participant). It does, however, write [tp-run-full-design/tier-N]-prefixed entries to decisions.md per the prefix convention adopted in detailed-design.md §Decisions (OQ5).
Arguments
{slug} (required) — kebab-case design name. Must match [a-z0-9-]+ per skills/_shared/validate-name.md. Identifies the design directory three-pillars-docs/tp-designs/{slug}/ and the branch tp/{slug}. Distinct from --task-id (see below): the slug is the in-repo key; --task-id is the opaque upstream reference (e.g., a Jira ticket or Linear issue ID).
--pickup-skill <name> (optional) — the /tp-pickup-* skill providing the task. If omitted, the orchestrator falls back to reading an already-seeded design.md in the design dir (manual-pickup escape hatch).
--task-id <id> (optional) — opaque upstream task identifier passed verbatim to the pickup skill (e.g., JIRA-1234, LIN-456, a Notion page ID). Required when --pickup-skill is provided; ignored in manual-pickup Mode B. The pickup skill uses {task-id} to look up upstream metadata; the orchestrator never interprets the value. Surfaced in the pickup contract as task_metadata.external_ref per design.md §Pickup contract.
--skip-design (optional) — opt out of Mode C's interactive-design front-end. When neither --pickup-skill nor --skip-design is passed, the orchestrator enters Mode C (see ## Tier 1.5). With --skip-design and no --pickup-skill, the orchestrator behaves as Mode B (read an already-seeded design.md).
--mode {full|design|plan|build} (optional, default full) — selects a contiguous slot-range sub-window of the 1–11 slot list. full (the default) runs the entire pipeline unchanged — backward-compatible, no existing invocation changes. design runs Slots 2–4 then a scoped PR and stops (no plan or worker). plan runs Slots 5–6 then a scoped PR and stops (requires design.md + detailed-design.md). build starts at Slot 7 (phase-implement) and runs to Slot 11 (requires all three design artifacts). See skills/tp-run-full-design/pipeline-modes.md for the full mode-axis specification (resolution, precondition gate, PR shapes, decisions.md tokens).
--max-tokens N (optional) — whole-run token budget cap. See ## Token budget.
--max-wall-clock SECS (optional) — wall-clock budget. Independent of --max-tokens.
--max-attempts N (optional, default 3) — per-cycle attempt budget for the orchestrator's self-healing loops: audit retry-with-advice (## Retry-with-advice (audits)) and worker-contract retry (## Retry & max-attempts). Default 3 makes the flow self-heal out of the box; --max-attempts 1 restores escalate-on-first-rejection for high-stakes runs.
--pr-reviewers <comma-list> (optional, default "copilot-pull-request-reviewer[bot]") — reviewers requested in Tier 6. The list is partitioned: human logins are requested via gh pr create --reviewer, and the Copilot bot is requested via the REST requested_reviewers endpoint (the gh pr create --reviewer path does not resolve the Copilot bot reliably). See ## Tier 6 — Step 2.
--no-review (optional) — suppress all Tier 6 review requests (both the human --reviewer and the Copilot REST request). Use for human-out-of-band or no-review runs. Implies --no-iterate (no review requested ⇒ nothing for Tier 7 to iterate).
--no-iterate (optional) — stop after Tier 6 (PR open) without driving the Tier 7 review loop. Restores the pre-Tier-7 "open the PR and hand back" behavior for operators who want to review the fresh PR themselves. Default is iterate (Tier 7 runs).
--force-takeover (optional) — claims the design lock from a prior owner per skills/_shared/collaboration.md.
Prerequisites
- The repo's
.gitignore must exclude .claude/worktrees/ (worker isolation pollutes this path; design.md §Prerequisites). If absent, this is a downstream-project misconfiguration — the orchestrator refuses to start.
gh CLI must be installed and authenticated (Tier 6 uses gh pr create).
- Project test command must be runnable from the repo root (Tier 5 invokes it).
Artifact policy
The orchestrator and its delegated subagents follow three rules about where work files land:
- do not write under /tmp —
/tmp/ is for the host OS's scratch space, not orchestrator-internal state. Worker outputs, decision logs, candidate artifacts, and ad-hoc scripts must live inside the repo where git tracks them and the operator can review them.
- do not write under
three-pillars-docs/tp-designs/{slug}/candidates/ from a worker subagent — the orchestrator owns that directory and writes its four artifact files (branch.txt, summary.md, test-results.json, telemetry.json) itself in Tier 3.5. Workers only compute and report; the orchestrator (via write_candidate_artifacts) is the sole writer.
- Ad-hoc orchestrator scripts and one-off demos belong under
three-pillars-docs/tp-designs/{slug}/demos/ — co-located with the design they support so the audit trail stays cohesive. Durable helpers — anything reused across runs or invoked by a SKILL.md — belong under skills/{skill}/scripts/.
Execution flow
- Run first-run preflight per skills/_shared/first-run.md.
- Validate
{slug} per skills/_shared/validate-name.md (must match [a-z0-9-]+).
- Read project docs per
skills/_shared/read-project-docs.md so each delegated tier inherits context.
- Walk the slot list via the ## Dispatch loop (
slot 1..N): each slot dispatches a subagent that runs its delegated --auto skill and returns a clipped envelope. The legacy inline tier ordering 1 → 2 → 3 → 3.5 → 5 → 6 maps onto the slots (Tier 4 is intentionally skipped for MVP — see ## Tier 4 below); the ## Tier sections detail each slot's per-tier semantics.
- On any non-retryable failure, fall through to ## Cleanup and exit with the appropriate non-zero code (Tier 6 fail-open is the lone exception — see that section).
Dispatch loop
The orchestrator is a lightweight driver: it never reads tier work product (design.md, plan.md, audit findings, council deliberation) directly. Instead it walks an ordered list of slots — one dispatched subagent per pipeline tier — and absorbs only each slot's clipped return envelope. This is what keeps the orchestrator under ~100k regardless of design size.
The orchestrator runs in the main conversation, never as a subagent. A subagent cannot spawn subagents — the harness exposes no agent-spawning tool one level down (architecture ADR "Subagent dispatch is single-level"). Dispatching a slot from within a subagent would fail outright, so every slot dispatch is a top-level, single-level fan-out: orchestrator → tier subagent, and no deeper. If this skill is ever itself invoked as a subagent, the dispatch loop cannot run; that is an unsupported invocation.
For slot 1..N in the slot list below, the orchestrator dispatches one subagent per slot:
Agent(
subagent_type="general-purpose",
isolation="worktree",
description="orchestrator/{slug}/{slot}",
prompt=compose(top_doc_refs, project_context_block, slot_args, soft_budget, hard_budget, explicit_return_contract),
)
Both kwargs apply to the main slot dispatches: subagent_type="general-purpose" and isolation="worktree" (each slot forks a fresh worktree from the design branch HEAD so the orchestrator's branch stays clean). Exception: the audit fan-out sub-dispatches (Slots 4/6/8: reader, Round-1/2 personas, synthesizer) skip isolation="worktree" — they are read-only and share tp/{slug} directly (see ## Audit fan-out (Slots 4/6/8)). compose(...) assembles the slot prompt from top-level project-doc references (read once, ~20k), the injected {project_context_block}, the slot's arguments, its soft/hard token budget (see ## Per-slot budget table), and the per-tier explicit_return_contract (see ## Per-slot return contract). On return the orchestrator runs the on-return sequence (see ## Return clipping) and decides per status: advance to the next slot, retry-with-advice, resume a handoff, escalate, or abort. The orchestrator fills {project_context_block} once per run from skills/_shared/project_context.py (load_context_block()) and threads it into every slot dispatch — the worker slot (tp-worker), the audit fan-out slots (tp-readonly-auditor), and the general-purpose slots alike — so no spawned tier re-derives the project's conventions/stack/domain-rules from the codebase (the same injection council Round 1 and the tp-phase-implement worker already carry). Omit the block when it is empty (absent project-context.md) so compose(...) degrades to today's exact prompt byte-for-byte.
The slot sections below give each slot's delegated skill and return-envelope class; the per-tier semantics (pickup contract, worker isolation, the Tier 3.5 gate, audits, PR fail-open) are detailed in the ## Tier sections that follow and are reconciled into the slot model in the final reconcile phase. --mode slot-range axis: see skills/tp-run-full-design/pipeline-modes.md for the mode-resolution rule, precondition gate, and per-mode PR shapes.
Slots
The orchestrator dispatches these slots in order (slot 1..N, where N = 9 fixed slots plus one phase-implement dispatch per plan phase). Each runs its delegated --auto skill inline and synthesizes a return envelope of the stated class, validated by parse_tier_return against the matching schema — except the pickup slot (its own pickup-contract-v1 shape; ## Pickup contract v1 validation) and the worker / phase-implement slot (the candidate.v1 path through run_tier_3_5.py).
Slot 1 — pickup
Resolves the task source into a locked branch + design.md (## Tier 1). Returns the pickup contract v1 envelope (unchanged shape — deliberately not folded into generator-return; see detailed-design §Decisions "Minor").
Slot 2 — design
Runs /tp-design {slug} --auto (Mode C front-end excepted; ## Tier 2). generator-return class.
Slot 3 — detail
Runs /tp-design-detail {slug} --auto. generator-return class.
Slot 4 — design-audit
Drives the council audit fan-out (## Audit fan-out (Slots 4/6/8)) over design.md + detailed-design.md instead of dispatching a single /tp-design-audit subagent. audit-return class; the verdict drives retry-with-advice vs. escalation.
Slot 5 — plan
Runs /tp-plan {slug} --auto. generator-return class.
Slot 6 — plan-audit
Drives the council audit fan-out (## Audit fan-out (Slots 4/6/8)) over plan.md + detailed-design.md instead of dispatching a single /tp-plan-audit subagent. audit-return class.
Slot 7 — phase-implement
Runs /tp-phase-implement {slug} --auto, dispatched as subagent_type="tp-worker" (write-capable surface — Read/Edit/Write/Grep/Glob/Bash; default model opus), once per plan phase, serial-within-phase — the P1 dogfood probe falsified 2-level parallelism (a subagent cannot spawn task sub-subagents; the harness exposes no agent-spawning tool one level down), so the phase subagent runs its tasks sequentially within its own budget. The worker / candidate machinery (## Tier 3 + ## Tier 3.5) is preserved: this slot keeps the candidate.v1 contract and routes its return through the unchanged run_tier_3_5.py, not parse_tier_return. Workers obey the file-size caps (CLAUDE.md §File Size Limits): an addition that would cross a cap splits by responsibility instead of growing the file — the file-size guard blocks hard-cap violations at commit.
Slot 8 — impl-audit
Drives the council audit fan-out (## Audit fan-out (Slots 4/6/8)) over design.md + plan.md and the candidate code — the code reaching members via --code-input (the Slot-8 code-input addendum), not as a single /tp-implementation-audit subagent (## Tier 5 Step 2). audit-return class; verdict-only — never edits code regardless of confidence. (The Tier 5 Step 1 regression check still runs first and gates the fan-out.)
Slot 9 — design-learn
Runs /tp-design-learn {slug} --auto (## Tier 5 Step 3). generator-return class; a synthesizer, not a gate. After Slot 9, ## Tier 5.6 closeout runs: the orchestrator folds the candidate onto tp/{slug} (git merge --no-ff, inline), runs verify_learn.py (learn-verify, advisory), then dispatches /tp-design-complete {slug} --auto to archive. The fold + learn-verify are orchestrator-inline and the archive is one --auto dispatch — Tier 5.6 is not a numbered slot of its own.
Slot 10 — PR
Opens the completion PR (tp/{slug} → {default}) with the fail-open semantics of ## Tier 6, after Tier 5.6 has folded the code and archived the design. (On a Tier 5.6 fold conflict it opens the legacy candidate→tp PR instead; ## Tier 6 — legacy fallback.) Hands off to Slot 11 unless --no-iterate/--no-review is set, in which case it is terminal.
Slot 11 — pr-iterate
Drives the completion-PR review loop to a reviewed-stable state. Terminal slot. The orchestrator owns the round loop directly: each round it fans out the ANGLES set as top-level general-purpose sub-agents, merges via merge_codereview_angles, posts via post_codereview_comment, shells python3 "$TP_ROOT"/skills/tp-pr-iterate/scripts/run_round.py with the merged findings, and dispatches /tp-pr-fix if the step says to — looping until run_round.py reports a terminal phase (converged / blocked-no-independent-review). Fix-commit sub-agents run with isolation="worktree" (their commits land in their own worktree, never the orchestrator's). Skipped under --no-iterate or --no-review. generator-return class; a non-converged loop is reported, never escalated past the merge-only gate.
Light slot mapping (weight-class: light)
When Tier 1 reads light (## Weight-class consumption (Tier 1)), the slot sequence narrows — same machinery, fewer dispatches:
- Slot 2 (
design) emits design.md + plan.md in one dispatch — the collapsed design.md and the thin plan.md from a single light-mode /tp-design sitting (the collapsed note must still pass validate_design_floor.py).
- Slots 3 and 5 are skipped (
detail, plan) — the light class has no detailed-design.md and plan.md already landed with Slot 2.
- Slots 4 + 6 merge into one audit fan-out — a single council pass over design.md + plan.md using the
--light merged conceptual+plan prompts (skills/tp-plan-audit/SKILL.md §Light mode prompts): same triad, single round (no Round 2), reader + 3 members + synth = 5 dispatches.
- Slot 8 = regression check + a single fidelity auditor — Tier 5 Step 1's regression check runs unchanged and still gates; the 3-persona fan-out is replaced by one verdict-only auditor working the fidelity checklist (
skills/_shared/weight-class.md §Light fidelity checklist), its finding confidences mapped through auto_verdict.compute_verdict ("$TP_ROOT"/skills/_shared/auto_verdict.py).
- All other slots (1, 7, 9, 10, 11) run unchanged. The per-slot return contracts and the C1 token accounting are untouched — light is fewer dispatches, not a different protocol.
Audit fan-out (Slots 4/6/8)
Each audit slot (design-audit, plan-audit, impl-audit) runs an
orchestrator-owned council fan-out instead of dispatching a single
/tp-*-audit --auto subagent. The orchestrator drives /council --orchestrator
(## ORCHESTRATOR MODE in skills/council/SKILL.md) itself — reading that mode
inline, never dispatching /council as a subagent — and issues the
council-{name} + synthesizer dispatches directly. Every arrow below is a
top-level single-level dispatch (orchestrator → subagent, no deeper), which
is what makes the fan-out feasible under L23. The council personas and the
synthesizer are read-only — they share tp/{slug} read-only with NO
isolation="worktree" (unlike the worker/phase-implement slot, whose
worktree-isolation posture is unchanged; see ## Dispatch loop).
The fixed sequence per audit slot is reader → Round 1 → Round 2 → synth →
clip:
- Reader (1 dispatch) — dispatch one
general-purpose subagent to confirm
the artifact paths exist and report their sizes (cheap, ~5k). This replaces
"load artifacts" without pulling contents into the orchestrator. This is a
real dispatch and consumes subagent_tokens — the reader IS counted in the budget
(## Per-slot budget table dispatch accounting; F1).
- Round 1 (N dispatches, parallel) —
/council --orchestrator --round 1 --members {triad} --artifacts {paths}
(+ --code-input base=tp/{slug},candidate=origin/candidate/{slug}/single,files=…
appended for the impl-audit slot only — see ### Slot-8 code-input addendum;
Slots 4/6 omit it and stay artifact-only).
The orchestrator validates the returned round-bundle, then each member
envelope in a second loop (## Return clipping two-step), clips each Round-1
envelope (keeps argument_summary + findings + per-finding confidence +
envelope confidence), discards the raw replies, and sums each dispatch's
subagent_tokens into the running total.
- Round 2 (N dispatches, parallel) —
/council --orchestrator --round 2 --members {triad} --round1 {bundle}
(+ the same --code-input base=tp/{slug},candidate=origin/candidate/{slug}/single,files=…
appended for the impl-audit slot only — Slots 4/6 omit it), where
{bundle} is the full Round-1 round-bundle file (every peer's verdict +
findings + argument_summary, F5), not a summary and not a thinned list —
so each persona can cross-examine concrete peer findings by index. Same
validate + clip + token-sum. Round 2 is skipped entirely under
--fast-audit (0 dispatches).
- Synth (1 dispatch) — assemble the synthesizer prompt via
build_synth_prompt(artifact_paths, round1, round2, slot) and dispatch one
general-purpose subagent with NO isolation="worktree" (F8 — read-only,
shares tp/{slug}, identical isolation posture to the council personas). It
reads the artifact paths, weighs the Round-1/Round-2 dicts (honoring each
challenged_finding_indices), and returns the existing audit-return
envelope.
- Clip — the orchestrator parses the synth reply via
parse_tier_return(reply, audit-return.v1.json), holds only the audit-return
dict (~5k), and the verdict drives the unchanged retry-with-advice /
escalation rules (## Retry-with-advice (audits)).
Default triad (F10): all three audit tiers default to the engineering triad
torvalds, ada, feynman — reusing tp-plan-audit's existing engineering triad, the in-scope
minimal default (the design forbids changing the council persona set or triad
definitions). council/SKILL.md already defines an architecture triad
(aristotle, ada, feynman) and a shipping triad (torvalds, musashi, feynman);
a follow-on could wire per-tier triads (e.g. architecture for design-audit) by
passing a different --members list — no schema or code change required, only
the orchestrator's default selection. --deep-audit → the full 18-member panel;
--fast-audit → the triad with Round 2 skipped.
Slot-8 code-input addendum (impl-audit only)
The five-step sequence above is shared by all three audit slots. For
impl-audit (Slot 8) only, the council members (Round 1 + Round 2) and the
synthesizer read the candidate code — the artifact-only audit (the question
design-audit / plan-audit answer) can never tell whether the code honors
the design. The reader does NOT review the code: it stays the cheap
general-purpose path-existence dispatch of step 1, additionally confirming that
the Slot-8 code-input refs/paths resolve — that tp/{slug} and
origin/candidate/{slug}/single exist and the touched-file paths are present —
so a broken ref-pair fails fast before the member rounds spend tokens. The reader
reports existence only; it never reads the diff body or judges the code. Slots
4/6 stay artifact-only (no --code-input, code_input=None on the synth call,
byte-identical to the five steps above); the addendum below is scoped to Slot 8
and changes nothing for design-audit / plan-audit.
- Refs reach the members via
--code-input. The orchestrator computes the
ref-pair once (inline, cheap) — base = tp/{slug},
candidate = origin/candidate/{slug}/single — and derives the touched-file
list with name-only three-dot git diff --name-only tp/{slug}...origin/candidate/{slug}/single (a path list, ~hundreds of bytes,
never the diff body). Each Round-1/Round-2 dispatch carries
--code-input base=tp/{slug},candidate=origin/candidate/{slug}/single,files=…
(## ORCHESTRATOR MODE in council/SKILL.md); every member runs its own
three-dot git diff tp/{slug}...origin/candidate/{slug}/single /
git show origin/candidate/{slug}/single:<file> and judges the code against
design.md + plan.md. The orchestrator never holds the raw diff.
- The synth call gains the 5th arg for Slot 8.
build_synth_prompt now takes
a defaulted code_input=None 5th parameter. For impl-audit the synth step
calls build_synth_prompt(["…/design.md", "…/plan.md"], round1, round2, "impl-audit", code_input={base, candidate, touched_files}); the synthesizer
reads the candidate code itself via those refs (it gets the refs, not the diff
body). For design-audit / plan-audit the existing 4-arg
build_synth_prompt(artifact_paths, round1, round2, slot) literal (step 4
above) stays correct — it is equivalently code_input=None, so no edit to that
literal is required; the parameter is defaulted and this addendum is where the
Slot-8 5-arg form is documented.
- Large-diff gate → audit-by-dimension (no truncation). Above 1500
changed lines (insertions + deletions from three-dot
git diff --shortstat tp/{slug}...origin/candidate/{slug}/single) Slot 8 engages audit-by-dimension
(## Handoff protocol — pre-split): one read-only dispatch per dimension
(consistency, coverage, codebase-fit), each carrying the same
--code-input, instead of one member reading the whole diff at once. The diff
is never trimmed — the fan-out widens by dimension so each unit stays within
budget. When the count exceeds 1500 the orchestrator appends
[tp-run-full-design/tier-5] impl-audit-large-diff <N> lines → audit-by-dimension
to decisions.md (Confidence: High) so an oversized candidate is visible rather
than silently fragmented.
Per-slot budget table
Each slot carries a fixed soft budget — a sizing hint passed into compose(...) so the slot knows how much room it has — and a single hard ceiling of 500k tokens / slot, well below the 1M harness limit so that a single handoff split (## Tier sections / handoff phase) can never overflow. These are static; there is no derivation math (detailed-design §Decisions "Static per-tier budget table").
| Slot | Soft budget | Light | Hard ceiling |
|---|
pickup | 50k | 50k | 500k |
design | 80k | 100k (collapsed design.md + thin plan.md) | 500k |
detail | 60k | — (skipped) | 500k |
design-audit | 200k | 150k (merged audit — Slots 4+6 as one fan-out) | 500k |
plan | 100k | — (skipped) | 500k |
plan-audit | 200k | — (merged into the 150k fan-out above) | 500k |
phase-implement | 200k / phase | 200k / phase | 500k |
impl-audit | 200k | 80k (single fidelity auditor) | 500k |
design-learn | 100k | 100k | 500k |
PR | 50k | 50k | 500k |
The audit slots (design-audit, plan-audit, impl-audit) are weighted higher (200k) because audits read the full design + plan + codebase; generators and the PR slot are lighter. phase-implement is budgeted per plan phase — one dispatch per phase, each with its own 200k soft budget. The Light column applies when Tier 1 reads weight-class: light (### Light slot mapping): the design slot grows to 100k (it emits two artifacts), the merged audit runs at 150k, the fidelity audit at 80k, and the hard ceiling is unchanged at 500k. The light numbers are tunable sizing hints, recorded here as the single source of truth.
Audit-tier dispatch accounting — the reader IS counted (F1)
Each audit slot fans out into the reader → Round 1 → Round 2 → synth sequence (## Audit fan-out (Slots 4/6/8)). Every one of those is a real top-level dispatch consuming subagent_tokens, so the reader is a counted dispatch and the per-tier total is 1 (reader) + N (Round 1) + N (Round 2) + 1 (synth):
| Mode | N (members) | Round 2? | Dispatches | Breakdown |
|---|
| default (triad) | 3 | yes | 8 | 1 + 3 + 3 + 1 |
--deep-audit (full panel) | 18 | yes | 38 | 1 + 18 + 18 + 1 |
--fast-audit (triad, no R2) | 3 | no | 5 | 1 + 3 + 0 + 1 |
These 8 / 38 / 5 counts are used everywhere the audit budget appears — this table, the SKILL prose, and the test_budget_table() grep-anchor literals. All accounting includes the reader; the dispatches are summed by the existing C1 subagent_tokens running total with no new budget mechanism (## Token accounting).
The soft budgets are advisory sizing only. The whole-run --max-tokens cap is the real constraint: it bounds the running sum across all dispatches (## Token accounting) and is checked at slot boundaries — after each slot returns and before the next is dispatched. A slot in flight always finishes before a boundary abort; there is no mid-slot preemption (## Token budget). The orchestrator itself stays under ~100k throughout because it only ever holds clipped envelopes plus the running token sum.
Token accounting (C1)
Budget enforcement reads an authoritative per-dispatch token count, not the subagent's self-report. After each Agent(...) dispatch returns, the orchestrator reads the harness subagent_tokens field from the Agent-tool return metadata (the same return envelope that carries agentId and worktreePath) and adds it to a running total that it sums across all dispatches. At each slot boundary it checks that running total against --max-tokens (## Token budget).
This is the C1 decision, and it is load-bearing. Claude Code exposes no running self-total to an agent, but each dispatch's authoritative cost is returned to the parent in subagent_tokens (spike H1). The P1 dogfood probe verified the field is live before any budget machinery was built on it: the outer probe dispatch returned subagent_tokens: 35322, corroborated by six council dispatches, so the committed probe verdict is C1-ABSENT? NO and whole-run budget enforcement is viable. (Had the probe found the field absent, this section would instead document --max-tokens as deferred / a slot-count heuristic — see plan Task 3.3's C1-ABSENT branch — but that branch was not taken.) The audit-slot fan-out dispatches (reader + Round-1 + Round-2 personas + synthesizer; ## Audit fan-out (Slots 4/6/8)) are summed by this same running total — ordinary Agent(...) dispatches counted by the existing C1 sum, no new budget mechanism (the 8 / 38 / 5 reader-counted totals in ## Per-slot budget table). The return envelope's telemetry.tokens_used is a separate, advisory/nullable field — the subagent's own self-report, which spike F4 showed undercounts by ~50%; it is informational only and not used for budget — the orchestrator sources the running total only from the harness subagent_tokens metadata, never the parsed envelope. Reading the budget number from the envelope is the C1 regression this rule exists to prevent.
Per-tier briefing emit
After each tier's outcomes land in decisions.md, the orchestrator refreshes the live fleet-state briefing — a CLI step, not a Python hook, so the ~100k budget is untouched: it shells out once per tier to the pro-tier python3 "$TP_ROOT"/skills/_shared/html_briefing/emit_run_briefing.py --slug {slug} --worktree {worktree} --tier {tier-id} --tokens {running-total} (running total from ## Token accounting / C1). The call is fail-open — a briefing failure (bad args, a raising producer, or — in the FREE core build — an absent html_briefing) never aborts the tier loop, and decisions.md stays the loop's durable record (html-briefing-flow Task 4.2); on success it writes the offline index.html + fleet-state.json + one ledger.jsonl row (the C2 timeline/cost source). It never passes --serve (a foreground localhost-only server for the human-initiated standalone surface — it would block the auto loop). See skills/_shared/html_briefing/ (pro-tier) and html-briefing-flow Task 4.3.
Per-slot return contract
Every dispatched slot is given an explicit_return_contract in its compose(...) prompt — the slot-level parallel of the worker's Tier-3 explicit_artifact_contract. It tells the subagent exactly how to run its tier and what to return:
- Run the tier's
--auto skill inline. The subagent follows the delegated skill's SKILL.md --auto instructions directly in its own context — it does not shell out to claude -p. Running inline is what keeps the dispatch's subagent_tokens visible to the orchestrator (## Token accounting / C1) and is why a single-level fan-out is sufficient.
- Let the skill write its native artifacts +
decisions.md entries under its own prefix. The delegated --auto skills are unmodified — they still only write decisions.md plus their artifacts and set an exit code; they emit no return envelope of their own. This rewrite adds no requirement to any delegated skill.
- Synthesize the return envelope itself. After the skill finishes, the subagent composes the tier-return envelope from the skill's
decisions.md entries + the skill's exit code — status (pass / needs-work / errored), plus the verdict + findings for audit slots and artifact_paths for generator slots — and emits it as the last fenced ```json block in its reply. This is the C2 decision: the orchestrator never interprets the raw skill output; it parses the subagent-synthesized envelope (## Return clipping) against the slot's schema class.
Because the delegated skills are untouched, envelope synthesis is a thin layer the dispatched subagent owns, not a change to the pipeline. The schema class per slot is given in ## Slots (generator-return / audit-return / pickup-contract; the worker / phase-implement slot keeps candidate.v1 and routes through run_tier_3_5.py).
Return clipping
When a slot subagent returns, the orchestrator runs this fixed on-return sequence before touching the next slot:
- Read
subagent_tokens from the Agent return metadata, add it to the running total, and check the total against --max-tokens at this slot boundary (## Token accounting, ## Token budget).
- Parse the reply with
parse_tier_return(reply, schema_path) for the slot's schema class — it extracts the last fenced ```json block and validates it. (The worker / phase-implement slot routes through run_tier_3_5.py and the candidate.v1 path instead.)
- Clip: keeps only the parsed dict and discards the raw reply.
parse_tier_return is the clip point — the orchestrator retains nothing else from the subagent, which is what bounds its context to ~100k.
- Branch on
status: pass → advance to the next slot; needs-work → retry-with-advice or escalate per the audit rules; handoff-pending → resume from the committed handoff; errored → escalate.
- Log an
[orchestrator/<slot>]-prefixed entry to decisions.md recording the status, the clipped summary, and the running token total.
Only the clipped dict survives into the next iteration; the raw subagent reply, its scratch reasoning, and any artifact contents never enter the orchestrator's context.
Record/replay hooks (--record/--replay): see record-replay.md.
Handoff protocol — pre-split (M2)
A dispatched subagent cannot self-yield mid-call: the harness exposes no "pause and hand back partial progress" primitive, so once a slot subagent is running the orchestrator cannot reach in and checkpoint it. A single oversized dispatch is therefore caught only at the next slot boundary — never interrupted mid-flight — consistent with the no-mid-tier-abort rule (## Token budget). "Checkpointed dispatch" does not mean a subagent pausing itself; it means the orchestrator splitting the work before it dispatches.
The mechanism is pre-split, decided on the orchestrator side. Instead of one giant dispatch for a long tier, the orchestrator issues a series of sequential dispatches, reading the harness subagent_tokens (## Token accounting) between them and stopping when the slot's work is done or its budget boundary is reached. Each long-tier class has a natural split axis:
design-audit / plan-audit / impl-audit → audit-by-dimension — one dispatch per audit dimension (e.g. consistency, coverage, codebase-fit) rather than one dispatch reading the whole design + plan + codebase at once.
plan → plan-by-section — one dispatch per plan section / phase group when the plan is large.
phase-implement → phase-by-phase — one dispatch per plan phase. This is already the Slot 7 model (## Slots): one phase-implement dispatch per plan phase, each within its own per-phase budget.
Pre-splitting is purely an orchestrator-side sequencing decision; the delegated --auto skills are unchanged. The orchestrator reads subagent_tokens between the sequential dispatches and sums each into the running total exactly as for any other dispatch, so a pre-split tier obeys the same whole-run --max-tokens cap (## Token budget). A dispatch that nonetheless overshoots its budget is still only caught at the next boundary — pre-split shrinks each unit so the overshoot is bounded, it does not add mid-dispatch preemption.
Cold-resume worklist
Each pre-split checkpoint is persisted to a committed worklist file at three-pillars-docs/tp-designs/{slug}/.handoffs/{slot}-{attempt}-{N}.md — {slot} is the slot name, {attempt} its retry-with-advice attempt (## Retry-with-advice counter), and {N} the sequential checkpoint index within that dispatch chain. The file is a markdown body with a handoff.v1 envelope embedded as its last fenced ```json block; on resume the orchestrator parses it with parse_tier_return(body, handoff.v1.json) (the handoff-pending branch of ## Return clipping).
The worklist is committed to git at each checkpoint, never held only in the orchestrator's context — that durability is what makes a cold-resume possible. If the orchestrator process dies mid-tier (SIGTERM, crash, host restart), a fresh /tp-run-full-design {slug} invocation reads the latest committed .handoffs/{slot}-{attempt}-{N}.md under tp-designs/{slug}/, recovers partial_state + next_action + files_to_continue_with + remaining_budget_estimate from the embedded envelope, and resumes the pre-split chain from the next checkpoint instead of restarting the whole tier. Because the worklist lives under tp-designs/{slug}/ and is committed, it survives both the orchestrator discarding its own context (## Return clipping) and any worker-worktree cleanup.
Tier 1 — Pickup
The pickup tier resolves the task source into a design.md and a locked branch the orchestrator can drive. Two modes:
Mode A — Pickup skill provided (--pickup-skill <name> AND --task-id <id> passed):
- Refuse with a clear error if
--task-id is missing — Mode A cannot proceed without an upstream identifier to hand to the pickup skill. (Append [tp-run-full-design/tier-1] missing-task-id to decisions.md and exit non-zero.)
- Invoke the pickup skill with the orchestrator identity flag and pass the slug + task-id through:
/{pickup-skill} --orchestrator --slug {slug} --task-id {task-id}
{slug} is the in-repo key the orchestrator already received as its required positional.
{task-id} is the opaque upstream reference; the pickup skill is responsible for resolving it into upstream metadata and seeding design.md.
- The pickup skill is responsible for:
- Validating the upstream task identified by
{task-id} is ready
- Creating
three-pillars-docs/tp-designs/{slug}/
- Creating and locking branch
tp/{slug} with owner = orchestrator (see ## Lock ownership)
- Seeding
design.md (or spike.md) if the upstream system carries that data
- Returning a pickup contract envelope on stdout (with
task_metadata.external_ref = {task-id} echoed verbatim so the orchestrator can re-cite it in the Tier 6 PR description)
- Read the returned pickup contract.
Mode B — Manual pickup (no --pickup-skill):
- Read existing
three-pillars-docs/tp-designs/{slug}/design.md (must exist).
- Acquire the lock per
skills/_shared/collaboration.md. If held, honor --force-takeover or refuse.
- Synthesize a minimal pickup contract from local state (no callbacks;
phase_hook, writeup_hook, escalate_hook all null).
Pickup contract v1 validation
The orchestrator validates the returned contract against this schema before doing any other work. The contract is JSON-shaped, version 1, and the validation rules are intentionally strict — a malformed contract aborts the run before Tier 2 spends tokens.
{
"version": 1,
"slug": "kebab-case-design-name",
"branch": "tp/{slug}",
"dir": "three-pillars-docs/tp-designs/{slug}",
"task_metadata": {
"title": "Human-readable task title",
"hypothesis": "What is unknown / what we want to learn",
"external_ref": "Opaque pickup-skill-specific reference",
"phase_hook": null,
"writeup_hook": null,
"escalate_hook": null
}
}
Validation rules — each maps to an explicit failure mode:
version is required and must equal 1. The orchestrator refuses any contract whose version it does not recognize. Append [tp-run-full-design/tier-1] unknown-pickup-version <value> to decisions.md and exit non-zero. Forward-compatibility shims are deferred to a v2 design — never silently downgrade.
slug, branch, dir, task_metadata are required top-level keys. Missing any → [tp-run-full-design/tier-1] missing-required-field <key> and exit non-zero.
slug must match [a-z0-9-]+ per skills/_shared/validate-name.md. Mismatch → [tp-run-full-design/tier-1] invalid-slug <value>.
branch must equal tp/{slug} exactly. Mismatch (e.g., pickup skill returned a candidate branch by accident) → [tp-run-full-design/tier-1] branch-slug-mismatch.
dir must equal three-pillars-docs/tp-designs/{slug} exactly and the directory must exist on disk.
task_metadata.title is required and non-empty (used in the Tier 6 PR title).
phase_hook, writeup_hook, escalate_hook may each be null or an opaque string. If null, the corresponding callback is omitted (fire-and-forget) and noted once in decisions.md as [tp-run-full-design/tier-1] hook-omitted <hook-name>. Per detailed-design.md §Decisions (OQ2), hook failures are Medium-confidence boundary events that do not abort the run.
Lock + branch protocol
The pickup skill (Mode A) or the orchestrator itself (Mode B) acquires the design lock per skills/_shared/collaboration.md. The orchestrator is a distinct lock-owner identity — see ## Lock ownership.
After Tier 1 succeeds, the orchestrator is on branch tp/{slug} and the lock's phase field reflects the current tier (e.g., phase: "tier-2" once Tier 2 begins). Each subsequent tier refreshes last_touched and bumps phase as it enters.
Weight-class consumption (Tier 1)
After the pickup contract validates, read the design's weight class (skills/_shared/weight-class.md):
python3 "$TP_ROOT"/skills/_shared/weight_class.py read three-pillars-docs/tp-designs/{slug}
This is a direct design.md frontmatter read — the pickup-contract v1 envelope is unchanged and the class never rides it (audit finding m1). The legacy ("full", "default") read (no/invalid frontmatter) routes as full — fail-safe toward more checking.
Routing rules by class:
-
full — the pipeline as documented below; no change.
-
light — the light slot mapping (see ## Slots and ## Per-slot budget table for the light column).
-
just-do-it — escalates to light: an unattended run warrants the audit floor. Log the escalation to decisions.md with exactly this entry (the auto-mode Decision Entry template, class transition named):
### [tp-run-full-design] weight-class escalation: just-do-it → light
**Question**: design.md declares `just-do-it`; may an unattended run skip the audit floor?
**Decided**: Escalated just-do-it → light for this run (design.md's declared class unchanged).
**Reasoning**: Unattended runs warrant the light audit floor; escalation is always allowed, de-escalation never (skills/_shared/weight-class.md §Escalation rule).
**Confidence**: High
-
spike — refuse (audit finding M1): /tp-spike-auto Phase 1 is interactive by design and cannot run unattended. This is a refusal with guidance, not a conversion — never silently re-class a spike as a full design. Append exactly this BLOCKED entry and exit non-zero:
### [tp-run-full-design] BLOCKED — spike-class design cannot run unattended
**Cause**: weight-class-spike-refusal
**Details**: design.md declares `weight-class: spike`. /tp-spike-auto Phase 1 (design Q&A) is interactive by design — run it manually; if a follow-on full design emerges, re-run this orchestrator against it. No spike→full conversion is attempted.
-
De-escalation is refused: the orchestrator may escalate ceremony above the declared class (logged as above), never de-escalate below it. Any flag, config, or heuristic requesting a lighter run than design.md declares takes the same BLOCKED path (**Cause**: weight-class-de-escalation-refused).
Tier 1 outcomes
- Success: pickup contract valid, lock held, branch checked out, weight class read and routed. Proceed to Tier 2.
- Failure: any validation rule above, or the spike-class / de-escalation refusal. Append the categorized decisions.md entry, exit non-zero. No partial state — Tier 2 never runs on an invalid contract.
Tier 1.5 — Mode C front-end (interactive design)
Mode C is the default behavior when the operator invokes /tp-run-full-design {slug} without --pickup-skill and without --skip-design. It exists for the "I want autonomous-from-here, but the design conversation needs a human" workflow — the orchestrator runs /tp-design interactively, then asks the operator whether to continue unattended.
Note on decisions.md prefix: Tier 1.5 emits tokens under the [tp-run-full-design/tier-1] prefix (not tier-1.5). This is deliberate — Tier 1.5 is a front-end to Tier 1's pickup flow, not an independent tier with its own concerns. Reusing the tier-1 prefix keeps grep '[tp-run-full-design/tier-1]' decisions.md complete for the entire pickup-and-confirm phase. The Tier 3.5 prefix (tier-3.5) is the structural parallel: it gets its own prefix because Tier 3.5 is its own tier with its own retry budget and failure taxonomy, whereas Tier 1.5 is purely a sequencing concern.
Default routing
When neither --pickup-skill nor --skip-design is set:
- No
design.md in the design dir — invoke /tp-design {slug} first and let it run its normal interactive conversation. Once /tp-design commits the new design.md, fall through to the confirmation prompt.
design.md already exists — re-enter /tp-design (per OQ4). The design skill itself handles the revise-or-fresh choice; the orchestrator does not special-case the existing-design path. Once /tp-design commits its update (or no-ops on a revise-no decision), fall through to the confirmation prompt.
Pass --skip-design to bypass this tier — Mode B's plain manual-pickup behavior resumes (the orchestrator reads the existing design.md and proceeds directly to Tier 2 audits without re-running /tp-design).
Confirmation prompt (blocking yes/no)
After /tp-design returns, the orchestrator asks the operator:
Go autonomous from here?
[y/n]
There is no countdown auto-proceed (OQ1) — a human must explicitly answer. The orchestrator blocks on stdin until it receives y/yes or n/no (case-insensitive). Any other input re-prompts.
Outcomes
y / yes — Append [tp-run-full-design/tier-1] autonomous-continue to decisions.md, advance to Tier 2 with the orchestrator-held lock. The rest of the run is unattended.
n / no — Terminal no-branch state per OQ7:
design.md is already committed (by the preceding /tp-design).
- The branch
tp/{slug} is preserved at the design commit.
- The lock owner restored to the invoking human (the orchestrator releases its claim cleanly without requiring
--force-takeover on the operator's next /tp-design-detail {slug} invocation).
- One
[tp-run-full-design/tier-1] no-autonomous-run entry is appended to decisions.md.
- Exit code 0.
The "no" path is the seamless hand-off — the operator gets a committed design they can pick up manually, with no leftover orchestrator state to clean up.
Tier 2 — Design pipeline chain
Slots 2–6 are the design pipeline. The orchestrator dispatches one subagent per slot (## Dispatch loop); each subagent runs one of the five design-pipeline skills in --auto mode inline within the subagent and synthesizes its return envelope (## Per-slot return contract). The orchestrator never runs these skills in its own context — it only reads each slot's clipped envelope (## Return clipping). Each delegated skill still writes [<skill-name>]-prefixed entries to decisions.md per skills/_shared/auto-mode.md; the orchestrator does NOT rewrite these entries — the native skill prefix is preserved, and only orchestrator-emitted entries use the [tp-run-full-design/tier-N] prefix.
The five slots dispatch in order, one skill each (run inline in the dispatched subagent):
| Slot | Skill | Return class |
|---|
2 — design | /tp-design {slug} --auto | generator-return (Shape A gate) |
3 — detail | /tp-design-detail {slug} --auto | generator-return |
4 — design-audit | /tp-design-audit {slug} --auto | audit-return |
5 — plan | /tp-plan {slug} --auto | generator-return |
6 — plan-audit | /tp-plan-audit {slug} --auto | audit-return |
Per-skill semantics
Each delegated skill follows one of the three auto-mode shapes defined in skills/_shared/auto-mode.md:
/tp-design --auto — Shape A (validator gate). Delegates to skills/_shared/validate_design_floor.py. Either PASS (proceed) or BLOCKED (a design.md floor violation — missing Problem/Vision-alignment/Scope/Behaviors). The orchestrator treats BLOCKED as a Tier 2 abort cause; see "Audit rejection" below. Any other exit or failure to launch → BLOCKED with Cause: floor-validator-crash, Details: captured stderr (truncated to 500 chars). Never treat a non-0/1 exit as PASS.
/tp-design-detail --auto — Shape B (generator). Produces detailed-design.md from design.md without user Q&A; every judgment call self-logs Confidence: High/Medium/Low. The orchestrator does not gate on Confidence here — generation always proceeds; Confidence: Low entries are informational and surface in the Tier 5 audit.
/tp-design-audit --auto — Shape C (audit with confidence-based dispatch). High-confidence findings auto-resolve (the skill applies the fix); any Medium or Low finding escalates BLOCKED.
/tp-plan --auto — Shape B (generator). Produces plan.md from detailed-design.md. Same gating semantics as design-detail.
/tp-plan-audit --auto — Shape C. Same High-auto-resolve / Medium-or-Low-escalate semantics as design-audit.
Audit rejection — self-heal, then escalate
The orchestrator is built to self-heal: an audit needs-work verdict does not stop the run. The orchestrator re-runs the generator that produced the rejected artifact, hands it the audit's findings as advice, and lets the audit re-judge — looping up to the per-cycle attempt budget (## Retry-with-advice (audits)). Every finding — high, medium, or low confidence — drives a re-spawn; confidence no longer gates retry-vs-escalate. This is on by default (--max-attempts defaults to 3).
Escalation to a human is the terminal fallback, not the first response. There are two terminal causes:
- Retry budget exhausted — the per-cycle budget (
--max-attempts) is spent and the audit still returns needs-work. Append [tp-run-full-design/tier-2] audit-retry-exhausted with the residual findings.
- Shape A design-floor BLOCKED (from
/tp-design — e.g. design.md missing Problem / Vision-alignment / Scope / Behaviors). This is the one structural floor that cannot self-heal: in Mode A/B the design.md is human/pickup-seeded, so re-running the validator cannot author the missing sections. It escalates without retry. Append [tp-run-full-design/tier-2] design-floor-blocked.
On either terminal cause the orchestrator does NOT proceed to the worker tier, and:
- Appends the categorized
decisions.md entry (audit-retry-exhausted or design-floor-blocked) with the verdict / residual-finding list and Confidence: High.
- Returns BLOCKED to the operator (exits non-zero from the orchestrator).
- Leaves the lock + branch as-is so a human can pick up via
/tp-session-restore {slug} and address it without re-running pickup.
The floor is never removed — self-healing changes only who tries first. The audit must still PASS for the run to advance, and the orchestrator never auto-merges; a human reviews the final PR. The orchestrator attempts the mechanical fix before spending a human's attention; when it cannot converge within the budget, the human still gets the gate. Setting --max-attempts 1 restores escalate-on-first-rejection for high-stakes runs.
Token-cap boundary check
The orchestrator checks --max-tokens at each slot boundary — after each design-pipeline slot subagent returns — summing the harness subagent_tokens into the running total (## Token accounting). See ## Token budget for the abort semantics; Tier 2 implements no boundary logic of its own.
Tier 3
Worker (one subagent per plan phase, each in an isolated worktree). Tier 3 is invoked once per plan phase — it spawns a worktree-isolated subagent for Phase N, hands it the design, plan, the target phase number, and an explicit artifact contract, and waits for the structured response that Tier 3.5 will validate. The candidate branch (candidate/{slug}/single) is shared across all phase dispatches: Phase 1 creates it; Phase 2+ fetches and continues from it.
Worker Agent invocation
worker_prompt = compose(design.md, plan.md, phase=N) + explicit_artifact_contract
Agent(
subagent_type="tp-worker",
isolation="worktree",
description="candidate/{slug}/phase-{N}",
prompt=worker_prompt,
)
isolation="worktree" is mandatory: it cuts a fresh git worktree under .claude/worktrees/agent-<id>/ so the worker's .git/index is independent of the orchestrator's. The orchestrator's branch (tp/{slug}) stays clean while the worker commits on the shared candidate branch. Note: the worker's auto-entry branch is worktree-agent-<id> (a Claude Code default, NOT the orchestrator's tp/{slug}), which is exactly why item 1 of the worker contract below requires an explicit branch setup step — without it the worker would build on the auto-entry branch and the orchestrator would have nowhere clean to merge from. This is S13 finding F2; see completed-tp-designs/agent-worktree-isolation-spike/d12-worker-contract-revision.md.
explicit_artifact_contract — 6 worker responsibilities
The contract appended to worker_prompt is a literal numbered list. Six items, in order, no negotiation:
- Set up the candidate branch for this phase.
- Phase 1:
git checkout -b candidate/{slug}/single {base-ref} — create the shared candidate branch off the orchestrator's base ref (typically tp/{slug} HEAD). Do NOT work on the auto-entry worktree-agent-<id> branch (S13 finding F2).
- Phase 2+:
git fetch origin && git checkout candidate/{slug}/single — continue building on the branch Phase 1 already pushed. Do not create a new branch.
- Implement this phase's tasks on the candidate branch. Execute only the tasks belonging to Phase N from
plan.md — prior phases are already committed on the branch. Each task is one red-green-refactor cycle and one commit on the candidate branch.
- Commit work on the candidate branch. One commit per plan task. No
--no-verify, no Co-Authored-By trailer, no git add -A — scope each commit to the files the task touched.
git push -u origin candidate/{slug}/{candidate_id} — push the candidate branch to origin. This is the boundary-crossing mechanism (S13 answer to Round 1 finding B2): once pushed, the candidate is durable even if the worker worktree is later cleaned up.
- Return a structured response as the final answer text — a fenced JSON block in the exact shape below, with no content after it. This block is the orchestrator's ONLY input from the worker; anything else in the response text is treated as scratch.
{
"schema": "tp-run-full-design/candidate/v1",
"candidate_id": "{candidate_id}",
"branch": "candidate/{slug}/{candidate_id}",
"sha": "<git rev-parse HEAD>",
"summary": "<short narrative — what was implemented, key tradeoffs>",
"test_results": {
"passed": <int>, "failed": <int>, "skipped": <int>,
"raw": "<test-runner stdout, truncated to 4000 chars>"
},
"telemetry": {
"duration_ms": <int>, "tokens_used": <int>, "tool_calls": <int>
}
}
- Do NOT write artifact files to the parent worktree. Specifically: do not create or edit anything under
three-pillars-docs/tp-designs/{slug}/candidates/. The orchestrator writes those artifacts itself in Tier 3.5 — the worker only computes and reports. (Why: Claude Code's harness blocks subagent .md Write calls as a deliberate signal that subagents should compute, not file-write. Fighting that signal is what got the prior worker-writes-artifacts contract scrapped — see d12-worker-contract-revision.md.)
Tier 3.5 — Validation gate
Tier 3.5 runs at the end of every phase dispatch, not deferred to the last phase — a malformed envelope is caught at the phase that produced it. Cross-reference the per-phase ordering paragraph in ## Phase-implement dispatch.
Tier 3.5 is the orchestrator's post-worker handling — it parses the worker's structured response, validates it against candidate.v1.json, writes the four candidate artifact files, and cleans up the worker worktree. Failures here decide whether --max-attempts retries (see ## Retry & max-attempts) or escalates.
No-op short-circuit
Before parsing anything: if the Agent(...) return envelope has no worktreePath field, the worker made no file system changes in its worktree (Claude Code auto-cleans empty worktrees before returning). This indicates the worker bailed out without doing the work. Action:
- Skip the artifact-write (there's nothing to validate or persist).
- Append
[tp-run-full-design/tier-3.5] worker-noop to decisions.md with Confidence: High.
- Escalate immediately —
--max-attempts does NOT retry this case (case (a) in detailed-design.md §Error Semantics; the worker producing nothing is a non-retryable structural failure, not a transient one). Per S13 finding F8.
Wrapper-driven pipeline
For the normal path (worker returned with worktreePath present), the orchestrator shells out to the wrapper at skills/tp-run-full-design/scripts/run_tier_3_5.py. The wrapper owns the composition of parse_candidate_response → write_candidate_artifacts → cleanup_worker_worktree → git ls-remote SHA cross-check. The orchestrator no longer imports the helpers directly.
Invocation:
python3 "$TP_ROOT"/skills/tp-run-full-design/scripts/run_tier_3_5.py
Pipe a JSON object on stdin: {worker_response, agent_meta: {agentId, worktreePath}, design_dir, candidate_id, slug}. Read the single-line JSON envelope on stdout: {status, case, event_token, detail}. The exit code carries the orchestrator's next action:
0 — proceed: status: "ok", case: null, event_token: "ok". Tier 3.5 succeeded; advance to Tier 5.
1 — retry per --max-attempts (case c only): status: "retry", case: "c", event_token: "schema-validation-error". The worker emitted a fenced JSON block with the canonical schema string but the payload failed validation — re-enter Tier 3 if attempts remain.
2 — escalate: status: "escalate", case in {"a", "b", "e", null}. Append the appropriate [tp-run-full-design/tier-3.5] <event_token> line to decisions.md and exit non-zero. case: null is reserved for wrapper-internal failures (event_token stdin-invalid, invalid-worktree-path, artifact-write-failed, cleanup-failed, or sha-check-failed) — these are environmental or contract violations, not retryable, and must not collide with exit 1 (case (c) retry).
The wrapper handles its own decisions.md side effects for cleanup: cleanup_worker_worktree detects a locked worktree (git worktree list --porcelain), force-removes it (--force -f), and — when a lock was forced — self-logs via [tp-run-full-design/tier-3.5] worktree-cleanup-locked <path>. It is internal to the helper and never surfaces to the orchestrator as a separate envelope case.
The wrapper's source of truth for candidate_id is the worker-reported value (extracted from the fenced JSON). The orchestrator's stdin top-level candidate_id is advisory routing context — used for correlation in the orchestrator's own decisions.md entries, never for path construction or branch reference inside the wrapper.
SHA cross-check (case (e), non-retryable)
After write_candidate_artifacts returns, the orchestrator runs a final sanity check on the candidate branch:
git ls-remote origin candidate/{slug}/{candidate_id}
If the remote head SHA does not match parsed["sha"], the worker's claim about what was pushed is wrong (push race, auth issue, or worker fabricated the SHA). Action:
- Append
[tp-run-full-design/tier-3.5] sha-mismatch {slug}:{parsed_sha}:{remote_sha} with Confidence: High. The sha-mismatch event-type token distinguishes this entry from the retry-attempt entries under the same tier-3.5 prefix (preserves the prefix scheme's machine-readability per detailed-design §Decisions (OQ5)).
- Escalate immediately — case (e) is non-retryable; an environmental issue, not a worker fault.
Tier 3.5 outcomes
- Success: 4 artifact files written, worktree cleaned, SHA cross-check passes. Proceed to Tier 5.
- Retryable failure (case (c)): re-enter Tier 3 per ## Retry & max-attempts.
- Non-retryable failure (case (a) no-op, (b) no-block, (e) sha-mismatch): escalate per the appropriate decisions.md entry above; exit non-zero.
Phase-implement dispatch (serial-within-phase) — Form SERIAL
The phase-implement slot (Slot 7) is dispatched once per plan phase, and each phase subagent runs its plan tasks serially within that single phase subagent — there is no 2-level parallelism. This is Form SERIAL, the form selected by the P1 dogfood-probe GATE VERDICT.
The probe verdict was nested-FAIL: a worktree-isolated subagent cannot spawn nested task sub-subagents at all — the harness exposes no agent-spawning tool one level down (see ## Dispatch loop). The pre-registered NESTED-OK form (a phase subagent fanning out parallel task sub-subagents and cleaning them up within its phase budget) is therefore omitted, not stubbed; only Form SERIAL ships. See decisions.md [orchestrator/probe] ("nested verdict? nested-FAIL") and plan.md Task 6.2.
Concretely, the phase subagent:
- Runs
/tp-phase-implement {slug} --auto inline (## Per-slot return contract), executing the phase's tasks one after another in its own context — the M1 serial-within-phase fallback the P1 gate pre-registered. Where /tp-phase-implement would normally spawn parallel worktree workers for independent tasks, under orchestrator dispatch it cannot (it is itself a subagent, and a subagent cannot spawn task sub-subagents), so it falls back to running those tasks sequentially.
- Stays within its single phase-implement soft budget (200k / phase; ## Per-slot budget table) and returns one candidate / phase envelope — the orchestrator sees only that envelope, never the per-task work (## Return clipping). The worker /
candidate.v1 machinery (## Tier 3 + ## Tier 3.5) is unchanged; this slot still routes its return through run_tier_3_5.py, not parse_tier_return.
Per-phase ordering (fixed sequence within each phase dispatch):
- Branch setup (contract item 1): Phase 1 creates
candidate/{slug}/single; Phase 2+ fetches and continues.
- Implement + commit (contract items 2, 3): run tasks one-after-another, one commit per task.
- push before synthesizing the envelope (contract item 4 precedes item 5):
git push -u origin candidate/{slug}/{candidate_id} — the SHA in the envelope must reference a pushed commit so the orchestrator's git ls-remote SHA cross-check (Tier 3.5 case e) is meaningful. Under the single-worker model, candidate_id == "single", so the branch is candidate/{slug}/single.
- Synthesize the
candidate.v1 envelope (contract item 5): the final candidate.v1 JSON block.
- Orchestrator runs
run_tier_3_5.py at the end of this phase (before dispatching the next phase): parse → write artifacts → cleanup → git ls-remote SHA cross-check.
If a future harness gains nested dispatch, the NESTED-OK form can be revived as a follow-on design; until then the falsified nested model is superseded by Form SERIAL, which obeys the single-level fan-out invariant of ## Dispatch loop.
Tier 4 — Council coordination (skipped in MVP)
Mode A0 has no Tier 4. The single candidate from Tier 3 is taken verbatim through Tier 5's audits; if those pass, Tier 6 opens the PR. There is no inter-candidate selection, no diff-voting, no persona overlay.
Adding Tier 4 requires:
/council gaining a code-candidate evaluation mode (--input over candidates/, voting semantics over diffs, not over positions) — Round 1 finding B1.
- A non-test discriminator between candidates (mutation testing, secondary-LLM audit, or council reading diffs directly) — finding V2.
These are out of scope and tracked as a separate multi-candidate follow-on design.
Tier 5 — Consolidation audits
Tier 5 takes the validated single candidate from Tier 3.5 and runs it through the consolidation audits — regression check, implementation audit, design-learn synthesis — as dispatched slots (## Dispatch loop), never inline in the orchestrator. There is no inter-candidate comparison in MVP (Tier 4 is skipped); the single candidate is either accepted or rejected verbatim.
Slot 8 (impl-audit) runs as two sequential dispatch surfaces, not one subagent: Step 1 is a distinct isolated dispatch (a regression-check subagent with isolation="worktree") that runs first; Step 2 is the orchestrator-owned read-only council fan-out (members share tp/{slug}, no isolation) that runs after, only on a green Step 1. Step 1 writes a working tree (checkout) and so must isolate; Step 2 only git show/git diffs and so legitimately shares. They are sequential phases of the slot, never the same dispatched subagent.
Step 1 — Regression check (distinct isolated dispatch)
The orchestrator dispatches a regression-check subagent for Slot 8 with isolation="worktree". This is its own isolated dispatch (it is NOT the council fan-out of Step 2). It first re-runs the project test suite on the candidate branch:
# checkout lands in the slot's OWN worktree (isolation="worktree"), NEVER the
# orchestrator's (## Branch hygiene). Fast iteration lane — no full ci-local here.
git fetch -q origin candidate/{slug}/single
git checkout candidate/{slug}/single
$(python3 "$TP_ROOT"/skills/_shared/iteration_lane.py --lane iteration --granularity phase)
Branch-hygiene requirement (load-bearing): the regression check needs the candidate's working tree, so it must git checkout the candidate branch — which is why the impl-audit slot is dispatched with isolation="worktree". A check running in a shared worktree would mutate the orchestrator's HEAD and orphan its in-flight commits (## Branch hygiene). Do not "optimize" this slot to share tp/{slug} — unlike the read-only audit fan-out sub-dispatches (Slots 4/6/8, which only git show/git diff), the regression check writes the working tree via checkout and must isolate.
The seam resolves the --fast lane in the dev repo and degrades to the project-discovered command (CLAUDE.md / Makefile / pyproject.toml / package.json, never pytest) in a consumer repo. It writes no stamp: the one mandatory full scripts/ci-local.sh run is the human's /tp-merge ci_local_stamp gate at land, not any orchestrator step. Full output stays in the subagent's context; the orchestrator sees only the reported result in the audit-return envelope. On a reported regression (any test the worker did not already mark failed/skipped in test_results):
- Append
[tp-run-full-design/tier-5] test-regression with the failing test names + Confidence: High.
- Escalate immediately — a regression in tests the worker reported as passing is a contract violation by the worker (the
test_results.passed count was wrong). Not retryable by re-running the worker. Operator pickup.
Step 2 — impl-audit slot (orchestrator-owned council code-audit fan-out)
Once Step 1's regression check is green, the orchestrator itself drives the
read-only council code-audit fan-out for Slot 8 (## Audit fan-out (Slots
4/6/8)) — a distinct dispatch surface from Step 1: the orchestrator owns it
directly (per ORCHESTRATOR MODE), its members share tp/{slug} with no
isolation (they only git show/git diff), and it runs after Step 1's
isolated regression-check subagent has returned green. This is sequential, not the
same subagent as Step 1. The fan-out is: reader → Round 1
→ Round 2 → synth → clip over design.md + plan.md and the candidate code,
the code reaching every member via --code-input (the Slot-8 code-input
addendum). This is the same orchestrator-owned fan-out design-audit /
plan-audit already run, only the inputs widen — the control flow is
unchanged. Step 1 gates Step 2 (sequential, not alternative): there is no
point auditing code that fails its own tests, so Step 2 runs only on a green
regression check. The fan-out is verdict-only — it never edits code
regardless of confidence — and the synthesizer computes the overall verdict from
the merged Round-1/Round-2 findings (the same confidence/verdict mix the
standalone audit skill would compute), returning it in the audit-return
envelope:
- All findings High (or no findings) → PASS or PASS WITH NOTES. Proceed to Step 3.
- Any Medium or Low finding → NEEDS WORK. Rather than escalating, the orchestrator triggers retry-with-advice (## Retry-with-advice (audits)): it re-spawns the
phase-implement worker with the finding list as advice and re-dispatches impl-audit to re-judge, looping up to --max-attempts. It appends [tp-run-full-design/tier-5] impl-audit-needs-work and escalates only when the attempt budget is exhausted (## Retry-with-advice escalation). (--max-attempts 1 restores escalate-on-first-rejection.)
The PASS vs PASS WITH NOTES split is informational — both proceed to Tier 6. The notes are surfaced in the Tier 6 PR description so the reviewer sees what passed-with-caveats.
Step 3 — design-learn slot (/tp-design-learn {slug} --auto)
The orchestrator dispatches the design-learn slot subagent (Slot 9), which runs /tp-design-learn {slug} --auto inline — Shape B generator. It reads the completed implementation and synthesizes:
- Project doc updates (architecture.md / product_roadmap.md / known_issues.md) — applied per the skill's normal write protocol.
- A
[tp-design-learn]-prefixed decisions.md trail of what was learned.
/tp-design-learn --auto never edits three-pillars-docs/vision.md — that load-bearing literal is enforced by the skill itself (a SKILL.md prose contract; see skills/tp-design-learn/SKILL.md). The orchestrator does not duplicate that check; the delegated skill owns it.
If /tp-design-learn --auto produces only High-confidence updates, Tier 5 is done and Tier 6 opens the PR. If it logs Medium/Low-confidence judgments, they flow into the PR description but do not block — design-learn is a synthesizer, not a gate.
Tier 5 outcomes
- All three steps pass → proceed to Tier 5.6 (closeout).
- Step 1 (regression) → escalate, exit non-zero (a non-retryable worker-contract violation; see Step 1).
- Step 2 (NEEDS WORK) → retry-with-advice (re-spawn
phase-implement); escalate + exit non-zero only when the attempt budget is exhausted (## Retry-with-advice escalation).
- Step 3 (design-learn errors) → log Medium-confidence, proceed (the candidate is already audited; missing project-doc updates do not invalidate the implementation).
Tier 5.6 — Closeout (fold → learn-verify → archive)
This is the closeout-before-merge terminal. Before this tier, the orchestrator stopped at an un-closed candidate→tp PR; now Tier 5.6 folds the candidate code onto tp/{slug}, verifies the learn propagation, and archives the design, so Tier 6 opens one completion PR (tp/{slug} → {default}) that already contains code + propagated docs + the archived design. One human merge then lands a closed design. The merge-only gate is preserved — Tier 5.6 is feature-internal, entirely below the gate; nothing here merges to {default}.
Step 1 — Fold the candidate into tp/{slug} (orphan-safe merge-in)
The orchestrator is pinned on tp/{slug} (## Branch hygiene). Folding is a merge-in, not a checkout — HEAD stays on tp/{slug}, so it does NOT trip the orphan hazard (which is checkout, not merge):
git merge --no-ff origin/candidate/{slug}/single -m "Fold candidate into tp/{slug}"
- The candidate is code-only and
tp/{slug} is artifacts-only ⇒ near-disjoint trees ⇒ the merge is clean in the common case.
- Conflict →
git merge --abort, then fall back to the legacy candidate→tp PR (## Tier 6 — legacy fallback) and append [tp-run-full-design/tier-5.6] fold-conflict (Confidence: High). Never auto-resolve a fold conflict — that would be the silent mutation the vision forbids; the human resolves it on the fallback candidate PR.
Step 2 — Learn-verification (advisory)
With the code now folded onto tp/{slug}, run the learn-verify grep:
python3 "$TP_ROOT"/skills/_shared/verify_learn.py --range {default}...tp/{slug} --json
It reports three-pillars-docs/** lines (living and completed-tp-designs/) that still name a symbol/file this design retired — the "learn ran ≠ docs match as-built" gap. Range note: {default}...tp/{slug} (three-dot) diffs merge-base→tp/{slug}, surfacing this design's deletions; the literal tp/{slug}...{default} would diff the wrong (base) side. Advisory only — verify_learn.py always exits 0 and fails open; flagged refs flow into the Tier 6 PR description and a [tp-run-full-design/tier-5.6] learn-verify decisions.md entry, and never block (the hard gate is the framework's CI closeout check, not this grep).
Step 3 — Archive (/tp-design-complete {slug} --auto)
Dispatch /tp-design-complete {slug} --auto (see that skill's ## Auto Mode): it git mvs the design dir to completed-tp-designs/, stamps completion frontmatter, rewrites Current Focus / Design Inventory, and commits Complete design: {slug} — whose pre-commit self-check runs the staged-blob guard verify_archive_staged.py (a stampless / lock-phaseless / dangling-cite archive can never be committed) — and it opens no PR (Tier 6 owns the single completion PR) and removes no worktree. Its step-3 learn-ran hard-block is auto-satisfied because Tier 5 Step 3 (Slot 9 design-learn) just ran. The archival commit lands on tp/{slug} (## Branch hygiene assert-before-commit applies).
Tier 5.6 outcomes
- Fold clean + archive committed → proceed to Tier 6 (completion PR
tp/{slug} → {default}).
- Fold conflict →
git merge --abort, open the legacy candidate→tp PR (## Tier 6 — legacy fallback), flag, exit 0 (work durable on the candidate branch; the human resolves the conflict).
/tp-design-complete --auto BLOCKs (learn somehow not satisfied) → it logs a BLOCKED entry to decisions.md; the orchestrator surfaces it and exits non-zero (operator pickup). Should not occur given Step 3 of Tier 5 ran.
Tier 6 — Completion PR open + fail-open
After Tier 5.6, tp/{slug} carries the folded code + propagated docs + the archived design. Tier 6's job is to push tp/{slug}, open the completion PR (tp/{slug} → {default}) with a structured description, and exit cleanly. There is no separate final/{slug} branch — tp/{slug} is the PR branch. The --require-pr-confirm operator-gate is a deferred feature (see ## Deferred features below).
Step 1 — Ensure pushed
git push origin tp/{slug}
Pushes the post-fold, post-archive tp/{slug} (the fold-merge + Complete design: commits from Tier 5.6). Idempotent if already pushed.
Tier 6 — legacy fallback (fold conflict)
If Tier 5.6 Step 1 hit a fold conflict and aborted, the candidate code is not on tp/{slug}. In that case ONLY, Tier 6 opens the legacy candidate→tp PR instead (--base tp/{slug} --head candidate/{slug}/single) so the human can resolve the conflict there, and the design is not archived (it stays in-flight). This is the pre-merged-design-closeout terminal, retained purely as the conflict escape hatch. The normal path is the completion PR below.
Step 2 — gh pr create + partitioned review request (F2)
The orchestrator opens the PR and then summons review itself — a PR with no reviewer requested is a loop that has nothing to classify (F2, pr-fix-targeting-and-auto-review). --pr-reviewers defaults to "copilot-pull-request-reviewer[bot]"; --no-review suppresses all review requests. Tier 6 is the sole initial completion-PR review requester in the autonomous path. (Cross-ref: tp-design-complete/SKILL.md ## Auto Mode — under --auto, /tp-design-complete defers the review request to Tier 6 and logs review-request-deferred-to-tier-6; Tier 7 / /tp-pr-iterate re-requests per round, idempotent — re-fires only on a new head SHA, fail-open.)
Partition the reviewer list before requesting — GitHub's Copilot bot is not requestable through the same path as human logins:
COPILOT_SLUGS = {"copilot-pull-request-reviewer[bot]", "copilot", "copilot[bot]"}
reviewers = [] if opts.no_review else opts.pr_reviewers.split(",")
humans = [r for r in reviewers if r.strip().lower() not in COPILOT_SLUGS]
bots = [r for r in reviewers if r.strip().lower() in COPILOT_SLUGS]
Create the PR through the shared PR-author chokepoint (skills/_shared/github_pr_author.py — unconfigured repos run the identical gh pr create underneath, byte-identical to before; request the human reviewers inline, drop --reviewer entirely when humans is empty — gh pr create --reviewer "" errors):
# Resolve the FREE chokepoint git-toplevel-first (see first-run.md §Resolve a FREE _shared script)
TOP="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [ -n "$TOP" ] && [ -f "$TOP"/skills/_shared/resolve_script.py ]; then RS="$TOP"/skills/_shared/resolve_script.py; else RS="$TP_ROOT"/skills/_shared/resolve_script.py; fi
GHPA="$(python3 "$RS" github_pr_author.py)"
python3 "$GHPA" create --context autonomous -- \
--base {default} \
--head tp/{slug} \
--title "{slug}: {task_metadata.title}" \
--body "$(structured PR description)" \
[--reviewer "<comma-joined humans>" # only when humans is non-empty]
{default} is the repo's base branch (usually master) — the completion PR merges the now-closed design (tp/{slug}, carrying folded code + propagated docs + archived design) back to the base. This replaces the pre-merged-design-closeout candidate→tp PR; the only time a candidate→tp PR is opened now is the fold-conflict fallback above.