Repository navigation
Limit how often a node runs with maxRuns instead of edge limits - #128
Merged
Merged
Conversation
Replace Edge.limit with an optional maxRuns on every node type. A rerun past it is still cloned but waits pending (wait reason "limit", limit_reached, limitedExecutionIds) until the owner raises maxRuns, which edits it in place, or releases it, which removes it and ends the rerun. The owner's replacement is never blocked; onLimit "fail" fails with RUN_LIMIT. Pi and DSH schemas, reminders, status text, and docs follow.
Epsirom
added a commit
that referenced
this pull request
Oct 25, 2026
Follow-up to #128. Part of RSI Mode (#118). `maxRuns` is the most runs a node's lineage can reach, and it never resets, including per round of an enclosing cycle (decided in #127). The owner adjusts it by updating the node. Before this change, that worked only while a rerun was waiting at the limit. On a running or settled head, any upsert replaced it: raising `maxRuns` ahead of time cancelled a running execution and reran it, and reran a settled one. ## What changes - An upsert whose definition differs from the current one **only in `maxRuns`** keeps a started head. A running head keeps running, and a settled one keeps its result and any hold. Later reruns (clones) use the new limit, so the limit can be raised or lowered at any time. - Any other change, including an unchanged upsert, still replaces a started head as before. A pending head (including a rerun waiting at its limit) is still edited in place. - The started head's own `node` keeps the definition it ran with; `snapshot().graph` shows the new `maxRuns`. - Docs: `docs/execution-control.md` (live updates, limits), `GraphUpdate.upsertNodes` doc, `docs/compatibility.md`, and the Pi/DSH READMEs and tool descriptions (`maxRuns`, `braid_update`). ## Tests `test/limits.test.ts` covers: - raising `maxRuns` while the head runs: no cancellation, and later reruns stop at the new limit - changing only `maxRuns` on a settled, held head: no replacement, and the hold is kept - any other change still replaces the head `npm run verify` passes (core 305, Pi 194, DSH 68, package smoke). ## Overlap with #127 #127 edits the cycle paragraphs of the same READMEs and docs. This PR puts its sentences elsewhere to avoid conflicts, but whichever merges second may need a trivial rebase.
Epsirom
added a commit
that referenced
this pull request
Oct 25, 2026
… new cycle (#127) Closes #114. Part of RSI Mode (#118). Builds on #128, where `maxRuns` replaced edge limits. ## What changes - **Validation accepts any cycle.** Unconditional, nested, and overlapping cycles, cycles with several entries or exits from any node, and decisions without an exit choice are no longer rejected. - **Closing edges only decide where each round starts.** Braid picks them per strongly connected part: it keeps the other edges as ordinary dependencies, and an edge that would complete a cycle closes it instead. Preference for closing: edges into an entry (a node an outside edge enters), then a decision's choice edges, then edges back to nodes listed earlier. Graphs accepted before keep their closing edge, except a rare cycle without an entry where a decision inside it has a choice edge back to an earlier-listed node; its round start can move. That is acceptable because the cycle rules are unreleased since 0.5; `docs/compatibility.md` notes it. `closingEdges(graph)` returns them all. - **Exits per round.** Edges that leave a cycle wait while any of its nodes reruns. For a cycle closed by a decision's choice edge they still wait for the final round. For an unconditional closing edge they fire in every round: downstream nodes outside the cycle run with each finished round (inactive ones wait for a later round). Round clones are created at the end of the scheduling pass, so exits are admitted before the next round takes the slots; #128's `maxRuns` check still applies when a clone is admitted. - **Overlapping cycles** that reach a shared node together merge: the first closing edge clones it, the second attaches to the clone as another input. - **Bounds.** Cycles are bounded by `maxRuns` on their nodes (#128): total runs, never reset per round of an enclosing cycle (tested and documented). - **Cycle notices.** Each submission or update that creates cycles emits `cycles_created` with each new cycle (`GraphCycle`: `nodeIds` starting where its rounds start, `edges` ending with its closing edge, `stops` = the decision choice edges on it, `limitedNodeIds` = its nodes with a finite `maxRuns`; at most 20, with `truncated`). `newCycles(graph, previous?)` computes the same list. Pi and DSH `braid` / `braid_update` results show it, and flag a cycle with nothing that stops or bounds it, so an accidental `test → fix` next to `fix → test` is noticed. - Pi and DSH tool descriptions, guidance, and READMEs describe the new rules. Pi's `display.ts` gets only the event-text case the exhaustive switch requires. ## Docs `docs/execution-control.md` (Cycles, Cycle notices), README graph schema and out-of-scope list, ROADMAP (#114 checked, scope row), and a 0.6 migration subsection in `docs/compatibility.md`. ## Compatibility Not labelled breaking: existing callers need no changes. TypeScript code with an exhaustive `switch` over `ExecutionEvent` needs a case for `cycles_created`. ## Decided: `maxRuns` never resets `maxRuns` is the most runs a lineage can reach, including in a nested cycle across rounds of the enclosing one; it does not reset per round. The owner raises or lowers it by updating the node; since #129, an upsert that only changes `maxRuns` keeps a started head instead of replacing it. ## Tests New `test/cycles.test.ts` covers: - an unconditional cycle with an exit that runs each round, ended at `maxRuns` - a nested cycle, and `maxRuns` counting across outer rounds - overlapping cycles merging at a shared node - cycle reports for submissions and updates, and the report cap Validation and closing-edge tests are updated, and Pi and DSH tests cover the notices. `npm run verify` passes on `main` with #128 and #129 (core 309, Pi 195, DSH 69, package smoke).
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.
Change
Part of RSI Mode (#118). Replaces the per-edge
limitadded in #124 with an optionalmaxRunson every node type. It lands before #127 (arbitrary cycles), which no longer needs to prefer limited edges when it picks closing edges, and before 0.6, so edge limits were never released.People think "review at most 3 times", not "this edge fires n-1 times", and with arbitrary cycles it is hard to tell which edge closes a cycle or to bound overlapping cycles at a shared node with one edge's limit. A node limit has none of those problems.
Behavior
maxRunscounts the executions of a node's lineage that start; skipped ones do not count. Unlimited when omitted orInfinity.maxRuns: 3on a node in a cycle allows three rounds.maxRuns, the clone is still created but stayspendingwith the new wait reasonlimit. Once its inputs are current, core emitslimit_reached(limit: "maxRuns", the waitingexecutionId) and lists it in the newBraidSnapshot.limitedExecutionIdsandgraph_waiting.limitedExecutionIds, so a run with nothing else active waits and Pi/DSH wake the owner with the waiting reminder.maxRunswith a normalupdate/upsertNodesedits the pending rerun in place and it starts.resume(orresumein an update) without raising ends the rerun: it is removed (execution_removed), the node keeps its last result, and nothing downstream reruns for it. In a cycle this mirrors the old "release without raising ends the loop": the decision chose the closing edge, so its exits stay inactive. If the same update raisesmaxRuns, the rerun starts instead.onLimit: "fail"the run fails with the new codeRUN_LIMIT.Removed
Edge.limit, the rule that a limit must sit on a cycle-closing edge, theedge_limithold source,limit_reached.edge, andEDGE_LIMIT. Cycle validation is otherwise unchanged.removeEdgesnow accepts onlyfrom,to, andchoice.Pi and DSH
maxRuns; edge schemas droplimit. Tool guidance,onLimittext, andbraid_update/braid_resumedescriptions explain raising or releasing a waiting rerun.WaitingItemof kindlimited(sourcemaxRuns) and says how to raise or release it.formatEdgeLimitandedgeLimiton completion notices are removed. Pi'sOutstandingJoband the single-shot drain warning include limited reruns.maxRuns=on nodes, thelimitwait reason, and reruns at their limit. The Pi live view restores the previous head when a rerun is released. The DSH panel drops edge limits and shows a node'smaxRuns./braid:reviewbounds its rounds withmaxRuns: 3on its writer node instead of an edge limit and still fails at the cap.Docs
Execution control (cycles, wait reasons,
graph_waiting, Limits), README graph schema and options, resource limits, the 0.6 migration indocs/compatibility.md(maxIterations nbecomesmaxRuns non a node in the cycle), ROADMAP scope table, and both adapter READMEs.Compatibility
Not breaking against released versions: edge limits were never released. Callers of 0.5 still migrate
maxIterationsas described in the 0.6 guide, now tomaxRuns.Validation
npm run verifypasses (core 303, Pi 194, DSH 68, package smoke). Edge-limit tests are replaced bymaxRunstests: raising in place and releasing to end a cycle,RUN_LIMIT, downstream reruns versus the owner's replacement, skipped runs not counting, releasing while an input still reruns, removing a node and releasing its rerun in one update, and validation. Pi and DSH tests cover the waiting reminder, schemas,/braid:review, and the live-view restore.