Skip to content

docs(readme): show the loop as a diagram rather than describe it - #1413

Merged
fdaviddpt merged 3 commits into
mainfrom
fix/1412
Sep 11, 2026
Merged

docs(readme): show the loop as a diagram rather than describe it#1413
fdaviddpt merged 3 commits into
mainfrom
fix/1412

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Closes #1412.

The README explained the loop in a paragraph and carried docs/oss.png, an image whose accuracy
nothing verifies and whose drift no guard would notice. The loop is now explainable end to end, and
prose is the wrong medium for it.

Adds a mermaid flowchart under Type it once: one human action (oss-workspace, once), the
question /oss:run asks, the branches it takes -- no config, inbound, release, curate, triage, or
otherwise dispatch -- the lane, the audit, the merge, and every arrow returning to /oss:run rather
than stopping. That last part is the claim this project actually makes, and it was nowhere on the
page.

Mermaid rather than an image, deliberately. GitHub renders it natively so there is nothing to
build or host; it is text, so it diffs and a wrong arrow shows up in review like any other wrong
line. An image drifts silently -- the defect class this project is named after, pointed at its own
front page.

Replace, don't append. The first draft added 33 lines and cut none.
tests/test_readme_trim_795.py refused it at 99 lines against its 80-line ceiling (#795: read like
a tool, not like a lab notebook
), which is exactly the append-don't-replace failure this repository
rejects everywhere else. The ceiling was not raised. Paid for by cutting:

  • the Type it once paragraph, six lines to four, since the diagram now states it better;
  • the More link list, where nine of ten entries wrapped onto a second line, now one line per link.

79 lines with the diagram in.

Not in scope: docs/oss.png stays. It is a banner carrying the tagline, not an architecture
drawing, and removing it is a decision about the page's visual identity rather than about accuracy.

Checks run

tests/test_readme_trim_795.py, tests/test_content_invariants.py, tests/test_freshness.py --
254 passed. assemble_changelog.py --check -- ok. Docs only, no code path changes.

One thing I cannot verify from here: that the mermaid block renders as intended on GitHub. The syntax
is standard flowchart TD with an & fan-in on the last edge, which is the only construct worth a
second look.

🤖 Generated with Claude Code

https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH

[AI-generated]

fdaviddpt and others added 3 commits September 10, 2026 08:44
The README explained the loop in a paragraph and carried docs/oss.png, an
image whose accuracy nothing verifies and whose drift no guard would notice.

Mermaid rather than an image, deliberately: GitHub renders it natively, it is
text so it diffs, and a wrong arrow shows up in review. An image drifts
silently, which is the defect class this project is named after pointed at its
own front page.

The first draft added 33 lines and cut none, and tests/test_readme_trim_795.py
correctly refused it at 99 lines against its 80-line ceiling (#795) -- the same
append-dont-replace failure this repository rejects everywhere else. Paid for by
cutting instead: the Type it once paragraph the diagram now states better, and
the More link list, where nine of ten entries wrapped onto a second line. 79
lines with the diagram in.

docs/oss.png stays: it is a banner carrying the tagline, not an architecture
drawing, and removing it is a decision about the pages visual identity rather
than about its accuracy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH
…1412)

Maintainer review of the first version: /oss:doctor is one of the two verbs
and did not appear at all, and -- worse -- the diagnose step was missing
entirely. That step is the whole subject of #1390: /oss:run checks the install
and the repo on every start, repairs what is ours, and carries on with a
missing capability marked unavailable rather than being replaced by the
diagnosis. Only a gap that makes the loop unsafe stops it, and each such gap
names what would clear it. None of that was on the page.

Both added, plus the unsafe-stop as its own terminal node so the refusal is
visible rather than implied.

Paid for inside the 80-line ceiling again rather than by raising it: the
launcher section, which the diagram now shows; the Type it once paragraph,
which restated the cadence-is-config rule docs/overview.md already carries; and
the tagline, rewrapped onto one line. 79 lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH
…1412)

The diagram showed the loop and hid the one property a reader needs before
leaving it running unattended: each tick runs in a context that is thrown away
after it, so hours of running cost what one tick costs rather than what all of
them summed would.

That was classified as internal vocabulary, following docs/overview.md rule
that the mechanism is not the product. Half right: the NAME (sub-manager) is
internal, the PROPERTY is the product. Someone deciding whether to run this
unattended is deciding on exactly this, and it was nowhere on the page.

Still 79 lines: the board node carries both facts on two lines instead of one,
and the paragraph was rewrapped rather than the ceiling raised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH
@fdaviddpt
fdaviddpt merged commit bc4e2ea into main Sep 11, 2026
10 checks passed
@fdaviddpt
fdaviddpt deleted the fix/1412 branch September 11, 2026 06:25
fdaviddpt added a commit that referenced this pull request Sep 11, 2026
… stop the /oss:run scheduler reading its own sub-steps (#1405, #1406, #1414) (#1418)

* feat: rank next_action's sources instead of arbitrating, and show inbound work on the statusline (#1405, #1406)

`scripts/next_action.py`'s `rank()` replaces `decide()`: every source
(inbound, release, curate, triage) is composed every time and returned
ordered, never stopped at the first fired/unresolved one. Every candidate
carries its evidence; an unresolved reading is listed as `could-not-tell`,
never dropped. `record_skip()` records a deliberate deviation from the top
candidate via `oss_state.append`. `unsafe` and `due: setup` stay outside
ranking, as refusals/preconditions rather than candidates.

`inbound_reading()` in `scripts/statusline.py` is the "one module, two
consumers" the design note on #1405 asked for: `refresh()` caches it on the
board's own clock for the statusline's `inb NisNpr` field (#1406), and
`next_action.py`'s `_fresh_inbound_reading` calls the same function fresh,
since the loop is about to act on it. `unanswered_comments` is always
`None` -- a per-thread comment walk is out of scope for this change and is
left as a follow-up rather than guessed at.

`commands/run.md` still reads the old four-state shape and needs a
follow-up to consume the new one; not touched here since it is owned by a
lane in flight on the same subsystem (fix/1389).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH

* fix: two self-review findings on #1405/#1406 -- a repeat-suppression regression and a missing statusline doc/guard entry

Explore reviewer found that `rank()`'s full composition (every source
evaluated every call) armed the curate/triage repeat-suppression receipt
the moment those candidates were merely evaluated, regardless of whether
they were the one actually taken -- reintroducing the permanent-divert
defect #1390/#1064/#1155 exist to close, one call later. Fixed:
`_route_already_seen` gains `arm=False`, `_curate_candidate`/
`_triage_candidate` default to it, and `rank()` re-arms only
`candidates[0]` once ranking is settled. Red repro and green fix both in
`tests/test_next_action_1389.py::test_a_lower_ranked_curate_candidate_is_not_falsely_suppressed`.

oss:auditor found `docs/status-line.md` and its own guard test
(`tests/test_readme_statusline_fields_650.py`) were not updated for the
new `inb` field -- the guard passed despite existing specifically to
catch this. Fixed both.

Three other findings (commands/run.md's stale shape, the new inbound
source having no repeat-suppression of its own, and inbound_triage.py
not actually being reused) and one auditor note (record_skip has no real
caller yet) are non-blocking per the ranking table and logged to
trap.d/ rather than fixed here or filed as issues.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH

* fix: precision in rank()'s re-arm comment

A follow-up lightweight review of the previous fix commit (855585f,
required by fix_commit_scope.py's needs-second-pass verdict at 8 files
touched) found the re-arm comment claiming "no extra forge call" was only
precisely true for curate; triage's re-run also re-invokes
triage_trigger.compute, which does its own local git/state-file reads.
Read-only and deterministic either way -- no correctness issue, comment
only. NO FINDINGS otherwise; both reviewer returns and this follow-up
classify cleanly via scripts/review_return.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH

* feat: commands/run.md consumes rank() and stops reading its own sub-steps (#1405, #1414)

Merged origin/main (the #1411/#1413 picker demotion, #1412's own fragment)
-- clean merge, no conflicts.

Two things in commands/run.md, as one coherent rewrite (#1414's own
sequencing note):

1. Step 2 now consumes next_action.py's rank() shape (unsafe / due:setup
   / ranked / nothing-due) instead of the old four-state decide() this
   file used to parse. A new next_action.py --record-skip CLI lets a
   markdown procedure actually call record_skip() -- a plain Python
   function with no other caller reachable from a shell.

2. #1414: the scheduler no longer reads commands/run/setup.md,
   scaffold.md, install-audit.md, triage.md, curate.md, changelog.md or
   commands/release.md directly in its own long-lived session -- the
   erosion #695 built the sub-manager/releaser split to prevent, one
   layer over. A new agent, agents/scheduler-step.md, is the one generic
   wrapper the first six share (they differ only in which file to read);
   release spawns oss:releaser directly; dispatch is unchanged
   (commands/tick.md was already a thin spawn wrapper, not a document to
   follow).

Budget tables updated: agents/scheduler-step.md and commands/run.md
raised with the measurement and reasoning in CLAUDE.md, per this repo's
own replace-or-raise convention.

Context-size measurement (#1414's own scope item): not-measured. #695 is
the precedent for measuring rather than reasoning, and this lane could
not run a multi-tick session to take the before/after comparison itself;
reported as not-measured rather than a reasoned-to number, per this
repo's own rule that an unmeasured claim is trusted least.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH

* fix: rank() must never write a receipt, only an explicit --take/--record-skip commitment may (#1405, #1414)

A second self-review round on the #1414 commands/run.md rewrite found a
real regression, independently corroborated by both spawns: rank()'s own
curate/triage repeat-suppression receipt was armed on every call,
including a plain read-only --json call, rather than only when a caller
actually committed to acting on the top candidate. #1414's own
--record-skip gave a caller a real reason to call rank() without taking
candidates[0] at all, collapsing "surfaced" and "acted on" back into one
event -- exactly the permanent-divert defect the earlier arm=False/
arm=True split (from the #1405 self-review round) was built to close.

Fixed: rank() no longer writes anything, ever. A new _arm_route_source
is the one place a receipt is persisted, called only from a new
--take <source> CLI (the ordinary case: source must equal candidates[0])
and from --record-skip (arms the source actually taken, once the skip
itself is recorded). record_skip() also gained a membership check on
taken_source against the real ranked sources, closing a second finding
both spawns raised independently: a typo used to sail through both
checks and land in the state file's permanent decision log exactly as
confidently as a real deviation.

commands/run.md's step 2 documents --take alongside --record-skip, and
each of the five remaining generic sub-steps (scaffold, install-audit,
triage, curate, changelog) now gets its own literal Agent(...) line
rather than one shared example a reader had to adapt by hand -- the
Explore reviewer found the shared form gave four of the five nothing but
the required path strings to notice a regression back to "read and
follow" prose.

One finding logged to trap.d/ rather than fixed here (non-blocking,
reasoned not observed): _record_skip_cli/_take_cli reload .oss.json a
second, independent time after rank()'s own internal load, a narrow
TOCTOU seam whose honest fix is exposing rank()'s own loaded config
rather than reloading -- a bigger change than its severity warrants in
this lane.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH

* fix: three docstrings still described rank()'s own removed arm mechanism

A second lightweight follow-up review of 4533e7d (required by
fix_commit_scope.py's needs-second-pass verdict at 6 files touched)
found _curate_candidate, _triage_candidate and _route_already_seen still
describing the pre-fix mechanism ("rank()'s own re-call... passes
arm=True") that 4533e7d removed entirely. A reader of any of these
three functions in isolation would come away believing rank() still
arms candidates[0], which is exactly the bug that commit fixed.
Corrected all three to name _arm_route_source/_take_cli/_record_skip_cli
as the only remaining arm=True call sites. NO FINDINGS otherwise; all
three reviewer returns (two on 6830151, one on 4533e7d) classify
cleanly via scripts/review_return.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UYdzDHX3k3C2Eh7B5hRVH

* fix(tests): scheduler-step.md joins NO_TEST_CONCERN in the delegated-test-run guard (#1414)

All four pytest legs on PR #1418 failed on tests/test_delegated_test_run_877.py:
a new Bash-granted agent definition must be placed in one of its three buckets,
and agents/scheduler-step.md was in none. The guard is right -- it exists so a
new agent cannot skip the question of whether it may run the test suite.

The answer: NO_TEST_CONCERN, beside triager.md. scheduler-step reads exactly one
of setup, scaffold, install-audit, triage, curate or changelog, none of which
runs test_command, and its own description forbids the two sub-steps that could
(dispatch and release have their own spawns). The reason is written beside the
set, where the guard reads it.

Found by CI, not by the lane: this guard was not in the set the lane ran. Worth
a trap fragment -- a lane adding an agents/*.md has a fixed list of guards to
run and this one was not on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The README describes the loop in prose and shows a picture nobody checks

1 participant