v0.9.0
Minor because the graph schema grew keys you may type. Compared by name
rather than by diff line: the seventeen long flags in flags.go are unchanged
and so are the eleven subcommands — the CLI surface is byte-identical to
v0.8.0. What grew is the FRAGMENT file schema, which gained exit: and made
nodes: usable where it was previously refused (ADR 0027). No node or graph
key was added, renamed or removed.
The headline is that the reusable unit is a loop, not a node: a fragment may
now carry several nodes and the edges among them, so a QA loop or a
review/repair round is citable the way a single node has been since ADR 0013.
Eight PRs: #176,
#177,
#181,
#182,
#183,
#184,
#185,
#186.
Added
- A fragment may declare a LOOP, not only a node
(ADR 0027,
#186). A
fragment file may now declarenodes:(several, with the edges among them)
plus a requiredexit:, and be cited with the sameuse:/with:a
single-node fragment is. Spliced ids are<using-id>/<internal-id>, which no
author and no planner may write, so a spliced node can never collide with an
authored one; entry nodes inherit the citing node'sdepends_on,cwd:and
worktree:propagate from it, anddepends_on: [<loop>]/
{{ artifacts.<loop> }}from downstream both resolve to the loop's exit.
exit:is never inferred from the unique sink — inference is right only
while there is exactly one, and when it is wrong it is wrong silently.
ADR 0013's rule is generalized, not weakened: a fragment may never name an
id it does not itself declare, of which "a single-node fragment may declare
no wiring at all" is now the special case, with every one of its tests kept.
Measured on the shipped corpus:adr-driven-dev's two hand-unrolled
review/apply rounds became twouse:of one fragment, 119 lines removed
for 53, and the one-direction discipline, both verdict contracts, the
apply's tool grant, its evidence gate and its retry stopped being written out
four times. Scheduler, snapshot, event feed and ledger are untouched — a
spliced node is an ordinary node, and a consumer that wants the loop view
groups by the<using-id>/prefix.
Changed
- A node's
budget_usdno longer refuses a Codex graph
(ADR 0026,
#185).
Preflight had one sentence for two different facts:agent:names a subagent
whose system prompt the node would otherwise lose (a different node — still
refused), whilebudget_usdis a USD ceiling a runtime that reports no USD
has nothing to bound. Inapplicable is not unsafe, so the graph now loads and
warns per node, naming the guard still in force — that node'stimeout:, or
the runner's 20m default. Measured ongraphs/*.yaml: five refused under
--runtime codexbefore, one after (adr-driven-dev, for itsagent:).
auto --max-goal-budget-usdstays refused and that is not an inconsistency:
it is checked only at a cycle boundary, so an unmeasurable ceiling would buy a
whole cycle before stopping to say it cannot be checked, where an inapplicable
node cap costs nothing extra. The loop stays bounded either way —
--max-cyclesis what bounds iterations.
internal/runner/shipped_graphs_runtime_test.gonow lints every shipped
graph under both runtimes and asserts the verdict by name, so a graph that
becomes unloadable under Codex failsmake testinstead of a user's run.
The Claude path is unchanged:ValidateGraphForRuntimestill returns on
its first line forRuntimeClaude, warning nothing and refusing nothing.
Fixed
-
The changelog guard no longer turns
mainred on its own maintenance
(#189,
#191). The guard added in
#188 counted two kinds of commit against themselves. A release cut does not
describe itself, so its own number is always absent from the section it just
wrote — green on the release PR, red onmainthe instant it landed. And a
changelog-only commit has no change to describe, so demanding an entry made
the check eat its own tail: the PR adding a missing entry is itself missing
one, and so is the PR adding that. Both are exempt now, recognised by what the
commit touched rather than by how its subject is worded — a cut changes
CHANGELOG.mdandversion.gotogether, which nothing else does; changelog-only
means exactly one file. Each exemption is mutation-checked: disabling either
turns the test red.Recorded because the trade is not free: the guard caught five genuinely
missing entries in this release, two of them user-visible fixes that would
have shipped a release page never mentioning them — and its edges then cost
four round trips, every one of them on a commit whose subject was the
changelog itself. -
A Codex run's live view says there is no tail, instead of showing nothing
(#182). The view polled
/api/transcriptevery three seconds per running node, and the endpoint looks
for<session-id>.jsonlunder~/.claude/projectsand nowhere else — so on a
Codex run every poll answered 204, for the whole run. The pointless polling
was the smaller half: an empty tail is indistinguishable from "the node
hasn't printed anything yet", so the view looked broken with no way to learn
it was working as designed./api/graphnow carries a note when the run's
runtime keeps no per-node transcript, and the page renders it in place of the
tail and stops asking. The endpoint gains no runtime branch. -
Snapshot.Runtimeis a property of the format, not of one writer
(#181). The field was
omitemptyanddocs/RUN-FEED.mdtells consumers an absent value means
claude — safe only becauseexecuteGraphhappened to canonicalize before
writing. A future caller ofrunstate.Writecould leave a schema-3 snapshot
with no runtime, which every consumer then reads as claude when it was not,
reopening the hole the schema-3 bump was taken to close.Snapshot.MarshalJSON
now canonicalizes, so the key is always present whichever writer produced it.
Reading is unchanged: an absentruntimein an existing file still means
claude.
Documented
-
ADR 0009's session-limit pause is the Claude runtime's promise, not the
engine's (#184, closing
#171). Detection matches
Claude's own prose, so there is nothing for another runtime's message to
match, and theRuntimeClaudegate inCLIRunneris the second layer rather
than the cause — deleting it would not add the pause under Codex, it would
add a pause that can never fire. So a new runtime does not owe a
session-limit signal; what it owes is the honest degradation ADR 0009 already
specifies. No behaviour changed. -
The user-facing documents now know about the second runtime
(#177) —docs/EXAMPLES.md
gains a section on what--runtime codexchanges,docs/RUN-FEED.mdtells
consumers the live-output supplement is Claude-only and that a Codex
cost_usdis0besidecost_unknown: truein the snapshot (present but not
authoritative), andplugin/README.mdsays which of the three plugin entry
points can reach the flag at all.
Repository
- The release body is now
CHANGELOG.md's own section plus a Contributors
line computed fromgit log, never goreleaser's commit-subject list
(#176). A missing section
fails the release. mainenforces a gate that can actually be met
(#183):testand
stressrequired, administrators included, and no required-approval count —
which was unsatisfiable for a solo maintainer and so was being bypassed with
--admin, which bypasses the tests too. Strictly tighter than before.