Repository navigation
Accept unconditional, nested, and overlapping cycles, and report each new cycle - #127
Merged
Merged
Conversation
Epsirom
force-pushed
the
chrok/peaceful-blackwell-9f3de3
branch
from
October 25, 2026 14:09
956a1a9 to
4f8a70d
Compare
4 tasks done
Epsirom
force-pushed
the
chrok/peaceful-blackwell-9f3de3
branch
from
October 25, 2026 14:31
4f8a70d to
c57f78f
Compare
Epsirom
added a commit
that referenced
this pull request
Oct 25, 2026
## Change Part of RSI Mode ([#118](#118)). Replaces the per-edge `limit` added in #124 with an optional `maxRuns` on 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 - `maxRuns` counts the executions of a node's lineage that start; skipped ones do not count. Unlimited when omitted or `Infinity`. `maxRuns: 3` on a node in a cycle allows three rounds. - When an edge reaching a settled head would rerun it past `maxRuns`, the clone is still created but stays `pending` with the new wait reason `limit`. Once its inputs are current, core emits `limit_reached` (`limit: "maxRuns"`, the waiting `executionId`) and lists it in the new `BraidSnapshot.limitedExecutionIds` and `graph_waiting.limitedExecutionIds`, so a run with nothing else active waits and Pi/DSH wake the owner with the waiting reminder. - Raising `maxRuns` with a normal `update`/`upsertNodes` edits the pending rerun in place and it starts. - Releasing it with `resume` (or `resume` in 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 raises `maxRuns`, the rerun starts instead. - An owner's replacement of a started node is never blocked; the limit only caps automatic reruns (cycle rounds and downstream cascades). - Under `onLimit: "fail"` the run fails with the new code `RUN_LIMIT`. ### Removed `Edge.limit`, the rule that a limit must sit on a cycle-closing edge, the `edge_limit` hold source, `limit_reached.edge`, and `EDGE_LIMIT`. Cycle validation is otherwise unchanged. `removeEdges` now accepts only `from`, `to`, and `choice`. ### Pi and DSH - Node schemas accept `maxRuns`; edge schemas drop `limit`. Tool guidance, `onLimit` text, and `braid_update`/`braid_resume` descriptions explain raising or releasing a waiting rerun. - The waiting reminder lists a rerun at its limit as a `WaitingItem` of kind `limited` (source `maxRuns`) and says how to raise or release it. `formatEdgeLimit` and `edgeLimit` on completion notices are removed. Pi's `OutstandingJob` and the single-shot drain warning include limited reruns. - Status text shows `maxRuns=` on nodes, the `limit` wait 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's `maxRuns`. - `/braid:review` bounds its rounds with `maxRuns: 3` on 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 in `docs/compatibility.md` (`maxIterations n` becomes `maxRuns n` on 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 `maxIterations` as described in the 0.6 guide, now to `maxRuns`. ## Validation `npm run verify` passes (core 303, Pi 194, DSH 68, package smoke). Edge-limit tests are replaced by `maxRuns` tests: 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. - [x] Relevant tests and type checks pass. - [x] User-facing docs are updated where needed. - [x] The title reads as a release-note line and the category label is correct. - [x] Breaking changes, if any, include migration guidance in the docs.
Epsirom
force-pushed
the
chrok/peaceful-blackwell-9f3de3
branch
from
October 25, 2026 14:35
c57f78f to
e344a28
Compare
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
force-pushed
the
chrok/peaceful-blackwell-9f3de3
branch
from
October 25, 2026 14:54
e344a28 to
51f08a2
Compare
Merged
4 of 5 tasks
Epsirom
added a commit
that referenced
this pull request
Oct 27, 2026
…hat wait for a choice (#131) ## Change Part of RSI Mode (#118). Follows #127. Found while testing 0.6 in Pi with a task-queue graph: `impl -> tests/docs -> merge -> review`, with `review -> impl [choice=again]` and `maxRuns` on `impl`. Braid closed the cycle at `impl -> merge`, so each round started at `merge`. `impl` ran only after review chose `again`, and the first `merge` ran without it. The heuristic was not changed. `impl -> merge -> review -(again)-> impl` has the same shape as `build -> check -(again)-> fix -> build`, where starting at `build` is right. So the graph author now says which one they mean, and Braid points out the ambiguity: - **`Edge.closesCycle: true`** makes an edge close the cycle it is on, so each round starts at its target. Braid still chooses closing edges for any other cycles. Validation rejects the flag on an edge that is not on a cycle. The flag is not part of the edge's identity, so changing it in an update means removing the edge and adding it back (tested). It is accepted by core, the replan tool schema, and the Pi and DSH edge schemas. Pi's panel keeps it on `edge_created`. - **`GraphCycle.choiceGated`** lists the cycle's non-closing choice edges whose target waits only on choice edges, meaning the target does not run until the decision picks it. Pi and DSH cycle notices add a line for each: > impl runs only after review chooses again, not before it in the first round; if each round should start at impl, set closesCycle: true on review -> impl [choice=again]. - **Reminder wording.** Waiting and node-timeout-pause reminders said "The graph timeout keeps running" even when no graph timeout was set. Pi and DSH `WaitingNotice` and `TimeoutPause` now carry `graphTimeoutMs` from the snapshot's limits. The sentence appears only when it is set, and not when the job is paused by `graph_timeout` itself. Docs: `docs/execution-control.md` (Cycles, Cycle notices), README, the Pi and DSH READMEs, and the 0.6 section of `docs/compatibility.md`. Compatibility: no changes needed from callers. Code that compares whole `GraphCycle` objects needs the new `choiceGated` field. ## Validation `npm run verify:checks` passes: core 312/312, Pi 196/196, DSH 70/70, plus type checks and examples. New tests: - `test/cycles.test.ts`: `choiceGated` is reported for the task-queue graph and disappears with `closesCycle`; with `closesCycle`, `merge@1` waits for `impl@1`, `tests@1`, and `docs@1`, and `impl@2` follows `review@1`; validation of the flag, and moving it with an update. - Pi and DSH: exact cycle-notice text with and without `closesCycle`, and the reminder with and without a graph timeout and when paused by `graph_timeout`. Pi also checks that its schema accepts `closesCycle`. Not re-tested in Pi yet, because the local Pi install points at the main checkout. - [x] Relevant tests and type checks pass. - [x] User-facing docs are updated where needed. - [x] The title reads as a release-note line and the category label is correct (see [release notes](https://github.com/Epsirom/braid/blob/main/docs/releasing.md#release-notes)). - [x] Breaking changes, if any, include migration guidance in the docs. - [ ] For release PRs: `.github/upgrade-notes.md` lists what users must do to upgrade.
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.
Closes #114. Part of RSI Mode (#118). Builds on #128, where
maxRunsreplaced edge limits.What changes
docs/compatibility.mdnotes it.closingEdges(graph)returns them all.maxRunscheck still applies when a clone is admitted.maxRunson their nodes (Limit how often a node runs with maxRuns instead of edge limits #128): total runs, never reset per round of an enclosing cycle (tested and documented).cycles_createdwith each new cycle (GraphCycle:nodeIdsstarting where its rounds start,edgesending with its closing edge,stops= the decision choice edges on it,limitedNodeIds= its nodes with a finitemaxRuns; at most 20, withtruncated).newCycles(graph, previous?)computes the same list. Pi and DSHbraid/braid_updateresults show it, and flag a cycle with nothing that stops or bounds it, so an accidentaltest → fixnext tofix → testis noticed.display.tsgets 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 indocs/compatibility.md.Compatibility
Not labelled breaking: existing callers need no changes. TypeScript code with an exhaustive
switchoverExecutionEventneeds a case forcycles_created.Decided:
maxRunsnever resetsmaxRunsis 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 changesmaxRunskeeps a started head instead of replacing it.Tests
New
test/cycles.test.tscovers:maxRunsmaxRunscounting across outer roundsValidation and closing-edge tests are updated, and Pi and DSH tests cover the notices.
npm run verifypasses onmainwith #128 and #129 (core 309, Pi 195, DSH 69, package smoke).