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
+5 -6
View File
@@ -272,12 +272,11 @@ codebase (compiler, type-checker, runtime, schema, API,
whatever the project owns), and for each one searches the
workspace for a currently-green test that ratifies it.
Dispatch carrier:
| Field | Content |
|-------|---------|
| `spec_path` | path to the spec just written in Step 6 |
| `iteration_scope` | which sections cover the imminent first iteration (verbatim from the brainstorm session) |
Dispatch carrier — the fields `spec_path` and `iteration_scope`,
defined authoritatively under **Carrier contract** in
`agents/grounding-check.md` (note `spec_path` is an *absolute*
path). That table is the single source for those fields; it is
deliberately not restated here, so the two files cannot drift.
The agent returns a `PASS` / `BLOCK` / `INFRA_ERROR` report.
Orchestrator interpretation:
+3
View File
@@ -54,6 +54,9 @@ section, not the whole spec.
## Carrier contract — what the controller hands you
This table is the authoritative definition of the carrier fields;
the dispatching skill references it rather than restating it.
| Field | Content |
|-------|---------|
| `spec_path` | absolute path to the spec draft you are checking |
+9 -14
View File
@@ -59,22 +59,17 @@ agent file governs the work.
## Handoff Contract
`docwriter` consumes (from orchestrator):
`docwriter` consumes (from orchestrator) the carrier fields
`scope`, `warning_target`, and `priority_items`, defined
authoritatively under **Carrier contract** in `agents/docwriter.md`.
| Field | Content |
|-------|---------|
| `scope` | Component / package / crate / module name(s) to document, or `all` |
| `warning_target` | Specific doc-tool warnings to clear, or `all` |
| `priority_items` | Optional: items the orchestrator wants documented first |
`docwriter` produces, for the orchestrator, the fields `status`,
`files_touched`, `warnings_cleared`, and `findings`, defined
authoritatively under **Output format** (with the `status` enum in
the Status protocol) in `agents/docwriter.md`.
`docwriter` produces:
| Field | Content |
|-------|---------|
| `status` | `DONE` / `DONE_WITH_CONCERNS` / `NEEDS_CONTEXT` / `BLOCKED` |
| `files_touched` | paths + which level (top-level / module-level / item-level) |
| `warnings_cleared` | count, plus the doc-check status |
| `findings` | items whose names or behaviour seemed confusing while documenting them (one line each, no prescriptions) |
Both contracts are single-sourced in the agent file; they are
deliberately not restated here, so the two files cannot drift.
The orchestrator decides whether findings become a follow-up
iteration; `docwriter` does not self-resolve.
+15
View File
@@ -57,6 +57,9 @@ Additionally:
## Carrier contract — what the controller hands you
This table is the authoritative definition of the carrier fields;
the dispatching skill references it rather than restating it.
| Field | Content |
|-------|---------|
| `scope` | Component / package / crate / module name(s) to document, or `all` |
@@ -160,6 +163,18 @@ At most 200 words, structured:
prescriptions. The orchestrator decides whether they become
a follow-up.
This section (together with the Status protocol above) is the
authoritative definition of what the role produces; the
dispatching skill references it rather than restating it. Named
for the orchestrator handoff, the produced fields are:
| Field | Content |
|-------|---------|
| `status` | `DONE` / `DONE_WITH_CONCERNS` / `NEEDS_CONTEXT` / `BLOCKED` (see Status protocol) |
| `files_touched` | paths + which level (top-level / module-level / item-level) |
| `warnings_cleared` | count, plus the doc-check status |
| `findings` | items whose names or behaviour seemed confusing while documenting them (one line each, no prescriptions) |
## Common Rationalisations
| Excuse | Reality |
+14 -15
View File
@@ -101,23 +101,22 @@ stay readable.
## Handoff Contract
`fieldtest` consumes (from orchestrator at cycle close):
`fieldtest` consumes (from orchestrator at cycle close) the carrier
fields `cycle_id`, `cycle_scope`, `axis_hints`, and `commit_range`,
defined authoritatively under **Carrier contract** in
`agents/fieldtester.md` (which also specifies the empty-`axis_hints`
fallback).
| Field | Content |
|-------|---------|
| `cycle_id` | e.g. `cycle-22` |
| `cycle_scope` | 1-3 sentences naming what shipped |
| `axis_hints` | bullet list, one per cycle axis the test should probe |
| `commit_range` | `<prev-cycle-close>..HEAD` |
`fieldtest` produces, for the orchestrator, the fields `spec_path`,
`examples_added`, and `findings`, defined authoritatively under
**Output format** in `agents/fieldtester.md` — single-sourced in
the agent file, deliberately not restated here so the two files
cannot drift.
`fieldtest` produces:
| Field | Content |
|-------|---------|
| `spec_path` | spec file under `paths.spec_dir` with a `fieldtest-` prefix |
| `examples_added` | list of fixture paths committed |
| `findings` | list, each with class (`bug` / `friction` / `spec_gap` / `working`) + recommendation |
| `status` | `clean` / `friction_found` / `bugs_found` / `infra_blocked` |
It additionally reports one skill-level `status` roll-up — one of
`clean` / `friction_found` / `bugs_found` / `infra_blocked`. This
field is owned here (it is not part of the agent's per-run status
protocol), so it is defined in this file only.
The orchestrator drives downstream:
+15
View File
@@ -57,6 +57,10 @@ ratified. This is part of the public-interface story.
## Carrier contract — what the controller hands you
This table is the authoritative definition of the carrier fields
(including the empty-`axis_hints` fallback below); the dispatching
skill references it rather than restating it.
| Field | Content |
|-------|---------|
| `cycle_id` | e.g. `cycle-22` |
@@ -252,6 +256,17 @@ The full text of the spec is in the spec file in the working
tree; do NOT reproduce it in the report. The orchestrator
reads the file before committing.
This section is the authoritative definition of what the role
produces; the dispatching skill references it rather than
restating it. Named for the orchestrator handoff, the produced
fields are:
| Field | Content |
|-------|---------|
| `spec_path` | spec file under `paths.spec_dir` with a `fieldtest-` prefix |
| `examples_added` | list of fixture paths committed |
| `findings` | list, each with class (`bug` / `friction` / `spec_gap` / `working`) + recommendation |
## What you DO NOT ship
- Bug fixes. You record bugs; `debug` writes the RED test;
+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.
+32 -1
View File
@@ -49,7 +49,10 @@ Additionally, every dispatch:
## Carrier contract
You receive from the outer orchestrator:
You receive from the outer orchestrator the following fields. This
table is the authoritative definition of each field's semantics;
the dispatching skill shows the call shape but references this table
rather than restating the semantics.
| Field | Content |
|-------|---------|
@@ -147,6 +150,34 @@ them as you switch roles:
- Spec-compliance check reference: `spec-reviewer.md`
- Quality check reference: `quality-reviewer.md`
##### Per-task sub-status vocabulary (authoritative)
The phases below produce these sub-status values. They are
*internal* to you — they describe the state of an inline
role-phase, not the status of a separate subagent, and are not
reported upstream. This table is the authoritative definition of
the vocabulary and the re-loop rule; the per-phase prose that
follows applies it, and the dispatching skill names it but
references this table. The vocabulary maps 1:1 to the phase
reference files listed above.
| 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 you do not know the dependency graph.
#### 2.1 — Implementer phase (inline)
Read `/tmp/iter-<iter_id>/task-K.md`. Adopt the implementer
+1 -1
View File
@@ -51,7 +51,7 @@ whole component.
| Field | Content |
|-------|---------|
| `task_text_path` | Absolute path to a file (typically `/tmp/iter-<iter_id>/task-<N>.md`) containing the verbatim task block extracted from the plan. Read this file as your first action. |
| `task_text_path` | Absolute path to a file (typically `/tmp/iter-<iter_id>/task-<N>.md`) containing the verbatim task block extracted from the plan. Read this file as your first action. This row is the authoritative definition of `task_text_path`; `spec-reviewer` receives the same field and cross-references it. |
| `scene_set` | Parent cycle, where this task fits, dependencies on earlier completed tasks |
| `cross_task_context` | Shared types, file structure decisions, naming conventions agreed for the iteration |
| `mode` | `standard` (plan-driven) or `mini` (debug handoff: RED test path + cause summary + minimal-fix constraint) |
+1 -1
View File
@@ -61,7 +61,7 @@ your spec for this review.
| Field | Content |
|-------|---------|
| `task_text_path` | Absolute path (typically `/tmp/iter-<iter_id>/task-<N>.md`) — same file the implementer received. Read this file as your first action. |
| `task_text_path` | The same file the implementer received — see the authoritative `task_text_path` definition in `agents/implementer.md`. Read this file as your first action. |
| `diff_command` | The shell command that produces the diff to review. Default and almost-always value: `git diff HEAD` (the working tree's full unstaged diff, accumulated across all tasks of this iter so far). Earlier tasks' changes are visible in this diff because no per-task commits are made — that is intentional; focus on the files the current task block claims it touches |
| `status_from_implementer` | `DONE` or `DONE_WITH_CONCERNS` — if `DONE_WITH_CONCERNS`, the concerns are quoted so you can decide if they affect spec compliance |
+7 -7
View File
@@ -72,13 +72,13 @@ Read the returned file-map. Lift the relevant entries into
the plan's "Files this plan creates or modifies" section; the
orchestrator may add or amend entries that recon missed.
Dispatch carrier:
| Field | Content |
|-------|---------|
| `spec_path` | path to the parent spec |
| `iteration_scope` | which sections of the spec this iteration covers |
| `focus_hint` | optional — subsystem or symbol to prioritise |
Dispatch carrier — the fields `spec_path`, `iteration_scope`, and
`focus_hint`, defined authoritatively under **Carrier contract**
in `agents/plan-recon.md` (which marks `spec_path`/`iteration_scope`
mandatory and `focus_hint` optional, and specifies the
BLOCKED-on-missing rule). That table is the single source for those
fields; it is deliberately not restated here, so the two files
cannot drift.
Recon does NOT decide decomposition. Two recon dispatches per
planner run is unusual but legitimate (e.g. one for the main
+5
View File
@@ -51,6 +51,11 @@ In addition:
## Carrier contract — what the controller hands you
This table is the authoritative definition of the carrier fields
(including which are mandatory and the BLOCKED-on-missing rule
below); the dispatching skill references it rather than restating
it.
| Field | Content |
|-------|---------|
| `spec_path` | Path to the spec under `paths.spec_dir` (mandatory) |