Conformance
Stability: stable.
Conformance levels
Section titled “Conformance levels”A runtime may name two claims for publication 0.1.0 separately:
- 0.1.0 static conformance means passing all 143 current model-free corpus cases. It covers only behavior that the corpus can exercise without a model.
- 0.1.0 invariant compliance means claiming that live execution satisfies the normative invariants, including behavior the static corpus cannot exercise.
Static corpus passage does not imply invariant compliance. A runtime may report the claims separately; complete 0.1.0 conformance requires both.
The corpus is the executable form of this specification: assemblies that must be accepted, assemblies that must be refused, and the exact behavior expected of each. It lives beside the specification, so a second runtime has something to be checked against rather than only something to read.
specification/ conformance/ accept/ <case>/ home/ optional: config.yaml, and assemblies/ when a case needs more than one assembly/ invocation the command line, one line expected.jsonl refuse/ <case>/ home/ optional assembly/ invocation expected.jsonlA case is a whole invocation, not only a folder. Several rules — which rung a value came from, an unresolvable model, an ambiguous provider, a missing flow — are about the home and the command line rather than the assembly, and a layout that held only an assembly could not express them. The invocation line holds the arguments handed to the assembly-check reader, and it names no verb: every rule the corpus states is within check’s reach, including the ones a run would meet first, because check reads the whole tree a run would read — every flow, and every subflow under it.
The suite runs without a model
Section titled “The suite runs without a model”Everything in the corpus is deterministic. No case calls a provider, so the whole suite runs in seconds, offline, on every commit, and a failure names a rule rather than a mood.
That is possible because most of this specification is not about what a model does. Reading the folder, typing each sentinel, ordering the stages, resolving options across the rungs, binding slots, refusing what is malformed — all of it happens before a model is reached, and all of it is checkable with the assembly-check reader.
Operational record conformance
Section titled “Operational record conformance”The runtime ships deterministic tests that stand in for the parts of the format the static corpus cannot reach: what a running writer emits, what a sealed record must hold, and how stages, retries, containers, and child runs settle. The full test notes are the source of this page, specification/conformance.md in the repository.
The check output contract
Section titled “The check output contract”The reader returns one JSON object per procedure row. A named flow starts with its definition. Other reachable definitions follow in bytewise path order, each immediately before that flow’s nodes:
{ "stage": "flows/review/FLOW.md", "flow": "flows/review", "type": "FLOW", "max_subflow_calls": 10}{ "stage": "02-assess/risk", "flow": "flows/review", "type": "STAGE", "input": ["read.txt"], "output": "risk.json", "files": ["STAGE.md", "schema.json", "gate.sh"], "workdir": "./risk", "options": { "intelligence": { "value": "default", "from": "assembly" }, "provider": { "value": "anthropic", "from": "assembly" }, "model": { "value": "claude-opus-5", "from": "assembly" }, "reasoning": { "value": "high", "from": "assembly" }, "timeout": { "value": 3600, "from": "default" }, "retries": { "value": 2, "from": "home" }, "local-context": { "value": "ignore", "from": "default" } }}A DESCEND definition also carries max_depth. Definitions carry no input,
output, files, skills, or model options. Each named-flow node carries flow as
its assembly-relative flow path. A child-only flow starts from
request.<runtime> and resolves options without root command or task rungs. A
selected flow reachable again as a child keeps its root input and options
and adds child_input and child_options when the child context differs. A
depth-dependent selected-root artifact adds child_outputs; a child-only row
adds possible_outputs when its exact reachable states disagree.
Every option carries the rung it came from, and from is one fixed word per
rung (the eight rungs):
command, task, stage, container, flow, assembly, home, default.
That is the field that makes “why did this use that model” answerable before
the run rather than after it.
An authored intelligence is stamped with the rung that named it. Its
resolved provider, model, and reasoning values carry that same rung. The name
records what ran without putting it in a prompt, while the resolved fields
snapshot the complete bundle so the result does not depend on later home table
changes. Retired literal model-choice fields are not authored alternatives;
they remain here only as resolved output fields.
A single-file stage is type STAGE like the folder form — the form shows in
files, which lists only the file itself.
An authored stage workdir appears as written. It is absent when the stage uses
the flow’s inherited working directory.
input is every name that may appear in $INPUT. For the stage after a
CHOOSE, that is one name per alternative and exactly one of them will be
there — which one depends on which alternative ran, and that is not statically
knowable, so the list names them all rather than guessing.
files lists the recognized files in the stage’s folder — the sentinel first,
the rest in name order (invariant 41), the entries of a
gate/ folder as gate/01-lint.sh — so a reader can see that a gate exists
without opening the directory. A single-file stage lists only itself.
Two option details: provider appears only when the selected intelligence
bundle supplies it — it has no built-in default — and carries the rung that
selected that intelligence. A container’s own frontmatter is rung container
on its own line, the same word its contents see. A stage’s line also carries
skills: the flattened names it would see, narrowest override applied, in
name order — which is what makes the scope rules assertable without a model.
A container emits its own line and then the lines for what is inside it. Its
line carries stage, type, options, and its own key — repeat or width —
and no input, output, or files, because a container produces nothing of
its own. A LOOP lists its contents once, since how many repeats there will be
is not knowable without running, and a CHOOSE lists every alternative, because
any of them could be the one that runs.
Given no flow — bot assembly check review — it validates the whole assembly exactly as
any invocation would, and reports the assembly agent first: stage is assembly, type is ASSEMBLY, files lists
ASSEMBLY.md, output is assembly.txt (no schema — text), and a scope
field names the flows and root subflows the agent could call, in name order
(running the assembly). Reachable
flow definitions and nodes follow. Traversal keeps exact flow identity,
mixed-flow call position, and consecutive self-call depth through ten child
edges while emitted definitions and nodes remain unique. Static output lists
all choice alternatives and one loop body but predicts no selection, repeat,
call, or fan-out item count. Definitions beyond the ceiling remain validated.
Everything this does is deterministic, which is what makes it the backbone of this corpus.
Accept cases
Section titled “Accept cases”Each holds an assembly and the JSONL output from the assembly-check reader that the conformance harness compares.
Two runtimes reading the same accept case produce the same lines. That is the claim the corpus exists to test.
Four groups. The corpus directory is the list — this prose names what each group pins down, never which cases do it or how many:
| Group | What they pin down |
|---|---|
| Shape | how a folder becomes a graph — the forms a stage takes, how stages order, how deep they nest, what a reader ignores (the graph) |
| Resolution | one case per rung — each proving a value came from where it should (the rungs) |
| Format selection | schema.json, schema.md, none — each proving the extension the next stage sees |
| Naming | what $INPUT holds after each container, including the alternatives after a CHOOSE; skills flattened across scopes; a collision resolving narrowest-first; identity inside a loop |
Refuse cases
Section titled “Refuse cases”Each holds an assembly that is malformed in exactly one way, and the refusal it
must produce: a code, a path, and an exit of 2 (refusals).
The expectation is the code and the path, never the sentence. Asserting on prose would couple every runtime to one runtime’s wording, and a rephrasing would break the suite. The sentence stays free to be improved.
A case asserts its faults as a set — code and path, order-free — because the order faults are reported in is not specified. Most cases isolate one fault, which is what keeps a failure legible; a case may hold several exactly to pin the report-every-fault rule (refusals), asserting the whole set.
At least one case per rule — several rules can share a code (invariant 39), so a code may be asserted by more than one case, and the corpus directory, not this prose, is the count.
The refuse set is where a specification earns its keep. Two implementations
agreeing about a correct assembly is easy; agreeing about a folder with two
sentinels, a CHOOSE.md listing an alternative that does not exist, a symbolic
link, or a gate with no executable bit is where formats quietly diverge.
The order it gets built
Section titled “The order it gets built”- The refusal vocabulary, because a case cannot assert on a code that does not exist (refusals).
- The refuse cases, which are mechanical once the vocabulary exists and are where most of the value is.
- The accept cases, which need the assembly-check reader to be producing its fields.
A new rule about an assembly a runtime reads is always a new case; it is a new code only when no existing code names the fault (refusals).
What a runtime claims
Section titled “What a runtime claims”A runtime that passes every case in the corpus implements the parts of this specification that do not need a model. It does not thereby implement gating, resumption, or anything else that happens once a model is running — those are claimed by reading the invariants and meaning it.