Skip to content

Let an edge pick where a cycle's rounds start, and warn about nodes that wait for a choice - #131

Merged
Epsirom merged 1 commit into
mainfrom
chrok/cycle-start
Oct 27, 2026
Merged

Epsirom merged 1 commit into
mainfrom
chrok/cycle-start

Conversation

@Epsirom

@Epsirom Epsirom commented Oct 27, 2026

Copy link
Copy Markdown
Owner

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.

  • Relevant tests and type checks pass.
  • User-facing docs are updated where needed.
  • The title reads as a release-note line and the category label is correct
    (see release notes).
  • Breaking changes, if any, include migration guidance in the docs.
  • For release PRs: .github/upgrade-notes.md lists what users must do to upgrade.

…hat wait for a choice

- Edge.closesCycle: true forces an edge on a cycle to close it, so each
  round starts at its target. Validation rejects it off a cycle.
- GraphCycle.choiceGated lists choice edges whose target runs only once
  the decision picks it; Pi and DSH cycle notices name such nodes and
  how to start rounds there instead.
- Waiting and timeout-pause reminders mention the graph timeout only
  when one is set and the job was not paused by it.
@Epsirom Epsirom added the enhancement New feature or request label Oct 27, 2026
@Epsirom
Epsirom enabled auto-merge (squash) October 27, 2026 12:16
@Epsirom
Epsirom merged commit 993b3a6 into main Oct 27, 2026
18 checks passed
@Epsirom
Epsirom deleted the chrok/cycle-start branch October 27, 2026 12:18
Epsirom added a commit that referenced this pull request Oct 27, 2026
…and open the Pi panel on the timeline (#132)

## Change

Part of RSI Mode (#118). Follows #131, from a second Pi run of the
task-queue graph (`impl`, `tests`, `docs` → `merge` → `review`, with
`review -> impl [choice=again]`). The cycle notice warned that `impl`
would not run in the first round. The agent passed the warning on but
did not set `closesCycle`, so `impl@1` was skipped and `merge` wrote the
implementation itself. Two status texts also misled the agent.

- **Guidance at submission.** The Pi `braid` description and the DSH
`edges` description now cover this pattern. When an outside edge enters
the cycle at another node, rounds start there, and a node whose only
input is a choice edge runs only after the decision chooses it. That is
right for a fix step, but if the node should also run in the first
round, write
`{"from":"review","to":"impl","choice":"again","closesCycle":true}` and
set it at submission. The round-start sentence #131 added read badly; it
now lists the order: the `closesCycle` target, else the outside entry,
else the node listed first. The `closesCycle` field descriptions say
when to set it.
- **New `WaitReason` `"choice"`.** A pending instance whose non-closing
inputs are all choice edges, while those inputs still wait, reports
`choice` instead of `predecessors`, `stale`, or `hold`. Pi and DSH
`braid_status` show it as `waiting: runs only after review chooses
again`. Before, the agent saw "an input has not run yet" on a node it
believed was a root.
- **Graph-timeout reminder.** It said: `It waits on suspended node
"merge" execution merge@2 (its graphTimeoutMs limit); the job pause (by
its graphTimeoutMs limit).` The agent read "its" as a per-node limit. A
job pause now names the executions it suspended, and limits are called
"the job's": `It waits on the job pause (by the job's graphTimeoutMs
limit), which suspended node "merge" execution merge@2.`
- **Pi panel opens on the timeline view.** `g` switches to the live
view, as before.

Docs: `docs/execution-control.md` (wait reasons, cycles), the Pi README
(views, wait reasons), and `docs/compatibility.md`.

Compatibility: code that handles every `WaitReason` needs a case for
`choice`; `compatibility.md` notes it. The panel views and the cycle
features are unreleased since 0.5.

## Validation

`npm run verify:checks` passes: core 313/313, Pi 197/197, DSH 71/71,
plus type checks and examples.

- Core: in the task-queue graph, `impl@1` waits with `choice`, and with
`closesCycle` it starts in the first round. In the existing `check ->
end [done]` loop, `end` now reports `choice` instead of `stale`.
- Pi and DSH: full status and focused status show "runs only after
review chooses again". A reminder for a graph-timeout pause names the
suspended execution once.
- Pi panel tests now start from the timeline; the `g` round trip through
the live view is still covered.

- [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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant