Skip to content

Conformance

Stability: stable.

A runtime may name two claims for publication 0.1.0 separately:

  1. 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.
  2. 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.jsonl

A 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.

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.

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 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.

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

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.

  1. The refusal vocabulary, because a case cannot assert on a code that does not exist (refusals).
  2. The refuse cases, which are mechanical once the vocabulary exists and are where most of the value is.
  3. 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).

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.