How a mission runs
A mission is one pass through the tree, from a person’s request to a published boundary or to a
question only a person can answer. Nothing here is optional sequencing advice: each step below is a
rule stated in SKILL.md, workflows/README.md, or resources/orchestrator.json.
1. Freeze the scope
SKILL.md starts by freezing one mission scope: “the unit, the target, inclusions and exclusions,
write roots, external effects, and what will count as proof. Two readings that would change any of
those is one focused question, not a guess.”
2. Bind the workspace
workspace.bind runs for any mission that reads or writes routed source. Nothing else may resolve a
checkout, and a similar directory name is never route authority. It returns one typed route receipt:
a verified checkout identity, its exact source head, and the closed runtime binding the mission may
consume. See workspace.bind.
3. Match a workflow, or compose one
The entry reads the when of every example in workflows/. A match is run as written and its
presets fill request.json; the person is asked only for fields with no default. No match means
composing a chain from the operators’ ## Next tables, under the rules
scripts/validate-workflows.mjs enforces on the examples: every required input of a branch is
produced by an earlier step, branches of one step share no write alias, a loop goes back to an
earlier step and carries maxRounds, and the chain ends at git.publish, release.deploy, or
user. A composed chain worth keeping becomes a new example.
4. One agent, one operator
One invocation of one operator is one agent, created fresh on the profile its operator.json names,
with only the grants and the aliases its Context table declares. At most three agents run at once.
An agent never starts another agent; a nested exchange — a critique, a review — is a second fresh
agent the orchestrator spawns for a branch that paused. See Resources.
5. request.json
The orchestrator writes the branch’s request/request.json and validates it with
scripts/validate-request.mjs before the agent starts. The gate is
templates/step/request.schema.json,
and it carries operatorId, step, parallel, sessionId, the contexts each with its frozen
head, the requirements the operator’s Requirements table declares, the inputs — each an
explicit path from the session root to a file an earlier branch produced — and resume.
6. The branch layout
Nothing is designed, written or committed outside a session: the first act of a mission is the
session folder and a validated request.json. Before any file outside the session folder is read in
order to change it, and before any file outside the session folder is written, state.json and
step-1/parallel-1/request/request.json exist and validate-request is green on them. An agent that
finds itself editing routed source, or publishing it, with no step-N/parallel-M under a session
stops with SESSION_MISSING and reports it — it does not write the session afterwards, because a
session written after the work records the work instead of gating it.
The session root is <Source>/.worktrees/sessions/<sessionId>/, with state.json beside the steps.
One branch is step-<N>/parallel-<M>/, and parallel-1 always exists:
step-<N>/parallel-<M>/
request/request.json the gate in; the orchestrator writes it
response/response.json the gate out; the only thing routing reads
response/response.md the operator's document kind
response/<other>.md when the operator has one
response/data/<name>.json machine kinds
response/artifacts/<file> everything else
<exchange>/request/ and response/ a nested exchangePaths point differently depending on where they are written: inside request.json inputs they run
from the session root (step-1/parallel-1/response/response.md); inside response.json fields and
an operator’s Outputs table they run from the branch (response/response.md).
7. The two validators, then routing
request/request.json -> validate-request.mjs -> agent writes response/ -> validate-response.mjs + the operator's validate.mjs -> routevalidate-response.mjs checks the shared gate and loads each kind’s contract; the operator’s own
validate.mjs checks what only that operator can check. Only then does routing look at
response.json:
doneadvances the chain to the next step the workflow names;waitingruns the nested exchange the response awaits, then resumes the same agent while sibling branches keep running;blockedreadsstop.
“A response that fails either validator does not route. Prose in response.md does not route. Only a
validated field of response.json does.”
8. Stop codes
A blocked branch carries a code. The code is looked up in the merged registry — operators/errors.json
for the codes several operators share, plus the operator’s own errors.json — for its domain, and
that domain resolves in routing.json to one of four kinds: operator invokes the named operator,
resume re-enters the same operator in a new step, user stops and reports what a person must
decide, external stops and reports what outside the runtime must change. A code whose disposition
is fallback never blocks: the agent performs the fallback, records it under ## Fallbacks taken,
and continues. See Stop codes and
the full registry.
9. Resume
A blocked branch stays on disk as evidence. It re-enters the same operator as a new agent in
step-(N+1)/parallel-1, with request.json.resume naming the blocked step and parallel. A resume
that adds no evidence, constraint, inventory or approval delta stops with NO_PROGRESS, and
SKILL.md is explicit about what that means: “the same input reached the same wall: report the wall
rather than trying again.”
10. The runtime a session is audited against
Nothing above needs a running product until a surface has to be looked at or a journey walked. When
one does, the runtime is not started by the operator that needs it: platform.operate owns it, and
it climbs one ladder, one rung per invocation, each rung attested before the next is attempted.
| Rung | What it does |
|---|---|
stack-up | Brings up the environment’s declared infrastructure and waits for its readiness probes |
locate | Resolves the project’s roles to routed checkouts through the workspace routes, never by directory name |
start-role | Starts a role’s server from the integration worktree, backend before frontend |
serve | Merges one session’s commit into the integration branch and runs the server on the result |
restart / reset | Starts the same head again, or clears the build cache first; both asked for by name |
stop | Kills the pid the entry recorded and releases the lease |
The branch it serves is the point. One product has one integration branch and one server on one fixed
port, and a session does not get a server of its own: serve merges its commit in and restarts that
one server, so the served head carries the work of every session that asked. Keeping the port fixed is
what keeps an identity client’s redirect URIs, a backend’s allowed origins and every registered
callback true; keeping it one branch is what makes two changes meet early, at integration, where a
conflict is a person’s five minutes rather than a blocked publication.
Two consequences are worth knowing before you read a receipt. A consumer never asks whether the served head equals the commit it applied — it asks whether its commit is an ancestor of that head, and both are printed side by side. And a session that asks while another is merging is queued behind the lease and told where it stands, rather than given a second server on another port.
11. When UAT runs
uat.verify drives a real product as a real person, and it is the one proof that cannot be inferred
from anything else, so it is worth saying plainly when it runs and when its absence is honest.
It runs only in a workflow that carries a uat.verify step — after this round, every example that
writes frontend source under mode: apply: frontend-refine, frontend-reconstruct,
frontend-new-surface, frontend-with-uat and full-feature. backend-feature writes no surface
and its when says so; release and content-unit publish no surface either.
Inside such a chain, exactly two things must come from outside it:
- a person who asked, by name —
requestedBy,featureandflowhave no defaults and the workflow declares them underasks, so the branch refuses rather than inventing a requester; - both admissions taken at the pinned commit — the
frontend-surface-auditand thequality-verification— plus theroutereceipt whose endpoint the run drives.
Everything else the run needs, it makes. A missing record is created, not reported. A flow nobody has run yet has no folder, no flow document, no seed, no account and no approved reference, and none of that stops anything:
- the flow document and the seed are drafted from the shipped template and named in the receipt as drafts, because a drafted flow is honest and an absent flow would have been a stop;
- the account is provisioned by
platform.operateagainst the identity the runtime registry declares for the bound route, its password set from the sealed shared credential resolved by name. A flow names its actors by alias and every alias gets its own dedicated account, per environment, inaccounts.<env>.json.IDENTITY_MISSINGis the hand-off that asks for this, not a verdict; - the first run leaves a candidate reference under
snapshots/, which only a person promotes.
The credential is a name everywhere it is written down. Its value reaches the request body of the provider’s administrative call, or the field of a sign-up or sign-in form in a driven browser, and nothing else — no file, no fixture, no recorded command, no capture, no receipt. Capture begins only after the sign-in redirect lands, and the validators scan the whole flow folder rather than trusting the promise.
Two stops on this path are honest, and they are the only two: a provider, sealed file or store that
cannot be reached at all is PROVISIONING_UNAVAILABLE, and a registry entry that declares no identity
is INVALID_INPUT naming the field it lacks. A route that answers with a sign-in screen is not
RUNTIME_UNAVAILABLE: it is serving, and what was missing was an identity.
The flow folder has one shape — flow.md, accounts.<env>.json, seed/, snapshots/,
runs/<runId>/ with runId as <yyyymmdd-HHMMss>-<commit7>, latest.json and history.md — and
templates/uat/README.md is its contract. The host repository tracks it, so a run’s evidence travels
with the product; only the session folder beside it is ignored.
12. The end of the session
The session folder is created by the orchestrator before the first agent starts and deleted when
git.publish finishes. What survives is what the steps published — commits, business heads, UAT
pairs, remote runs — and any audit record the owner copied out first. A blocked session keeps its
folder, and its session branch, for the resume.