yakanban

yakanban

Let your AI agents work the board your team already uses. No migration, no second UI, nothing for your product owner to learn.

Your coding agent just finished a task. Nobody else knows: no ticket moved, no claim taken, and the agent in your other terminal is about to start the same bug.

Give them the board your team already has. The one below is this project's own, captured when this page was built: yakanban is built with yakanban, and its board is public.

brew install aramponi/tap/yakanban
scoop bucket add aramponi https://github.com/aramponi/scoop-bucket
scoop install yakanban
go install github.com/aramponi/yakanban/cmd/yakanban@latest
VERSION=$(curl -fsSL https://api.github.com/repos/aramponi/yakanban/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)
OS=$(uname -s | tr '[:upper:]' '[:lower:]'); ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
curl -fsSL "https://github.com/aramponi/yakanban/releases/download/$VERSION/yakanban_${VERSION#v}_${OS}_${ARCH}.tar.gz" | tar xz yakanban

The clip above is a recorded session on a demo repository. Every other terminal block on this page is captured output, from the board this project runs on.

$ yakanban board --refresh
yakanban (github)
https://github.com/users/aramponi/projects/2

COLUMN       TASKS  WIP  BLOCKED  CLAIMED
Backlog      0      —    —        —
Todo         1      —    —        —
In Progress  1      —    1        —
Review       0      —    —        —
Done         25     —    —        —

total 27   blocked 1   overdue 0
priorities medium 20  high 7

What changes for your team: nothing

Most projects that need a board have one. So setup adopts rather than provisions: point yakanban at the project the team is already using and it takes the columns as it finds them, adding only the fields it needs to store a priority, a claim and a dependency. Nothing is renamed, nothing is deleted, and the product owner's view does not change under them.

Old issues are left alone too. An issue that was never put on the board is still readable, and the first write is what puts it there, so a repository with years of history joins one ticket at a time, as work actually reaches it, instead of arriving as a migration nobody asked for.

The greenfield case is the same command with nothing to adopt: it creates the project, links it to the repository and gives it the default columns.

Two kinds of user, one board

A developer and an agent work in a terminal. A product owner works in a browser, on the project board they were already using. Neither of them is looking at a copy, and nobody had to install anything to see the other's work.

yakanban is a client, not a store. Every write goes straight to the tracker; reads pass through a one-minute cache so an agent can poll a board without spending someone's API quota. There is no state on disk to reconcile, and nothing to lose when a laptop dies.

That has a price, and it is worth saying out loud: without a network you have no board. Offline was traded for never being wrong about who owns what.

The claim is the whole trick

Two agents pointed at the same backlog will pick the same ticket. A claim is a soft lock, an agent name and an expiry written into two ordinary project fields, so the second agent is refused and goes to find other work.

Ordinary fields, because a human has to be able to see them. Open the project in the browser and the claim is a column like any other: who holds what, and until when. Nothing about the coordination is invisible to the person who did not install anything.

Expiring, because an agent that dies mid-task must not take a ticket with it. The lock lets go by itself after an hour, and a long task renews it by writing a progress note.

pick claims the next available ticket and reads the claim back before returning it. That read is the reason several agents on one board is safe: list-then-claim checks nothing, because the answer is stale by the time it arrives.

$ yakanban list --unclaimed --not-blocked --status todo
ID  STATUS  PRIORITY  TITLE                                                         TAGS  ASSIGNEES  FLAGS
34  Todo    medium    {{.Repo}} in branch and worktree templates is the checkout'…  bug

The instructions ship with the binary

The hard part of handing a board to an agent is not the API, it is telling it when to claim, when to park a task and when to stop and ask. Two skill files carry that, and they are compiled into the binary, so a downloaded release installs them with no checkout. An agent that reads them drives a ticket the same way as every other agent on the repository, which is the difference between a board and a log of what one assistant felt like doing.

skill install detects Claude Code, Codex, Cursor, Gemini CLI, Antigravity, Hermes, Pi and OpenClaw, shows you what it found and lets you change its mind before it writes anything. A file you have edited since is never overwritten without --force: each one carries a hash of the text yakanban wrote, so "stale" and "you changed this" are different answers.

One shape for every command

Fifteen commands, and every one of them takes the same global flags. There is no command that prints JSON and another that does not.

CommandWhat it does
initProvision the board and write the descriptor
listList and filter tasks
show IDFull detail of one task
create TITLEOpen an issue and put it on the board
edit IDChange fields, claim, block, append to the body
move ID STATUSChange column (--next / --prev also work)
pick --claim AGENTClaim the next available task, atomically
branch IDCreate a branch and attach it to the ticket
handoff ID --claim AGENTPark a task with a note for whoever picks it up
delete ID --yesClose the issue and archive its board item
boardColumn counts, WIP pressure, blocked and overdue
syncDrop the read cache and refetch
configShow the resolved configuration
agent-nameGenerate a claim identifier
skill installInstall the bundled agent skills

Output you can pipe, exit codes you can branch on

--compact prints one tab-separated line per record, which is what you want when the reader pays by the token. --json prints the whole record when it does not. Both are on every command, alongside --table, --no-color, --refresh and --no-cache.

The exit codes are the part a script actually reads. Code 5 in particular is not an error: it means another agent got there first, and the correct response is to pick something else.

$ yakanban list --compact -n 8
1	Done	high	Add `yakanban skill install` to install the bundled agent skills	#enhancement
2	Done	medium	Link a branch to a task with GitHub linked branches	#enhancement
3	Done	medium	Support branching models (trunk-based, git flow) for branch and worktree naming	#enhancement deps:2
4	Done	medium	Let `skill install` propose the detected agents instead of deciding, and support hermes and pi	#enhancement deps:1
5	Done	high	Exercise the release build in CI before the first tag depends on it	#enhancement
6	Done	medium	Publish a Homebrew tap so yakanban installs with brew	#enhancement deps:5
7	Done	high	Decide how GitLab maps onto the yakanban model, tier by tier	#enhancement
8	Done	medium	Resolve provider capabilities at runtime instead of compile time	#enhancement
CodeMeaning
0success
1unexpected error
2bad input or missing configuration
3task not found
4authentication or scope problem
5the task is claimed by another agent
6the provider cannot express what you asked for

It borrows the login you already have

yakanban stores no credentials. It reads $YAKANBAN_GITHUB_TOKEN, $GH_TOKEN or $GITHUB_TOKEN, and when none of them is set it asks the GitHub CLI for the token you are already using: gh auth token. So gh is a prerequisite for the ordinary path, and the error says so rather than failing somewhere deeper.

That is the whole authentication story, and it is deliberately not ours. There is no login command, no keychain entry and no token file to leak, because the credential belongs to a tool that already manages it, and in CI, where gh is not the natural answer, one environment variable takes over.

One thing will catch you once. Projects v2 sits behind its own OAuth scope, so an account that authenticated with gh before ever touching projects needs it added:

gh auth refresh -s project

Both cases, no credential at all or a credential without the scope, exit 4, and in both the message is the instruction:

$ yakanban board --refresh
yakanban: authentication failed: `gh auth token` failed: no oauth token found for github.com (run `gh auth login`)
$ echo $?
4

What we didn't build

No second web UI

The tracker has one, and the people who do not use a terminal are already logged into it. A web UI of our own would be a second place for the board to be wrong.

No local mirror of the board

Tempting, and it would have bought offline reads. It would also have bought a sync direction, a conflict policy and a class of bug where the agent and the human disagree about reality. The cache holds reads for a minute and forgets them; that is the whole of it.

No release automation, and no git flow back-merge

branch creates branches and links them to tickets. It stops there. Cutting releases is release engineering, and a back-merge that a tool silently skips is how a hotfix gets lost. yakanban tells you the back-merge is yours and leaves it undone rather than half done.

No merge request management

Pull requests belong to the forge, which already reviews them, checks them and merges them. Wrapping that in another CLI would add a layer whose only job is to be out of date.

delete closes; it does not destroy

yakanban delete closes the issue and archives the board item. The history stays. An agent with a shell and a bad afternoon should not be able to permanently remove anybody's work.

No published gh extension

The same binary answers to gh yakanban ... when it is installed under the name gh-yakanban, and make gh-extension builds it. It is not shipped in releases: gh extension install resolves a *repository* whose name begins with gh-, and this one does not, so a published binary could never be installed the documented way. It would have been dead weight in every release.

No provider abstraction written before the second provider

The domain knows nothing about GitHub, and a backend is one package plus one line in a registry. That is as far as the abstraction goes today, because the shape of the second one is not knowable from the first.

For agents

The site serves an llms.txt at its root: the command reference, the exit codes, the two skill files and the backend mapping, as links an agent can follow instead of a page it has to scrape.

It is generated from the same sources as this page. A hand-written contract is the first file to go stale, and a stale contract is worse than none.