Technical contracts / workflow
GraphSpec
GraphSpec describes the work, allowed control paths, and typed data flow for one run.
What GraphSpec controls
Use this reference when editing graph.json. It explains node order and where each node gets input; the contract tables list the allowed fields and node shapes. A graph is the full workflow document, while a node is one named part that may call a worker, group other nodes, or end the run.
In the built-in software-change graph, one node asks a worker to edit the repository, verifier nodes judge the result, and structural nodes decide whether to finish or enter a bounded repair path. Bindings carry typed results between those nodes.
GraphSpec does not choose Codex or Claude, a provider, a model, machine size, or credential values. RuntimePlan controls those execution choices.
A minimal GraphSpec
This graph accepts an object with one string field named task. Its root sequence passes that value to one worker, then ends the run successfully; because the worker changes the shared checkout and returns no structured data, both output sections use null.
{
"profile": "openengine.graph.full/v1",
"initialInput": {
"kind": "record",
"fields": {
"task": {"type": {"kind": "string"}, "required": true}
}
},
"policy": {"policy": "policy.native-v2@1", "default": "deny"},
"root": {
"kind": "seq",
"name": "run",
"state": {
"kind": "record",
"fields": {
"task": {"type": {"kind": "string"}, "required": true}
}
},
"children": [
{
"kind": "step",
"name": "worker",
"worker": "builtin.agent.software-worker@1",
"instructions": "Complete the requested change in the shared workspace.",
"input": {
"kind": "record",
"fields": {
"task": {"type": {"kind": "string"}, "required": true}
}
},
"output": {"kind": "null"},
"inputBindings": [
{
"target": ["task"],
"value": {"source": "state", "path": ["task"]}
}
],
"writeBindings": [],
"attempts": 1
},
{
"kind": "succeed",
"name": "done",
"output": {"kind": "null"},
"bindings": []
}
],
"promotedStatePaths": []
}
}profile | Selects the full v1 graph language. |
|---|---|
initialInput | Defines the JSON shape that input.json must match. |
policy | Selects the versioned policy checked before workers run. |
root | A seq node runs its children in the order written. |
worker | The step reads task from sequence state and sends it to one registered worker. |
done | The succeed node is the required explicit end of the normal path. |
RuntimePlan needs a binding for worker, since that node executes an agent. The structural run node and terminal done node do not receive runtime bindings.
GraphSpec contract
Use this contract when you edit raw graph JSON or need to understand a schema error. GraphSpec is a recursive tree: the root contains a node, and structural nodes may contain more nodes. Closed means the document accepts only the listed fields and kinds.
GraphSpec requires every document field. The graph can describe work and control flow, but it cannot contain typed commands, executable paths, endpoints, credentials, or provider configuration. Optional instructions guide a worker; they do not grant runtime authority.
openengine.graph.full/v1profile · initialInput · policy · rootstep · verifierseq · choice · par · loop · mapsucceed · failDocument
Fields found at the top level.
profile- Selects the required graph language: openengine.graph.full/v1.
initialInput- Declares the closed PayloadType that submitted input must match.
policy- Points to a versioned policy. Default deny grants nothing unless that policy allows it.
root- Contains one GraphNode. Structural nodes may contain more GraphNodes.
Wire rules
Naming and size rules shared by the document.
kind- Selects one allowed object shape. Zeroshot rejects unknown kinds and fields.
identifiers- Node, field, and enum names use 1–128 bytes; a letter or _ comes first.
stable refs- Worker and policy references use name@positiveVersion and allow at most 256 bytes.
field paths- A field path is a non-empty identifier array with at most 64 segments.
collections- Non-empty grammar lists and enum sets allow at most 4,096 entries.
Node contracts
All nine node kinds need a stable name. A structural node contains other nodes; an executable or terminal node is a leaf, so it cannot contain children. Read the group summary first, then check the field list for the kind in your graph.
Executable
Call a registered worker. RuntimePlan supplies a binding for each node in this group.
step
Ask one worker to do work and return ordinary output.
name- Gives the node its stable NodeName.
worker- Selects an exact versioned WorkerRef.
instructions?- Adds optional prompt guidance; maximum 16,384 UTF-8 bytes.
input- Declares the PayloadType the worker receives.
output- Declares the PayloadType the worker returns.
inputBindings- Copy DataSelector values into named input fields.
writeBindings- Copy out, signal, or diagnostic values into state.
timeoutMs?- Positive JavaScript-safe integer. Omit to run until completion or cancellation.
attempts- Full-v1 allows 1–100; the current runtime admits exactly 1.
verifier
Ask one worker to judge work and return finite signals plus diagnostic data.
name- Gives the node its stable NodeName.
worker- Selects an exact versioned verifier WorkerRef.
instructions?- Adds optional prompt guidance; maximum 16,384 UTF-8 bytes.
input- Declares the PayloadType the verifier receives.
output- Declares the PayloadType the verifier returns.
inputBindings- Copy DataSelector values into named input fields.
writeBindings- Copy out, signal, or diagnostic values into state.
timeoutMs?- Positive JavaScript-safe integer. Omit to run until completion or cancellation.
attempts- Full-v1 allows 1–100; the current runtime admits exactly 1.
signals- Maps signal fields to non-empty finite sets of enum labels.
diagnostic- Declares the diagnostic PayloadType.
Structural
Control order, branching, concurrency, and repetition while keeping local state.
seq
Run child nodes in the order written.
name- Gives the group its stable NodeName.
state- Declares the group's local PayloadType.
children- Lists one or more GraphNodes in execution order.
promotedStatePaths- Exposes selected state paths to the enclosing group.
choice
Test branches in written order and run the first one whose guard is true.
name- Gives the group its stable NodeName.
state- Declares the group's local PayloadType.
branches- Lists one or more { when: Guard, node: GraphNode } branches.
otherwise?- Runs this optional fallback GraphNode if no guard is true.
promotedStatePaths- Exposes paths defined by every alternative that can complete.
par
Run branches concurrently, then continue when the selected join condition is met.
name- Gives the group its stable NodeName.
state- Declares the group's local PayloadType.
branches- Lists one or more GraphNodes to run concurrently.
promotedStatePaths- Exposes state paths that are safe for the selected join.
join- all, any, quorum { count }, or first { when }.
loop
Run the body once, then decide whether to stop or repeat.
name- Gives the group its stable NodeName.
state- Declares the group's local PayloadType.
body- Contains the GraphNode that repeats.
until?- Tests an optional Guard after each body run; loops are do-while.
maxIterations- Sets a positive iteration limit; maximum 100.
promotedStatePaths- Exposes paths defined when the loop completes.
map
Run one body instance for each item in a selected array.
name- Gives the group its stable NodeName.
state- Declares the group's local PayloadType.
body- Contains the GraphNode run for each item.
over- Selects an array with a DataSelector.
maxItems- Sets a positive item limit; maximum 1,024.
promotedStatePaths- Collects exposed array paths in input order.
Terminal
End the selected control path and record success or failure.
succeed
Finish successfully and return typed output.
name- Gives the terminal node its stable NodeName.
output- Declares a null or record PayloadType.
bindings- Copy DataSelector values into terminal output fields.
fail
Finish unsuccessfully with one finite failure reason.
name- Gives the terminal node its stable NodeName.
reason- Returns an EnumLabel. Zeroshot reserves the label unhandled.
Payload types
A PayloadType is a contract for the shape of input, state, or output. Zeroshot checks that a value produced by one node fits the type expected at its destination before the run starts.
null · boolean · integer · number · string | The allowed primitive kinds. Integer is the only primitive that widens, to number. |
|---|---|
record | A named field map. Each field declares { type: PayloadType, required: boolean }. |
array | A list whose items all use one recursive PayloadType. |
enum | A non-empty finite set of unique identifier labels. |
Type compatibility follows structure. Arrays compare their item types; enum labels must fit within the destination set, and records compare fields recursively. Every required target field must also be required in the source, though the source may contain extra fields. Unions, references, tuples, regex constraints, and arbitrary JSON Schema are outside this type system.
Selectors, bindings, and state
State is typed working memory owned by a structural node. A selector reads one value; a binding copies that value into a node input, state field, or terminal output. Nothing moves implicitly.
Data flow
DataSelector | { source: state | item, path } reads from state or the current map item. Only that map body may use item. |
|---|---|
InputBinding | { target, value: DataSelector } writes selected data into executable input or terminal output. |
NodeOutputSelector | { node, channel: out | signal | diagnostic, path } reads a completed node. signal and diagnostic are verifier-only. |
WriteBinding | { value: NodeOutputSelector, target } copies successful node data into state. |
promotedStatePaths | Lists the local-state paths exposed to the enclosing structural group. |
A child group normally keeps its state private. When an enclosing group needs one of those values, the child lists the field path in promotedStatePaths; GraphSpec calls that explicit handoff state promotion.
State promotion rules
state | Incoming state must subtype each structural group's declared local state. |
|---|---|
seq · loop · par/all | Every promoted path must exist when the group completes normally. |
choice · non-all par | Every alternative that can complete must define a compatible promoted value. |
map | A promoted array collects one typed body write per item in input order. |
V1 builds a payload from individual required leaves rather than binding the whole object in one operation. Executable inputs and succeed outputs must therefore be null or constructible records, and binding targets cannot overlap. Runtime errors stay in the control system instead of becoming readable data.
Guards and joins
A guard is a typed yes-or-no check over finite control results, such as a verifier signal or a timeout. A join tells a parallel group when enough branches have completed to continue.
Guard grammar
in | One ControlSelector and a non-empty enum-label set. |
|---|---|
all | A non-empty guard list where every guard must be true. |
any | A non-empty guard list where at least one guard must be true. |
not | One nested guard. |
k_of_n | A positive count, non-empty selector list, and enum-label set. |
k_of_map | A positive count, one bounded map selector, and enum-label set. |
Parallel joins
all | Continue after every branch completes normally. |
|---|---|
any | Continue after one branch completes normally. |
quorum | Continue after count branches complete normally. |
first | Continue when a completed branch makes the authored Guard true. |
Control domains
ControlSelector | { name, source: signal | error | group, field? }. |
|---|---|
error | timeout · crash · malformed · refusal |
loop.terminated | converged · exhausted |
map.overflow | ok · overflow |
par.joined | reached · quorum_unreachable for all, any, and quorum. |
par.raced | satisfied · no_satisfier for first. |
verifier.signal | The finite labels declared by the selected signal field. |
Guards form a typed control syntax tree rather than executable source text, so a choice tests them in authored order and selects the first true branch. Guard fields accept none of the following: JavaScript, JSONPath, regular expressions, commands, or free-form text.
How Zeroshot accepts a graph
Valid JSON passes only the parser. Zeroshot accepts a graph after four separate checks:
- 1. ParseRead the closed wire shape and check names, fields, and size bounds.
- 2. VerifyCheck types, data flow, reachable paths, termination, state promotion, and ceilings.
- 3. ResolveFind each exact worker version and compare its declared contract.
- 4. CompileBuild canonical intermediate representation (IR), structural bounds, and a SHA-256 identity.
Node names must be unique across the graph. Every output or control reference must point to a known earlier result on every possible path, and references cannot form a cycle. Choices need reachable, exhaustive branches; loops use do-while behavior. Every normal-success path must reach an explicit terminal node.
Normative limits
Graph nodes | 4,096 |
|---|---|
Graph depth | 64 |
Guard nodes | 4,096 |
Assignments per finite check | 65,536 |
Loop iterations | 100 |
Map items | 1,024 |
Attempts per executable node | 100 in full-v1; current runtime admits 1 |
One-run executable node count | 65,536 |
Total loop-body entries | 65,536 |
Peak concurrency | 1,024 |
Only step and verifier nodes count as executions. For a bounded retry, put the work inside a graph-level loop or repair path. timeoutMs is optional; omit it to run until completion or cancellation. An explicit value must be a positive JavaScript-safe integer. Built-in nodes and provider sessions have no execution deadline. Cloud applies a separate 72-hour limit to the whole run.
Start from a working graph
Export a built-in full-profile graph before editing raw JSON. After you change it, replace --template with --graph ./graph.json in the run command.
zeroshot template list
zeroshot template show software-change > graph.json
zeroshot --help