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.

graph.json
Zeroshot
{
  "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": []
  }
}
profileSelects the full v1 graph language.
initialInputDefines the JSON shape that input.json must match.
policySelects the versioned policy checked before workers run.
rootA seq node runs its children in the order written.
workerThe step reads task from sequence state and sends it to one registered worker.
doneThe 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.

Document and node familiesopenengine.graph.full/v1
documentGraphSpecprofile · initialInput · policy · root
root.kindGraphNode
Executablestep · verifier
Structuralseq · choice · par · loop · map
Terminalsucceed · fail

Document

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 · stringThe allowed primitive kinds. Integer is the only primitive that widens, to number.
recordA named field map. Each field declares { type: PayloadType, required: boolean }.
arrayA list whose items all use one recursive PayloadType.
enumA 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.
promotedStatePathsLists 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

stateIncoming state must subtype each structural group's declared local state.
seq · loop · par/allEvery promoted path must exist when the group completes normally.
choice · non-all parEvery alternative that can complete must define a compatible promoted value.
mapA 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

inOne ControlSelector and a non-empty enum-label set.
allA non-empty guard list where every guard must be true.
anyA non-empty guard list where at least one guard must be true.
notOne nested guard.
k_of_nA positive count, non-empty selector list, and enum-label set.
k_of_mapA positive count, one bounded map selector, and enum-label set.

Parallel joins

allContinue after every branch completes normally.
anyContinue after one branch completes normally.
quorumContinue after count branches complete normally.
firstContinue when a completed branch makes the authored Guard true.

Control domains

ControlSelector{ name, source: signal | error | group, field? }.
errortimeout · crash · malformed · refusal
loop.terminatedconverged · exhausted
map.overflowok · overflow
par.joinedreached · quorum_unreachable for all, any, and quorum.
par.racedsatisfied · no_satisfier for first.
verifier.signalThe 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. 1. ParseRead the closed wire shape and check names, fields, and size bounds.
  2. 2. VerifyCheck types, data flow, reachable paths, termination, state promotion, and ceilings.
  3. 3. ResolveFind each exact worker version and compare its declared contract.
  4. 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 nodes4,096
Graph depth64
Guard nodes4,096
Assignments per finite check65,536
Loop iterations100
Map items1,024
Attempts per executable node100 in full-v1; current runtime admits 1
One-run executable node count65,536
Total loop-body entries65,536
Peak concurrency1,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.

Terminal
Zeroshot
zeroshot template list
zeroshot template show software-change > graph.json
zeroshot --help