kanban-pro
# kanban-pro
**A kanban board your coding agents natively understand — and a state machine they can't
wander out of.**
> **Don't want to read this?** Paste this into any agent that can browse — Claude Code,
> Codex, ChatGPT, whatever you use:
>
> ```text
> Do I need this? https://github.com/galvani/kanban-pro
> ```
>
> It finds [`llms.txt`](llms.txt), which is written for the agent rather than for you:
> what works today versus what's still planned, who should use it, **who should walk
> away**, how it compares to a plain kanban / Jira+MCP / your agent's own to-do list, and
> what AGPL-3.0 means for whatever you're planning to build. It's told not to sell you
> anything and to say plainly if you don't need it.
>
> Then, in the same conversation: `Yes — install it for me and prove it works.` It runs
> the commands itself (needs [uv](https://docs.astral.sh/uv/); no clone), verifies the
> package builds *before* touching your config, and proves the server works by creating a
> board and moving a card — not by trusting that a config entry means success.
You run coding agents every day — Claude Code, Codex, whatever comes next. They do
real work: fix bugs, ship features, review each other's changes. But their *tasks*
live in chat scrollback. You come back to your desk asking: what is my agent doing
right now? What's blocked? What did it finish while I slept? There is no board that
both you and the agents can see and update.
kanban-pro is that board — and, once you have more than one agent, rather more than a
board. A kanban board is a passive signalling surface: no move is illegal, and the rules
live in your team's habits. That works when the workers are people who can be told. It
does not survive a cheap model on a long unattended run. So underneath the board there is
a **state machine** (a card may move only along transitions you declared), **gates** (a
card cannot advance until verification someone *else* specified has been recorded against
it), and a **leased work queue** (a claim expires, so a crashed agent's card comes back).
The board is what you watch and intervene through; those three are what actually hold.
It's a real kanban (boards, columns, cards, comments) served over **MCP**, the
protocol your agent harness already speaks, so registering it once turns every agent
session into a worker on a shared, **rule-enforced pipeline**: it pulls its own card,
leases it so nobody else takes it, moves it only along transitions you declared legal,
reports what it found, and asks you when it's stuck. Every one of those steps is stamped
with which agent did it. Concretely:
- **Make the pipeline strict, not suggested.** Each board carries a **state machine** over
its columns (`ready → running → review → done`, and nothing else), stored on the board
itself (no config file) and administered over MCP with `set_flow`/`set_transitions`. An
illegal move is *refused*, not logged and allowed. Agents call `list_transitions` instead
of guessing — and `list_work` inlines each card's legal moves, so a worker sees its
options without a second call. A one-off card can carry its own inline machine or the
`free-roam` escape, and `force=true` always works but stamps `forced: true` on the event.
**Overrides are allowed and never silent** — the audit trail is the safeguard, not a lock.
(The tools are still spelled `flow`; prose calls it a state machine. See
[SPEC.md § Terminology](SPEC.md#terminology).)
- **Let agents pull their own work.** `list_work` answers "what should I work on?" —
the agent's cards, each with its legal moves inline — and an atomic claim/lease
(TTL + heartbeat + crash-reclaim) guarantees two agents never grab the same card. A
crashed worker's lease expires and its card returns to the queue on its own.
- **Give all your agents one shared board.** One `claude mcp add` line per harness;
multiple harnesses share the same store safely, each under its own identity — a Claude
Code session, a Codex run, and a Hermes dispatcher all working the same pipeline.
- **Always know who did what.** Every connection declares an actor
(`agent:claude-code`, `human:jan`); every write lands in an append-only change-log.
Ask `list_changes` and see exactly which agent moved which card, and when.
- **Sleep through agent mistakes.** A misfiring agent can't one-shot destroy data:
deletes are archive-first (purge only what's already archived), board/column deletes
refuse while cards remain, WIP limits are enforced on every move, and retried
creates with an idempotency key return the original instead of a duplicate. When an
agent hits a decision it isn't entitled to make, it **raises an attention flag** routed
through the change-feed — instead of guessing or dying silently.
- **Let the fleet escalate within itself.** An attention flag names *who* should answer,
and that target is any actor — `agent:architect` as readily as `human:jan`. A coder that
finds the ticket ambiguous bounces the decision to the agent whose call it is, files the
question in its work report, and moves on; only what no agent may decide reaches you.
- **Read a status report, not scrollback.** Each card carries a structured
**work report**: what it's about, the plan, findings, verification checks, the
verdict, the handoff — and the agent's open questions. Agents write one section at a
time (`record_work_report`, upserted by item id, never a blind blob rewrite); you
answer a question with `answer_work_report_question`, and the answer is mirrored back
as a normal comment. This is the handoff contract between one agent and
the next — a reviewer reads the coder's findings and checks, not its transcript.
- **Keep your own board *next to* the team's Jira.** Per-board routing (`routes.toml`)
puts a whitelisted Jira project and your native boards behind the same server, one
shared change-log and cursor across both — a routed card's id is its Jira key, resolved
by shape, nothing to prefix or mount by hand. A move on a routed card lands in
kanban-pro's own store, never back on the ticket; a comment does too unless the route's
`writes` names `comments`, which posts it to the ticket with `@Name` resolved to a real
mention, and a `create_card` on the board files an issue where `writes` names `issues`.
Trello is not built (🔜).
## Built for harness-driven agentic pipelines
One agent working one card doesn't need any of this. The moment a *harness* is dispatching
work — a dispatcher spawning workers, a coder handing to a reviewer, a rebaser retrying a
conflict, all unattended while you sleep — the board stops being a to-do list and becomes
the **control plane**. That is what kanban-pro is shaped for.
The failure modes of an unsupervised agent fleet are specific, and each has a mechanism
here rather than a convention:
| The failure | What stops it |
|---|---|
| Two workers pick up the same card | Atomic claim/lease. The second `claim_card` loses, deterministically. |
| A worker dies holding a card | TTL + heartbeat. The lease expires and the card is reclaimable — no stuck lane, no cleanup job. |
| An agent skips the review gate | The board's state machine refuses the transition. Not a lint, not a prompt instruction — a rejected call. |
| An agent decides to "clean up" the board | Archive-first deletes; a live card cannot be purged; column deletes refuse while cards remain. |
| A retried tool call creates a duplicate card | Idempotency key returns the original result, with no second change-log event. |
| A lane silently fills up | WIP limits are enforced on every move, over any backend. |
| An agent guesses at a decision that was yours | It raises an attention flag and files a question on the card, routed to you through the change-feed. |
| Nobody can reconstruct what happened | Append-only change-log, every write stamped with the acting agent, forced moves flagged `forced: true`. |
Prompt instructions are advisory: an agent that drifts, or a cheap model on a long run,
will step outside them. These are **enforced at the API**, so drift surfaces as a refused
call the agent must handle, and a deliberate override survives as evidence in the log.
The pieces compose into a real pipeline. A dispatcher creates cards in `triage`. Workers
`list_work`, claim, and move along the declared transitions — `ready → running`, then `review`.
Each writes its plan, findings, and checks into the card's work report, so the reviewer
inherits a **structured handoff** instead of the previous agent's transcript. A blocked
worker raises attention and waits for your answer rather than inventing one. Meanwhile
`wait_changes` lets a notifier, a dashboard, or the next stage in your harness block on
the feed and wake the instant something moves — a durable, cursored queue where every
"message" is a card with an owner, a history and a verdict.
The state machine this board actually runs — `triage → todo → scheduled → ready →
running → blocked → review → done`, with `waiting for mr`, a `won't do` cancel lane, the reopen
edge, and an ad-hoc `staging` lane — is the `agent-lifecycle` preset (`init_board(preset=
"agent-lifecycle")`); `set_flow`/`set_transitions` edit it live.
## Quick Start
```bash
uv sync # install deps (incl. dev tools)
uv run kanban-pro-mcp # MCP server (stdio) over the native SQLite store
uv run kanban-pro-mcp --profile memory # ... over an ephemeral in-memory board
```
Pass `--actor kind:name` (e.g. `agent:claude-code`, `human:jan`) so every write is
attributed in the change-log.
The store lives at `~/.local/share/kanban-pro/kanban.db` (override: `KANBAN_PRO_DB`).
## Install into your harness
Two shapes. **One keyed daemon** is the intended one wherever more than one agent works the
board: a single `kanban-pro-mcp --http --keys` process, every client arriving over HTTP
with its own bearer key (actor and board scope come from the key, never from the client),
one poller for any routed Jira board, one change log. Get the registration line:
```bash
uv run kanban-pro-mcp --print-config claude --url http://127.0.0.1:8775/mcp --key-env CLAUDE_KANBAN_TOKEN
```
which prints, for Claude Code:
```bash
claude mcp add --transport http -s user kanban-pro http://127.0.0.1:8775/mcp \
--header 'Authorization: Bearer ${CLAUDE_KANBAN_TOKEN}'
```
The harness expands `${CLAUDE_KANBAN_TOKEN}` from its own environment when it connects, so
the token lives in your secrets manager and the process that launches the harness carries
it (`keys-pro run --as claude -- claude`); a missing variable fails the connection with a
named warning rather than silently reaching the board as nobody.
**Stdio spawn** — no daemon, no port — is the single-user shape: the harness starts its own
server and `--actor` is a claim the client makes about itself. It cannot carry a key and
sees no routed board.
```bash
uv run kanban-pro-mcp --print-config claude # or: codex | opencode | hermes
claude mcp add kanban-pro -s user -- \
uv run --directory /path/to/kanban-pro kanban-pro-mcp --actor agent:claude-code
```
Several harnesses may register either shape; over stdio each spawns its own process and
they share the SQLite store safely.
Then install the agent skills so your sessions know how to drive the board — an
orchestrator (plan work, set up a board's state machine) and a pull worker (claim/move/report):
```bash
uv run kanban-pro-mcp --install-skills # -> ~/.claude/skills (never overwrites)
uv run kanban-pro-mcp --install-skills /some/dir # or a custom skills dir
```
**Any OS (mac/Windows/Linux), no clone needed** once the repo has a remote: install
[uv](https://docs.astral.sh/uv/), then `uvx --from git+<repo-url> kanban-pro-mcp`, or
`uv tool install` to put `kanban-pro-mcp` on PATH.
## What it looks like in practice
An agent session over MCP (all real today except the `PRO-12` human-readable card
keys, which are 🔜 — ids are uuid hex for now):
```
agent> list_boards
→ [{id: "b1", name: "kanban-pro"}]
agent> create_card {title: "Add retry logic to the sync worker",
placements: [{board_id: "b1", column_id: "todo", position: 0}]}
→ Card PRO-12 created (actor agent:claude-code, logged)
agent> move_card PRO-12 → doing
→ conflict: WIP limit reached on 'doing' (3/3)
agent> list_transitions PRO-12
→ source: flow (board) — legal from todo: [doing]
agent> move_card PRO-12 → done
→ conflict: board flow does not allow todo -> done; use force=true to override
agent> move_card PRO-12 → done, force=true
→ Card moved. The event carries forced=true — never silent.
human> list_changes since=41
→ [{seq: 42, actor: "agent:claude-code", op: "card.moved", forced: true, …}]
```
## Configure it
One thing is required: **say who you are**. `kanban-pro-mcp` with no arguments gives you
the native SQLite board and free movement, but a connection that declares no actor may
only *read* — every write is refused, because an event nobody can be held to makes the
board look audited while telling you nothing. Everything else has a working default, and
the [configuration guide](docs/configuration.md) covers each in full.
| Setting | How | Default |
|---|---|---|
| Which backend | `--profile <name>` / `KANBAN_PRO_PROFILE` | `default` (native SQLite) |
| Who is writing | `--actor <kind:name>` / `KANBAN_PRO_ACTOR` | none — **required for writes** (per-board opt-out: `ext["anonymous_writes"] = "allow"`) |
| Where the board lives | `KANBAN_PRO_DB` | `~/.local/share/kanban-pro/kanban.db` |
| What a card id looks like | `board.id_scheme` (per board) | `short:6` — 6 readable chars |
| Which moves are legal | `set_flow` / `set_transitions` (per board) | none — free movement |
| Who may connect at all | `--keys [file]` / `KANBAN_PRO_KEYS` (HTTP only) | off — no authentication |
| Which boards a connection may reach | `--board` / `--board-ro` | all of them |
| One named operation on an otherwise read-only board | `--allow BOARD=OP` / a keyfile's `allow` table | none granted |
| Other backends behind this process | `routes.toml` (`$KANBAN_PRO_ROUTES` or the XDG config dir) | absent — single-backend, unchanged |
### Keys and per-board access — both optional
Over stdio, each client spawns its own server and `--actor` is a claim it makes about
itself. Over HTTP that stops working: whoever reaches the port writes as that server's
actor. A **keyfile** replaces the claim — each key carries the actor it writes as and the
boards it may read and write, so one server serves every agent:
```toml
# ~/.config/kanban-pro/keys.toml — chmod 600, or it is refused
[keys.verde]
token = "kp_…"
actor = "agent:verde"
write = ["verde"] # read + write
read = ["shared"] # read only; `read` with no `write` = a read-only key
```
Two rules: **auth on means the key is required** (no exempt paths — everything is `401`
without one), and **no permission granted means access denied** (a key with no `write`/`read`
reaches no board, never all of them; `write = ["*"]` is the one way to be unconfined, and you
have to type it).
```bash
uv run kanban-pro-mcp --issue-key verde --actor agent:verde --board verde # generate one
uv run kanban-pro-mcp --http --keys # then serve
```
Scope is enforced on reads as well as writes — `list_boards`, `list_work` and the change
feed are all filtered — and a call the server cannot resolve to a board is refused rather
than passed through. **Neither is on unless you ask for it**: no `--keys` means no
authentication, and no `--board` means unconfined, exactly as before.
A key stops accidents and stray clients, not a local attacker: everything running as your
uid can read the keyfile. See the [configuration guide](docs/configuration.md#api-keys--when-the-actor-should-not-be-self-declared)
for what that does and does not buy you.
### Card ids
A board says what its cards are called — `id_scheme`, set when the board is created and
changeable with `update_board`. The default is `short:6` (6 readable chars); `uuid` opts
back into the old 32-hex shape:
```jsonc
init_board(board_id="ops", preset="simple-kanban", id_scheme="seq:OPS") // OPS-1, OPS-2
```
`short:8` gives `k7f3q9xw`, `prefix:KAN:6` gives `KAN-k7f3q9`, `seq:KAN` counts `KAN-1`,
`KAN-2`, `KAN-3`. Each board counts on its own, and switching a board's scheme leaves the
ids it already handed out alone — an id is an opaque string. Re-shaping the ids on a board
that already has cards is `kanban-pro-migrate reid`: it mints a fresh id per card in the
target scheme and keeps the old one resolving forever as an alias.
### Workflow rules
A card can move anywhere until you give the board a **flow** — the legal column→column
moves, keyed by column id, stored on the board itself and set over MCP:
```text
set_flow("b1", {
"b1:todo": ["b1:doing"],
"b1:doing": ["b1:review", "b1:todo"],
"b1:review": ["b1:done", "b1:doing"],
})
# or one lane at a time: set_transitions("b1", "b1:review", ["b1:done", "b1:doing"])
# or start a NEW board pre-wired: init_board("b1", preset="agent-lifecycle")
```
Every edge references a real column on that board — `set_flow` refuses a dangling id, and
deleting a column strips the edges that named it, so the machine can't drift from the columns.
A card overrides its board's machine with `ext["kanban_pro.scheme"] = "free-roam"` (unrestricted)
or carries its own one-off flow inline in `ext["kanban_pro.flow"]`. A column named in no
edge stays free, so you can keep an ad-hoc lane without governing it. **No flow at all →
the whole board is free-roam:** enforcement is opt-in and never appears uninvited.
A board may also declare **named routes**: alternative workflows over the same columns, for
work that takes a shorter path than the default. `set_route("b1", "no-customer", {...})`
declares one with the same shape as `set_flow`; a card follows it with
`ext["kanban_pro.route"] = "no-customer"`, `list_transitions` reports the name in `route`, and
the board page shows it on the card with a selector while the card still rests in a backlog
lane. A route governs only the lanes it names, exactly like the flow, so a lane is shut out
of a route by listing it as a key with no out-edges and never as a target. A card naming a
route the board does not declare cannot move at all until the name is fixed or the route
declared — it never falls back to the default. `set_route(..., {})` drops a route.
Agents never guess — `list_transitions` (and every item `list_work` returns) carries the
card's legal moves. An illegal move is refused; `force=true` performs it anyway and
stamps `forced: true` on the event. Overrides are always allowed, never silent.
WIP limits are separate: they live **on the column** (`update_column`), not in the flow,
and kanban-pro enforces them over any backend.
New boards come pre-wired from a preset — `blank`, `simple-kanban`, `docs`, or
`agent-lifecycle` (the swarm lifecycle this board runs) — via `init_board(preset=…)`.
### When an agent needs you: the attention flag
`raise_attention(card_id, reason, for_actor="human:jan")` flags the card, shows it on the
board, and puts an `attention.raised` event on the change-feed carrying the reason and
the target — so a listener can deliver the question wherever you are. You answer;
`clear_attention` retires the flag.
A flag says how loudly it speaks: `severity="block"` (the default) **halts** the card
until someone clears it, while `warn` and `info` are visible on the board and on the feed
but let the work carry on — so a worker can flag something worth knowing without stopping
the card.
Attention is the **signal**, not the content: the question itself goes in the card's work
report under `questions[]`, which you resolve with `answer_work_report_question`
and which is mirrored back as a normal comment.
### Listeners: getting events out
Every write lands in the append-only change-log. A listener is anything that reads it
from a cursor it stores — no broker, no registration. Probe the head once with
`wait_changes(since=-1)`, then loop: `wait_changes` blocks until events land and returns
the next cursor. Persist that cursor and a listener that was down resumes exactly where
it stopped, dropping nothing and re-delivering nothing.
A runnable one — long-polls the feed, DMs Slack on card moves and on attention raised for
you — is in [examples/notifier/](examples/notifier/README.md).
## One board API, many backends, gaps polyfilled
Out of the box, kanban-pro *is* the board — cards live in its own SQLite store. But
the board API is deliberately separated from where cards are stored, via **adapters**.
The scenario that motivates this: your team tracks work in Jira. You point kanban-pro
at a whitelisted Jira project, and your agents see and discuss real Jira tickets through
the exact same safe, attributed kanban tools — no agent ever learns the Jira API or
holds a Jira token. By default that's **read-only toward Jira**: a comment your agent
adds lands in kanban-pro's own store, merged into `list_comments` alongside Jira's real
ones, and no transition reaches the ticket. A route whose `writes` names `comments` posts
the agent's comment to the ticket instead, `@Name` resolved to a real mention when exactly
one person on the ticket matches — the returned comment says who was notified and who
was not — and one whose `writes` names `issues` lets `create_card` file a real issue,
handed back with its key as the card id. And where Jira lacks something kanban-pro offers
(WIP limits, per-board flow, checklists), kanban-pro **fills the gap itself** — and
tells you honestly which is which: query `capabilities` and each one reports
**`native`** (the backend does it), **`polyfilled`** (kanban-pro does it on top), or
**`unavailable`**. It never lies about what's real.
**Your data stays where it belongs.** When a backend is attached, that backend is the
system of record — kanban-pro does not quietly copy your cards into its own SQLite. The
adapter maps canonical fields onto the backend's fields, and everything the backend knows
that the canonical model doesn't rides back out through `ext` (Hermes's harness columns
arrive as `ext["hermes"]`, verbatim). kanban-pro supplies storage **only for what the
backend has nowhere to put** — and only then. Rules that store nothing (WIP limits, flow
enforcement) are pure enforcement: no data, so no split. Data that a backend genuinely
can't hold falls to kanban-pro's **overlay**, keyed to the backend's own ids. Since only
that last case creates a second home for data, the goal is to shrink it: where the
backend has *any* usable container (a comment, a description, a custom field), the
polyfill is **written through** into it so the backend stays authoritative and can show
the data in its own UI. *Write-through encoding is designed, not yet built (🔜) — today
polyfilled comments and relations live in the overlay.* Full breakdown:
[docs/configuration.md](docs/configuration.md#9-where-your-data-lives).
```
your agents (Claude Code, Codex, …)
│ MCP tools (51)
▼
┌──────────────────── kanban-pro core ────────────────────┐
│ actor stamping · change-log · delete guards · │
│ WIP + flow enforcement · capability polyfills │
└───────┬───────────────────┬────────────────────┬────────┘
native SQLite memory jira
(default system (ephemeral, (polled; whitelisted;
of record) for tests) comments per route)
```
Adapters today: **`native`** (the default SQLite system of record), **`memory`**, one
harness adapter (`hermes` — the pattern for wiring in your own harness's built-in
kanban), and **`jira`** — a Jira project is a board, a ticket a card, the card id the
Jira key. It runs under its own credential-holding process (`keys-pro run --as
gitlab-pro`, reading a Jira email + API token that never touch a file an agent reads)
and is **read-only toward Jira** unless a route says otherwise: it polls a whitelisted
set of projects into the shared change-log, and neither a transition nor a field write
reaches a real ticket — a comment lands in kanban-pro's own store, merged back into
`list_comments` alongside Jira's own (tagged `ext.origin: "jira"`), or is posted to the
ticket on a route whose `writes` names `comments`; a route whose `writes` names `issues`
creates an issue from `create_card`. All four pass one shared contract test suite.
Pick the backend with a **profile** — `--profile default` / `--profile memory` (or
`KANBAN_PRO_PROFILE`). A profile bundles an adapter with its settings; kanban-pro
always exposes the **full canonical surface** regardless of the backend's gaps.
**And you're not limited to one world.** Per-board **routing** (`routes.toml` — see
[configuration.md](docs/configuration.md)) puts several backends behind one process at
the same time: your native boards plus a whitelisted Jira project, one shared
change-log and cursor across both. A card id resolves to its backend by SHAPE — a Jira
key looks like `PROJ-123`, a native `seq:`/`prefix:` board can mint the same shape, and
a genuine collision refuses rather than guessing — so there is no mount prefix to type
and no separate address space to learn. An absent `routes.toml` reproduces today's
exact single-backend behavior. Copying a card across backends with a provenance link
and confirmation-gated sync remains 🔜 — routing dispatches each board to its own
backend, it does not copy or sync one card between two.
Boards move, too: a generic **migration tool** (`kanban-pro-migrate`) copies any
profile into any other — idempotent, dry-run first, provenance-stamped, the import
itself attributed in the change-log. It has run for real: a 172-card board with 608
comments imported port-to-port.
## The board is also your message bus
Look at the mechanics and you'll notice kanban-pro quietly replaces the queueing
infrastructure an agent fleet would otherwise need:
- The **change-log** is an append-only event stream with consumer cursors — an agent
(or your Slack notifier) reads `list_changes since=<seq>` and resumes exactly where
it left off. Kafka-style offsets, no broker to run. ✅
- **Push without polling loops:** `wait_changes` long-polls the same cursor and returns
the moment events land (instantly for writes through this server), so a consumer
blocks instead of spinning. ✅
- **Claim/lease** is the competing-consumers pattern: atomic claim with a TTL,
heartbeats, crash-reclaim = redelivery. Two agents never grab the same card. ✅
- The **attention flag** is routing: "this needs a decision" targeted at a specific
agent or human, carried in the event stream for notifiers to deliver. ✅
- **Durable subscriptions** (🔜 webhook listeners with per-listener cursors + retry,
and MCP notifications) round out fan-out.
The difference from a real broker: here every "message" is a **card** — durable,
stateful, attributed, with history — and the queue is a **board**, which you can read,
reprioritize and answer through the same tools your agents use.
## Why not X?
**If you are a human choosing a kanban board, you probably want one of the others.**
kanban-pro has no user accounts, no hosting and no web interface, and is version 0.1.0. It does have API keys
and per-board read/write scopes — but they are sized for telling your own agents apart, not
for holding a boundary against people: everything running as your uid can read the keyfile.
Trello, Linear, Jira, Vikunja, Wekan and GitHub Projects each beat it on UX, mobile,
multi-user and maturity, and it isn't close. The full, cited breakdown — including who should walk
away — is in [docs/comparison.md](docs/comparison.md).
What *is* hard to find elsewhere is the coordination machinery an unattended agent fleet
needs. Surveyed 2026-07-10 across sixteen products:
| | Enforced flow (refuses illegal moves) | WIP enforced on write | Resumable cursor feed | Atomic claim/lease |
|---|---|---|---|---|
| **kanban-pro** | ✅ | ✅ | ✅ | ✅ |
| Jira | ✅ validators | ⚠️ soft | ⚠️ org audit only | ❌ |
| Linear | ❌ categories | ❌ | ⚠️ GraphQL cursor | ❌ |
| Trello | ❌ | ⚠️ soft | ⚠️ `since` polling | ❌ |
| GitHub Projects v2 | ❌ automation | ⚠️ soft | ⚠️ webhooks | ❌ |
| Vikunja · Planka · Taiga · Kanboard | ❌ | ❌ | ❌ | ❌ |
| Wekan | ❌ | ⚠️ blocks (server-side unverified) | ❌ | ❌ |
| Agent boards (Flux, Backlog.md, `kanban-mcp` ×2) | ❌ | ⚠️ one of them | ⚠️ SSE at best | ❌ |
**Claim/lease appears in nothing else surveyed** — every other tool's "assignment" is a
last-write-wins field two agents can both grab. Neither does anything else combine a
no-miss cursor feed with transition enforcement.
Be clear about what is *not* novel, though. MCP kanbans are a crowded category (at least
ten exist). Self-hosting is table stakes. Jira has enforced workflows and has for twenty
years. [multidimensionalcats/kanban-mcp](https://github.com/multidimensionalcats/kanban-mcp)
already ships structured work reports; [Backlog.md](https://github.com/MrLesk/Backlog.md)
already does archive-first deletes; [Flux](https://github.com/sirsjg/flux) is a real,
active, git-native agent board. Those three are the closest prior art. What has no
precedent found is the *bundle*: claim/lease **and** a resumable cursor **and** enforced
flow **and** capability-honest adapters, self-hosted, MCP-first.
Two further corrections to what this README used to say: classic kanbans are **not**
MCP-illiterate any more — Trello, Jira, Linear and GitHub all ship *official* MCP servers
now, and the rest have community ones. And the backend-proxy idea isn't new either
(Composio, Unified.to); the honest `native`/`polyfilled`/`unavailable` reporting is the
part that is.
| | kanban-pro | Unified task APIs (Unified.to) | MCP aggregators (Composio Rube) | Agent boards (Flux, Backlog.md) | Per-backend MCP (Atlassian, Linear, GitHub) | Classic kanbans (Planka, Vikunja) |
|---|---|---|---|---|---|---|
| Self-hosted | ✅ | ❌ SaaS | ⚠️ self-host path | ✅ | varies | ✅ |
| Backend-agnostic | ✅ one model, any adapter | ✅ normalize-only | ➖ many apps, per-app tools | ❌ own store only | ❌ one backend | ❌ own store only |
| MCP-native | ✅ primary interface | ❌ | ✅ | ✅ | ✅ | ⚠️ community servers |
| Capability polyfill | ✅ delegate → polyfill → honest `unavailable` | ❌ gaps are just missing | ❌ | ➖ n/a | ❌ | ❌ |
| Agent-safety semantics | ✅ archive-first, guarded deletes, WIP, claim/lease | ❌ | ❌ | ➖ partial | ❌ raw backend semantics | ❌ |
| Actor audit trail | ✅ per-connection actor + change-log | ❌ | ❌ | ➖ | ➖ backend's own | ➖ |
| Multi-user / permissions | ❌ **single-user** | ✅ | ✅ | ➖ | ✅ | ✅ |
## Architecture
Ports & adapters (hexagonal), consumed **MCP-first / shell-first** (agent harnesses
are the primary clients; HTTP is secondary):
```
harnesses / clients
│ MCP (primary) · CLI (🔜) · HTTP (secondary) — thin, stateless
▼
core/ — Recording(Augmenting(adapter)): actor stamping + change-log,
delegate/polyfill routing, guards, dedupe
▼
KanbanBackend port ──▶ adapter ──▶ backend
▲
canonical domain model (Pydantic)
```
Interfaces never talk to an adapter directly — everything goes through `core/`, so no
interface can bypass the guards or the audit trail. Directory layout:
[AGENTS.md](AGENTS.md#architecture-ports--adapters); design: [SPEC.md](SPEC.md).
## Documentation
- [llms.txt](llms.txt) — the agent-facing brief. Hand the repo to an AI agent, ask "do I
need this?", then let it install and verify.
- [docs/configuration.md](docs/configuration.md) — **start here to configure it**:
profiles, actors, workflow rules, WIP limits, the attention flag, and listeners
- [docs/comparison.md](docs/comparison.md) — cited comparison against 16 boards, including
**who should walk away** and what here isn't actually novel
- [CHANGELOG.md](CHANGELOG.md) — what changed, for people who *use* it — including a frank
**known limitations** list
- [SPEC.md](SPEC.md) — what and why (canonical model, the core+passthrough decision,
capability model)
- [JOURNAL.md](JOURNAL.md) — decisions and rationale: what was rejected, what broke, why
- [TODO.md](TODO.md) — open backlog (nothing in it is done)
- [AGENTS.md](AGENTS.md) — conventions & hard rules for coding agents, incl. how to
author a new adapter
- [docs/internals.md](docs/internals.md) — **how it fits together**: the layer stack, the
invariants, the event kinds, `ext` versioning, and the traps. Read this before changing code.
- [docs/methods.md](docs/methods.md) — every operation + its MCP projection
- [docs/hermes-kanban.md](docs/hermes-kanban.md) — ground truth for the first harness
adapter & its migration map
## Status / Roadmap
**Working today:** the canonical model and port, four adapters behind one contract
suite (native, memory, hermes, and a Jira adapter, whitelisted per project, writing
nothing but a comment or a new issue and only where a route allows it),
per-board routing so several of them share one process and one change-log, per-operation
ACL on top of board scope, the augmenting layer (WIP enforcement, comments/relations
polyfill, honest capability reporting), the MCP server (51 tools + 9 resources), actor
identity + the append-only change-log with both the `list_changes` pull feed and the
`wait_changes` long-poll, the state machine engine (per-board transitions, inline
per-card machines, free-roam, audited force), structured work reports with
human-answerable questions, and the generic migration tool — all tested, and verified
live against a real production board.
**Next (🔜):** the CLI, a canonical HTTP surface, bulk operations, flow hooks/validators, a Trello adapter, cross-board copy/link with
confirmation-gated two-way sync, write-through polyfill encoding, human-readable card
keys (`PRO-12`), MCP push notifications, and durable webhook listeners. Roadmap:
[SPEC.md](SPEC.md#roadmap); the full queue: [TODO.md](TODO.md). Anything marked 🔜 does
not run today.
## License
**[AGPL-3.0-only](LICENSE)** — free software. Copyright © 2026 Jan.
Use it, run it, fork it, change it. If you distribute a modified version — or run one as a
service others can reach — you must publish your source (AGPL §13). Fixes and ideas are
asked for, not compelled: see [CONTRIBUTING.md](CONTRIBUTING.md).
TDQS
Scored across 37 tools
Each tool has a clearly distinct purpose, with detailed descriptions that prevent ambiguity. The verbs and nouns are specific (e.g., 'claim_card' vs 'heartbeat_claim' and 'release_claim'), and overlapping operations are differentiated by context (e.g., 'list_work' vs 'list_cards').
All tool names follow a consistent verb_noun pattern in snake_case, with clear actions like 'create', 'delete', 'list', 'update', and specific nouns. No mixing of conventions or vague verbs.
With 37 tools, the server is comprehensive but slightly heavy. Each tool serves a distinct function in the Kanban lifecycle, covering boards, columns, cards, comments, relations, placements, claims, workflow, and auditing. The count is justifiable but at the upper bound of typical scope.
The tool surface covers most CRUD operations and advanced features (claims, workflow transitions, change feed, work reports). Minor gaps exist: lack of explicit label management (only present in get_board) and inability to create or modify workflow schemes, but core Kanban operations are fully supported.