coxswain-tools
Deterministic tools the agent seats call instead of spending tokens. Anything a seat would otherwise do by reading a file wholesale and reasoning about it — summing a run's cost, counting what a traced node did, cleaning up after a run, serving a plan — is a function here that reads it and answers.
The mould, every time: a pure core with no I/O, the filesystem and the network at the edges, dry-run by default where a write is involved, and tests that never touch a real run.
The fifth repository
| Repository | Owns |
|---|---|
coxswain-cartridges |
who a run works for |
coxswain-graphs |
what runs, and the harness that runs it |
coxswain-crew |
who speaks |
coxswain-tools |
what the seats run so they do not have to think |
Tools that read coxswain-graphs' records belong to neither; a seat routes to them by name the way it routes to skills. Nothing here names an employer, a tracker, or a person — CI refuses it.
Install
See docs/getting-started.md for the full setup, from cloning all three repositories to a verified first run.
Not yet on PyPI: once the first tag ships, this package will be coxswain-tools, installable as pip install coxswain-tools or uv tool install coxswain-tools.
git clone https://github.com/ppfenning/coxswain-tools ~/repos/coxswain-tools
cd ~/repos/coxswain-tools && uv venv && uv pip install -e ".[dev]"
uv tool install -e . # `coxswain-tools` on PATH for every seat
Commands
Bare cox, with no subcommand, opens the coxswain session: a real Claude Code
session with the coxswain plugin loaded, working directory at the profile's
workspace_dir. It also takes the chair lock under chair-<YYYY-MM-DD> before
starting; a live foreign holder is printed, not stolen, and the session starts
anyway. agent-tools still works this release as a deprecated alias for cox.
cox runs usage RUN_ID [--runs-dir RUNS_DIR] [--json] usage stats and cost for one run
cox runs trace RUN_ID [--runs-dir RUNS_DIR] [--role ROLE] [-v] the tool-call trace for one run
cox runs clean RUN_ID --repo REPO [--worktree-root WORKTREE_ROOT] [--runs-dir RUNS_DIR] [--profile PROFILE] [--apply] [--force] delete a run's worktree and branches locally
cox runs land RUN_ID --repo REPO [--task TASK] [--phase PHASE] [--label LABEL] [--force] [--no-claim] [--worktree-root WORKTREE_ROOT] [--apply] [--no-merge] [--runs-dir RUNS_DIR] [--profile PROFILE] [--gate ticket|phase|epic|full] merge a run's branch into the target repo
cox runs review --pr PR [--profile PROFILE] review a contributor PR with the review graph and post the verdict
cox runs recover RUN_ID TASK_ID --repo REPO [--runs-dir RUNS_DIR] [--profile PROFILE] [--dry-run] merge an approved task's commit into its phase branch after an escalated merge
cox runs series [--runs-dir RUNS_DIR] [--json] [--append APPEND] per-run summary rows across a runs directory
cox runs wait [--runs-dir RUNS_DIR] [--max-seconds MAX_SECONDS] [--interval INTERVAL] [--json] block until a busy lane's run exits, then print its outcome lines
cox runs events [--runs-dir RUNS_DIR] [--follow] [--json] poll a run's log for structured events
cox runs top [--runs-dir RUNS_DIR] [--interval INTERVAL] [--once] live table of busy lanes (runs in flight); --once prints it and exits
cox runs bar [--runs-dir RUNS_DIR] one Waybar JSON line: busy lanes, cost, class idle|running|attention
cox runs notify [--runs-dir RUNS_DIR] [--once] [--interval INTERVAL] [--replay] desktop notifications for exits, quarantines, budget stops and cost
cox runs detail RUN_ID [--runs-dir RUNS_DIR] [--json] one run's timeline, objection and last tool calls
cox runs stranded [--runs-dir RUNS_DIR] [--profile PROFILE] [--json] every approved task record whose work item is not done, with its remedy
cox runs cause RUN_ID TASK_ID ticket|code|harness|unknown [--note NOTE] [--runs-dir RUNS_DIR] [--profile PROFILE] record why one run's attempt at a task was quarantined
cox courier send REF --to TO --note NOTE [--profile PROFILE] append a bus entry naming a courier reference
cox courier inbox [--label LABEL] [--profile PROFILE] list this label's unacknowledged bus entries
cox courier ack ID [--profile PROFILE] acknowledge one bus entry by id
cox versions [--root ROOT] [--manifest MANIFEST] component versions against the manifest
cox install --root ROOT [--manifest MANIFEST] [--provider PROVIDER] [--with FLAG] [--team TEAM] [--workspace WORKSPACE] [--edge] [--dry-run] clone/update coxswain components against the manifest
cox upgrade --root ROOT [--manifest MANIFEST] [--provider PROVIDER] [--with FLAG] [--team TEAM] [--workspace WORKSPACE] [--to TO] [--dry-run] fetch and check out newer pinned versions; refuses dirty checkouts
cox home [--profile PROFILE] the live dashboard: runs, leader, backlog
cox stats ingest [RUNS_DIR] [--db DB] [--work-store-root WORK_STORE_ROOT] [--cartridges-repo CARTRIDGES_REPO] load usage, task, node and launch records into stats.db
cox stats roles [--db DB] [--json] [--cartridge-sha CARTRIDGE_SHA] [--provider-profile PROVIDER_PROFILE] landed rate, attempts-to-land and $/landed per role and model
cox stats explain ROLE [--db DB] [--json] the failure-class breakdown behind one role
cox stats series [--db DB] [--json] [--cartridge-sha CARTRIDGE_SHA] [--provider-profile PROVIDER_PROFILE] per-run summary rows read from the stats store
cox stats coverage [--db DB] [--json] known/total provenance rows for runs, calls and tasks
cox stats bounds [--db DB] [--json] [--level strict|moderate|liberal] [--profile PROFILE] [--write WRITE] n/p50/p95/max and strict/moderate/liberal candidate ceilings per role and model
cox stats spend-mix [--db DB] [--json] per-model token counts and cost share by class, plus the build-only split
cox stats chair [RUNS_DIR] [--since SINCE] [--json] [--session SESSION] [--work-store-root WORK_STORE_ROOT] [--projects-dir PROJECTS_DIR] [--profile PROFILE] harness PRs and $, chair $ per PR, hand-finished lands and quarantine $ by cause
cox stats examples [RUNS_DIR] --role ROLE [--out OUT] [--since SINCE] the local system-one backend's training file (JSON lines) from task records
cox stats system-one [RUNS_DIR] [--role ROLE] [--since SINCE] [--json] [--propose] [--plans-dir PLANS_DIR] shadow-to-on graduation report per role, and a proposal for the maintainer to approve
cox stats lanes [--runs-dir RUNS_DIR] [--hours HOURS] [--json] per hour, the average and peak busy lanes and the idle minutes, from the store's run spans
cox usage assess [--json] [--runs-dir RUNS_DIR] [--profile PROFILE] the pacing verdict for the current spend window
cox plan serve DIR [--kind KIND] [--check] [--no-open] serve a visual plan through the local bridge
cox epic watch PIDFILE [--log LOG] [--max-seconds MAX_SECONDS] [--interval INTERVAL] [--json] poll a detached run's pidfile until it exits
cox route context [--profile PROFILE] [--json] the routing profile's resolved context
cox route status [--profile PROFILE] [--json] [--all] what is queued or running for this profile
cox route file [--profile PROFILE] [--repo REPO] [--title TITLE] [--body BODY] [--phase PHASE] [--intake] [--from-intake FROM_INTAKE] file a new ticket for the harness
cox route pull [--profile PROFILE] [--source SOURCE] [--dry-run] file intake tickets from a source, once per link
cox route lint INITIATIVE_DIR [--repo REPO] static ticket lint over a filed initiative, work-shape.md §3
cox route groups [--profile PROFILE] print the newest plans/intake-groups/<date>.md file, work-shape.md §5
cox route chair take [--profile PROFILE] [--label LABEL] [--pid PID] [--steal] take the chair lock if no live chair holds it
cox route chair beat [--profile PROFILE] [--label LABEL] [--pid PID] [--run RUN] refresh the chair lock's heartbeat
cox route chair release [--profile PROFILE] [--label LABEL] [--pid PID] release the chair lock this session holds
cox route chair status [--profile PROFILE] [--json] the chair lock's holder and computed state
cox route chair clear [--profile PROFILE] [--force] remove the chair lock file, refusing a live holder unless --force
cox route chair chat [TEXT] [--profile PROFILE] [--read] [--since SINCE] [--json] [--as-leader] append to or read the leader chat thread (runs/leader.chat.jsonl)
cox route sync [--profile PROFILE] [--item ITEM] [--project PROJECT] [--workspace WORKSPACE] [--dry-run] mirror the work store onto the profile's tracker
cox route launch epic [--profile PROFILE] --initiative INITIATIVE [--repo REPO] [--fix-attempts FIX_ATTEMPTS] [--dry-run] [--include-blocked] [--tier-ceiling cheap|standard|deep] [--effort-ceiling low|high] [--force] [--no-claim] [--label LABEL] launch the epic graph against a filed initiative
cox route launch decompose [--profile PROFILE] --idea IDEA --initiative-id INITIATIVE_ID [--dry-run] [--tier-ceiling cheap|standard|deep] [--effort-ceiling low|high] [--force] [--no-claim] [--label LABEL] launch the decompose graph against an idea
cox route launch cos [--profile PROFILE] [--dry-run] [--tier-ceiling cheap|standard|deep] [--effort-ceiling low|high] [--force] [--no-claim] [--label LABEL] launch the cos graph
cox route launch sweep --idea IDEA --initiative-id INITIATIVE_ID [--label LABEL] [--dry-run] launch the sweep graph against an idea
cox router select --role ROLE [--db DB] [--profile PROFILE] [--json] the effective tier for a role under the profile's router: off|shadow|on flag
cox steward propose [--db DB] [--profile PROFILE] [--json] write each candidate clearing the evidence bar as a new intake file; never edits a provider profile
cox setup doctor [--profile PROFILE] [--repo REPO] [--json] check this machine's profile against what it needs
cox setup install --root ROOT --team TEAM --workspace WORKSPACE [--provider-profile PROVIDER_PROFILE] [--skills-root SKILLS_ROOT] [--assume a|r] [--plugins] [--hook] [--force-profile] [--dry-run] [--window-ceiling-usd WINDOW_CEILING_USD] [--weekly-ceiling-usd WEEKLY_CEILING_USD] clone components and write a profile for this machine
These route subcommands are built by hand in build_parser and are not yet
rows in the command table, so the renderer does not emit them:
cox route launch epic --initiative DIR [--repo PATH] [--fix-attempts N] [--dry-run] start the harness detached with a pidfile and log, and AGENT_GRAPHS_TRACE_DIR set to `<runs_dir>/<run-id>-trace` so every node writes a trace; exit 2 on a missing profile or harness venv, a missing initiative.md, a dirty repo, or a live run of the same initiative
cox route launch decompose --idea FILE --initiative-id ID [--dry-run] start the harness detached; exit 2 on a missing profile, harness venv, or idea file
cox route launch cos [--dry-run] start the chief of staff detached: it reads intake and runs, dispatches within the bound, and consumes what it dispatched
cox route sync [--item ID] [--project OWNER/N] [--dry-run] one-way mirror of the work store onto a GitHub Projects board; exit 2 if gh is not authenticated
cox route sync --project OWNER/N accept an existing project instead of creating one named "Coxswain" under the repo's owner
cox setup a small terminal UI over setup doctor, setup install and cartridge init (needs a terminal)
cox runs bar is a Waybar custom module: one JSON line of {text, tooltip, class} per poll, class one of idle, running, attention (a run
quarantined or budget-stopped since its last exit). Click launches the same
floating window as the dotfiles' runs top chord; right-click opens the HUD.
// ~/.config/waybar/config.jsonc
"custom/runs": {
"exec": "cox runs bar --runs-dir ~/runs",
"return-type": "json",
"interval": 5,
"on-click": "runs-top-float",
"on-click-right": "cox-hud"
}
Reading a shared store
Commands that read run records read them from one store. Cox finds the store
URL the way graphs does.
It reads the routing profile named by AGENT_TOOLS_PROFILE, or ~/.config/agent-tools/profile.yaml when that variable is unset (the directory keeps the deprecated agent-tools name).
It follows that profile's provider_profile key to the provider profile, and
takes the storage_url key from there. When the key is
absent or empty, the store is SQLite at runs_dir/cox.db. The lookup is
resolve_store_url in agent_tools/store_url.py, and it returns the full URL.
A Postgres URL needs the extra. Install it with:
pip install 'coxswain-tools[postgres]'
Without it, a Postgres URL fails with a message that names this extra. Cox only reads a Postgres store. It opens the connection read-only and never writes to it, and a SQLite store that does not exist yet is not created.
The notes below cover route status, usage assess and the stats group.
cox route status keeps its layout and its JSON shape. The rows still come from
the pidfiles and logs in the local runs directory, so it lists the runs this
machine started. What changes is freshness. A run's heartbeat is read from its
row in the store lease table, so it follows the heartbeat that whichever
machine holds the lease last wrote. Each run with a pidfile costs one query, so
each one pays a network round trip.
cox usage assess keeps its verdict line and its JSON shape. Its ceilings still
come from window_ceiling_usd and weekly_ceiling_usd in the profile. Spend is
the union of the local *.usage.json files and every ended store run that has
no local file. On a shared store the window therefore adds runs from every
machine to the runs this machine recorded. Each read of the store pays a
network round trip, so a call is slower than against a local file.
The stats commands differ one from another.
stats ingestandstats system-oneread run usage from local usage files and the store. Their results add runs from every machine, and each store read pays a network round trip.stats ingeststill writes the derived tables to the local SQLite file named by--db.stats chairtakes only its usage calls from the store. Its landed tasks,land.jsonl, chair file, session transcripts and work items are local files, so those parts still cover this machine alone.stats lanesreads run spans from the store, so on a shared store it shows the lanes of every machine. Liveness for a run with no end stamp still comes from its lease.stats roles,stats coverage,stats explain,stats series,stats spend-mixandstats boundsread only the--dbfile. Their output does not change, and it is as fresh as the last ingest.stats examplesreads the task records under the local runs directory. Its output does not change and covers this machine alone.
The Postgres test cases in tests/test_run_store_backends.py run only when
COX_TEST_POSTGRES_URL is set to a database where the test may create a
schema. Without it they are skipped and only the SQLite cases run.
Maintainers
cox dev moved: run uv run --frozen python -m devtools <command> ... from the coxswain checkout (from 0.15.0); cox dev and cox release print that pointer and exit 2.
Every command that reads a record is pure over parsed data and unit-tested
against fixtures; every command that writes is dry-run unless --apply.
The route group reads one profile, ~/.config/agent-tools/profile.yaml (the directory keeps the deprecated agent-tools name):
team, cartridges_dir, skills_roots, provider_profile, harness_dir,
workspace_dir, and assume, the gate answer detached runs are started
with. forge names how cox runs land lands work: local (the default,
plain git: the task commit fast-forwards the default branch after the land's
own checks pass, then pushes when origin exists) or github (a pull
request, its checks, and a squash merge, through gh). Other forges register
by entry point under coxswain.forges. Integrations that name a vendor (intake sources, forges,
trackers) live in ppfenning/coxswain-plugins; install
it beside cox. --profile PATH overrides the location for one command,
AGENT_TOOLS_PROFILE overrides it for a shell, and the default path is read
when neither is set. Without a profile, route context prints one line and
exits 0; file, launch and status exit 2 and name the path they looked
for. --dry-run on either launch prints the argv, the pidfile and the log
path and starts nothing.
Why this exists
One day of live epics found every defect by reading a usage file or a trace, by hand, in a chief-of-staff's own turns: summing costs, counting Bash calls, noticing a node read a 1,900-line file whole. Each of those readings cost tokens and produced the same answer every time. They are functions now, and the steward seat runs them.
Release files for coxswain-tools 0.17.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| coxswain_tools-0.17.0.tar.gz | 423.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| coxswain_tools-0.17.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 673.4 kB
Release files / coxswain_tools-0.17.0.tar.gz
| Download URL | coxswain_tools-0.17.0.tar.gz |
|---|---|
| Size | 423.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a43b4da94c35a4bcb6dab1cd961daa3fbb3a81c4f4c3d1bf0e2e809fb9005f6c
|
|
BLAKE2b-256 checksum How to use checksums |
b2e09194a791d53fd34351848731e8f62bdcedeb733bdc50d1e1a6d2d0a8fdaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / coxswain_tools-0.17.0-py3-none-any.whl
| Download URL | coxswain_tools-0.17.0-py3-none-any.whl |
|---|---|
| Size | 250.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bf0e2e235b3a245046aa0394883d749b53fbb9952cf31c10ee8a3ed808edeb58
|
|
BLAKE2b-256 checksum How to use checksums |
2c61b3f0539b27a367bb5f3240d5581df5938254b6e1abc50a96f58ed9970267
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log