Ten Minutes to a Board
No preamble. Here is the install, the first item, and where your data actually goes.
Install
cargo install --git https://github.com/Congruentsys/arrow-kanban arrow-kanban
⚠ Two things that will waste your time otherwise. Install from git, not crates.io — the crates.io name is a 0.0.0 placeholder that predates the project and installs nothing useful. And name the package explicitly: the repo holds two packages with binaries — the root crate plus a workspace member — so without the trailing arrow-kanban cargo refuses rather than guessing which you meant.
Neither is guessable, and the README documented the broken form until this chapter was written. It is fixed now; you are getting the version that ran.
Needs Rust 1.85+ (edition 2024). 43 seconds cold on an M-series laptop, including the full dependency build — Arrow and Parquet are not small. The title says ten minutes because that is the honest ceiling on an unknown machine; the number I measured is the one above.
A board, from nothing
$ arrow-kanban init --theme software
Initialized arrow-kanban in .arrow-kanban/ (theme: software, 7 templates)
`.arrow-kanban/` is NOT ignored by git, so it will show as untracked. Two options:
* COMMIT it — the board is shared and versioned with the repo
(~20 KiB of git objects per commit for a small board),
and the board then follows the branch you are on.
* IGNORE it — add `.arrow-kanban/` to .gitignore to keep the board
local-only; a fresh clone starts empty.
That is the whole setup. No server, no account, no daemon, no network — the default build has no network dependencies at all. It is a binary and a directory.
The commit-or-ignore question is real and the tool is right to make you answer it. Commit it and your board branches with your code, which is either exactly what you want or a merge conflict in a Parquet file. Ignore it and the board is yours alone.
$ arrow-kanban create feature "Cache the schema lookup" \
--body "Hypothesis: the 8.8ms per-entity cost is dominated by the store
query, not the substring match. Target: under 3ms."
Created FT-1300: Cache the schema lookup
$ arrow-kanban board
── backlog (1) ──
F FT-1300 Cache the schema lookup
── in_progress (0) ──
(empty)
Both commands returned instantly. Install to first item was under a minute.
Those numbers in the column headers are the point, not decoration
backlog (1) and in_progress (0) are counts, and the second one is the one that earns the board its keep. A work-in-progress limit is not a limit on how much you may do — it is a limit on how many things you are allowed to be halfway through.
Three items at 90% finished deliver nothing. One item finished delivers one thing. A board makes the first state visible, which is the only reason it beats a list of tasks; a list is happy to let you start everything.
This matters more with agents than without, because an agent will cheerfully start a fourth thing while three sit unfinished, and it has no discomfort to warn it. The limit has to be in the tool.
When a Tool Earns Its Place makes the full case, including the argument for not adopting a board at all. It is written for the other track, and the WIP section is worth borrowing whichever track you are on.
Now the part worth your attention
Go looking for your item on disk.
$ grep -r "FT-1300" .
Binary file ./.arrow-kanban/items.parquet matches
Binary file ./.arrow-kanban/runs.parquet matches
That is not your item. That is a byte match inside a blob.
The text really is in there — Parquet stores a string column uncompressed, so the characters FT-1300 sit in the file as characters. What you cannot do is read the row. There is no line, no record boundary, no field. grep can tell you the bytes occur; it cannot tell you the status, the id, or which column any of it belongs to.
$ ls -l .arrow-kanban/
config.yaml
items.parquet 6140
item_comments.parquet 860
runs.parquet 2362
templates/
$ head -c 4 .arrow-kanban/items.parquet | xxd
00000000: 5041 5231 PAR1
Three Parquet files, about nine kilobytes. Your item is a row, in a columnar store, with a typed schema. Every text tool you own will treat that as an opaque blob, and every tool that understands the schema will hand you the record in one call.
This paragraph shipped false, and how is the useful part
The first version of this chapter said grep finds nothing. I had run it. It printed nothing.
A reviewer on a different machine ran the same command and got the two Binary file … matches lines above. Both of us were reporting honestly.
The cause was not the platform. My shell had a grep function that shadowed the real one and routed to a different tool with -I — ignore binary files. So the command I typed was not the command that ran, and it silently skipped the only files that mattered.
The lesson underneath is a level below "run it before you write it", which I had done:
⚠ Confirm the tool you invoked is the tool you think it is. A wrapper, an alias, or a shell function can change what a command means without changing what you typed, and it will not tell you.
command grep or /usr/bin/grep bypasses a shell function. type grep shows you whether one exists. Ten seconds, and it is the difference between a measurement and a story.
That is the design, not a side effect. The reason to build a kanban this way is that the consumer is not a person reading a diff — it is an agent that reads its own board thousands of times an hour and needs the whole thing resident, typed, and queryable without shelling out to parse anything.
The honest trade, since this is the whole argument
You have just lost something real. A Markdown work item shows up in git diff — you can read what changed, review it, and understand a board's history with no tooling at all. A Parquet row cannot do any of that: your diff is a binary blob, and two branches touching the same board is a conflict you cannot hand-resolve.
We ship both, because they are for different readers. If a person is the one who reads the board, the file wins — that is yurtle-kanban, and it is what the beginner track uses.
If an agent is the one reading it, resident and typed wins, and it is not close.
The full argument, with what we are not claiming yet →
⚠ More technical than this chapter, and written for a different audience. Skip it freely.
What --theme actually changes
The item vocabulary, and nothing else:
$ arrow-kanban init --theme software # 7 templates: feature, chore, epic, …
$ arrow-kanban init --theme nautical # 13 templates: expedition, voyage, hazard, signal, …
Same store, same commands, same columns. The theme decides what kinds of thing your board can hold, which matters more than it sounds — the type is what later lets you ask "show me every hazard still open" rather than grepping titles.
Coming from the file-based one
If you have a Markdown board already:
$ arrow-kanban migrate --help
Migrate markdown files to Arrow store. REPLACES the board with what it finds in
the config's scan_paths — it does not merge into the existing items.
⚠ Read that help text before running it, not after. It replaces rather than merges, and it says so — which is the right design and an easy sentence to skim past.
What is rough
--version is not a flag. arrow-kanban --version returns error: unexpected argument. It is the first thing most people type after an install to confirm it worked, and it fails. Filed.
The install command in the README was wrong until this chapter's walk ran it — same issue. Fixed, but worth knowing that it happened, because it is the failure mode this whole handbook is about: an instruction written from a source rather than from a run.
Where to go next
- Skills — encoding a workflow so it runs the same way twice
- Many Agents, One Rule — what changes when it is not one agent, and the one rule that does not bend
- The repo — Congruentsys/arrow-kanban, MIT, and the
--helpis more complete than this page