agent-kanban 0.1.0

Kanban CLI for multiple concurrent LLM agents to coordinate on tasks, backed by SQLite
agent-kanban-0.1.0 is not a library.

agent-kanban

A command-line kanban board built for multiple concurrent LLM-agent processes to coordinate on tasks without stepping on each other. There's no server, no daemon, and no shared in-memory state to get out of sync — just a single SQLite file per project that any number of agent-kanban invocations (from any number of agents/processes, at once) can safely read and write directly.

It exists because "just have the agents write to a shared TODO.md" doesn't survive contact with concurrency: two agents can read the same file, both see a task as unclaimed, and both start working on it. agent-kanban makes claiming a task an atomic, race-free database operation, so exactly one agent ever wins a given task — verified under real multi-process contention.

Project discovery works like git: agent-kanban init creates a .kanban/ directory in the current folder, and every other command walks up from the current working directory to find it, so you can run agent-kanban from any subdirectory of a project. Pass --db <path> to point at an exact database file instead, bypassing discovery entirely.

Install / Build

Requires a Rust toolchain (stable, edition 2024).

Install straight to ~/.cargo/bin (already on PATH for most Rust setups):

cargo install --path .

This isn't published on crates.io, so cargo install agent-kanban (without --path) won't find it — --path . builds and installs directly from this checked-out source. Re-running the same command after pulling new changes rebuilds and overwrites the old install; unlike installing by name from crates.io, --path installs don't need --force to reinstall.

Or just build it without installing anywhere:

cargo build --release

The binary is produced at target/release/agent-kanban. Put it on your PATH, or invoke it directly:

./target/release/agent-kanban --help

Quick start

$ agent-kanban init
{"status":"initialized"}

$ agent-kanban agent register alice
{"created_at":"2026-07-03 12:00:00","id":1,"name":"alice"}

$ agent-kanban add \
    --title "Add input validation to /login" \
    --priority high \
    --tag backend \
    --tag security \
    --test '{"describe":"rejects empty password","input":"{\"password\":\"\"}","output":"400 error"}'
{
  "created_at": "2026-07-03 12:00:00",
  "executor": null,
  "id": 1,
  "priority": "high",
  "status": "todo",
  "tags": ["backend", "security"],
  "tests": [
    {
      "describe": "rejects empty password",
      "input": "{\"password\":\"\"}",
      "output": "400 error"
    }
  ],
  "title": "Add input validation to /login",
  "updated_at": "2026-07-03 12:00:00"
}

$ agent-kanban claim 1 --agent alice
{..., "executor": "alice", "id": 1, "status": "in_progress", ...}

Claiming sets status to in_progress in the same atomic step — no separate "start work" call needed.

# ... alice implements the change, runs the test above by hand, confirms it passes ...

$ agent-kanban move 1 --status done
{..., "executor": "alice", "id": 1, "status": "done", ...}

A second agent racing for the same task gets a clean failure instead of silently overwriting the first — claiming is atomic, so exactly one caller ever wins, even if both request it at the same instant:

$ agent-kanban agent register bob
{"created_at":"2026-07-03 12:00:00","id":2,"name":"bob"}

$ agent-kanban claim 1 --agent bob
{"error": "task 1 is already claimed"}

Other lifecycle operations, shown on a second task:

$ agent-kanban add --title "Write onboarding docs" --priority low \
    --test '{"describe":"docs exist","input":"n/a","output":"file present"}'
{..., "id": 2, "status": "todo", ...}

$ agent-kanban claim 2 --agent alice
{..., "executor": "alice", "status": "in_progress", ...}

# Un-claim a task instead of finishing it (resets status to todo, clears executor)
$ agent-kanban release 2
{..., "executor": null, "status": "todo", ...}

# Edit a task (only allowed while unclaimed and not done)
$ agent-kanban edit 2 --priority medium
{..., "priority": "medium", ...}

# Delete a task (only allowed while unclaimed and not done)
$ agent-kanban remove 2
{"removed": 2}

# Board overview: counts per status column, plus each agent's current workload
$ agent-kanban status
{"agents":{"alice":1,"bob":0},"backlog":0,"done":1,"in_progress":0,"review":0,"todo":0,"total":1}

Command reference

Command Flags Behavior / restrictions
agent-kanban init Creates .kanban/ (and board.db) in the current directory.
agent-kanban agent register <name> Registers an agent name. Must be done before that name can claim work.
agent-kanban agent list Lists all registered agents.
agent-kanban agent remove <name> Deletes the agent. Any tasks it currently holds are auto-released (executor cleared, status reset to todo) first, in the same transaction — an agent is never left dangling as a claim-holder that no longer exists.
agent-kanban add --title T, --priority P, --tag t (repeatable), --test '<json>' (repeatable, required, ≥1) Creates a task. New tasks start at status todo. Each --test must be a JSON object with exactly describe, input, output string fields; at least one is mandatory (enforced by a DB CHECK constraint and by application-level validation).
agent-kanban list --status S, --tag T, --executor A, --priority P, --sort priority|created_at Lists tasks, filters combinable. --executor matches by agent name. --sort priority orders by real severity (urgent > high > medium > low), not alphabetically; --sort created_at orders chronologically; with no --sort, tasks come back in creation order.
agent-kanban show <id> Prints a single task.
agent-kanban claim <id> --agent <name> --agent <name> Atomically assigns the task to <name> via a compare-and-swap update, only if the task is currently unclaimed. Exactly one caller wins under concurrent contention; losers get a clean {"error": ...}. Fails if <name> isn't a registered agent, without touching the task.
agent-kanban move <id> --status S --status S Changes a task's status.
agent-kanban release <id> Un-claims a task: clears executor and resets status back to todo.
agent-kanban edit <id> --title T, --priority P, --tag t (repeatable), --test '<json>' (repeatable) Updates a task's fields. Refuses to act on a task that is currently claimed (release it first) or whose status is done. A task can never be edited down to zero tests.
agent-kanban remove <id> Deletes a task. Same restriction as edit: refuses on a claimed or done task.
agent-kanban status Board overview: a task count for each status column (backlog, todo, in_progress, review, done — all five, even at zero) plus total, and an agents object with every registered agent's current claimed-task count (including agents holding nothing).

Global flags:

  • --pretty and --table are mutually exclusive output modes. --pretty prints indented JSON for humans; without it, output is compact JSON on a single line, intended for other programs/agents to parse. --table renders a human-readable table instead of JSON — an aligned table for list-shaped results (list, agent list), a two-column FIELD/VALUE table for a single result (show, add, ...). Nested values (tags, tests) render as inline compact JSON within their cell rather than a nested table.
  • --db <path> uses that exact database file instead of discovering .kanban/ by walking up from the current directory — useful for scripting against a specific project without cd-ing into it, or keeping the database somewhere other than .kanban/board.db. For init, creates the file there (making parent directories as needed) instead of the default location.

Every command prints JSON to stdout on success (or a table with --table). On failure, it prints {"error": "message"} to stderr and exits non-zero — the primary consumer of output is other programs, not a human reading prose. Both --pretty and --table apply to error output too, for consistency.

Concurrency model

agent-kanban stores its state in a single SQLite database (.kanban/board.db) opened in WAL mode with foreign_keys=ON and a busy_timeout of 5000ms. WAL mode lets readers and writers proceed concurrently without blocking each other, and the busy timeout means a writer that arrives while another transaction holds the write lock waits and retries instead of failing immediately — so many agent-kanban processes (potentially one per agent) can hit the same file at once without hand-rolled locking. The schema version is stamped via PRAGMA user_version on init; opening a project created by a newer, incompatible agent-kanban fails with a clear error instead of silently misinterpreting a schema it doesn't understand.

The part that actually matters for correctness is that every state-changing operation on a task is a single, self-contained SQL statement with its precondition baked into the same WHERE clause — not a read-then-write in application code, which would leave a window for another process to change the row in between:

  • agent-kanban claimUPDATE tasks SET executor = ?, ... WHERE id = ? AND executor IS NULL. If two agent processes race to claim the same task, the database serializes the two UPDATEs; the one that runs first flips executor from NULL to its name and reports success, and the second one's WHERE clause no longer matches anything, so it affects zero rows and agent-kanban reports a clean "already claimed" error.
  • agent-kanban release — the mirror image: ... WHERE id = ? AND executor IS NOT NULL, so a stale release can never clobber a task that was released and re-claimed by someone else in the meantime.
  • agent-kanban edit / agent-kanban remove — both require executor IS NULL AND status != 'done' in the same guarded statement, so a claim landing between a caller's mental model and the actual edit/delete can't silently slip through.

Each of these has been verified under real multi-process contention (hundreds of concurrent attempts across test runs, spawning actual separate OS processes against the same database file, not just threads), always with exactly one well-defined winner and never a corrupted or split-brain outcome.

Tests are specs, not executables

The tests attached to a task are acceptance-criteria specifications, not code agent-kanban runs. Each one is a JSON object with three string fields:

  • describe — what behavior is being checked
  • input — the input/scenario
  • output — the expected result

agent-kanban stores and validates the shape of these specs; it never executes them. Confirming a task's tests actually pass is the responsibility of the agent doing the work, before it moves the task to done.

Every task must have at least one test, and this is non-negotiable: it's enforced by a database CHECK constraint (a non-empty JSON array) and re-validated in application code on every add and edit. A task can never be edited down to zero tests. This exists because TDD matters — a task isn't really "done" unless it had a concrete, checkable definition of done attached from the moment it was created, not invented retroactively after the code was written.

License

MIT — see LICENSE.