refactor: single-source carrier/handoff contracts across skills

Apply the debug-skill pattern (commits 6410732..de42974) to the
remaining skills: the agent file is the single source for each
interface field's semantics; the SKILL.md copy is reduced to field
names plus a pointer, marking the agent's contract table
authoritative. Because SKILL.md loads into orchestrator context and
agents/*.md into the subagent's fresh system prompt with no
transclusion, duplicated field tables drift apart over time.

- brainstorm: carrier (spec_path/iteration_scope) -> grounding-check;
  the absolute-path requirement now lives only in the agent.
- planner: carrier (spec_path/iteration_scope/focus_hint) ->
  plan-recon, including the mandatory/optional markers and the
  BLOCKED-on-missing rule SKILL.md had omitted.
- fieldtest: carrier + produced fields -> fieldtester; the skill-level
  `status` roll-up (clean/friction_found/bugs_found/infra_blocked),
  which is not part of the agent's run-status protocol, stays defined
  in SKILL.md only.
- docwriter: carrier + produced fields -> docwriter agent.
- implement: carrier (iter_id scratch-dir/stats/not-a-branch
  semantics) -> implement-orchestrator; per-task sub-status vocabulary
  moved into the orchestrator-agent (it runs the loop in a fresh
  context and could not read SKILL.md at runtime, yet referenced "the
  sub-status table" by name); task_text_path single-sourced in
  implementer with spec-reviewer cross-referencing.

audit was already the reference implementation (pointers, no restated
contracts) and is unchanged.

closes #2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 11:10:10 +02:00
parent de42974056
commit 0b969838c0
12 changed files with 131 additions and 72 deletions
+24 -27
View File
@@ -59,32 +59,22 @@ ON `PARTIAL` OR `BLOCKED`, THE ORCHESTRATOR-AGENT WRITES `BLOCKED.md` AT THE REP
## Per-task sub-status mechanics
The orchestrator-agent reads and follows this table verbatim
every dispatch. Sub-status values are *internal* to the
orchestrator-agent — they describe the state of an inline
role-phase, not the status of a separate subagent. The
vocabulary is preserved because it maps 1:1 to the phase
reference files (`implementer.md` / `spec-reviewer.md` /
`quality-reviewer.md`) the orchestrator-agent consults at
each role-switch.
Each dispatch, the orchestrator-agent runs an internal per-task
loop over a sub-status vocabulary — `DONE`, `DONE_WITH_CONCERNS`,
`NEEDS_CONTEXT`, `non_compliant` / `changes_requested`, `BLOCKED`,
`unclear`, and tool/infra errors — bounded by a 2-retries-per-
failure-mode re-loop limit (the 3rd attempt escalates to
`BLOCKED`). These values are *internal* to the orchestrator-agent:
they describe the state of an inline role-phase, not a separate
subagent, and are not reported upstream.
| Sub-status | Action |
|------------|--------|
| `DONE` | next phase / next task |
| `DONE_WITH_CONCERNS` | accumulate concern, next phase |
| `NEEDS_CONTEXT` (1st2nd) | re-attempt the phase with expanded context |
| `NEEDS_CONTEXT` (3rd) | stop → `BLOCKED` to orchestrator, reason `context-exhausted` |
| `non_compliant` / `changes_requested` (1st2nd) | switch back to implementer mindset, repair with the check's report as repair brief, then re-run the check phase |
| `non_compliant` / `changes_requested` (3rd) | stop → `BLOCKED` to orchestrator, reason `review-loop-exhausted` |
| `BLOCKED` (implementer phase) | stop → `BLOCKED` to orchestrator, reason verbatim |
| `unclear` (spec-compliance phase) | stop → `BLOCKED` to orchestrator, reason `spec-ambiguous` |
| Tool / infra error | stop → `BLOCKED` to orchestrator, reason `infra` + raw error |
Re-loop limit: 2 retries per failure-mode per task. The 3rd
is `BLOCKED` to the orchestrator. `skip task K, continue` is
intentionally NOT a mode — tasks have implicit ordering
dependencies and the orchestrator-agent does not know the
dependency graph.
The mapping of each value to its action (and the `BLOCKED` reasons
`context-exhausted` / `review-loop-exhausted` / `spec-ambiguous` /
`infra`) is defined authoritatively under **Per-task sub-status
vocabulary** in `agents/implement-orchestrator.md` — the file the
agent actually carries at runtime in its fresh context. That table
is the single source; it is deliberately not restated here, so the
two files cannot drift.
## The Process — orchestrator side
@@ -95,9 +85,9 @@ For a standard iteration:
```
Agent("implement-orchestrator", {
mode: "standard",
iter_id: "<iter_id>", // e.g. "ct.2.3", "or.1", "23.4"
iter_id: "<iter_id>",
plan_path: "<path under paths.plan_dir>",
task_range: [3, 8] // optional
task_range: [3, 8]
})
```
@@ -113,6 +103,13 @@ Agent("implement-orchestrator", {
})
```
The blocks above show the call shape; each field's semantics
(including the load-bearing `iter_id` rule — it names the scratch
dir and stats filename, NOT a branch) are defined authoritatively
under **Carrier contract** in `agents/implement-orchestrator.md`.
That table is the single source for the field semantics; they are
deliberately not restated here, so the two files cannot drift.
Before dispatch: ensure the working tree is clean
(`git status --porcelain` empty). The orchestrator-agent's
Phase 0 will refuse to start on a dirty tree.