0.4.0
Cut on 2026-09-08. Every component is tagged v0.4.0 in lockstep; the manifest table on the
Releases page pins them. coxswain-tools is published to PyPI from that tag, so
uv tool install coxswain-tools gets you the cox command.
Status: beta, unchanged.
Breaking: the leader lock is now the chair
cox route leader is gone. Every subcommand moved:
cox route leader take -> cox route chair take
cox route leader beat -> cox route chair beat
cox route leader release -> cox route chair release
cox route leader status -> cox route chair status
Anything scripted against the old names breaks. A heartbeat script written during this release's own development broke on exactly this, mid-session — if you drive the lock from a loop or a cron entry, change it before upgrading.
The rowing vocabulary was already everywhere else — coxswain, crew, seat, regatta, cartridge as
rigging sheet, graph as course. The Regatta Chair runs the event; "leader" was the last word that
did not belong. coxswain-tools carries the rename, and agent_tools/leader.py remains as a
deprecation shim re-exporting from agent_tools.chair, so imports keep working until every
consumer moves. The shim's docstring names the exact condition under which it can be deleted.
Historical run logs stay readable. events.py parses ^(?:leader|chair) taken: and its
siblings, both spellings, permanently — not behind a deprecation window. The 250 run logs in an
existing workspace are immutable evidence, and a future tidy-up must not make them unparseable.
A test asserts an old-worded line still parses.
cox route chair clear
The lock could be diagnosed as stale and not cleared. status computed stale, release
refused unless the calling process held the lock, and take --steal bound the lock to whatever
short-lived process invoked it — so a lock taken from a cox subprocess was dead the moment the
command returned. Clearing one meant deleting runs/leader.json by hand.
cox route chair clear removes a lock whose holder is provably not live, refuses a live one
without --force, and prints what it removed. It holds the lock across the read-decide-unlink
sequence, as take, beat and release already did — a reviewer caught that a take landing
mid-clear would otherwise be deleted silently, letting two sessions hold the landing loop.
Measured budget ceilings for plan and decompose
coxswain-cartridges gives two seats their own role_budget_usd entries, each with the
measurement in the comment beside it.
decompose had never had one and inherited standard's $0.35, a number the file's own comment
says was calibrated from plan nodes ("~10 turns, under $0.10"). Three runs of the same
decompose on the same input cost $0.4454, $0.3936 and $0.2200 — the problem is variance,
not a uniformly dearer seat, and $0.35 sat almost exactly on that spread's mean.
plan was raised for a different reason. Across 310 recorded calls: median $0.123, p95
$0.317, and a maximum of $0.345 against a $0.35 ceiling. A distribution that stops
$0.005 short of its limit is censored — every plan that would have cost more died and wrote
no usage record, so the tail was missing from the very data used to set the number. At that
median, 92% of plan calls are unaffected; the change only stops the ceiling killing plans that
must read several modules to compose them.
cox stats ingest (preview)
coxswain-tools gains the first half of a run stats store: a SQLite schema, pure derivation
functions, and cox stats ingest, which loads a run corpus into workspace/stats/stats.db.
On the development workspace it ingested 321 runs, 4,061 calls and 281 tasks, and reported the
one file it could not parse rather than swallowing it.
It also recovers runs that die on budget, which write no usage.json at all — 101 such calls
worth $29.20 existed only inside trace files.
There is no query surface yet. cox stats has ingest and nothing else, and the
calls-to-task attribution is not applied, so the store cannot yet answer the questions it exists
for. Both land in a later release; this is shipped as preview because ingest is idempotent and
re-runnable, and the corpus it reads is being cleaned up by cox runs clean in the meantime.
releasable workflow
coxswain gains .github/workflows/releasable.yml: a nightly and manually dispatchable check
that the umbrella and all four manifest components — coxswain-cartridges, coxswain-graphs,
coxswain-tools and coxswain-crew — are green on their default branches. It answers "is
everything releasable" on a schedule instead of at release time.
Components
coxswain-cartridges carries the budget ceilings. coxswain-tools carries the chair rename,
chair clear and the stats preview. coxswain carries the releasable workflow and these
notes. coxswain-graphs and coxswain-crew are unchanged since v0.3.0 and are tagged in
lockstep.