Worked example / custom graph

Build a review loop

Write a GraphSpec that sends one coding task through a worker and two independent reviewers. A rejection returns to the same worker node with both diagnostics.

Files you will build

This example uses one typed input document, a GraphSpec, and a RuntimePlan. The graph controls order and data flow; the runtime file binds only the three nodes that execute agents.

input.jsonSupplies the task and empty feedback fields for the first pass.
review-loop.graph.jsonDefines the worker, reviewers, loop, and terminal result.
review-loop.runtime.jsonChooses Codex, OpenAI, models, sessions, and connection fields.

1. Define input and graph state

GraphSpec starts with a profile identifier, an input type, and a policy. The record has a task plus one feedback field per reviewer. Both feedback strings begin empty, then the reviewers overwrite them after each pass.

Graph envelope
Zeroshot
{
  "profile": "openengine.graph.full/v1",
  "initialInput": {
    "kind": "record",
    "fields": {
      "acceptanceFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "codeFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "task": {
        "type": {
          "kind": "string"
        },
        "required": true
      }
    }
  },
  "policy": {
    "policy": "policy.native-v2@1",
    "default": "deny"
  }
}

The root sequence, loop, and loop body repeat this state type because each structural node owns local state. A binding moves values into executable input, while a promoted path moves updated state back to the enclosing node.

2. Add the worker

The worker receives the original task and both diagnostics. On the first pass those diagnostics are empty; after a rejection, the loop invokes this same node again with the latest reviewer messages.

worker step
Zeroshot
{
  "kind": "step",
  "name": "worker",
  "worker": "builtin.agent.software-worker@1",
  "instructions": "Implement the requested change. If reviewer feedback is present, address both diagnostics before returning. Work in the shared repository and run focused checks.",
  "input": {
    "kind": "record",
    "fields": {
      "acceptanceFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "codeFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "task": {
        "type": {
          "kind": "string"
        },
        "required": true
      }
    }
  },
  "output": {
    "kind": "null"
  },
  "inputBindings": [
    {
      "target": [
        "task"
      ],
      "value": {
        "source": "state",
        "path": [
          "task"
        ]
      }
    },
    {
      "target": [
        "acceptanceFeedback"
      ],
      "value": {
        "source": "state",
        "path": [
          "acceptanceFeedback"
        ]
      }
    },
    {
      "target": [
        "codeFeedback"
      ],
      "value": {
        "source": "state",
        "path": [
          "codeFeedback"
        ]
      }
    }
  ],
  "writeBindings": [],
  "attempts": 1
}
nameRuntimePlan must contain a node binding with the exact key worker.
inputBindingsCopies task and reviewer feedback from loop state into agent input.
outputUses null because the agent changes files in the shared repository.
writeBindingsStays empty because the worker produces no structured state value.

3. Run two reviewers together

A par node starts the acceptance and code verifiers against the same checkout. Verifiers do not edit files. Each returns a verdict signal and a diagnostic message; its write binding stores that message in the matching feedback field.

reviews group
Zeroshot
{
  "kind": "par",
  "name": "reviews",
  "state": {
    "kind": "record",
    "fields": {
      "acceptanceFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "codeFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "task": {
        "type": {
          "kind": "string"
        },
        "required": true
      }
    }
  },
  "branches": [
    {
      "kind": "verifier",
      "name": "acceptance",
      "worker": "builtin.agent.acceptance-verifier@1",
      "input": {
        "kind": "record",
        "fields": {
          "task": {
            "type": {
              "kind": "string"
            },
            "required": true
          }
        }
      },
      "output": {
        "kind": "null"
      },
      "inputBindings": [
        {
          "target": [
            "task"
          ],
          "value": {
            "source": "state",
            "path": [
              "task"
            ]
          }
        }
      ],
      "writeBindings": [
        {
          "value": {
            "node": "acceptance",
            "channel": "diagnostic",
            "path": [
              "message"
            ]
          },
          "target": [
            "acceptanceFeedback"
          ]
        }
      ],
      "attempts": 1,
      "signals": {
        "verdict": [
          "accepted",
          "rejected"
        ]
      },
      "diagnostic": {
        "kind": "record",
        "fields": {
          "message": {
            "type": {
              "kind": "string"
            },
            "required": true
          }
        }
      },
      "instructions": "Check the implementation against the requested behavior. Do not edit files. Accept only with concrete evidence; otherwise return actionable feedback."
    },
    {
      "kind": "verifier",
      "name": "code",
      "worker": "builtin.agent.code-verifier@1",
      "input": {
        "kind": "record",
        "fields": {
          "task": {
            "type": {
              "kind": "string"
            },
            "required": true
          }
        }
      },
      "output": {
        "kind": "null"
      },
      "inputBindings": [
        {
          "target": [
            "task"
          ],
          "value": {
            "source": "state",
            "path": [
              "task"
            ]
          }
        }
      ],
      "writeBindings": [
        {
          "value": {
            "node": "code",
            "channel": "diagnostic",
            "path": [
              "message"
            ]
          },
          "target": [
            "codeFeedback"
          ]
        }
      ],
      "attempts": 1,
      "signals": {
        "verdict": [
          "accepted",
          "rejected"
        ]
      },
      "diagnostic": {
        "kind": "record",
        "fields": {
          "message": {
            "type": {
              "kind": "string"
            },
            "required": true
          }
        }
      },
      "instructions": "Review correctness, safety, integration, and maintainability. Do not edit files. Return actionable feedback when rejecting."
    }
  ],
  "promotedStatePaths": [
    [
      "acceptanceFeedback"
    ],
    [
      "codeFeedback"
    ]
  ],
  "join": {
    "kind": "all"
  }
}

The all join waits for both reviewers. The group then promotes both feedback paths into the iteration state, and the iteration promotes them again into loop state so the next worker pass can read them.

4. Stop on two acceptances

Loops evaluate until after the body, so the worker always gets at least one pass. The guard converges only when both verifier signals equal accepted; any rejection starts another iteration until the four-pass ceiling is reached.

Loop exit
Zeroshot
{
  "until": {
    "kind": "all",
    "guards": [
      {
        "kind": "in",
        "value": {
          "name": "acceptance",
          "source": "signal",
          "field": "verdict"
        },
        "labels": [
          "accepted"
        ]
      },
      {
        "kind": "in",
        "value": {
          "name": "code",
          "source": "signal",
          "field": "verdict"
        },
        "labels": [
          "accepted"
        ]
      }
    ]
  },
  "maxIterations": 4
}

A loop reports converged or exhausted through its group control. The final choice turns that control value into an explicit graph terminal.

Loop result
Zeroshot
{
  "kind": "choice",
  "name": "loop_result",
  "state": {
    "kind": "record",
    "fields": {
      "acceptanceFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "codeFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "task": {
        "type": {
          "kind": "string"
        },
        "required": true
      }
    }
  },
  "branches": [
    {
      "when": {
        "kind": "in",
        "value": {
          "name": "review_loop",
          "source": "group",
          "field": "terminated"
        },
        "labels": [
          "converged"
        ]
      },
      "node": {
        "kind": "succeed",
        "name": "done",
        "output": {
          "kind": "null"
        },
        "bindings": []
      }
    }
  ],
  "otherwise": {
    "kind": "fail",
    "name": "review_attempts_exhausted",
    "reason": "review_attempts_exhausted"
  },
  "promotedStatePaths": []
}

5. Save the complete GraphSpec

Save this document as review-loop.graph.json. Structural nodes such asrun, review_loop, reviews, and loop_resultneed no RuntimePlan entries because they do not start agents.

review-loop.graph.json
Zeroshot
{
  "profile": "openengine.graph.full/v1",
  "initialInput": {
    "kind": "record",
    "fields": {
      "acceptanceFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "codeFeedback": {
        "type": {
          "kind": "string"
        },
        "required": true
      },
      "task": {
        "type": {
          "kind": "string"
        },
        "required": true
      }
    }
  },
  "policy": {
    "policy": "policy.native-v2@1",
    "default": "deny"
  },
  "root": {
    "kind": "seq",
    "name": "run",
    "state": {
      "kind": "record",
      "fields": {
        "acceptanceFeedback": {
          "type": {
            "kind": "string"
          },
          "required": true
        },
        "codeFeedback": {
          "type": {
            "kind": "string"
          },
          "required": true
        },
        "task": {
          "type": {
            "kind": "string"
          },
          "required": true
        }
      }
    },
    "children": [
      {
        "kind": "loop",
        "name": "review_loop",
        "state": {
          "kind": "record",
          "fields": {
            "acceptanceFeedback": {
              "type": {
                "kind": "string"
              },
              "required": true
            },
            "codeFeedback": {
              "type": {
                "kind": "string"
              },
              "required": true
            },
            "task": {
              "type": {
                "kind": "string"
              },
              "required": true
            }
          }
        },
        "body": {
          "kind": "seq",
          "name": "review_iteration",
          "state": {
            "kind": "record",
            "fields": {
              "acceptanceFeedback": {
                "type": {
                  "kind": "string"
                },
                "required": true
              },
              "codeFeedback": {
                "type": {
                  "kind": "string"
                },
                "required": true
              },
              "task": {
                "type": {
                  "kind": "string"
                },
                "required": true
              }
            }
          },
          "children": [
            {
              "kind": "step",
              "name": "worker",
              "worker": "builtin.agent.software-worker@1",
              "instructions": "Implement the requested change. If reviewer feedback is present, address both diagnostics before returning. Work in the shared repository and run focused checks.",
              "input": {
                "kind": "record",
                "fields": {
                  "acceptanceFeedback": {
                    "type": {
                      "kind": "string"
                    },
                    "required": true
                  },
                  "codeFeedback": {
                    "type": {
                      "kind": "string"
                    },
                    "required": true
                  },
                  "task": {
                    "type": {
                      "kind": "string"
                    },
                    "required": true
                  }
                }
              },
              "output": {
                "kind": "null"
              },
              "inputBindings": [
                {
                  "target": [
                    "task"
                  ],
                  "value": {
                    "source": "state",
                    "path": [
                      "task"
                    ]
                  }
                },
                {
                  "target": [
                    "acceptanceFeedback"
                  ],
                  "value": {
                    "source": "state",
                    "path": [
                      "acceptanceFeedback"
                    ]
                  }
                },
                {
                  "target": [
                    "codeFeedback"
                  ],
                  "value": {
                    "source": "state",
                    "path": [
                      "codeFeedback"
                    ]
                  }
                }
              ],
              "writeBindings": [],
              "attempts": 1
            },
            {
              "kind": "par",
              "name": "reviews",
              "state": {
                "kind": "record",
                "fields": {
                  "acceptanceFeedback": {
                    "type": {
                      "kind": "string"
                    },
                    "required": true
                  },
                  "codeFeedback": {
                    "type": {
                      "kind": "string"
                    },
                    "required": true
                  },
                  "task": {
                    "type": {
                      "kind": "string"
                    },
                    "required": true
                  }
                }
              },
              "branches": [
                {
                  "kind": "verifier",
                  "name": "acceptance",
                  "worker": "builtin.agent.acceptance-verifier@1",
                  "input": {
                    "kind": "record",
                    "fields": {
                      "task": {
                        "type": {
                          "kind": "string"
                        },
                        "required": true
                      }
                    }
                  },
                  "output": {
                    "kind": "null"
                  },
                  "inputBindings": [
                    {
                      "target": [
                        "task"
                      ],
                      "value": {
                        "source": "state",
                        "path": [
                          "task"
                        ]
                      }
                    }
                  ],
                  "writeBindings": [
                    {
                      "value": {
                        "node": "acceptance",
                        "channel": "diagnostic",
                        "path": [
                          "message"
                        ]
                      },
                      "target": [
                        "acceptanceFeedback"
                      ]
                    }
                  ],
                  "attempts": 1,
                  "signals": {
                    "verdict": [
                      "accepted",
                      "rejected"
                    ]
                  },
                  "diagnostic": {
                    "kind": "record",
                    "fields": {
                      "message": {
                        "type": {
                          "kind": "string"
                        },
                        "required": true
                      }
                    }
                  },
                  "instructions": "Check the implementation against the requested behavior. Do not edit files. Accept only with concrete evidence; otherwise return actionable feedback."
                },
                {
                  "kind": "verifier",
                  "name": "code",
                  "worker": "builtin.agent.code-verifier@1",
                  "input": {
                    "kind": "record",
                    "fields": {
                      "task": {
                        "type": {
                          "kind": "string"
                        },
                        "required": true
                      }
                    }
                  },
                  "output": {
                    "kind": "null"
                  },
                  "inputBindings": [
                    {
                      "target": [
                        "task"
                      ],
                      "value": {
                        "source": "state",
                        "path": [
                          "task"
                        ]
                      }
                    }
                  ],
                  "writeBindings": [
                    {
                      "value": {
                        "node": "code",
                        "channel": "diagnostic",
                        "path": [
                          "message"
                        ]
                      },
                      "target": [
                        "codeFeedback"
                      ]
                    }
                  ],
                  "attempts": 1,
                  "signals": {
                    "verdict": [
                      "accepted",
                      "rejected"
                    ]
                  },
                  "diagnostic": {
                    "kind": "record",
                    "fields": {
                      "message": {
                        "type": {
                          "kind": "string"
                        },
                        "required": true
                      }
                    }
                  },
                  "instructions": "Review correctness, safety, integration, and maintainability. Do not edit files. Return actionable feedback when rejecting."
                }
              ],
              "promotedStatePaths": [
                [
                  "acceptanceFeedback"
                ],
                [
                  "codeFeedback"
                ]
              ],
              "join": {
                "kind": "all"
              }
            }
          ],
          "promotedStatePaths": [
            [
              "acceptanceFeedback"
            ],
            [
              "codeFeedback"
            ]
          ]
        },
        "until": {
          "kind": "all",
          "guards": [
            {
              "kind": "in",
              "value": {
                "name": "acceptance",
                "source": "signal",
                "field": "verdict"
              },
              "labels": [
                "accepted"
              ]
            },
            {
              "kind": "in",
              "value": {
                "name": "code",
                "source": "signal",
                "field": "verdict"
              },
              "labels": [
                "accepted"
              ]
            }
          ]
        },
        "maxIterations": 4,
        "promotedStatePaths": []
      },
      {
        "kind": "choice",
        "name": "loop_result",
        "state": {
          "kind": "record",
          "fields": {
            "acceptanceFeedback": {
              "type": {
                "kind": "string"
              },
              "required": true
            },
            "codeFeedback": {
              "type": {
                "kind": "string"
              },
              "required": true
            },
            "task": {
              "type": {
                "kind": "string"
              },
              "required": true
            }
          }
        },
        "branches": [
          {
            "when": {
              "kind": "in",
              "value": {
                "name": "review_loop",
                "source": "group",
                "field": "terminated"
              },
              "labels": [
                "converged"
              ]
            },
            "node": {
              "kind": "succeed",
              "name": "done",
              "output": {
                "kind": "null"
              },
              "bindings": []
            }
          }
        ],
        "otherwise": {
          "kind": "fail",
          "name": "review_attempts_exhausted",
          "reason": "review_attempts_exhausted"
        },
        "promotedStatePaths": []
      }
    ],
    "promotedStatePaths": []
  }
}

6. Bind the repeated worker

RuntimePlan uses the GraphSpec node name as its map key. The worker gets anode_instance session so loop revisits reuse the same provider conversation; GraphSpec state still carries the diagnostics explicitly.

nodes.worker
Zeroshot
{
  "kind": "agent",
  "model": "gpt-5.6-sol",
  "sessionScope": "node_instance",
  "connections": {
    "openai": [
      "OPENAI_API_KEY"
    ]
  }
}

The connection declaration grants this node only OPENAI_API_KEY from the connection named openai. The JSON contains the field name, never its value.

7. Bind the reviewers

The verifier keys match acceptance and code exactly. Theirexecution scope opens a fresh provider session for each dispatch, which keeps a later review independent from the previous pass.

Reviewer bindings
Zeroshot
{
  "acceptance": {
    "kind": "agent",
    "model": "gpt-5.6-sol",
    "sessionScope": "execution",
    "connections": {
      "openai": [
        "OPENAI_API_KEY"
      ]
    }
  },
  "code": {
    "kind": "agent",
    "model": "gpt-5.6-sol",
    "sessionScope": "execution",
    "connections": {
      "openai": [
        "OPENAI_API_KEY"
      ]
    }
  }
}

This plan uses one model for all agents, but node bindings may choose different model IDs or effort values as long as the document keeps one harness and provider.

8. Save the complete RuntimePlan

Save the file as review-loop.runtime.json. The plan has exactly three keys because the graph has one step and two verifiers; an extra structural-node key would fail admission.

review-loop.runtime.json
Zeroshot
{
  "harness": "codex",
  "provider": "openai",
  "size": "medium",
  "nodes": {
    "worker": {
      "kind": "agent",
      "model": "gpt-5.6-sol",
      "sessionScope": "node_instance",
      "connections": {
        "openai": [
          "OPENAI_API_KEY"
        ]
      }
    },
    "acceptance": {
      "kind": "agent",
      "model": "gpt-5.6-sol",
      "sessionScope": "execution",
      "connections": {
        "openai": [
          "OPENAI_API_KEY"
        ]
      }
    },
    "code": {
      "kind": "agent",
      "model": "gpt-5.6-sol",
      "sessionScope": "execution",
      "connections": {
        "openai": [
          "OPENAI_API_KEY"
        ]
      }
    }
  }
}

9. Validate, then run

Save the initial values below as input.json. The empty feedback strings make the first worker input satisfy the declared record before any reviewer has run.

input.json
Zeroshot
{
  "task": "Add a health-check endpoint that returns HTTP 200. Add focused tests and keep the change scoped.",
  "acceptanceFeedback": "",
  "codeFeedback": ""
}

Validate all three files before starting an agent. This checks JSON shape, graph data flow, loop termination, worker contracts, RuntimePlan coverage, and connection declarations without submitting the run.

Validate
Zeroshot
zeroshot run \
  --title "Review loop validation" \
  --graph ./review-loop.graph.json \
  --runtime-config ./review-loop.runtime.json \
  --input ./input.json \
  --validate-only

Run from a clean local checkout when you want the accepted edits to remain in the current repository. The worker changes files in place.

Local run
Zeroshot
export OPENAI_API_KEY="..."

zeroshot run \
  --title "Verified health-check endpoint" \
  --graph ./review-loop.graph.json \
  --runtime-config ./review-loop.runtime.json \
  --input ./input.json