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.maxIterationsandmaxReviewRestarts, the cycle and repair limits.- One node per role:
generator,evaluator,repair, and aseatper reviewer with itslane, 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)
npx tsx review-loop.ts:
Output
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 whenrequireDiversity 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-recordedevent. A primary call and a fallback call have separate records. A call without a reported identity recordseffective: null. - Rejected engine records. For scripted events, the loop status records
invalid receipts in
engineReceiptRejections, each with codeINVALID_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.
evidencePathsnames 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
ABORTEDpauses the run.ENGINE_UNAVAILABLEskips 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.
createGraphExecutorrefuses a mismatch withSTORED_GRAPH_MISMATCHbefore 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
failwithCONVERGENCE_REVIEW_EVIDENCE_INVALIDand dispatches no seat. Reopening the run keeps that failure. Correct the evaluator and start a new run.
Limits
maxIterations,maxReviewRestartsandretryCapPerNodebound the dispatch count the plan describes.seatConcurrencysets the review batch size.- The output of a
completedecision gives the cycle count (iterations), the repair count (restarts), the result for each seat, and any blocking findings from seats outside the accepted quorum.
Full file
Full file
examples/review-loop.ts
Next steps
- A review panel with a threshold: the same idea
inside a
workflow(), withagreeas the threshold. - Outside graph types: the contract this form is built on, and how to write a form of your own.
- Graph executor: the failure codes the form consumes and the recovery rules.