From 04a24e4b75f981e026d57422a2bb66e2884ade38 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Sat, 3 Oct 2026 22:21:08 +0200 Subject: [PATCH] The return of the Plan agent repaired without a word to the developer `/surface-plan` writes the return of the `Plan` agent "as it is". Twice in the six runs of the first campaign of the evaluations the return could not be written so: it arrived once with `<` and `>` escaped, which the state script would not have read as slice markers, and once with absolute paths, which the plan folder must not hold. The command repaired both, rightly, then told the developer of a "deviation" on a document they never read (#70). Refs #70 --- ARCHITECTURE.md | 2 +- skills/surface-plan/SKILL.md | 2 +- skills/surface-plan/templates/plan.md | 2 +- tests/prompts/test_surface_plan.py | 7 +++++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 58001a7..4bbcf38 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -23,7 +23,7 @@ host CI --runs----> surface-status check --require conformant ``` - **Commands** are three skills the developer types. `/surface-plan` explores, interviews, has the plan drafted and writes it with its gates, has the blueprint drawn and cross-checked, then commits, pushes and opens a draft pull request. It then stays in the conversation and takes amendments there, as it does after a block during planning, since a reply that approves nothing needs no new launch. `/surface-execute` approves the drafted revision by being launched, then dispatches slices, gate runs, reviews and fixes. It puts a plan change proposal and the ceiling to the developer in the conversation: a refusal or a resumption goes on in the session, while an accepted change or an amendment goes to `/surface-plan`, since this command cannot load it and never writes the plan. `/surface-status` reports, runs the conformity check, and abandons a plan on confirmation. -- **Agents** are four roles of the chain, and one agent built into Claude Code. The `Plan` agent drafts the plan, in the form it chooses: a good plan is what it already does well, so the chain ships no definition for it and holds the plan only to the minimum it reads, the numbered acceptance criteria, the slices behind their markers, each enough for an agent that starts fresh, and the `gates` block ([ADR 0035](docs/adr/0035-plan-drafted-by-the-built-in-plan-agent.md)). It has no write tool: it returns the plan, and `/surface-plan` writes `plan.md` as returned. The extractor draws `blueprint.md` from `plan.md`, as long as the feature needs, since the developer reads all of it. A blueprint has a fixed frame and a free body: it opens with the idea, the acceptance criteria and the scope, and closes with the sensitive zones, while the extractor cuts the body by what the developer decides separately, and keeps the cut of the previous revision, so that a revision is read against the one before. No heading carries a number: a section is cited by its title, which an amendment does not move, where a number would shift and send a report to another section. Five aspects are gone through whatever the cut, the data schema, the boundaries, the sequences, the state machines and the algorithms, and one closing line names those the plan leaves alone, since silence must not read as "unchanged". A diagram is drawn where what it shows has a shape that prose flattens, with the existing elements the change attaches to. The checker looks for what the plan does and the blueprint does not show, never for a cut, a short section or a diagram that is not there. The executor carries out a slice or a fix. The reviewer judges the branch against the approved blueprint, writes `conformity.md` when it finds nothing, with the changed files of the critical zones, judges a break an executor suspects, and corrects that list of files when the script refuses it. +- **Agents** are four roles of the chain, and one agent built into Claude Code. The `Plan` agent drafts the plan, in the form it chooses: a good plan is what it already does well, so the chain ships no definition for it and holds the plan only to the minimum it reads, the numbered acceptance criteria, the slices behind their markers, each enough for an agent that starts fresh, and the `gates` block ([ADR 0035](docs/adr/0035-plan-drafted-by-the-built-in-plan-agent.md)). It has no write tool: it returns the plan, and `/surface-plan` writes `plan.md` as returned, but for two repairs it makes without a word to the developer, who never reads the plan: a return that arrived escaped, whose markers the script would not read, and a path of the machine. The extractor draws `blueprint.md` from `plan.md`, as long as the feature needs, since the developer reads all of it. A blueprint has a fixed frame and a free body: it opens with the idea, the acceptance criteria and the scope, and closes with the sensitive zones, while the extractor cuts the body by what the developer decides separately, and keeps the cut of the previous revision, so that a revision is read against the one before. No heading carries a number: a section is cited by its title, which an amendment does not move, where a number would shift and send a report to another section. Five aspects are gone through whatever the cut, the data schema, the boundaries, the sequences, the state machines and the algorithms, and one closing line names those the plan leaves alone, since silence must not read as "unchanged". A diagram is drawn where what it shows has a shape that prose flattens, with the existing elements the change attaches to. The checker looks for what the plan does and the blueprint does not show, never for a cut, a short section or a diagram that is not there. The executor carries out a slice or a fix. The reviewer judges the branch against the approved blueprint, writes `conformity.md` when it finds nothing, with the changed files of the critical zones, judges a break an executor suspects, and corrects that list of files when the script refuses it. - **The state script** derives the state, refuses illegal steps, runs the gates, and answers the skills and the host's CI. - **A plan folder**, `docs/plans/-/` by default, holds the specs, the exploration, the interview, the plan, the blueprint, the reports (`checks/`, `reviews/`, `plan-changes/`, `gates/`), `conformity.md` and `journal.jsonl`. It is the whole state of a plan. - **The installer** copies the chain into the host, updates it, and checks it for drift. diff --git a/skills/surface-plan/SKILL.md b/skills/surface-plan/SKILL.md index 58d351e..ad09bb0 100644 --- a/skills/surface-plan/SKILL.md +++ b/skills/surface-plan/SKILL.md @@ -75,7 +75,7 @@ The conversation is the developer's, the plan folder is the repository's. 3. Folder. `/-/`: `plans_dir` from `.claude/surface.json`, `docs/plans` when the file or the key is missing; today's date; a short slug of the need, another one if the folder exists. Write `specs.md`: the developer's text as given, or the content of the file their arguments name, verbatim, but for the paths of their machine: a path under a home directory becomes a neutral form such as `/path/to/...`, here and in every answer you write down, since the plan folder is committed and the repository's checks read it. Tell the developer what you replaced. When the specs are in another language than the documents', add their translation below the text, under a heading that says so. Then `surface-status record plan-opened --json`. 4. Exploration. Before any question, read what the feature touches, as far as the interview needs: the domain objects it extends, the closest precedent (the last thing added the same way), the decisions of step 1 that bear on the area, and what the code already settles of the need, so that no question is asked that the code answers. The path through the code, layer by layer, is left to the agent that drafts the plan. Write `exploration.md` from `${CLAUDE_SKILL_DIR}/templates/exploration.md`, with the findings of step 1, the gates of the plan among them (see "The gates"). 5. Interview. List what the specs leave open, then ask (see "Asking a question"), in `interview.md` from `${CLAUDE_SKILL_DIR}/templates/interview.md`. Stop when what remains is a matter of implementation you can decide: it goes to the plan as an assumption. Then `surface-status record interview-closed --json`. -6. Plan. Launch a fresh `Plan` agent (see "The agents") and wait for its return: the plan, whole, since it has no write tool. Write that return to `plan.md` as it is: its design and its form are the agent's, and you rework neither. Every document you write in the plan folder must pass the gates of step 1, which read it too: a decision still to come or an option set aside is described, never cited by a record number that does not exist. Then check the minimum the chain reads, below, and nothing else. A return that is no plan, or a plan that fails this minimum: launch a fresh agent once more; then tell the developer and stop. +6. Plan. Launch a fresh `Plan` agent (see "The agents") and wait for its return: the plan, whole, since it has no write tool. Write that return to `plan.md` as it is: its design and its form are the agent's, and you rework neither. Two repairs are not a rework, and are not reported to the developer, who never reads the plan: when the markers arrived escaped, `<!--` for `` marker, alone on its line, and enough for an agent that starts fresh. A slice number the journal has seen is never reused. - The `gates` block, which holds the gates you found and no other, in their order: this one you correct yourself (see "The gates"). diff --git a/skills/surface-plan/templates/plan.md b/skills/surface-plan/templates/plan.md index 13dbffb..e716a6a 100644 --- a/skills/surface-plan/templates/plan.md +++ b/skills/surface-plan/templates/plan.md @@ -2,7 +2,7 @@ Revision . Execution instructions for the agents, slice by slice, drafted from `specs.md`, `exploration.md` and `interview.md` of the plan folder, and from the code. Written in the language `exploration.md` names in its repository rules: translate the headings, keep the markers and the `gates` tag as they are. The plan stays alive: an executor that deviates amends it in the commit of its code. -The agent that drafts the plan designs it and lays it out as it sees fit: the decisions and the options set aside, the tests, the risks, the assumptions taken instead of a question go where they read best, each as long as the feature needs, none filled for its own sake. The chain reads three things only, which the plan holds under the three headings below, whatever stands around them. No code beyond a signature or a schema fragment. The plan is committed and the repository's checks read it: no path of a machine, and a decision still to come or an option set aside is described, never cited by a record number that does not exist. The agent has no write tool: it returns the plan whole as its answer, and nothing around it, and the command that launched it writes `plan.md`. +The agent that drafts the plan designs it and lays it out as it sees fit: the decisions and the options set aside, the tests, the risks, the assumptions taken instead of a question go where they read best, each as long as the feature needs, none filled for its own sake. The chain reads three things only, which the plan holds under the three headings below, whatever stands around them. No code beyond a signature or a schema fragment. The plan is committed and the repository's checks read it: every path is written from the root of the repository, never as a path of a machine, and a decision still to come or an option set aside is described, never cited by a record number that does not exist. The agent has no write tool: it returns the plan whole as its answer, and nothing around it, and the command that launched it writes `plan.md`. When `plan.md` already exists, the agent drafts its next revision, which carries every amendment and every accepted plan change of `interview.md`. A slice that a `slice-done` line of `journal.jsonl` records is built: it stays, under its number. What is left to build goes into the slices that have not run, or into new ones. diff --git a/tests/prompts/test_surface_plan.py b/tests/prompts/test_surface_plan.py index 8c7acd5..362277d 100644 --- a/tests/prompts/test_surface_plan.py +++ b/tests/prompts/test_surface_plan.py @@ -370,6 +370,13 @@ def test_the_command_writes_the_plan_as_returned_and_checks_only_the_minimum() - assert positions == sorted(positions) assert "since it has no write tool" in plan assert "its design and its form are the agent's, and you rework neither" in plan + # What the script or the repository's checks could not read is repaired, and never told. + assert "Two repairs are not a rework, and are not reported to the developer" in plan + assert "when the markers arrived escaped, `<!--` for `` marker, alone on its line",