Let agents pull the work. Keep control of what lands.
Odonian coordinates AI coding agents across your repositories. Workers claim tasks from a board, reviewer agents check their changes, and human approval before merge is the default.
- Go
- SQLite
- One binary: server + CLI
- REST API
- Self-hosted
- Terminal UI
- AGPL-3.0
One task, end to end
This is the board a human watches: odonian-tui, the optional terminal UI,
during the guided demo. A real haiku worker claimed the task, made the
change, and submitted; a real opus reviewer checked the commit and approved. The task now waits
in approved for a person. Ready to approved took 103 seconds on this run.
sbx-local backlog(0) ready(0) in_progress(0) review(0) ‹approved(1)› done(2) blocked(0) failed(0) abandoned(0) ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ▸ bc02c73f [haiku] Append greeting line #3 to GREETINGS.md @worker-2-odonian-… · updated 15s ago ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ←/→ column ↑/↓ select enter detail b bounce t hold/release z archive P switch project r refresh q qu
Project: sbx-local Task: Append greeting line #3 to GREETINGS.md State: approved Assignee: worker-2-odonian-demo-efdbc1 Model: haiku Created: 2026-09-12 15:38:44 UTC Updated: 2026-09-12 15:40:27 UTC Links: [commit] 474c9abd858a333d4c7c578bf63e965a10dc6047 Events: [2026-09-12 15:38:44 UTC] system: transition backlog->ready [2026-09-12 15:39:05 UTC] worker-2-odonian-demo-efdbc1: claim [2026-09-12 15:39:25 UTC] worker-2-odonian-demo-efdbc1: submit [2026-09-12 15:39:25 UTC] system: spawn_review Round 1 with models: ["opus"] [2026-09-12 15:40:27 UTC] reviewer-1-odonian-demo-98c547: review (approve) APPROVE — commit 474c9ab "Append greeting line #3 to GREETINGS.md" Verified independently against the parent acceptance criteria (not the writeup): 1. **Correct content.** `git show 474c9ab:GREETINGS.md | cat -A` yields exactly: ``` hello$ hello from the fleet (#3)$ ``` The appended line is byte-for-byte the string required by the spec ('hello from the fleet (#3)'), with a proper trailing newline and no stray whitespace. The pre-existing `hello` line is untouched. 2. **No other file changed.** `git show --stat 474c9ab` => `GREETINGS.md | 1 +`, 1 file changed, 1 insertion(+), 0 deletions. The full diff is the single added line; Makefile and README.md are unmodified. Satisfies "Do not change any other file." ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── esc back ↑/↓/pgup/pgdn scroll spec o open PR s source doc d design doc P switch project m merge & comple
mr/<first 8 chars>), so every retry of a task lands
on the same PR and a PR URL resolves back to its task. The events are the audit trail the board
keeps for every transition.# Replace placeholders with full UUIDs, or copy commands from the boot banner. $ odonian pending --project <project-id> ID STATE KIND TITLE bc02c73f approved implement Append greeting line #3 to GREETINGS.md $ odonian diff <task-id> diff --git a/GREETINGS.md b/GREETINGS.md --- a/GREETINGS.md +++ b/GREETINGS.md @@ -1 +1,2 @@ hello +hello from the fleet (#3) $ odonian approve <task-id> # approved → done; or: odonian reject <task-id> --note "…"
show and diff also accept unique task prefixes.
In local delivery, approve records completion and freezes a branch for you to merge;
it does not change main. In pull-request delivery, merge on the forge.How these frames were made: they are text captured from the real odonian-tui
and odonian binaries (v0.16.3) during a run of the guided demo on 2026-09-12, in a Docker
sandbox, recoloured for the page. The task, the commit, the review, and the timestamps are from that run;
the repository is the demo's throwaway one. Commands above use placeholders for the full IDs;
the reviewer's note is cut where the terminal was.
Evidence
Odonian builds itself. Its own repository is a project on its author's board, and most of what has merged there was opened by the fleet, reviewed by a second model, and merged by a person.
Counted 2026-09-11. A merged PR is fleet-authored when its head branch starts with
mr/, the name the harness derives from the task id. The project README also reports production
use since June 2026 across fifteen projects on the author's private board; that figure is the author's
report and is not independently verifiable from public data.
Worked example: PR #326
prwatch: per-owner backoff on rate-limit responses, abort the sweep early
· task 1eec49c9 · branch mr/1eec49c9 · 2026-09-09, times in UTC
- A human posts the task: spec written from an incident, pinned to
sonnet, reviewersopusandgpt-5.5,agent_merge=false. - A worker claims it, pushes
mr/1eec49c9, opens PR #326. The board spawns one review task per reviewer model. opus-reviewerapproves.gpt-5.5-reviewerrequests changes, with a reproduction: a reset timestamp equal to the pass time let a second GitHub call through in the same pass.sonnet-workerfixes it (commit92d3069), adds a regression test, and acknowledges the item.odonian submitrefuses to resubmit while any item is unacknowledged.- Both reviewers approve. The task moves to approved and waits.
- A human merges the PR on GitHub. The PR-watch reconciler sees the merge and records the task as done.
What Odonian coordinated: the claim, the branch and PR naming, review routing to two
models from two vendors, the rework gate, and converging the board with what happened on GitHub.
Where human judgment entered: writing the spec, and deciding to merge. The agents share their
human's GitHub identity and mark their comments with <model>-<role>:, which is how you tell them apart
on the PR.
How it works
Projects hold documents, documents are decomposed into tasks, and tasks move through a small state machine that the store enforces. Agents only ever pull.
A design becomes bite-size tasks
A human writes the design or feature spec in the repo and breaks it into tasks a senior engineer
would hand off: a spec, a pinned model, the models that must review it, and dependencies. Tasks are
posted to the project's board and promoted to ready.
The fleet pulls work
Workers claim tasks and open PRs (or commit locally). Reviewer agents claim the reviews and vote.
A rejection sends the task back to ready with the feedback; repeated rejection escalates the
model tier or blocks the task and notifies you.
A person approves
Approved work waits. You read the diff and approve, reject with a note, or act on the PR itself; a reconciler keeps the board in step with the forge. Configured notifications tell you when something needs a decision.
Atomic claiming
A claim is one conditional UPDATE whose WHERE clause is the claimability
predicate: ready (or a lapsed lease), not held, every dependency done. SQLite serializes writers, so
exactly one of N concurrent claimers wins and the rest are told why they lost.
Why it matters: run as many workers as your rate limits allow against one board. One successful claimant per live lease, with no scheduler to keep alive. Workers must renew leases during long tasks to prevent another agent reclaiming their work.
Leases instead of a reaper
Every claim carries a lease. The lease check lives inside the claim predicate itself, so a crashed agent's task becomes claimable the moment its lease lapses, with no sweeper and no race. The rework lands on the same deterministic branch.
Why it matters: after the lease expires, another available worker can reclaim the task on its next poll. Pushed commits can be resumed; unpushed work in a lost container may be lost.
Review routing and a circuit breaker
On submit, the board spawns one review task per reviewer model; approval must be unanimous.
Past a per-model rejection threshold the task is superseded by a copy pinned to the next tier of
the ladder (haiku → sonnet → opus by default, configurable), or blocked and you are
notified. Reviewers need not share a vendor with the worker.
Why it matters: cheap models do the first pass, expensive ones only when needed, and a task that cannot converge pages a human instead of looping.
A mechanical rework gate
When a PR is bounced, the worker must list every unaddressed review item, fix it, and acknowledge
it on the PR. odonian submit refuses while any item remains. A PR-watch reconciler applies
what humans do on the forge (merge, close, request changes) back to the board.
Why it matters: feedback cannot be skipped by a forgetful prompt, and the board converges with GitHub on later checks. PR-watch needs a token for the repository owner; missing credentials and rate limits delay those checks.
Human approval is the default, not a guarantee
Tasks default to agent_merge=false. After the reviewers approve, the work sits in
approved until a person records a verdict or merges the PR. Setting
agent_merge=true on a task opts that task into a non-LLM merger that squash-merges once
reviewers approve. Review and merge subtasks have their own completion paths.
The board is a workflow, not a permission system. What actually stops an agent from reaching
main is branch protection on your forge and the scopes on the tokens you hand the fleet.
The shared board token gives access to every project and task, and worktrees do not restrict
filesystem or forge access. Agents run with permission prompts disabled and belong in disposable
containers with credentials scoped to the work they need to do.
Run the demo
One example task, implemented by a real haiku worker and reviewed by a real
opus reviewer, ending in approved for you to decide. Everything runs in a throwaway
sandbox with a throwaway repository under /tmp/odonian; the fleet does not push to GitHub.
The Odonian source checkout is mounted from your host and remains writable from the sandbox.
The full walkthrough, with expected output at each step, is
docs/demo.md.
You will need
- Docker Sandboxes (
sbx). The fleet runsclaudewith permission prompts off, so it belongs in an isolated container. - Go 1.25.6+,
git,jq,curlinside the sandbox. The script builds the binary there. - Claude Code CLI, logged in. Workers and reviewers are
claude -psessions. The run uses real model calls on your Claude account (a capped auth probe, then worker and reviewer sessions; competing claims and rework can add sessions). On a subscription that is plan usage, on an API key it is billed. In three measured runs on 2026-09-12 the task took 68 to 112 seconds fromreadytoapproved, after a boot that includes building the binary. - Codex is optional for this demo, whose reviewer is
opus. With Claude installed and authenticated, skipsbx-agent-setup.shand boot directly. That optional setup script installs missing Claude and Codex CLIs usingnpm, registry access, and passwordlesssudo; installation failures stop that script.
1 · Sandbox and boot
# on the host mkdir -p ~/src git clone https://github.com/boldfield/odonian ~/src/odonian cd ~/src/odonian sbx run --name odonian-demo claude . # log claude in, then exit sbx exec -it --workdir "$PWD" odonian-demo bash # inside the sandbox, already at the mounted checkout's absolute path # optional broader setup; skip if claude is installed and authenticated # bash harness/sbx-agent-setup.sh bash harness/sbx.sh --seed-demo
2 · Watch, inspect, decide
# on the host, from the Odonian checkout: open a second sandbox shell sbx exec -it --workdir "$PWD" odonian-demo bash # inside that second shell; the boot banner prints these export ODONIAN_URL=http://localhost:8080 ODONIAN_TOKEN=sbx-local-token ODONIAN_HOME=/tmp/odonian export ODONIAN_DELIVERY_MODE=local_commit ODONIAN_REPO=/tmp/odonian/repo ODONIAN_WORKTREE_HOME=/tmp/odonian/worktrees export PATH=/tmp/odonian/bin:$PATH tail -f /tmp/odonian/logs/workers.log /tmp/odonian/logs/reviewers.log # Ctrl-C stops this log viewer; leave the boot shell running. # copy these commands with their full IDs from the boot banner odonian pending --project <project-id> # empty until review or approved odonian diff <task-id> odonian approve <task-id> # freezes a branch; or reject --note "…"
The boot builds the binary, checks that claude is authenticated with a live probe, starts the
server on :8080 (reusing its SQLite file on later runs), creates the repo and the project, posts the task, and
starts two workers and two reviewers. It ends with [sbx] Odonian fleet is UP. and the exact
commands for your board. Approval records done and preserves a branch for you to merge;
it does not merge into main. Clean up with bash harness/sbx.sh stop, then
rm -rf /tmp/odonian after saving any work you want to keep.
Run it on your own repository
Run the fleet in a disposable sandbox or container with authenticated Claude and GitHub CLIs.
The repository you give it must have origin/main.
# Go 1.25.6+; build and start the server git clone https://github.com/boldfield/odonian && cd odonian && make build ODONIAN_TOKEN=your-secret-token ./bin/odonian server # REST API on :8080, SQLite at ./odonian.db # in each fleet terminal, from the built Odonian checkout export PATH="$PWD/bin:$PATH" mkdir -p ~/.odonian test -e ~/.odonian/env || cp harness/env.example ~/.odonian/env chmod 600 ~/.odonian/env ${EDITOR:-vi} ~/.odonian/env # set URL, matching token, full project UUID, absolute repo path # create a project, register a design doc, post and promote tasks through docs/api.md first cd harness && ./worker.sh worker-1 # ./reviewer.sh reviewer-1 in another terminal
The running guide covers the fleet on a laptop, in a sandbox, and on Kubernetes; the harness README covers slots, worktrees, per-owner forge tokens, and multi-project mode; configuration lists every variable.
Why “Odonian”
From Ursula K. Le Guin's The Dispossessed: the Odonians of Anarres organize work through voluntary association. Postings are claimed by the people who will do them because the work is worth doing; no boss dispatches anyone. Two convictions from that book shape how this system behaves.
Work is claimed, not assigned. There is no assignment API and no scheduler. An agent asks for the next task it can do and takes it, or is told why it cannot. Capacity is whatever you point at the board, a stuck agent affects only its own lease, and the substrate's only promises are that claiming is atomic and dependencies are honored.
Machines labor; a person makes the living judgment. Agents write and review every line, but the merge decision is a state in the machine that defaults to a human, reviewers can be a different vendor from the author, and the board keeps an audit trail of who did what. The AGPL license is the same politics.
The mark is the syndicate: an unclosed ring of agents, and the moon holding the gap, the human.