feat(proof): first-class bounded convergence loops in DAG schema#184
Draft
tonyketcham wants to merge 2 commits intotoeknee/flatbread-task-dag-instances-18a9from
Draft
feat(proof): first-class bounded convergence loops in DAG schema#184tonyketcham wants to merge 2 commits intotoeknee/flatbread-task-dag-instances-18a9from
tonyketcham wants to merge 2 commits intotoeknee/flatbread-task-dag-instances-18a9from
Conversation
Captures the design rationale for moving the existing --converge-on/--max-iterations CLI singleton into a DAG-native `loops` array. Decision recap: - DAG `depends_on` edges stay acyclic (causality + parallelism). - Bounded refinement (research → critique → refine, fix → test → fix) becomes an explicit DAG.loops[] config instead of a back-edge. - The CLI flag stays valid; loops just lift the same shape into the JSON file so multiple convergence tasks can stack in one run and reproducible runs do not depend on remembered flags. Out of scope (this proposal): new stopWhen predicates beyond 'no-blockers', nested loops, cross-loop coordination. Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Adds DAG.loops[] — an optional array of bounded convergence loops
that runs after the main rank loop completes. Generalizes the legacy
--converge-on/--max-iterations CLI singleton:
- Multiple loops per run (one per convergence task).
- Optional explicit `reexecute.tasks` allow-list, validated to lie
inside the convergence ancestor cone (off-cone tasks would break
filtered topological ordering).
- Default `reexecute: { kind: 'ancestors' }` matches the CLI
behavior bit-for-bit.
- `stopWhen: 'no-blockers'` is the only predicate today; the
schema is open for future predicates without further churn.
Schema, parsing, and re-execution id resolution all live in
dag.ts/converge_loop.ts; the runner consumes a single canonical
ResolvedConvergenceLoop list whether the user supplied DAG.loops or
the CLI flag (the two cannot be combined — the runner errors at
startup to avoid silent precedence rules).
DAG.budget.maxIterations continues to apply per-loop. Loops execute
sequentially in declaration order; each loop's BUDGET-EXCEEDED is
independent and surfaces via the existing per-task tally.
Tests (AVA, packages/proof/src/__tests__/loops.test.ts):
- 18 cases covering parser acceptance, validation rejections,
default-filling, ancestor-cone enforcement, multi-loop ids, and
reexecute selector resolution.
- Smoke verified end-to-end via the proof CLI: --init-only with a
loops-bearing DAG renders the canvas, and combining --converge-on
with DAG.loops fails fast with the expected message.
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary of changes
Generalizes the
--converge-on/--max-iterationsCLI singleton in@flatbread/proofinto a DAG-nativeloopsarray, so a single run can stack multiple convergence tasks (e.g. one for the implementation reviewer, one for the docs reviewer) and so DAG-emitting tooling can declare loop intent reproducibly.Resumes from cursor agent
bc-0ff9d782-…, which discussed cyclic vs acyclic task graphs inproofand concluded:depends_onis causality + parallelism, not iteration.This PR lifts that loop primitive into the DAG JSON.
What's new
{ "title": "implementation + adversarial review", "loops": [ { "id": "review-loop", "convergeOn": "review", "maxIterations": 3, "reexecute": { "kind": "ancestors" }, "stopWhen": "no-blockers" } ], "tasks": [/* … */] }convergeOnandmaxIterationsare required.reexecuteis optional.{ kind: 'ancestors' }(default) mirrors the legacy CLI behavior.{ kind: 'tasks'; tasks: [...] }is an explicit allow-list, validated to lie inside the convergence ancestor cone (off-cone tasks would break filtered topological ordering).stopWhenis optional. Today the only value is'no-blockers'; the schema is open for future predicates without further churn.idis optional and defaults toloop-${convergeOn}.DAG.budget.maxIterationskeeps applying per-loop and the existingBUDGET-EXCEEDEDterminal status carries through unchanged.DAG.loopsare mutually exclusive — supplying both fails fast at startup rather than picking a silent precedence rule.Backward compatibility
loopsparses unchanged.--converge-on <id> --max-iterations <N>keeps working end-to-end. Internally it synthesizes a single-element loops array so the runner has one canonical code path.extractConvergenceFindings, thefindings-dirsidecar contract, theextraContextstitching format, andtransitiveAncestorsare all unchanged. Existing reviewer prompts keep working.Out of scope (this PR)
stopWhenpredicates beyond'no-blockers'(e.g.'oracle-pass', score thresholds).The full design rationale is in
docs/proposals/proof-bounded-convergence-loops.md(commit 1).Files changed
docs/proposals/proof-bounded-convergence-loops.md— proposal & rationale.packages/proof/src/dag.ts—DAGConvergenceLoop,LoopReexecute,LoopStopWhen,ResolvedConvergenceLooptypes, validation,resolveConvergenceLoops().packages/proof/src/converge_loop.ts— newresolveLoopReexecuteIds()helper.packages/proof/src/run_dag.ts—runConvergenceLoopaccepts a precomputedreExecIdsset +loopId;main()iteratesdag.loops(or a synthesized loop from the CLI flag).packages/proof/src/index.ts— re-exports new types/helpers.packages/proof/src/__tests__/loops.test.ts— 18 AVA cases.Testing
pnpm --filter @flatbread/proof typecheckpnpm --filter @flatbread/proof buildpnpm exec ava packages/proof/src/__tests__/loops.test.ts— 18/18 passingpnpm test:ava— 73/73 passing across the workspacepnpm test:vitest— 46/46 passing across@flatbread/codegen+@flatbread/utilspnpm lintpnpm build(full monorepo)proof --init-only --dag <loops.json>writes the canvas;proof --dag <loops.json> --converge-on berrors with--converge-on cannot be combined with DAG.loops (DAG "loop-smoke" already declares 1 loop(s)). Remove one.Notes
The user originally asked for
/proofto drive a planning + implementation + self-review loop on this PR with Opus 4.7 (HIGH) and GPT 5.4 (MEDIUM). Three earlier resume attempts on this same agent thread errored out trying to launch the runner, so this PR ships the planning + implementation + tests directly and lets/proofride the newDAG.loopsconfig in a follow-up rather than blocking the change behind another runner spin-up. The implementation here is exactly the schema/proofwould self-review against, so the follow-up is now a one-flag config change instead of "remember to pass--converge-on review --max-iterations 3".Please don't delete this checklist!
Does this introduce any non-backwards compatible changes?
Does this include any user config changes?