Skip to content

0.5.0

Cut on 2026-09-08, the same day as 0.4.0. Every component is tagged v0.5.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. Nothing here breaks a 0.4.0 install.

The run stats store answers questions

0.4.0 shipped cox stats ingest as a preview with no query surface. This release completes it. coxswain-tools carries all of it.

cox stats roles reports, per role and model, how many calls were made, how many were attributed to a ticket, the landed rate, attempts-to-land, and cost per landed task — with a coverage figure on every row. A number computed from a partial join says so beside itself rather than presenting as complete. cox stats explain <role> breaks a role's failures down by class; cox stats series reads the per-run series from the store. All three take --json.

The calls-to-task join is applied. A spike on 2026-09-07 found that sum(fix_loop.attempts) over a run's task records equals its build call count in 171 of 184 runs, and that the mismatches concentrate on quarantined and budget-stopped runs. The ingester now verifies that identity per run and assigns build calls to tickets in order where it holds — and refuses to assign where it does not, marking join_confidence='none'. On the development corpus that lands at 553 of 670 build calls, 82.5%, with the unjoined 117 being exactly the failed runs the spike predicted would resist. An analysis that silently dropped them would report that everything succeeds.

Runs that died on budget are recovered from their traces. A budget-stopped run writes no usage.json at all, so before this its cost was invisible to every aggregate. The trace's final result line carries total_cost_usd and subtype; ingest now reads it. 101 such calls worth $29.20 appeared on the development corpus the first time it ran.

The headline numbers were wrong, and are now right. When the query surface first ran, two figures would have led a reader to a false conclusion. failure_class labelled 2,857 of 4,139 calls empty_patch — every reviewer, arbiter, planner and handoff, roles that produce no patch by design. It is now scoped by role: 0 non-patching calls carry that label. And landed_rate read 0.0623, because landed was read only from the run record, where one landing path sets it. The work store holds the truth — 220 items at state: done — and is now an outcome source with its own provenance marker:

before   landed  18   quarantined 180   unknown 94
after    landed 280   quarantined  13   unknown  2      (261 resolved from the work store)

The headline row now reads landed_rate=0.9506 at coverage=0.8284. The platform lands about 95% of its tasks, not 6%. That was never a fact about the platform, only about which field got read — and a stats store whose headline figures are wrong on two thirds of rows is worse than none, because it looks authoritative.

One number is still not computed, and says so. cost_per_landed reads 0.0 because the calls-to-task cost rollup into tasks.cost_usd is not yet written; the joined calls carry the cost ($346.33 on the development corpus) but it is not summed onto the task. Filed, first in line after this release. It reads as zero rather than as a plausible wrong figure, which is the lesser failure, but it is a failure.

Regime. runs.provider_profile was NULL on every row despite the autonomy ledger and every node record carrying it. It is now populated at ingest, with counters asserting the sources sum to runs_ingested, and cox stats roles and series can group or filter by (cartridge_sha, provider_profile) — an aggregate spanning a configuration change now says so instead of presenting one silent number. It took four review rounds, each finding one more untested path, and landed when the last one had a test.

Deferred, deliberately. Resolving a historical provider_profile_sha from git history is held back: the question regime exists to answer is about runs from now on, so recording the hash going forward answers it and resolving history does not. The ticket carries the reviewers' bar so nothing is rediscovered.

Decompose gets the headroom its position deserves

coxswain-cartridges raises decompose's ceiling to $2.00. The argument is leverage, not size: decompose runs once per initiative where every other seat runs once per task, so it is the cheapest place in the system to be generous and the most expensive to be tight. A rename that cost $53 across 22 runs did so substantially because its decomposition split two files that share a test and left on-screen copy owned by no ticket.

The measurement made the same case from the other side: over 16 recorded decompose calls, the four most expensive had no usage record at all — recovered from traces, because they died. The old ceiling had been sampling only survivors.

Decompose refuses prose surfaces and splits across repositories

coxswain-graphs closes the graphs-loop-quality initiative, thirteen tasks. The two landed in this release: every surface a decompose names must resolve to a real path in the checkout or a (new) path under an existing directory, and a task whose surfaces do not resolve is refused as unbuildable rather than filed — never silently dropped. Tasks whose surfaces span repositories split into sibling initiatives with blocked_on, and the stack is read from the tree rather than from the idea's prose. An intake file whose frontmatter already carries an initiative is linked, not re-queued.

releasable actually runs

0.4.0 shipped .github/workflows/releasable.yml with plain YAML scalars containing :, so it parsed as nothing and failed every push including the release tag. coxswain fixes it; the nightly check of the umbrella and all four components now runs.

Components

coxswain-tools carries the stats store. coxswain-cartridges carries the decompose ceiling. coxswain-graphs carries the decompose fixes. coxswain carries the releasable fix and these notes. coxswain-crew is unchanged since v0.4.0 and is tagged in lockstep.