skip to content

0.3.0

Words and Endings

This release aims to purify the Gantry engine. Two key subsystems have been redesigned from scratch: The pattern language and the handling of stopping and resuming runs.

332 commits by Gantry runs

89% of the release

41 commits by non-Gantry sessions

11% of the release

373 commits 23 entries 6 sections

Gantry 0.3.0 covers the six days from the 0.2.2 release to the 2026-08-20 cut: 373 commits in 23 units of work — 10 gantry runs contributing 332 commits, and 13 hand or interactive-agent sessions and standalone commits contributing the other 41.

Read this before upgrading. Every pattern file authored against the old format stops loading, and runs on disk from 0.2.2 or earlier cannot be resumed by this binary. The pattern language was redesigned from first principles and is not compatible with what 0.2.2 spoke: a pattern file that declares version is refused with an incompatible-version error, and there is no reader for the retired format. --goal, --loop, --design and --redesign are gone from the CLI, and design.toml, redesign.toml, careful.toml, steady.toml and build-reviewers-refuse-plan-edits.toml are withdrawn from the shipped corpus with the nine prompts under config/agent-prompts/design/. The bundled corpus is now build, map and until, plus the milestone and sprint they call. --until is the one spelling of the standing-acceptance run, and --param binds a declared entry parameter by name. The header key stop_handler is refused by the loader; on_stop replaces it.

The release is named for its two redesigns. The pattern language is now eight words in four groups, each of which names an arrow, a way of composing arrows, or a value carried on an arrow, and nothing else: pattern is the only thing that can be defined, agent, gate and command are the only three step kinds, for, if, stop and call are the only compositions, and output is the only thing a step leaves behind. block, rule_block, channel, produces, fan-out, inner unit, side_loop, goal cycle, roster, work list and a thirteen-entry step-kind list are deleted rather than renamed. The language contains no numbers at all — a count a decision needs is a query a command computes over the record at the moment of the decision, so nothing is incremented and nothing can drift. Recursion is the only unbounded control flow, and a recursive call must sit in tail position on every path, which the loader checks over the call graph.

Stopping, resuming and recovering was redesigned on the same terms and built over six milestones. A run now has one problem ending — PatternRunOutcome::Stopped carrying a typed stop record — where four encodings competed before, and Disposition survives only in the quarantined cold reader that places finished runs written in the old vocabulary. Every stop carries a class from a closed set (step, absence, plan, gate, record, merge), an on_stop hook table keyed on it, and an evidence package the stop card renders instead of citing paths it never read. The replay cursor was rebuilt per frame, and the resume baseline gate became replay admission. Every anticipated stop in sprint.toml now routes through a briefed recovery step, so no stop is reached without an agent having looked at the problem first. One shortfall is recorded rather than hidden: the merge-site re-derivation was descoped, so the merge-conflict resolver is still briefed by a prompt built in code.

Two large runs finished what the language redesign left behind. finish-the-production-world landed eleven sections — per-invocation state frames with the run's shape written down durably, boundary capture that honours the boundary it was asked for, rollback parks under refs/gantry/discarded/, the prompt markers agents were promised, the map roster driven end to end, and five event variants with no producer deleted tree-wide. one-word-for-a-unit-of-work settled the vocabulary on task; what still says sprint in the source is exactly the sprint pattern's own name and the record keys that name what old records on disk actually contain.

What a run shows you was rebuilt against what it actually recorded. The redesign had taken every event above the step with it, so a run had no title, no task names, an empty sidebar and Implementing on every card; one task tree keyed by a composed task path replaces the two-level vocabulary that could not survive an engine with no fixed levels. A task the run dies inside is now closed with the verdict its body earned rather than drawn as running forever. Card headlines are a thing the pattern says and the engine reports rather than a constant the engine guesses from where a file sits on disk. And a replayed step's card names its step instead of the interpreter's step-kind discriminant.

Two fixes in this release destroyed nothing and would have. A session boundary rollback replayed a repository-wide branch snapshot through git update-ref from the run worktree, and because refs/heads/ lives in the common git dir it reverted other runs' merges and the operator's commits and deleted branches the run never created — observed eleven times across two repositories. The rollback now targets the worktree's own branch only, compare-and-swap against the boundary's observed value, and names itself in the reflog. Separately, a run's name had been taken from the first line of about.md on every read since a deletion pass removed the pin, so a run was renamed whenever an agent rewrote that line; the pin is production code again.

The pattern language, redesigned

Eight words in four groups, the bundled corpus rewritten around them, and everything that named the retired format or its vocabulary removed.

  1. 84 commits, 8f438a064 efbead855 run pattern-language-redesign replay

    The pattern language is eight words, and nothing else

    A gantry run replaced the pattern language with one designed against a single test: every word must name an arrow, a way of composing arrows, or a value carried on an arrow. What survived is `pattern` as the only definable thing, with the engine-provided empty pattern `nothing` reserved; `agent`, `gate` and `command` as the only step kinds; `for`, `if`, `stop` and `call` as the only compositions; and `output` as the only thing a step leaves behind, declared on the step that produces it rather than in a pattern-level table with a `producer =` string. Deleted rather than renamed: `block`, `rule_block`, `fan-out`, `channel`, `produces`, `inner unit` and its six bookkeeping operations, `side_loop`, `goal cycle`, `roster`, `work list`, `cursor`, and a step-kind list that had grown to thirteen entries by promoting one pattern's behaviour to a primitive. `if` over a declared value set is checked exhaustive at load time, so an omitted verdict arm is a load error rather than a silent fall-through. The language holds no numbers — no retry budget, no repetition statistics, no numeric predicate — because a count a decision needs is a query a `command` computes over the record at that moment and discards, and an authored counter is a second copy that can disagree with the first. Recursion replaces every bounded-repetition word the language had accumulated, and the loader enforces tail position over the call graph. The bundled corpus was rewritten around the new words, `goal.toml` and `ralph.toml` were deleted and `until.toml` written in their place, and `manual/spec.md` was rewritten as the format's reference and is no longer draft. There is no tolerant reader and no migration: a file declaring `version` is refused with an incompatible-version error.

  2. 2 commits, bf6671780 9459d496a session

    There is one format, so nothing in the tree calls it the second one

    Hand work removed the 277 identifiers across the crate that still distinguished the format from one nobody can load — `format2`, `format_2`, `format-2`, `Format2` — along with the `PatternFormat` enum, its two loader functions, and `VersionedPatternDefinition`, a one-variant wrapper whose only job was to say which format. The refusal of a retired header shape is kept; the machinery that made it look like a choice between two supported formats is not. The same session retired the vocabulary the redesign had deleted from the compiled-in manual and from the engine's published descriptions.

  3. 38c39c9ab commit

    The shipped corpus is build, map and until

    Hand work withdrew five patterns that were never general — `design.toml`, `redesign.toml`, `careful.toml`, `steady.toml` and `build-reviewers-refuse-plan-edits.toml` — along with the whole of `config/agent-prompts/design/`, nine prompts no shipped pattern hands to a step. `--design` and `--redesign` leave the CLI with the patterns that claimed them. The `until` pattern is now named `until` everywhere it is that pattern being named: the flag is `--until`, the parameter is `until_source`, and `--loop` and `--goal` die outright rather than surviving as hidden aliases.

  4. 12 commits, be6ffb005 3442f9148 run every-agent-step-is-implementing replay

    An agent step declares its stage, model, effort and harness

    A gantry run gave the `agent` step a `stage` key and three per-step selection overrides, all closed sets enforced at parse time. The step's role, harness, model and reasoning budget now come from what the pattern declared instead of a hardcoded worker budget, and the journal records `agent:plan`, `agent:build`, `agent:review` or `agent:util` accordingly, so the run's own record stops describing every agent as the same thing. The five bundled patterns carry sixteen explicit `stage` declarations; the about-card writer now runs under the support stage rather than burning a worker. The three new closed sets are published to the reference the website renders.

  5. 2 commits, da85ebd43 1822c005a session

    The two planners declare on_plan_change = nothing

    Hand work made `build.toml`'s and `milestone.toml`'s planners declare `on_plan_change = "nothing"` for their own briefs directories, which is the complementary half of the engine-side fix that stopped treating a declared file output as a plan change. The same pass purged retired pattern vocabulary and stale claims from the shipped pattern corpus's comments and the compiled-in agent-prompt catalog.

Finishing the engine behind the language

The eleven sections the redesign's loss audit named, one task tree at any depth, and one word for a unit of work.

  1. 21 commits, 1a579cb37 ca391cb96 run one-word-for-a-unit-of-work replay

    A unit of work is a task

    A gantry run settled the engine's vocabulary on one word. `unit` and `work-list entry` are gone with it — `SprintCard` became `about::TaskCard`, `Sidecar.sprints` became `Sidecar.tasks`, `unit_dir` became `task_artifact_dir`. What still says `sprint` in the source is exactly what the plan carved out: the `sprint` pattern's own name and prompts, the retired pipeline's card strings the record-repair pass reproduces verbatim, and the historical record keys that name what old records on disk actually contain — renaming those would break the reader of a record the run cannot rewrite. The run built the sweep and its integration merge green but could not finish itself, blocked by a replayed directory output binding absent, and the operator concluded it by hand; that defect is fixed in the entry below.

  2. 7 commits, 69bb9b8cf ea6d2a521 session

    One task tree, at whatever depth the pattern nests it

    Hand work restored everything that reads *about* a run, which the language redesign had taken with it when it deleted the drivers: no run title, no task names, an empty sidebar, every card falling back to `Implementing`, `task_id` carrying raw `step:N` pattern positions, and a finished run announcing `0 tasks merged to main` after merging two milestones. The old vocabulary could not be ported because it encoded exactly two levels of nesting in an engine that no longer has fixed levels; one event keyed by a composed task path, at any depth, replaces it. The fixes that followed the same day close a task with the verdict its body earned rather than leaving it drawn as running forever when the run dies inside it, stop truncating task paths to a bare id, stop treating a task still waiting for its planner as a child of the task before it, and make a stop screen offer a launcher only where it can run.

  3. 42 commits, 8e87d84e1 ce961fbb1 run finish-the-production-world replay

    The eleven things the redesign left unfinished

    A gantry run discharged the loss audit written against the new engine, in eleven sections. Every declared directory, ledger row and `PROGRESS.md` now resolves under its invocation's own state root, and the run's shape is written down durably in `state/run-shape.toml` so cold readers get the same answer — a record written before that file exists is recognised as such rather than misread. Boundary capture honours the boundary it was asked for instead of always returning the current head, and an iteration's start is persisted so a resume restores what the iteration actually began from. Rollback discards are parked under `refs/gantry/discarded/` rather than dropped. The prompt markers agents were promised are filled in — the diff, the gate output, the plan-change diff, the source plan and the scoped plan files — and every dead marker is purged from the shipped prompts with a gate that keeps them out. The map roster runs end to end from a fixed selector. Five event variants with no producer anywhere are deleted tree-wide and the sweep is a permanent test. A live `gantry merge` against a paused run refuses and names the pause-merge-resume workflow. Task enumeration cross-checks the ledger, skipping a task already recorded `DONE` independently of the replay cursor. And `--param` binds a declared entry parameter by name.

Stopping, resuming, and recovering

One problem ending, a typed stop record with its evidence, a closed stop-class set with hooks keyed on it, and a replay cursor rebuilt per frame.

  1. 8 commits, fb16db755 b03ffa444 run conclude-the-merge-and-normalize-r

    Two defects that stopped a finished run at its merge

    A gantry run fixed the pair of unconditional defects that had stopped the vocabulary sweep at its own merge. The merge-conflict resolver's prompt forbade committing, the session close then ran a `git commit` that git categorically refuses while `MERGE_HEAD` is set, and the grounding check required the very merge commit the prompt forbade — so every textual conflict at finish dead-ended there. The engine now concludes the resolved integration merge itself, which is what the doctrine that Gantry owns every commit on the run branch already implied. Separately, a resume bound a replayed output raw, bypassing the trim, the closed-value validation and the presence-to-path resolution the live path performs: a replayed closed-value output kept its trailing newline and was rejected as undeclared, and a replayed directory output bound as absent, so every `build` run resumed past its planner stopped at the `for` with no workaround on disk.

  2. 1ed8b095a commit

    A session rollback touches the worktree's own branch and nothing else

    Hand work fixed a rollback that could destroy work the run never created. A session boundary rollback replayed a repository-wide branch snapshot through `git update-ref` from the run worktree, and because `refs/heads/` lives in the common git dir, a stale snapshot reverted other runs' merges and the operator's commits and deleted branches the run had never touched — silently, with a blank reflog entry, observed three times in one project and eight in another. The delete sweep is gone; the one write targets the run's own branch, a compare-and-swap against the boundary's observed value refuses to revert a concurrent advance, the rollback runs before the HEAD reset so it cannot follow an agent's checkout, and every rollback names itself in the reflog. A moved ref the worktree does not own is recorded as a violation and never acted on.

  3. 107 commits, f78992a88 c122633a5 run stop-resume-recovery-build replay

    One ending, a typed stop record, and an agent in front of every stop

    A gantry run rebuilt stopping, resuming and recovery over six milestones, from an agreed first-principles design. A run now has one problem ending — a stopped outcome carrying a typed stop record of route, position, class, note and evidence — where four separate encodings competed before; `Disposition` survives only in the cold reader that places already-finished runs, in the record-maintenance verbs quarantined with it, and in the published descriptions, and is on no live decision path. Every stop carries a class from the closed set `step`, `absence`, `plan`, `gate`, `record`, `merge`, published in the format reference; the header key `on_stop` replaces `stop_handler`, which the loader now refuses, and its hook table is keyed on the class with `any` as the catch-all. The stop card renders the stopping step's declared outputs and typed evidence labels instead of naming a report path it could not read and inferring provenance by searching detail strings. The replay cursor is rebuilt per frame against a frame identity, the resume baseline gate became replay admission, and the dead script-replay arm went with them. Every anticipated stop in `sprint.toml` now routes through a briefed recovery step before its `stop`, so no stop is reached without an agent having looked at the problem. Partial merge is no longer keyed on the ending: an operator's request is an input to finalization rather than a property of the stop, which also closes the hole where a captured stop bypassed the partial-merge path entirely. Two shortfalls are recorded rather than hidden — the merge-site re-derivation was descoped, so the merge-conflict resolver and the semantic gate resolver are still briefed by prompts built in code, and neither the `record` nor the `merge` hook ever dispatches.

The task pipeline and the reviewer

What a reviewer owes the brief it is judging, what a rejected attempt leaves behind, and the gate an agent is told to run.

  1. 19 commits, c56681f7b a926630cd run the-reviewer-owns-the-brief replay

    A rejected attempt is a record, and the reviewer repairs the brief

    A gantry run made the task pipeline's repeated attempts legible to the agent making them. An agent step's journal entry now records the verdict and note it earned, and the worker preamble renders the recovery block per task, so an executor on its fourth attempt reads what the first three were rejected for — the block that was empty for all eleven attempts of the incident that prompted this work. The reviewer's standing job is now stated as repairing the brief where the scoped files make it expressible, with the forbidden move — rewriting a brief so delivered-but-wrong work passes — stated separately, and the reviewer is shown which files those are. A `sprint` may declare an `attempt_ceiling`, unbounded by default and set to two by the bundled `build`, enforced by a `command` step routing to three stops rather than by an engine counter. A declared file output is no longer observed as a plan change, which is what had been dispatching a plan-change review against the planner's own writes. And gate resolution became one contract, so the substituted gate path and the gate steps can no longer disagree, with absence reported as absence rather than as a path.

  2. 2 commits, 10c2c9baa 995c6a1bf session

    An agent under a call invocation is told the gate its ancestors wrote

    Hand work fixed a substituted gate path that named a file no gate-builder ever writes. A retry adds a call segment to the invocation path, so a re-executed task was briefed with a gate under its own retry directory while the gate stages resolved the containing task's gate — and the executor's prompt makes running that path mandatory, so an executor found the file absent, declared itself blocked, and stopped a run whose milestone gate passed. The path now walks up from the invocation state root to the run's record root and names the first gate that exists. The same session stopped a step's own declared output from registering as a change to the plan.

What a run shows and records

Card headlines the pattern declares rather than the engine guesses, a record every surface can read, and the tail of a run that used to be lost.

  1. 11 commits, df2049f92 33a2800e4 run post-completion-record-events replay

    The end of a run reaches the run's record

    A gantry run closed the gap between the last record commit and the end of a run. Everything written after that commit — the stop summary, the final review's logs, the last stage's state directories — never reached the canonical record, which is disproportionately the interesting part for anyone reading how a run ended. A record-tail module, the `backfill-record` verbs with a dry run, a late final record commit, and a reclamation guard that reports and protects the tail rather than destroying it implicitly all landed. The run's own merge was blocked by uncommitted changes in the shared checkout; it stopped correctly, and its resume then declared the work merged without retrying, leaving eighteen files reachable from no ref. The work was recovered by hand and merged, and the fix is the entry below.

  2. 6 commits, 191851443 f8efc69fd run every-surface-reads-the-record-the replay

    A resume no longer declares a blocked merge already built

    A gantry run fixed two display surfaces that stated things about a run its own record does not support. A cold read of a finished run now projects the real tree of tasks the run built, instead of a flat list of phantom pattern positions. And the route a resume takes now asks whether the home branch actually contains the run branch before declaring the work already built — the defect that lost the previous entry's eighteen files. Two shortfalls are recorded: the ledger join for legacy single-level records still misses, and the count reconciliation the plan named was not built.

  3. 2 commits, b671575e8 f3492420b session

    A task verdict is written to the run's own repository

    Hand work fixed a commit that Gantry asked projects to make impossible. An invocation-local ledger row was committed as a path in the project's own history, but Gantry itself asks projects to ignore `.gantry/` in the main checkout, so in any project that followed that advice the add failed and the run stopped. The row now goes to the book repository, the same commit the enclosing task's verdict already takes.

  4. 574b53a40 commit

    A pattern's task depth is declared at run start

    Hand work stopped the run rail from changing shape halfway through. It read the run's depth off the task tree, which cannot answer the question until a second level exists, so between the milestone planner finishing and the first task list landing every milestone drew as a leaf. The depth is now walked out of the resolved pattern graph at run start, which the pattern always knew.

  5. 22 commits, 4040203ca 2026e7659 run every-card-headline-is-true replay

    A card headline is what the pattern says, not what the engine guesses

    A gantry run made the activity feed's headlines a declared property of the step rather than a constant inferred from where a file sits on disk. Of the first thirteen cards of a real run, four were true: two different decompositions shared one sentence, three cards named a replanning agent Gantry had retired, and a gate was called a post-review re-gate because a directory existed under the task's path — a filesystem probe standing in for a structural fact, which had fired seven times against six ordinary task reviews. A step now declares its headline, the engine reports it, the gate kinds are named for what they distinguish, and the card summaries come from the record the run wrote.

  6. 61f8c0951 commit

    A run's name is pinned again when its about card is written

    Hand work restored a pin that a later deletion pass had removed as collateral. A run's name had been permanent since it was first pinned, but with the pin gone every run since took its name from the first line of `about.md` on every read, so the name moved whenever an agent rewrote that line — 25 of 214 run directories on disk carry a pinned name, and the rest were re-reading.

  7. 1a3f24ba1 commit

    A replayed step's card names its step

    Hand work stopped every resume from opening with a run of cards naming the interpreter's step-kind discriminant — `Replayed agent:plan` three times over three different steps. The replayed step record now carries the step's title, resolved through the same fallback the live card uses, and both front ends print it with the kind as the detail line. A journal written before the field renders exactly as it did.

Maintenance

  1. 2 commits, ee532768e cc6ddc19e session

    A release build takes the tag, not whatever HEAD happens to be

    Hand work fixed the release builder to snapshot the tag it was asked for. During the 0.2.2 cut another session was committing to the default branch, and the first attempt produced artifacts three commits past the tag before that was noticed. The third-party notices were refreshed to what 0.2.2 shipped in the same session.

  2. 18 commits, cba6bb869 2380d4734 session

    The compiled-in prose audited to one voice and one vocabulary

    An interactive agent session audited the prose Gantry publishes and corrected it against three standards — look a name up rather than coin one, every sentence carries a fact or goes, and a genre fixes person and tense. What that changed in the release: the descriptions the binary compiles in and publishes as its reference, which had accumulated figures where literal terms exist and a reader address where reference prose has none, and the agent prompts, where the naming standard and the terseness calibration are now stated at the point the prose is generated.

  3. 672e356eb commit

    A headless run's process exits when the run ends

    Hand work broke a shutdown cycle that left finished headless processes resident — five were alive on the author's machine, the oldest for nine days. The engine command thread held a clone of the front end's command sender and looped waiting for that sender to drop, while the front end dropped it only after the engine returned, and the engine returned only when every sender was gone. The thread now also ends on a flag the engine raises when it returns.