The Graph
The control graph
Section titled “The control graph”Stability: stable.
The graph is the folder tree. A stage’s position decides when it runs, and nothing else does.
There are no edge keys — nothing named next, goto, on-pass, or on-fail.
Nothing invokes a stage by name. A stage runs because the flow reached it.
Moving a folder changes the graph; editing frontmatter does not.
The sentinels
Section titled “The sentinels”A sentinel file names what the folder that holds it is, and sentinel names are
in capitals. Four of the sentinels type containers — LOOP.md,
CHOOSE.md, PARALLEL.md, and FANOUT.md; the others type a flow or a stage,
which are not containers (stage types). FANOUT.md is
the container whose contents are not folders: it runs one subflow per item and
places what those runs produced, and its own chapter is
provisional while the rest of this one is settled.
A sequence is a folder of numbered entries that run in order and holds no
sentinel: a flow’s contents, a branch of a PARALLEL, an alternative of a
CHOOSE. It is a sequence because of where it sits, not because of a file
inside it.
A container holding two sentinels, or a container’s folder holding none where one is required, is a malformed assembly.
| Sentinel | What the folder is |
|---|---|
FLOW.md |
a flow (flow) |
STAGE.md |
a stage that runs once, in its numbered position (stage) |
LOOP.md |
a stage that repeats what is inside it (loop) |
CHOOSE.md |
a stage that runs one of several alternatives (choose) |
PARALLEL.md |
a stage that runs branches at once (parallel) |
FANOUT.md |
one subflow run for each checked list item (fan-out, provisional) |
DESCEND.md |
a flow whose stages may call it again (descend) |
A stage that needs no files of its own is a single markdown file, 01-name.md,
and needs no sentinel to say what it is.
Numbered and named
Section titled “Numbered and named”A numbered entry runs in sequence: 01-read.md before 02-analyze/ before
03-report.md. Two entries sharing a number is an error.
A named entry does not run in sequence. The branches of a PARALLEL and the
alternatives of a CHOOSE are named rather than numbered, because a number
would be a lie about them.
The name — the part after the number, when there is one — is the entry’s identity. It appears in the record, and it is the filename the next stage finds its input under.
What every container shares
Section titled “What every container shares”A container has an input, a position, an exit code, and its own frontmatter. It has no hooks, no schema, no gate, and no checklist: those exist to judge an output, and a container produces none of its own. To check what came out of a container, write a stage after it.
A container’s frontmatter sets the same keys a stage’s does — every one of
them, retries included, except the stage-only workdir
(the stage) — and everything
inside it inherits them, so a PARALLEL can put its branches on a cheaper
model or a tighter retry budget
without repeating the key in each one (invariant 31). It sits
between the stage and the flow in the order things resolve
(invocation).
A CHOOSE with a body runs an agent of its own, and retries bounds that
agent’s send-backs the way it bounds any stage’s. A LOOP with a body runs no
agent of its own: its question is put to the agent of its last stage
(loop), and the answer round spends that stage’s retries.
A non-zero exit stops the flow whatever the type. What differs between types is only which of their contents run, and how many times.
| Sentinel | Its key |
|---|---|
LOOP.md |
repeat |
PARALLEL.md |
width |
CHOOSE.md |
none of its own |
FANOUT.md |
items, subflow, width, max-items (fan-out, provisional) |
How a container’s work reaches the next stage
Section titled “How a container’s work reaches the next stage”A container passes along what the stages inside it produced. The next stage’s
$INPUT is a directory, so several outputs arrive as several named files and
nothing has to be combined (slots).
| After a | $INPUT holds |
|---|---|
STAGE |
one file, named after the stage |
LOOP |
one file, named after the loop, from the last stage of the last repeat |
CHOOSE |
one file, named after the alternative that ran |
PARALLEL |
one file per branch, each named after its branch |
FANOUT |
one file per item, each named after its item id (fan-out, provisional) |
PARALLEL and FANOUT can contribute more than one file. This is why $INPUT is a directory. FANOUT’s rows here carry its own chapter’s provisional label.
The filesystem is the namespace, so three branches producing three formats reach the next stage intact. An author who wants them as one file writes a stage that reads several and writes one, which is an ordinary stage doing an ordinary thing.
Where an agent gets a say
Section titled “Where an agent gets a say”Almost nowhere. The graph is the author’s, written in folders, and no agent can
change it. The exceptions are the control tools, each a place where the format
wants a judgment a model makes better than a path expression: marking a
checklist item, refusing or reporting a fault for a stage, ending a loop,
selecting an alternative, calling a subflow the author placed in scope, and
confirming that this stage’s temporary contents are disposable
(control tools). Six of them are the ordinary
closed list every stage draws from; subflow is granted separately, only where
the author placed a subflow in scope.
Nesting
Section titled “Nesting”Any container holds any container, to any depth, with three exceptions. The
third is FANOUT.md, which holds no container and stands in no container: it
is a numbered folder in the root sequence of a named entry flow, never first,
never last, never nested, and never in a subflow (fan-out). A
branch or alternative that is directly a PARALLEL is refused
(tail-container): it is one name that would have to carry several outputs,
the tail rule below met in another position. A LOOP or a CHOOSE stands
anywhere a stage does except last — each passes along one output, but the
tail below is a stage’s alone — so
a branch of a PARALLEL can be a LOOP, and an alternative of a CHOOSE
can be another CHOOSE. And a loop holds no loop (invariant 40): a
LOOP inside a LOOP, however many containers sit between them, is refused —
loop-nested. Identity has one repeat, and a stage under two loops would
need two. Iteration inside iteration is a flow calling a flow: the stage in the
outer loop calls a subflow that loops, and each run’s identity stays flat
(subflows).
There is no depth limit. What a container does to its contents does not change with depth — it has an input, it runs what is inside it, and it passes along what they produced — so a limit would be a number with nothing behind it.
The last entry of a sequence is a stage
Section titled “The last entry of a sequence is a stage”Every sequence ends in a stage, never in a container.
This is what keeps the tail rules simple. A flow’s output is its last stage’s
output, so a flow ending in a PARALLEL would have several. A LOOP’s question
is put to the agent of its last stage, so a loop ending in a container would
have nobody to ask. An alternative passes along its last stage’s output under
the alternative’s name, so an alternative ending in a PARALLEL would have
several files and one name.
An author who wants a fan-out at the end writes the stage that reads it, which is the stage that was going to have to exist anyway.
What the tree may contain
Section titled “What the tree may contain”Symbolic links anywhere in an assembly are refused and never followed, so the folder a reader sees is the folder that runs.
Hidden dot-entries are ignored: they name nothing in the graph, they are not
hashed, and nothing in them is executed. An assembly kept in git carries its
.git directory without the format having an opinion about it. Anything an
assembly runs lives where a reader can see it.
README.md is permitted in any folder and is inert: it names nothing in the
graph, nothing in it runs, and nothing refuses it — documentation is welcome
everywhere. LICENSE is permitted at the assembly root, so an assembly can be
a repository somebody publishes.
LOOP.md
Section titled “LOOP.md”Stability: stable.
A loop is a stage that repeats what is inside it.
03-refine/ LOOP.md 01-revise.md 02-review/ STAGE.md schema.jsonThe folder is typed by LOOP.md and holds numbered stages, which run in order,
over and over.
repeat
Section titled “repeat”repeat: 10One key, required on every LOOP.md, and it is always a ceiling: the most
times the loop may run. A runtime never invents a bound
(invariant 32).
---repeat: 10---
Is the draft ready to publish? Another repeat is only worth it if you havesomething specific to change.The body is the question. Writing one is what asks the agent; leaving it out is what does not.
- No body. The loop runs
repeattimes and nothing decides anything. - A body. The loop runs until the agent says stop, and never more than
repeattimes.
This is the same rule every sentinel follows: the body of the file is the prompt.
Who is asked, and when
Section titled “Who is asked, and when”The last stage inside the loop, because it is the only one that has seen the whole repeat.
Its agent stops. The checklist, the schema, and the gate clear it (gating). Only then is it asked the question, and it answers with a control tool (control tools). Asking whether to go around again about work that just failed its gate would be asking about nothing.
The question sits between the checks and the hooks: it is asked after the last
check clears and before success runs and the output seals, because until the
answer comes back the stage is not over — an agent may refuse or report a fault
at the question, and either ends a stage whose checks had already passed
(the runtime). The answer round runs inside the
stage’s own timeout, like everything else the agent does. An agent that
stops without answering the question is held and asked again, spending a
retry like any failing round — retries bounds send-backs whatever caused
them, and an unanswered question is a round that failed.
The agent is told the question and not how many repeats remain — an agent that knows it is on the last one approves the last one. Which repeat it is on is derivable from its slot paths, and that is accepted; the ceiling is what never leaks.
Running out
Section titled “Running out”A loop that reaches repeat while the agent is still saying continue
fails, with cause rejected — the assembly’s own bound said no
(the record).
The instruction was “until it is ready,” and it never got there. Ten unfinished
repeats is not a success with the tenth draft.
A loop with no body cannot run out. It ran the number of times it was told to.
repeat is therefore a real budget and not a safety net. A loop asking a
question with repeat: 1 fails the run the first time the agent says continue,
and one asking with repeat: 3 fails after three clean, gate-cleared repeats.
Input and output
Section titled “Input and output”The loop’s first stage receives the loop’s own input on the first repeat. On
every repeat after that it also receives the previous repeat’s output, as
another named file in $INPUT (slots):
$INPUT/request.md the loop's input, every repeat$INPUT/review.json the previous repeat's last stage, from the second onEvery name that can appear there is known before the run starts — after a
CHOOSE the loop’s input is one of a known set — so two that could collide are
a malformed assembly and the run refuses, naming them.
The stages after the first one inside a loop are ordinary sequential stages: each receives the output of the one before it, repeat after repeat.
The loop passes along the output of its last stage from its final repeat,
under the loop’s own name: what leaves 03-refine/ above arrives in the
next stage’s $INPUT as refine, with the extension the producing stage’s
schema chose. Inside the loop the stages see each other’s names; outside it,
which stage wrote the result is the loop’s business
(invariant 26).
In the record
Section titled “In the record”Each repeat is recorded separately, and every stage inside the loop is identified by which repeat it ran in (the record). A stage that ran four times is four entries, not one.
The decision that ended the loop is recorded with it. ended_by is stop when the agent stops, limit when the authored count ends the loop, or the exact failed-body cause: refused, exhausted, rejected, blocked, timeout, or fault. The event retains the body’s reason. An outside signal writes the signal sequence and no conflicting loop_done.
CHOOSE.md
Section titled “CHOOSE.md”Stability: stable.
A choose stage picks one of several alternatives and runs only that one.
04-respond/ CHOOSE.md escalate/ 01-page-oncall.md 02-write-incident.md patch/ 01-write-fix.md revert.mdThe alternatives are named, not numbered. They do not run in sequence, so a number would be a lie about them. An alternative is a single stage file, a stage folder, or a folder holding numbered stages that run in order.
Who chooses
Section titled “Who chooses”An agent. The body of CHOOSE.md is that agent’s prompt, and it lists the
alternatives:
------
- `escalate` — the change is unsafe and a human has to see it now- `patch` — the problem is small and mechanical- `revert` — the change should not have landedAn alternative is named in a code span at the start of a top-level list item. That is the whole of the syntax: everything else in the body is guidance, and a code span anywhere else names nothing.
Every alternative is something to do. A choice is between courses of action, and one of them always runs.
The agent reads the input and names its choice with the select tool
(control tools). It has the stage’s $INPUT,
$TMP, $SKILLS, $PWD, and $SUBFLOWS when any subflow is in scope, and
no $OUTPUT, because a choice is not an output. It may refuse or report a
fault, which fails the stage.
An agent that stops without selecting, or that names something that is not an
alternative, is told so and asked again in the same session — the same send-back
a failing check earns (gating). retries bounds
it, and the stage fails when they run out.
In the record the chooser is the choose folder itself, with a retry counter
and no output, and its session sits where any stage’s session sits
(the record).
There is no other chooser. A deterministic branch — “if the input says
critical, escalate” — is a script’s job, and a script belongs in a stage or a
gate, not wearing a CHOOSE.md: this element exists for the judgment call a
program cannot make. A CHOOSE.md with no body has no chooser and is refused
(body-missing). A choose with exactly one alternative is refused
(chooser-invalid): a “choice” with one course of action is a sequence wearing
the wrong sentinel. A choose folder with no alternatives at all is refused
(folder-empty).
The names must match
Section titled “The names must match”Every name listed in the body must be a folder or file in the choose folder, and every folder or file must be listed. A name with nothing to run, or something to run that was never listed, is a malformed assembly and the run refuses before it starts.
Input and output
Section titled “Input and output”The alternative that runs receives the choose stage’s input, unchanged.
The choose stage passes along that alternative’s output, and the next stage receives it named after the alternative (slots):
$INPUT/escalate.jsonSo a stage after a choice can tell which way the choice went without being told — the filename says it. Whether it does anything with that is the author’s business.
Alternatives may produce different shapes, because they are different things.
In the record
Section titled “In the record”The record names what was chosen, what else was available, and the agent’s reason (the record). Alternatives that did not run are named, so a reader can see what the run declined to do.
PARALLEL.md
Section titled “PARALLEL.md”Stability: stable.
A parallel stage runs several branches at once.
02-assess/ PARALLEL.md research/ 01-gather.md 02-summarize.md risk/ STAGE.md schema.json cost.mdBranches are named, not numbered. They do not run in sequence, so a number would
be a lie about them. A branch is a single stage file, a stage folder, or a
folder holding numbered stages that run in order — risk/ above is a stage
folder, typed by its STAGE.md.
PARALLEL.md has one key of its own, width, and no body. Nothing is asked of
an agent here.
Every branch gets the same input
Section titled “Every branch gets the same input”The parallel stage’s input is handed to each branch unchanged. Branches do not
see each other’s inputs, outputs, or scratch. They do share the working tree —
$PWD is one tree (slots) — and this specification does not
pretend otherwise: branches that would fight over the same files are the
author’s to separate.
Nothing is merged
Section titled “Nothing is merged”Each branch writes its own $OUTPUT, and the stage after the parallel stage
receives all of them as named files in its $INPUT (slots):
$INPUT/research.txt$INPUT/risk.json$INPUT/cost.txtThere is no merge rule because there is no merge. The filesystem is the namespace, the branch name is the key, and three branches producing three different formats is not a problem to be solved — markdown next to JSON next to a PNG all work, because nothing is trying to combine them.
The stage that follows addresses each branch by name. Its prompt is told the names, so it does not have to discover them (prompt construction).
An author who does want one file writes a stage that reads the three and writes one. That is an ordinary stage doing an ordinary thing, and the format needs no opinion about it.
width: 3How many branches run at once. All of them, unless this says otherwise. When
width holds some branches back, branches start in name order
(invariant 41): a pool of width slots, the next name
starting when a slot frees. Which branches had started by the time one failed
is a fact of timing — a fast sibling frees a slot early — so the record says
which ran, per branch, rather than the format promising the same set on every
runtime. A runtime records how many ran concurrently, and the recorded order
of branches never implies the order they finished in.
width is at most 32, authored or defaulted; more is refused
(refusals).
When a branch fails
Section titled “When a branch fails”The parallel stage fails. Branches already running are allowed to finish first, and their outputs are kept in the run even though nothing downstream reads them (the record).
When more than one branch failed, the exit code is the one from the first failing branch in name order. Name order is chosen because it is the same before the run as after it: completion order would make the exit code depend on timing.
They are not cancelled because they are working in a shared tree
($PWD), and an agent killed in the middle of a tool call leaves
that tree in a state nobody chose. Letting them land is cheaper than reasoning
about what half a branch did.
Branches that had not started do not start.
FANOUT.md
Section titled “FANOUT.md”Stability: provisional.
This control may change in a later pre-1.0 release.
A fan-out is a container, and the sentinels chapter says so (the sentinels). Every sentence naming FANOUT in a settled chapter carries this chapter’s label, not that one’s. A fan-out runs one authored subflow for every item in a checked JSON list. Bot derives the complete work set from the immediately preceding stage. The producing agent does not call the subflow itself.
FANOUT.md is a numbered folder in the root sequence of a named entry flow. One ordinary JSON stage must precede it. One ordinary stage must follow it. A fan-out cannot be first, last, nested, or placed in a subflow. Its folder holds only FANOUT.md and optional README.md.
items: jobssubflow: workerwidth: 2max-items: 16The sentinel has no body and exactly these four keys. items names a top-level array in the preceding JSON object. subflow names an in-scope subflow whose final node is an ordinary stage. width limits concurrent children. The bounds satisfy 1 <= width <= max-items <= 32.
Each array entry has exactly id and input. An id matches [a-z0-9][a-z0-9_-]{0,127} and is unique. An input is a JSON object. Bot refuses an empty list, malformed JSON or UTF-8, a document above 1 MiB, or any invalid entry before it starts a child.
Bot sorts items by the UTF-8 bytes of their ids and invokes the selected subflow once per item. Each child request is compact JSON for {id,input}. At most width children run at once. An ordinary child failure or machinery fault does not stop siblings. An outside signal stops new launches and settles started children.
Fan-out succeeds only when every child succeeds with one sealed output from the selected subflow’s final root stage. Bot opens each accepted output once and fixes its descriptor and initial size. The next stage receives that held snapshot as one <id>.<extension> input for every item. A path replacement cannot redirect the copy, an append is not chased, and a partial or hash-disagreeing copy is removed before the successor starts. The output has no inspection-size limit. A failed fan-out never passes a partial set. Resume runs the whole fan-out again from the retained predecessor output.
The parent record binds the retained manifest and sorted plan in fanout_start, records one sorted subflow_call disposition per item with via: "fanout", then writes fanout_done. Child records keep the existing subflow layout. Completion timing never controls row order or aggregate failure selection. The record keeps shape 1 because the new events and fields are additive.
DESCEND.md
Section titled “DESCEND.md”Stability: stable.
A flow that may call itself.
DESCEND.md stands in the place of FLOW.md as a flow folder’s sentinel. The
flow is otherwise ordinary — numbered stages, the same frontmatter grammar, a
required description — and it may sit in flows/ or in any subflows/
scope, invoked or called like any flow (subflow).
What the sentinel changes is one thing: every stage inside the flow has the
flow itself in scope as a subflow. A stage of an ordinary flow can never call
the flow it stands in, because a folder cannot contain itself and placement is
the only grant. DESCEND.md is the author writing the one grant placement
cannot express.
max-depth
Section titled “max-depth”description: Break a document into questions it can answer, and answer themmax-depth: 11One key of its own, required: an integer from 1 through 11 and the longest unbroken chain of this flow’s self-calls. A run invoked from outside is at depth 1. A stage may
call its own flow only while the current depth is below max-depth; at
max-depth, the flow is simply not in its own stages’ scope, and the agent is
told about the subflows that remain, which may be none.
max-depth bounds this flow’s self-calls and nothing else. Another flow
between two invocations restarts the self-chain: this flow reached through a
subflow it called stands at depth 1 again. The runtime separately allows at
most ten subflow calls in a mixed-flow chain (subflow); that
fixed safety ceiling makes 11 the largest useful authored depth: the root is at
position 0 and depth 1, then ten child calls reach depth 11.
What it is for
Section titled “What it is for”Decomposition the author cannot foresee. A twenty-page document does not announce how it should be split; a codebase question does not say in advance how many smaller questions it contains. A descend flow reads its input, hands the pieces it chooses to invocations of itself, and composes what comes back — each child a full run of the same flow, with the same checks, one level down (subflow).
The author still owns everything but the split: what the flow does, what its stages are, what gates the work, and how deep it may go. The agent owns only what placement could never say — how this particular input divides.
In the record
Section titled “In the record”A self-call is a subflow call and is recorded as one: an event in the parent’s record and a complete child run beneath it, at every level. The depth of each invocation is recorded with it, so a reader can see the shape of the descent without reconstructing it.
Subflows
Section titled “Subflows”Stability: stable.
A subflow is a flow a stage can call. The stage’s agent hands the runtime an input, the flow runs as a complete run of its own, and the answer comes back: onto disk always, into the agent’s context when it is small.
A subflow call is an invocation. The child receives what any run receives — request bytes and a working directory — and returns what any run returns, its flow’s output. No session crosses in either direction (invariant 38). The rest of the world calls this a subagent; here the thing called is a flow, gates and all, which is what makes the answer worth trusting.
An assembly can keep an oracle/ subflow — one stage on an expensive model,
high reasoning — and any stage with it in scope hands it the hard question and
keeps working on its own.
The three scopes
Section titled “The three scopes”A subflows/ directory may sit at three places, and its position decides who
can call what is inside it — the same shape as skills.
| Where | Callable by |
|---|---|
| the assembly root | every stage |
| a flow folder | the stages of that flow |
| a stage folder | that stage alone |
Each entry in a subflows/ directory is a flow: a folder typed by FLOW.md,
or by DESCEND.md when it may call itself (descend). A subflow
belongs at the narrowest scope where it is useful, like a skill.
A subflow is a flow folder, so it may carry its own skills/ and subflows/,
resolved narrowest-first like every other scope.
The scopes are flattened into one list, and names collide by overriding,
narrowest first — one name is one subflow. The agent is told each subflow’s
name and its description, and nothing about where it came from.
A subflow is not an entry point. Invocation resolves names in flows/ and
nowhere else (invocation), so the only way to reach a subflow
is to be a stage with it in scope. The reverse holds too: a stage can never
call a flow, only a subflow. The one agent with both in scope is the assembly
agent, which is what runs when a run names no flow
(running the assembly).
The call
Section titled “The call”A stage with any subflow in scope has the subflow tool
(control tools). One call submits a batch of one or
more requests, and a batch of one is just a call:
{ "calls": [ { "flow": "oracle", "input": "Is this migration safe to run twice?" }, { "flow": "summarize", "input-file": "$TMP/page-03.md" } ]}Each entry names a flow in scope and gives the input as text or as a path to a file whose bytes become the input — which is how an agent chains one subflow’s output into another’s input without carrying the content through its own context. The input is the child run’s request, delivered the way any run’s request is delivered (invocation).
A batch holds at most 32 calls; a larger one comes back as a tool result to split, not as a failure.
A tool payload is JSON, and no shell expands it, so the runtime does: any
slot variable in an input-file path — $TMP, $SUBFLOWS, $SKILLS,
$INPUT, $OUTPUT, $PWD, a declared slot — is expanded by the runtime
before the path is read, uniformly, which is what lets an agent name files by
slot in a tool call the way it does everywhere else.
The runtime runs the batch’s children at once and the tool returns when the
last of them ends. Each child is a run: fresh sessions, its own stages and
checks and clocks, and the parent’s $PWD as its inherited default. How many
children an agent starts is its own business; a call chain has a fixed ceiling
of ten subflow calls, and a child’s time is never on the parent’s clock
(invariant 22).
What a child inherits is what invariant 38 allows across the boundary and
nothing more: the input as its request, the parent’s $PWD, and the
assembly’s declared slot values — a child of the same assembly cannot run
without them. It does not inherit the invocation: the parent run’s
command-line and task-file overrides are rungs of that invocation, and the
child resolves its options from its own sentinels, the home, and the defaults.
A child stage that explicitly sets workdir resolves that path from the root
workspace selected by the original bot run start --in; omission is what preserves
the parent’s effective $PWD.
$SUBFLOWS — where the answers live
Section titled “$SUBFLOWS — where the answers live”Every call whose input crosses the subflow boundary lands in the
$SUBFLOWS slot, numbered in the order the calls were made, across
batches, starting at 1:
$SUBFLOWS/ 1/ input.txt output.md 2/ input.json output.jsonEach call’s folder holds exactly what crossed the boundary, as a typed pair.
input carries the extension of what was sent: .txt for inline text, the
file’s own extension for an input-file. A file extension must contain only
ASCII letters and digits and fit the retained request.<extension> directory
entry. Bot refuses another suffix before it creates a child. output carries the extension the
child’s final stage’s schema chose. Both names are constant whatever flow ran —
composing over calls never depends on what a flow was called; which flow it was
is in the tool result and the record. The agent composes over these files by
path and never learns where the child’s run record lives.
Bot rejects a flow outside the caller’s resolved scope before it expands or
reads an input-file. That disposition keeps its call number but creates no
folder. Inline text already exists at the boundary, so its refusal retains the
text descriptor in the parent record without creating a folder.
What enters the context
Section titled “What enters the context”The tool result reports, for each call: its number, its flow, how it ended, the
output’s path under $SUBFLOWS, and its size in bytes and lines. The content
itself is injected when it is small and pointed at when it is not:
- At most 10,000 bytes: the whole output, inline.
- Larger: a marked prefix that, including its marker, is at most 10,000 bytes, and the path to the rest.
The numbers are deliberate limits on what enters the context automatically; the
agent reads the rest from $SUBFLOWS when it wants it.
Failure is an answer
Section titled “Failure is an answer”A child that fails or refuses does not fail the parent. The tool result carries how it ended — the same cause vocabulary as any run (the record) — and the reason, in the child’s own words when it refused. The call’s folder keeps the input and no output. The parent read an answer that says no, and what it does about that is what it is for.
Evidence, not product
Section titled “Evidence, not product”Child answers are evidence for the parent, never the parent’s output. The stage
still writes its own $OUTPUT, and its own checklist, schema, and gate judge
only that (invariant 24). An agent that wants a child’s answer
to be its output copies it there and stands behind it.
In the record
Section titled “In the record”Every call is one event in the parent’s record: the flow, any admitted input,
and how it ended. A child that starts is recorded under the parent stage’s
attempt, with its own record and sessions, in the ordinary record shape, read
through its parent (the record, inspection). An
inline request records its text, size, and hash. When a child starts from an
input-file, the request records the normalized retained child request path,
size, and hash. It never records the expanded absolute source path. A child
record-writer failure leaves that child record visibly incomplete; the parent
call keeps the normalized input descriptor and says its machinery failed
without inventing an exit or cause.
On disk that is a run directory in the ordinary shape, nested where the call
was made — stages/<stage>/<repeat>/<attempt>/subflows/<n>/, the number
matching the call’s folder under $SUBFLOWS. A child’s scratch is its own
directory under the calling run’s scratch root, named nothing a reader could
guess, so it leaves with the run when the run’s scratch does. A child is evidence of its
parent’s work, so it lives inside the parent’s run and travels with it when the
run is moved or archived; bot run list lists top-level runs only, and a child is
reached through its parent.
Placement is a tree, and a folder cannot contain itself, so placement nests
only as deep as an author physically wrote. Call chains are not placement:
assembly-root subflows are in every stage’s scope, a stage inside one subflow
can call another, and two subflows can call each other by turns. The runtime
allows at most ten subflow calls in one chain. A root run starts at cumulative
position 0, each child advances it by one, and a stage at position 10 has no
subflows in scope. Every call is in the record; its depth is the greater of
its self-chain depth and cumulative position.
A flow is removed from its own stages’ scope before anything else is added to
it, whether it was placed at the assembly root or beside a stage. The rule is
the runtime’s, not a consequence of where the folder sits: without the marker
below, a flow named review is not in the scope of the stages inside review.
The one thing placement cannot express is a flow seeing itself in its own
scope, and that takes the one marker written for it
(descend) — whose max-depth bounds that self-chain. The fixed
runtime ceiling bounds mixed-flow chains separately.