Skip to content

feat(mar-888): a versioned agent recipe behind every scaffolder, validated before any write - #358

Merged
Thebeatkicks merged 8 commits into
masterfrom
000henrik/mar-888-build-recipe
Sep 7, 2026
Merged

Thebeatkicks merged 8 commits into
masterfrom
000henrik/mar-888-build-recipe

Conversation

@Thebeatkicks

@Thebeatkicks Thebeatkicks commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

MAR-888, stage 2 of MAR-886. Base: codex/agent-foundation at 821c239, which
already carries stage 1's SDK (MAR-887, PR #356, now on master). Full handoff:
docs/foundation/stage-2-handoff.md.

What this is

Three programs write a DASH agent folder — agent-kit/scaffold.ts for a
terminal, lib/sample-agent.ts for "Try a sample agent", and
tools/dash-mcp/src/scaffold.ts for a coding assistant — and each one assembled
its own manifest v2 literal: the same manifest_version, safety_contract,
monitoring block and agent_dom skeleton, transcribed three times. Two had
already drifted in ways nobody chose. Stage 1 closed the fork in the runtime
those three write; this closes the fork in the document they write about it.

agent-kit/recipe.ts holds one AgentRecipe (recipe_version: 1) and one
planFromRecipe. The three scaffolders are adapters over it and carry only what
actually differs between the agents they build: the steps, the sources, the
connections, what a run emits, the permissions claimed, and the panel.

Validated before the first byte

validateRecipe runs the recipe's own rules and then puts the manifest it
would write through validateManifest and checkManifestConstraints — DASH's
own import verdict — and planFromRecipe decides everything before it returns
anything, so there is no state in which half a project exists. It refuses an
unusable id, a relative or traversing directory, a symlinked target, a folder
with somebody's files in it, a write inside DASH's own agents root, and a
dependency pinned to a range rather than an exact version.

Definition and manifest cannot drift unnoticed

The recipe travels with the agent as agent.recipe.json, and
checkRecipeAgainstManifest reports where a hand-edited manifest has come apart
from it. create-dash-agent --check <folder> and dash_agent_validate both run
it. The check earns its keep because the edited manifest is usually still
valid — the test asserts that before asserting the drift is caught.

Instructions and evals

Every scaffold now carries AGENT_BUILDER.md — which files a coding assistant
may edit and which it must not — and evals/, four checks that spawn the real
agent under the runner protocol against a loopback fixture server and a broker
double: a normal run, a run with nothing to read, a run where a source fails,
and a run where every brokered request is refused. Node builtins only, no
dependency, no network beyond loopback, no model and no key.

Evidence

  • pnpm typecheck clean.
  • pnpm test: 284 files, 5383 passed, 13 skipped, exit 0. Nothing failed.
  • tests/agent-recipe.test.ts: 31 new tests, including one that runs the real
    CLI against a folder with somebody's work in it and asserts the work is still
    there and nothing else is.
  • pnpm build:agent-kit, then the real CLI into a scratch folder, then
    node evals/run-evals.mjs against that agent: 4 of 4 checks passed.
  • pnpm build:renderer, pnpm build:shell, then the installed-style shell
    smoke on a scratch store: 85 PASS / 0 FAIL, [smoke] all proofs passed.
    6b's detail shows agent.recipe.json, AGENT_BUILDER.md and evals/
    travelling with the sample DASH creates on a first run. Runner retired
    (status 202, RETIRED).

Evidence class: fixture tests, a real generated agent under a real
Supervisor and under its own eval harness, and an installed-style shell on a
scratch store. Nothing proven on the installed build; %APPDATA%\orchestratedash
was never opened.

Deviations, argued in the handoff

  1. agent.recipe.json is deliberately not added to
    AGENT_KIT_PROJECT_FILES (a file this lane does not own, and the list the
    smoke's 6b reads).
  2. No dash_agent_recipe tool — documented in
    docs/foundation/mcp-recipe-contract.md with the reasoning.
  3. The sample-refresh hook the brief made conditional does not exist, and why.
  4. Four files outside the ownership list were touched:
    electron/sample-agent.ts and scripts/build-shell.mjs under the lane's
    conditional permission, plus one line each in tests/agent-sdk.test.ts and
    tests/runner-telemetry.test.ts so they compile.

No ADR, no migration, no contract change. Nothing in runner/**,
lib/store.ts, lib/db.ts, lib/views/**, app/** or
agent-kit/template/dash-agent-sdk.mjs — lane F3's files are untouched.

🤖 Generated with Claude Code

Thebeatkicks and others added 3 commits September 7, 2026 18:10
Three programs write a DASH agent folder and each assembled its own
manifest v2 literal: the same manifest_version, safety_contract,
monitoring block and runtime/trigger/locations/control skeleton,
transcribed three times. Two had already drifted in ways nobody chose.

`agent-kit/recipe.ts` holds one `AgentRecipe` (recipe_version 1) and one
`planFromRecipe`. The Agent Kit's scaffolder, `lib/sample-agent.ts` and
`tools/dash-mcp/src/scaffold.ts` are now adapters over it, each carrying
only what actually differs between the agents they build: steps, sources,
connections, what a run emits, permissions, and the panel.

`validateRecipe` runs the recipe's own rules and then puts the manifest it
would produce through `validateManifest` and `checkManifestConstraints` —
DASH's own import verdict — before `planFromRecipe` returns a single file.
It refuses an unusable id, a relative or traversing directory, a symlinked
target, a non-empty folder, a write inside DASH's own agents root, and a
dependency pinned to a range rather than a version. Everything is decided
before anything is returned, so a refusal cannot leave half a project.

The recipe travels with the agent as `agent.recipe.json`, and
`checkRecipeAgainstManifest` reports where a hand-edited manifest has come
apart from it. `create-dash-agent --check <folder>` and
`dash_agent_validate` both run it.

Every scaffold also gets `AGENT_BUILDER.md` — which files a coding
assistant may edit and which it must not — and `evals/`, four acceptance
checks that spawn the real agent under the runner protocol with a local
fixture server and a broker double: a normal run, a run with nothing to
read, a run where a source fails, and a run where every brokered request
is refused. Node builtins only, no dependency, no network, no model.

`lib/sample-agent.ts` amends the recipe rather than the finished manifest,
so DASH's own sample does not create a folder its own drift check calls
drifted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…act this leaves the MCP

The fixture is the recipe the MCP builder produces for a three-step
competitor scout, generated from a finished interview draft rather than
written by hand. tests/agent-recipe.test.ts walks it from validateRecipe
through planFromRecipe, files on a real disk, validateManifest read back
off those bytes, and a real Supervisor spawning what was written until it
hands DASH a digest that passes the artifact contract.

Also pinned: every refusal, one proving the real CLI leaves a folder with
somebody's work in it exactly as it found it, that a hand-edited manifest
which still validates is caught as drift, and that DASH's own sample is
the Agent Kit's agent plus exactly two declarations.

docs/foundation/mcp-recipe-contract.md says what the other repository's
export_build_brief would have to emit, why there is no dash_agent_recipe
tool, and — in its own section — that no live integration exists and the
committed fixture is a local example rather than evidence of one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ions

What was verified and how: typecheck clean, 284 test files and 5383 tests
green in one full run, the four generated evals passing against a real
scaffolded agent under the real runner protocol, --check catching a
hand-edited manifest that still validates, and 85 PASS / 0 FAIL on an
installed-style shell smoke against a scratch store whose 6b proof lists
agent.recipe.json, AGENT_BUILDER.md and evals/ travelling with the sample.

Four deviations argued rather than buried: the recipe is deliberately not
in AGENT_KIT_PROJECT_FILES, there is no dash_agent_recipe tool, the
sample-refresh hook the brief made conditional does not exist, and four
files outside the ownership list were touched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Thebeatkicks
Thebeatkicks marked this pull request as ready for review September 7, 2026 16:41
Thebeatkicks and others added 5 commits September 7, 2026 18:42
Its docblock still opened with "the whole folder, as data" and described a
manifest this file no longer assembles. It now names what is left here — the
one statement of how an agent this tool builds differs from the Agent Kit's —
and keeps the record of why they differ, which is still true.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`unsafeRelativePath` returned a clause and the caller wrapped it, which read
as "That build may not overwrite "../package.json" walks out of the project
directory." A refusal is the one string somebody will actually be shown, so
each branch now returns the whole sentence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Orchestrator ruling on this packet's deviation 1. AGENT_KIT_PROJECT_FILES
gains agent.recipe.json as an optional entry, beside the manifest it
generated.

Optional for a third reason, different from both already in that list.
sources.json, README.md and .gitignore are optional because an agent can
outgrow them (MAR-595 finding 9); the manifest, package, program, runtime
and install script are required because losing one is a real incomplete
build. The recipe is optional because every agent scaffolded before this
packet has none, and requiring it would tell somebody their build was
incomplete over a file missing only because of when their agent was made.

It also removes an asymmetry with no reason behind it: the MCP's handoff
walks the project directory and has always carried whatever is in it, so
until now a kit-built agent reached DASH without its recipe while an
MCP-built one reached it with one.

Two tests: the handoff carries the recipe with the same bytes that are on
disk and an agent id that still matches, and a project with no recipe still
produces a handoff with the manifest and the program in it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… see

Records the orchestrator's ruling on deviation 1, the two tests behind it,
and why smoke proof 6b needed no change: electron/smoke.ts imports neither
AGENT_KIT_PROJECT_FILES nor projectFiles nor the kit's writeHandoff, and 6b
logs the sample plan's own file list, which already carried the recipe.

Also records what merging lane F3's PR #357 actually did. merge-tree was
clean and the merge produced no conflict, because the two branches changed
two different files -- and they disagreed anyway: F3 bumped the runtime's
SDK_VERSION to 1.1.0 while agent-kit/recipe.ts still pinned 1.0.0. That
would have shipped agents running 1.1.0 whose own agent.recipe.json claimed
1.0.0. tests/agent-recipe.test.ts's first assertion reads the constant out
of the runtime file and caught it, which is the whole reason it exists.
Fixed here, the fixture regenerated to match, and the four evals re-proved
against F3's instrumented runtime rather than assumed.

And the honest version of the union's test results: the first full run had
eight timeouts at the default 5s, all on the first freshStore() in six
files, all passing alone, with a clean second full run. Contention, not
behaviour -- flagged for the orchestrator because migration 38 raised what
every fresh store costs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Thebeatkicks
Thebeatkicks merged commit cfbe48c into master Sep 7, 2026
2 checks passed
Thebeatkicks added a commit that referenced this pull request Sep 7, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant