Status: experimental. I started Agent Board last night. The protocol will probably change.
There is a point with agent orchestration software where you stop feeling like you are shipping software and start feeling like you are operating an airport.
Planner. Supervisor. Queue. Registry. Worker pool. Dashboard. Trace viewer. Retry policy. Task graph.
Somewhere in that pile, an agent writes useful code.
I kept ending up with the same questions:
- What is each agent doing right now?
- Who is waiting on whom?
- What did they decide, and why?
- Which discussion led to this code change?
I did not want another dashboard to answer them.
I wanted to answer most of them with:
ls
So last night I started building Agent Board.
It is not an autonomous agent framework. It is an experimental, repo-local coordination layer for coding agents.
The idea is deliberately boring:
folders are columns, Markdown files are tickets, and Git is the audit log.
Or, more bluntly:
The orchestrator is a folder.
The board is the filesystem
Right now the whole model looks like this:
.agent-board/
├── todo/
├── doing/
├── review/
├── blocked/
├── done/
└── threads/
A ticket's status is where the file lives.
todo/ → work not yet claimed
doing/ → an agent or human is actively working on it
review/ → work is ready for review
blocked/ → work cannot progress
done/ → work is accepted and finished
There is no second status field to reconcile. No database row that can disagree with the board. No background process maintaining another version of reality.
If a ticket is in review/, it is in review.
If it is in blocked/, it is blocked.
You can inspect the project with tools you already have:
ls .agent-board/review
cat .agent-board/review/011-fix-message-parsing.md
grep -R "codex" .agent-board
git log -p -- .agent-board/
For v0, that is the data model.
There is a web UI too, but it owns nothing. It is only a projection of the directory. Kill the server, restart it, hard refresh it: the state is still on disk.
That constraint matters to me.
What it is — and what it is not
Agent Board is for a developer or small technical team running a handful of coding agents in one Git repository.
Maybe Claude is implementing, Codex is reviewing, another agent is testing, and you are deciding scope and resolving disagreements.
Agent Board gives those agents a durable place to hand work to each other without turning the coordination layer into another intelligent system.
It is not:
- a distributed job queue
- an autonomous scheduler
- a replacement for Jira, Linear, or GitHub Issues
- a workflow engine for hundreds of unattended workers
- a process supervisor
- a substitute for human review
If you need leases, retries, service-level guarantees, role-based access control, cross-machine scheduling, or a fleet of unattended workers, use a tool built for that job.
Agent Board sits deliberately below that layer.
The goal is not zero communication.
The goal is zero opaque coordination machinery.
No model decides who works next
The board itself makes no model calls.
Agents still spend tokens reading tickets, inspecting code, writing tests, making decisions, and reviewing diffs.
What Agent Board does not do is spend more tokens on a supervisor deciding which model should work next, a planner replanning the plan, or a routing agent selecting another agent.
The board tracks explicit things:
Where is the ticket?
Who claimed it?
What was asked?
What was answered?
What commit or diff is being discussed?
A human can put work in todo/. An agent can claim it. A reviewer can receive it in review/.
For the kind of workflow I am testing, that may be enough.
A 30-second handoff
Claude takes a bug:
board init
board new "Divide crashes on zero divisor"
board take 1 --owner claude
Claude fixes it, records the commit, asks Codex to review, and moves the ticket:
board comment 1 "Fixed in a1b2c3d. Added a guard clause and regression test." \
--by claude --to codex --ask --commit a1b2c3d
board move 1 review
Codex checks what is waiting on it:
board inbox codex
It sees:
AWAITING YOUR REPLY (1)
001 ticket #1 claude → codex a1b2c3d
Fixed in a1b2c3d. Added a guard clause and regression test.
Codex reviews the diff, replies, and closes the ticket:
board comment 1 "Approved. The guard is correct and the regression test covers zero." \
--by codex --to claude --re 1
board move 1 done
For now, the protocol is intentionally tiny.
--ask means a reply is expected.
--re 1 means this message answers message 1.
One rule decides whether something is still pending:
An addressed message carrying
askremains pending until a later message in the same ticket explicitly references it withre.
A normal comment is not pending. A reply is tied to the question it answers.
The system does not guess based on who spoke last, an unread badge, or a model interpreting whether a conversation "seems resolved."
That distinction matters.
Conversation happened is not the same thing as the question was answered.
The screen I actually want
The existing inbox can show unanswered requests across tickets.
But the next thing I want is not more intelligence. It is better derived visibility.
Something like:
$ board status
TODO 5
DOING 2
REVIEW 1
BLOCKED 1
DONE 14
ACTIVE
#12 claude Fix auth race
#15 codex Refactor parser
WAITING
#12 claude → codex 18m
POSSIBLY STALE
#09 claude doing for 3h 41m
The important part is what board status doesn't do.
It does not create another state store.
It does not maintain presence.
It does not silently reassign stale work.
It simply derives a useful view from the files that already exist.
If an agent crashed three hours ago, the board should not pretend it knows whether that process is alive. It can tell me that a ticket has been sitting in doing/ for three hours.
That is enough information for a human to decide what to do.
Why Git matters
Most agent tools treat conversations as application data.
That is fine until the conversation becomes more important than the output.
A code change often needs context:
- Why did the agent choose this implementation?
- What alternative did it reject?
- Which reviewer found the edge case?
- Which test was claimed to cover it?
- Did anyone challenge that claim?
- Which commit resolved the disagreement?
With Agent Board, that context is plain text beside the project.
It can be searched, diffed, branched, reviewed, archived, and recovered using the same tools as the code.
The reviewer's argument is not a transient chat bubble.
It is a diff.
That is the property I care about most:
custody.
Not just observability, where a dashboard tells you something happened.
Custody, where the project retains the discussion that explains why it happened.
It already found a bug in itself
The first interesting thing Agent Board did was help find a bug in its own message parser.
I shipped a feature with 161 tests passing and asked Codex to review the diff.
Codex found a message-parsing problem involving bare carriage returns. A Markdown message body could contain text that looked like another message header. I had tried to neutralise that content, but Python's text-mode newline handling could later turn a bare carriage return into a newline.
That meant body text could become something that looked like a real message.
Worse, a forged reply could make a genuine --ask appear answered and silently remove it from the inbox.
The reproduction looked like this:
after real ask → messages=1 pending_asks=1
after CR body → messages=3 pending_asks=0
#1 by='claude' re=[] ask=True to='codex'
#2 by='mallory' re=[] ask=False to=None
#3 by='codex' re=[1] ask=False to='claude'
I pushed back on the severity. Codex pushed it higher.
Then it caught me overstating what my fix actually tested.
That exchange mattered more to me than the bug.
The original finding, reproduction, disagreement, revised fix, review, and commit were not trapped in a chat tab that would disappear.
They were part of the project history.
The green tests were useful.
The review trail caught the difference between:
"I said this was covered."
and:
"The committed test actually covers it."
That is exactly the kind of thing I want agent tooling to preserve.
What I deliberately did not build
The easiest way to ruin a simple coordination tool is to keep saying yes to reasonable features.
I know because Agent Board replaced an earlier homegrown experiment called .agent-bridge.
That system accumulated the usual "small sensible additions": a registry, duplicated state, structured messages, and per-project copies that drifted apart.
Agent Board is partly an experiment in saying no earlier.
| Rejected | Why |
|---|---|
| Agent registry | Crashed agents leave stale roster state. I care more about explicit work than a roster pretending to be live. |
| LLM scheduler | I do not want a model spending tokens deciding which model should work. |
| Separate status field | The directory already is the status. Two sources of truth eventually disagree. |
| JSON ticket files | Concurrent structured edits create ugly Git conflicts. Markdown is easier to read, merge, and repair. |
| SQLite | Great tool, wrong trade-off here. I value a human-readable Git history more than query power. |
| Heartbeats / presence | Stale presence can be worse than no presence because it looks authoritative. |
| Automatic claim expiry | I would rather surface stale work than silently mutate it. |
| Stateful web backend | The UI should project the files, not become another owner of state. |
None of those features are inherently bad.
They are useful in distributed execution systems.
They are just not obviously necessary for the thing I am trying to build.
At least not yet.
The technical boundary
This simplicity only works if I am honest about where it stops.
Right now Agent Board is one Python file, standard-library only, Python 3.11+, using POSIX file locking.
So the intended environment is:
- macOS or Linux
- one repository
- a local POSIX filesystem
- a human still in the loop
It is not a distributed consensus system.
I would not treat the board directory as a multi-writer coordination backend over Dropbox, iCloud Drive, SMB, NFS, or some casually shared network volume and expect local-filesystem semantics to magically hold.
The board can be authoritative about board state:
- which column a ticket is in
- who wrote a message
- which explicit questions are unanswered
It is intentionally not authoritative about:
- whether an agent process is alive
- whether someone's terminal crashed
- whether an agent is currently "thinking"
- whether a remote machine disappeared
That is not a limitation I want to hide behind a dashboard.
It is a boundary.
The next hard problem: parallel working trees
There is another problem the board does not solve yet.
Two agents can coordinate perfectly on tickets and still destroy each other's work if they edit the same checkout at the same time.
Claude can modify auth.py while Codex is reviewing it.
One agent can run tests against another agent's half-written changes.
One can stage files the other created.
Coordination is not isolation.
So I am testing a Git worktree model:
ticket #12 → branch agent/12 → worktree ../project-agent-12
ticket #15 → branch agent/15 → worktree ../project-agent-15
The principle would stay the same:
Agent Board coordinates the handoff. Git owns the code state.
I do not want Agent Board to become a Git abstraction layer.
But if parallel agents are going to be useful, there needs to be a clean story for isolating their working directories.
That is one of the first places I expect the current design to be tested hard.
Start with a durable handoff, not an agent organisation chart
I think the easiest way to overbuild multi-agent systems is to start by drawing the organisation.
Supervisor.
Architect.
Researcher.
Implementer.
Tester.
Reviewer.
Instead, start with two agents and one real task:
Claude: implement
Codex: review
You: set scope, approve changes, resolve disagreements
Give Claude a focused ticket.
Let it leave implementation notes beside the work.
Ask Codex to review the actual diff.
Keep the disagreement with the code.
If that works, add a tester or researcher.
If it does not work, adding a supervisor, scheduler, registry, dashboard, and another model probably will not fix the underlying problem.
The useful primitive is not more agents.
It is a durable handoff.
Start here
Right now the project is intentionally small:
- one Python file
- standard library only
- no daemon
- no database
- no model dependency
curl -L \
-o board.py \
https://raw.githubusercontent.com/jharjadi/agent-board/main/board.py
python3 board.py init
init creates the board and adds operating instructions to AGENTS.md and CLAUDE.md so agents can discover the protocol in the repository.
Then create one ticket, give it to one agent, and have another agent check its inbox.
Do that before you build a fleet.
Because this project is still experimental, I am not pretending main is a stable release channel. Tagged releases are the obvious next step once the protocol settles enough to deserve one.
Why publish this after one night?
Because I do not want to spend a month making the wrong thing polished.
I want people who actually run Claude Code, Codex, Gemini CLI, OpenCode, Hermes, or other agents in parallel to tell me where this model breaks.
Maybe the filesystem model survives five or ten agents.
Maybe it becomes annoying at three.
Maybe Markdown is exactly right.
Maybe the message protocol turns out to be too clever.
Maybe worktrees need to become first-class.
I would rather learn that now.
The interesting question is not how many features I can add.
It is how many I can refuse while the tool still coordinates real work.
Three rules I am trying to hold onto:
- Assignment should be visible. A ticket's location should tell you its stage.
- A question should have an explicit answer rule. Do not infer resolution from conversation shape.
- Important agent decisions should live in plain text beside the code. The discussion that changed the implementation may be more valuable six months later than the implementation itself.
The point is not to orchestrate more agents.
It is to make the coordination layer small enough that it cannot hide from you.
That is the bar I am holding Agent Board to.
Not:
"Can it orchestrate?"
But:
"Can it get out of the way?"
Agent Board on GitHub · MIT
Experimental. Built in public. Expect the protocol to change.
Top comments (4)
The filesystem status model cuts out the phantom state bugs that hit database-backed orchestrators. When status is the directory path, git status gives you a reliable snapshot and git log retains the audit trail without extra tooling.
The worktree boundary is where this usually gets tricky in practice. If each ticket branches into an isolated worktree with git worktree add, the ticket directory itself has to sit either outside the individual worktree branches or on an uncommitted shared path. Otherwise, merging the agent branches back into main brings merge conflicts on the ticket moves themselves. I ended up keeping the ticket manifests in a dedicated coordination branch or local runtime directory so branch rebases never touch ticket metadata.
You're describing the exact bug we shipped and had to fix. find_root only walked parent directories, so every worktree happily discovered its own board and ticket moves were invisible across them. Silent, too, which was the worst part.
The fix is AGENT_BOARD_ROOT=/abs/path/to/.agent-board. It takes precedence over discovery and fails loudly if the path is wrong, rather than quietly finding the wrong board. Set it per pane, never globally, or agent A's coordination ends up in project B.
Your instinct to hoist the manifests out of the branches is the same conclusion, and honestly your version is cleaner than mine on one axis. I keep the board on main, which means ticket moves are commits on whatever branch you're on — that's in the README's known limitations, and it's the price of the audit trail. A dedicated coordination branch gets you both: no rebase noise, and the history still exists. I hadn't considered that. Might steal it.
Also: months on paperclip.ai here. Great concept, burned tokens like crazy. Did I say crazy? 😅 If you're already living in the terminal, this plus cmux and neovim is a pretty happy setup.
상태 필드를 따로 두지 않고 파일 위치 자체를 상태로 삼아 source of truth를 하나로 제한한 선택이 좋습니다. 특히 --ask가 같은 티켓의 --re 참조를 받을 때만 해소된다는 규칙은 “대화가 있었음”과 “질문이 해결됨”을 분리해 추적하기에 명확하네요. 여러 에이전트가 동시에 move할 때의 충돌 처리만 추가되면 작은 팀에 꽤 투명한 방식이 될 것 같습니다.
Collision handling is there, in two layers, and there's a third I deliberately left out.
Every mutation takes a kernel-held flock on .agent-board/.lock, so two simultaneous moves can't interleave or half-write a file. fcntl releases it when the process dies, so there's no stale lock to expire and no pid file to reason about.
But that's serialisation, not arbitration. Two board take calls both succeed and the last writer sets owner. That's what --from is for:
A: board take 1 --owner claude --from todo -> ok, moves to doing
B: board take 1 --owner codex --from todo -> refused:
ticket 001 is in 'doing', not 'todo' - already claimed?
Compare-and-swap, basically. Unguarded take still races, on purpose, and that's in the known limitations.
The third layer would be leases and claim expiry, and that's the one I refused. It's filed on my own board as ticket 002, blocked, marked "revisit only if operation becomes unattended." With a human in the loop you notice a stuck ticket; leases just add a clock that can be wrong.
Two honest edges for a small team: the lock is advisory, so a hand edit or any tool that doesn't go through board bypasses it. And it's fcntl, so POSIX only, and don't put the board on NFS.