Skip to content

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.