Skip to content

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 16 Aug 16:07
· 10 commits to main since this release
5812604

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 declare nodes: (several, with the edges among them)
    plus a required exit:, and be cited with the same use:/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's depends_on, cwd: and
    worktree: propagate from it, and depends_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 two use: 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_usd no 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), while budget_usd is 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's timeout:, or
    the runner's 20m default. Measured on graphs/*.yaml: five refused under
    --runtime codex before, one after
    (adr-driven-dev, for its agent:).
    auto --max-goal-budget-usd stays 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-cycles is what bounds iterations.
    internal/runner/shipped_graphs_runtime_test.go now lints every shipped
    graph under both runtimes and asserts the verdict by name, so a graph that
    becomes unloadable under Codex fails make test instead of a user's run.
    The Claude path is unchanged: ValidateGraphForRuntime still returns on
    its first line for RuntimeClaude, warning nothing and refusing nothing.

Fixed

  • The changelog guard no longer turns main red 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 on main the 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.md and version.go together, 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/transcript every three seconds per running node, and the endpoint looks
    for <session-id>.jsonl under ~/.claude/projects and 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/graph now 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.Runtime is a property of the format, not of one writer
    (#181). The field was
    omitempty and docs/RUN-FEED.md tells consumers an absent value means
    claude — safe only because executeGraph happened to canonicalize before
    writing. A future caller of runstate.Write could 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 absent runtime in 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 the RuntimeClaude gate in CLIRunner is 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 codex changes, docs/RUN-FEED.md tells
    consumers the live-output supplement is Claude-only and that a Codex
    cost_usd is 0 beside cost_unknown: true in the snapshot (present but not
    authoritative), and plugin/README.md says 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 from git log, never goreleaser's commit-subject list
    (#176). A missing section
    fails the release.
  • main enforces a gate that can actually be met
    (#183): test and
    stress required, 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.