Skip to main content
Build a draft, review and repair cycle as a stored graph, with a quorum of independent reviewers and the writer kept out of it. Use convergence when the loop must be a graph form the executor records and resumes; for the same idea inside a workflow(), a stage with reviewedBy is shorter. This is the eval loop as a form: reviewers decide whether the draft goes to repair or the loop ends. The definition names one generator, one done check, one repair node and the review seats:
examples/review-loop.ts (excerpt)
convergence sends the draft through the done check and then to the reviewers. When reviewers return findings, the repair node gets them and the draft goes round again. The loop ends when enough reviewers accept or a limit runs out. A review loop needs:
  • quorum, how many seats must accept.
  • maxIterations and maxReviewRestarts, the cycle and repair limits.
  • One node per role: generator, evaluator, repair, and a seat per reviewer with its lane, the engine identity it runs under.

Compile and decide

The form is pure: it folds recorded events into state and returns the next command. Compile it, fold the events, and ask what runs next:
examples/review-loop.ts (excerpt)
The example scripts the events for two passing reviews from different providers and model families, and calls no engine. Run it with npx tsx review-loop.ts:
Output
One iteration, no repair, both seats accepted and no findings: the decision is complete. planDigest is the digest of the plan the form resolved, and bounds are the dispatch counts the plan describes.

Exclude the writer

For engine calls the graph executor manages, a reviewer can’t count toward the quorum if its reported provider or model family matches any writer or repair call, even when requireDiversity is false, and even for a cached pass after a repair. A reviewer without a reported provider and model family can’t count. With requireDiversity, no two seats in the quorum can share a provider or a model family either. The definition must keep every writer and repair target, including declared substitutions, separate from every review target and substitution by both provider and model family. Different adapters, models or lane names don’t remove a conflict. Skippable seats follow the same rule. The check rests on what the engine reported. A call that died before reporting counts as all of its declared targets, and the executor records an unknown identity when it recovers an unfinished engine attempt. The runtime doesn’t verify the provider behind an engine’s report, and calls made inside an adapter or wrapper aren’t recorded separately. Data-only nodes have no engine identity, so their results don’t establish provider separation for work done outside the runtime’s engine calls.

Read the records

  • Node records. The form accepts dispatch, completion, failure, pause and resume records for each node attempt.
  • Engine records. Each call through the executor records its requested and reported adapter, provider, model family and model in an engine-attempt-recorded event. A primary call and a fallback call have separate records. A call without a reported identity records effective: null.
  • Rejected engine records. For scripted events, the loop status records invalid receipts in engineReceiptRejections, each with code INVALID_ENGINE_RECEIPT, the node id, position, sequence and reason. The field is absent until a receipt is rejected, and a rejection leaves accepted identities and the quorum unchanged.
  • Review records. A seat pass includes its confidence, input hashes and workspace fingerprint. Evaluator evidence names one proof artifact digest; every seat dispatch receives it and every seat result must echo it. A missing or changed digest makes the result invalid and dispatches the seat again while its retry limit permits, and its findings can’t enter repair inputs or finding counts. evidencePaths names the input hashes that can invalidate a seat; leave it out and every input hash counts.
  • Repair and policy records. Findings can send the form to its repair node; a later cycle keeps valid seat passes and reruns invalid seats. New evaluator evidence invalidates each seat whose named input hash changed. A producer can also invalidate an in-flight seat or record a limit pause.

Failure

  • ABORTED pauses the run. ENGINE_UNAVAILABLE skips a declared skippable seat and pauses for a required one. Other node failures use the declared retry cap before a required seat pauses as unresolved.
  • A stored plan from another version is refused. The convergence graph type is at version 3, and a stored plan records the type version it was compiled from. createGraphExecutor refuses a mismatch with STORED_GRAPH_MISMATCH before a node starts, and doesn’t convert the plan. Start a new run to use a plan compiled from the current version.
  • An evaluator without valid review evidence returns fail with CONVERGENCE_REVIEW_EVIDENCE_INVALID and dispatches no seat. Reopening the run keeps that failure. Correct the evaluator and start a new run.

Limits

  • maxIterations, maxReviewRestarts and retryCapPerNode bound the dispatch count the plan describes. seatConcurrency sets the review batch size.
  • The output of a complete decision gives the cycle count (iterations), the repair count (restarts), the result for each seat, and any blocking findings from seats outside the accepted quorum.
examples/review-loop.ts

Next steps