Every card is a fighter. The master works for you. Local‑first
Kungfu Kanban is a kanban board that runs on your machine, where each card is a Claude Code agent on your subscription login — no API keys, no token billing, no cloud. Cards create, route, run, and review themselves on full auto; your only job is the merge button.
The four forms.
Say the work exists
“Create a kungfu todo” in any terminal session. A pasted plan. Your open GitHub issues. Cards write themselves — acceptance criteria included.
The Sensei routes
Model, effort, priority, skills — triaged per card, automatically. Haiku for drills, Fable for the tournament.
The dojo runs itself
Cards launch, retry with feedback, step down models, and absorb rate limits — unattended, inside your guardrails.
PRs arrive, shipped
Repo cards open real pull requests on their own; merged ones file themselves into Done. You hold the merge button — that’s the whole job.
It is a kanban board.
The cards do the work.
The discipline, itemized.
No API keys. On purpose.
Every card runs the Claude Code CLI on your subscription login. The runner strips
ANTHROPIC_API_KEY from the environment, so it can never silently fall back to
pay‑per‑token.
Every fighter on the card
Fable, Opus, Sonnet, Haiku — model and belt‑stripe effort picked per card. When one model hits a cap or an outage, launches step down the ladder automatically and climb back when it clears.
“Create a kungfu todo”
The board auto‑installs a Claude Code skill on your machine, so any session in any project can queue work onto the board: say the word, and it drafts the cards, drops them in the inbox, and the Sensei triages them.
installed for you at boot. zero setup.A sensei for the queue
Triages new cards, dispatches runs, reviews finished work against your acceptance criteria and the actual PR diff — not just the agent’s self-report — and merges green PRs itself when your autonomy setting allows. Three autonomy levels; deleting cards is never on the table.
Repo cards ship PRs
Coding tasks run in an isolated git worktree and open a real pull request via gh when
done. Every PR runs CI; PR watch ships merged cards and spawns fix agents for conflicts.
Cards that wait their turn
Give a card “Runs after” prerequisites and it waits in Queued until every one is actually done — a done card with a still-open, unmerged PR still counts as blocking. The Sensei chains multi-card plans itself.
Batches run one lane at a time
Cards from the same import or plan cluster under a collapsible group header. Only one card per group is ever in progress, a queue-group button launches the whole batch, and finishing one fires a single notification instead of one per card.
Rate limits, absorbed
Hit a subscription limit and the board requeues the card, pauses all auto flow, shows a countdown, and resumes everything when the window resets. Nothing burns against the wall.
Markdown in, backlog out
Any plan that reaches the inbox — a paste, a dropped .md, your open GitHub issues —
becomes cards on its own: model, priority, and acceptance criteria parsed from the text, Sensei
triage included.
The dojo in your pocket
Token gate + Tailscale serve puts the board on your phone, tailnet‑only. ntfy pushes tell you when a card lands in Review. Installs as a PWA.
Everything on the record
One Express server, plain JSON files in data/, every transcript and manager decision on
disk. Back up a folder, own your board.
Fewer tokens per card. On purpose.
The board ships with ponytail — a vendored skill that makes every agent take the laziest solution that works: reuse before rewrite, standard library before dependencies, the shortest diff that ships. Fewer output tokens per card means more cards out of every rate-limit window. Auto-installed at boot, pre-selected on new cards, deselect any time.
~54% less code, ~20% cheaper, ~27% faster — ponytail’s own benchmarks.A sensei for the queue.
The Manager reads every card, routes it to the right model, launches runs, and reviews the results against your acceptance criteria. You choose how much rope it gets.
The manual.
Complete setup, every feature. It’s one Express server and a folder of JSON — ten minutes to a working dojo, an evening to master it.
Requirements
| Thing | Why | Check |
|---|---|---|
| Node 20+ | server runtime (uses global fetch) |
node -v |
| Claude Code CLI, logged in on your subscription | runs every card | claude --version, then claude → /login |
| git | worktree isolation for repo cards | git -v |
GitHub CLI (gh), authed |
only for “Open PR when done” | gh auth status |
| macOS | desktop notifications (osascript); the rest works anywhere |
— |
| Tailscale (optional) | use the board from your phone | tailscale status |
Install & run
git clone https://github.com/LeahyCC/kungfu-kanban.git && cd kungfu-kanban
npm install
npm start # → http://localhost:4747
That’s it for local use. The server binds 127.0.0.1 only by default — nothing is
exposed until you deliberately set up remote access (chapter 10).
The board
Columns: Backlog → Queued → Running → Review → Done.
- + New card (or drag a card to Queued, or ▶ Run in the drawer) launches it.
- parallel (toolbar, 1–8, default 2) caps concurrent sessions so you don’t burn subscription rate limits; extra launches wait in Queued and start automatically as slots free up.
- Click any card for the drawer: live transcript, token/turn stats, and a
claude -r <session-id>command to resume that session in your terminal. - Finished runs land in Review (with a vermillion error stripe if they failed). ✓ Done ships them — hanko seal included.
- A restart — deliberate or a crash — can’t corrupt your board: state writes are atomic, and a graceful stop (including a launchd restart) marks any running cards into Review instead of leaving them orphaned.
- Live updates arrive over a persistent connection with a heartbeat, so the board recovers automatically from flaky wifi or your laptop sleeping instead of quietly going stale.
- Cards sharing a Group cluster under a collapsible header; only one card per group is ever in progress, a ▶ button on the header queues the whole batch, and the group fires one notification when it finishes instead of one per card (details →).
- ⚙ Settings (toolbar): default working directory, repos directory, ntfy topic, notification toggles, PR watch. ☀ / ☾ toggles the day/night dojo; night is the default.
Follow-up prompts
Open any card that has run and type in the follow-up box under the transcript
(“fix the bug where…”, “also add…”). The run resumes the same CLI
session (claude -r), so the agent keeps its full context — and for worktree
cards it re‑enters the original worktree, so a follow-up push updates the existing PR. Follow-ups
respect the parallel cap and queue like any run.
Card fields
| Field | Maps to | Notes |
|---|---|---|
| Title | — | shown on the card, PR title, notification text |
| Prompt | the claude -p prompt |
what the agent should do |
| Working directory | process cwd |
repo dropdown (scans the ⚙ Settings repos directory for git repos) or any path |
| Model | --model |
default / fable / opus / sonnet / haiku |
| Effort | --effort |
default / low / medium / high / xhigh / max |
| Permissions | --permission-mode |
acceptEdits (default), auto, plan,
dontAsk, bypassPermissions — see Security
|
| Agent | --agent |
your custom agents from ~/.claude/agents/*.md |
| Git worktree | --worktree |
isolates the run on its own branch |
| Open PR when done | post-run gh pr create |
requires worktree — see chapter 05 |
| Priority | sort order (0–3) | 2+ shows the vermillion square |
| Acceptance criteria | manager review rubric | the Sensei approves/rejects against this |
| Group | batch label | clusters related cards under one collapsible header; one card per group runs at a time |
| Runs after | deps (task ids) |
waits in Queued until every listed card is actually done — an open, unmerged PR still counts as blocking |
| Repeat | schedule | 6h every 6 hours, 14:30 daily — see chapter
08 |
| Skills | injected into the prompt | pick from installed skills, or ✦ auto-select to let the agent choose |
A dependency isn’t satisfied by a card just reaching Done — if that card opened a PR that’s
still open and unmerged, the dependent keeps waiting; a merged PR, or a card that never opened one,
releases it. The Sensei chains multi-card plans itself (chapter 06), and
imported cards chain the same way via after:/sequential: (chapter 07).
Dependency & merge-gate semantics, as their own reference page →
Skills and agents are auto-discovered at load, no config: personal skills from
~/.claude/skills/*/SKILL.md, plugin skills from every enabled plugin (namespaced
plugin:skill), agents from ~/.claude/agents/*.md. A filter box narrows the
skill list as you type.
The board also ships ponytail (vendored, MIT) — the lazy-senior-dev discipline:
smallest working diff, standard library before dependencies, fewer output tokens per card.
Auto-installed at boot and pre-selected on new cards; deselect it per card when you want the full
ceremony.
Repo cards → real PRs
Give a card a working directory that’s a git repo with an origin remote, check
Git worktree + Open PR when done, and run it. One-time setup:
gh auth login with push scope. After the agent succeeds:
- The worktree is located by asking git for the branch named
kanban-<card-id>— wherever the CLI put it. - Anything the agent left uncommitted is committed.
- If the branch has no commits beyond the base, the flow stops — no empty PRs.
- The branch is pushed to
originandgh pr createopens a PR: title = card title, body = prompt + acceptance criteria. - The PR link lands on the card, in the drawer, and in your phone notification.
Every step logs to the card transcript (⇡ lines). Failures (no remote, gh
not authed, push rejected…) log an ✕ line and leave the card in Review — the work is
still in the worktree, nothing is lost.
PR watch — merged PRs ship, conflicted PRs self-heal
Every N minutes (⚙ Settings, default 10, 0 = off) the board checks each Review card’s PR via
gh:
- Merged — by you, or by the Sensei itself once every check is green (chapter 06) — moves the card to Done and notifies you.
- Closed without merge → noted once on the card transcript.
- Conflicting → with auto-fix on (default), the board spawns a fix card: a fresh agent run inside the original worktree that merges the base branch, resolves the conflicts preserving both sides, and pushes — updating the PR in place. Max 2 attempts per PR, one active fixer at a time; after that you get a “needs you” notification and it stops.
main pushed. Agent worktrees branch from the default
branch, so an unpushed main is how you get avoidable conflicts in the first place.The Manager (the Sensei)
An LLM manager — itself a claude -p structured-output call on your subscription — that
triages new cards (model/effort/skills/priority routing), dispatches queued work, reviews finished
cards against their acceptance criteria and their actual PR diff, chains multi-card plans with
dependencies, and answers you in chat (“plan the auth refactor into cards”,
“what’s blocking?”).
Review & fix powers
A review card carries its real PR diff into the Sensei’s context, not just the agent’s self-report — the diff is what catches ordering, logic, and safety issues a self-report misses. From there:
- followup_task resumes the same agent session with a precise, named fix instead of burning a full retry — reserved for small, specific flaws; reject_task still handles approach-level failures that need a fresh attempt.
- merge_pr ships the card’s PR itself once every CI check on it is green and the base branch is correct, instead of approving and leaving a “done” card still blocking its dependents. It skips with a note when checks are red, pending, or haven’t reported yet.
- It resolves entries in the error tracker (⚠ chip in the header — permission stops, wrong-base PRs, failed launches) by fixing the operation, not the code, then marking the entry handled.
Repeated or stale actions are no-ops rather than errors — running a card that’s already running, approving one already done — so a Sensei decision made against a snapshot that’s since changed underneath it can’t double-fire a run.
Autonomy ladder (Manager tab)
| Level | Can do without you |
|---|---|
suggest (default) |
nothing — every action waits for your ✓ |
semi |
create / route / run cards; approve-reject verdicts still wait |
auto |
everything, within guardrails |
Deleting cards is never available to the manager, at any level.
Triggers
On task finish (review it), on new card (triage it), every N minutes (0 = off), and chat. Each trigger is one manager invocation — mind your rate limits before enabling the interval.
Guardrails
- Max launches/hour (default 10).
- Max retries/task (default 2) — rejected cards re-run with the manager’s feedback appended to the prompt.
- Permission ceiling (default
acceptEdits) the manager can’t assign beyond. Guardrail-blocked actions become suggestions instead of executing.
Management style: freeform standing instructions (“prefer haiku for docs tasks”, “never auto-approve migrations”) — no code changes needed. The manager’s own model/effort (default opus/medium) are configurable; it can also be disabled entirely.
The Sensei’s actions & autonomy levels, as their own reference page →
Importing cards from Markdown
Turn a plan into a backlog in one paste. Two entry points:
- ⇪ Import (toolbar): paste markdown or pick a file — works from your phone.
- Watch folder: drop
.mdfiles intodata/inbox/— cards appear automatically and the file is archived todata/inbox/imported/.
Two formats, auto-detected. Sections — every ## Heading becomes a card:
the heading is the title, leading key: value lines set fields, an
### Acceptance subsection becomes the acceptance criteria, everything else is the prompt.
Optional frontmatter sets file-wide defaults:
---
cwd: /Users/you/project
model: sonnet
worktree: true
openPr: true
group: auth-refactor
queue: true
---
## Fix the flaky login test
model: opus
priority: 2
The test in auth.spec.ts fails intermittently because…
### Acceptance
- passes 10x in a row
## Update the README badges
after: Fix the flaky login test
Recognized keys (case/space-insensitive): cwd (dir/repo),
model, effort, permissions, priority (0–3),
worktree, openPr (pr), agent, skills,
acceptance, group (batch), base
(prBaseBranch — for repos whose branch guards reject PRs straight into main),
after (deps/needs — the exact title of an earlier card;
repeat the line for several), sequential (chain every card in the file to the one
above it), and queue (autoqueue — launch every card on import instead of
leaving them in Backlog; chained cards park in Queued and cascade as prerequisites ship). A card
with no body uses its title as the prompt.
Checklist — a file with no ## headings: every unchecked
- [ ] item becomes a card (checked items are skipped).
✨ Drafting: describe the work in plain English in the Draft box and the Sensei writes the card doc for review. Pick a repo and check 🔍 explore repo first to let it read the actual code before writing — slower, but cards reference real files. ↻ Refine iterates on the draft in the same session; a live preview shows what will be created and warns about titles already on the board.
⇣ From issues: pick a repo and pull its open GitHub issues into cards. Their PRs
include Fixes #N, so merging the PR closes the issue automatically — issue → card → agent
→ PR → merged → closed.
From Claude Code: the board ships a skill — generated by lib/skill.js — that teaches every
Claude Code session, in any project, to do this on request: say “create a kungfu todo for
…” and it drafts the cards, drops the file in the inbox, and confirms the import.
Nothing to install — the server writes it to
~/.claude/skills/kungfu-todo/ at every start, with your clone’s real paths and
port baked in, and keeps it current as the board updates. ⚙ Settings shows its status alongside a
manual Install/Update button if you ever need it.
Imported cards land in Backlog tagged import, and the Sensei gets one
triage ping per batch — paste a plan, and routing happens for you.
Scheduled cards
Give a card a Repeat value in the editor to run it on a schedule:
6h(or6) — every 6 hours (fractional hours like0.5hare allowed)14:30— daily at 14:30 (24-hour local time)
The server checks once a minute. When a card is due, it’s cloned into a fresh one-shot
card that launches via the normal runner — clones respect the parallel cap and flow through
the columns like any other card. The scheduled card itself stays in Backlog carrying
a ⏱ badge and never moves on its own; drag it elsewhere and it stops firing until
it’s back in Backlog. Clear the Repeat field to turn scheduling off.
Notifications
When a card lands in Review (or fails), you get notified. User-stopped runs don’t notify.
macOS (on by default)
Fires via osascript. Toggle in ⚙ Settings. If nothing appears: System Settings →
Notifications → allow notifications for “Script Editor” / your terminal.
Phone (ntfy)
- Install the ntfy app (iOS / Android) — no account needed.
- Pick a long, unguessable topic name (ntfy topics are a public namespace — anyone who knows the
name can read it): e.g.
kk-$(openssl rand -hex 8). - Put it in ⚙ Settings → ntfy topic on the board.
- In the ntfy app: + → Subscribe to topic → same name.
Pushes include the card title, and tapping one opens the PR when there is one. Because the topic is public-by-obscurity, keep card titles free of secrets. 🔔 Test notification (in ⚙ Settings) fires both channels on demand.
Use it from your phone (Tailscale)
The server refuses to bind beyond loopback without an access token, because the runner executes code. The safe path is a token + Tailscale — the port stays on loopback and Tailscale proxies it inside your tailnet only:
# 1. one-time: create the token (this enables the login gate)
openssl rand -hex 16 > data/auth-token
# 2. run the board
npm start # logs: "token gate: ON"
# 3. one-time: serve it over your tailnet with HTTPS
tailscale serve --bg 4747
tailscale serve status # shows your https://….ts.net URL
On your phone: install Tailscale, sign in to the same tailnet, open the URL, enter the token once —
it’s stored as a cookie for a year. Scripts/API calls can send
Authorization: Bearer <token> instead.
Install it as an app: the board is a PWA. On iPhone, open the tailnet URL in Safari → Share → Add to Home Screen — you get a standalone full-screen app with the robot icon and night-dojo status bar.
- Token can also come from the
KFK_TOKENenv var (overrides the file). - Rotate it by regenerating
data/auth-token(old cookies stop working). tailscale serveis tailnet-only. Never usetailscale funnelor a public port-forward for this app.- To stop sharing:
tailscale serve --https=443 off(ortailscale serve reset).
Subscription limits & model fallback
Both of these are automatic — nothing to configure.
Cooldown
When a run dies on a usage/rate limit, the board parses the reset time from the CLI’s error (falls back to a 1-hour backoff), requeues the card (it wasn’t a real failure), and pauses all auto flow — queue pumping, Sensei triggers, PR auto-fix — until the limit resets. A ⏳ countdown chip shows in the header and your phone gets a ping. Manual run clicks during cooldown queue instead of burning against the wall. When the timer expires everything queued launches automatically and you get a “training resumes” notification.
Model fallback
A model-specific cap or outage (an Opus-hours limit while Sonnet still works, a 529 overload) doesn’t pause anything — the failed model is blocked temporarily (30 min for caps, 10 for overloads), the card requeues, and launches step down the ladder fable → opus → sonnet → haiku until the block expires. Cards keep their configured model and climb back up automatically; a ⬇ chip in the header shows what’s stepped down, and the transcript notes every substitution. The Sensei’s own runs substitute too.
Run at login (optional)
~/Library/LaunchAgents/com.kungfu-kanban.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.kungfu-kanban</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string> <!-- `which node` -->
<string>server.js</string>
</array>
<key>WorkingDirectory</key><string>/Users/YOU/path/to/kungfu-kanban</string>
<key>EnvironmentVariables</key>
<dict>
<!-- launchd's PATH is minimal; claude + gh + git must be findable -->
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/tmp/kungfu-kanban.log</string>
<key>StandardErrorPath</key><string>/tmp/kungfu-kanban.log</string>
</dict></plist>
launchctl load ~/Library/LaunchAgents/com.kungfu-kanban.plist # start now + at login
launchctl unload ~/Library/LaunchAgents/com.kungfu-kanban.plist # stop
Configuration reference
Environment variables
| Var | Default | Meaning |
|---|---|---|
PORT |
4747 |
listen port |
HOST |
127.0.0.1 |
bind address; anything non-loopback requires a token |
KFK_TOKEN |
— | access token (overrides data/auth-token) |
Files (data/, gitignored — this is all app state)
| File | Contents |
|---|---|
tasks.json |
all cards |
settings.json |
parallel cap, default cwd, ntfy topic, notification toggle, manager config,
maxRunMinutes hung-agent watchdog (default 120, 0 disables it) |
manager.json |
pending suggestions, chat history, launch timestamps |
manager-log.jsonl |
manager activity log |
transcripts/<task-id>.jsonl |
per-card transcript |
auth-token |
access token (create to enable the gate) |
Back up data/ to keep your board; delete it to factory-reset. Individual sessions can
always be reopened in the terminal with claude -r <session-id> (shown in each
card’s drawer).
Troubleshooting
- Card fails instantly with “Failed to launch claude CLI” —
claudeisn’t on the server’s PATH (common under launchd; fix the plist PATH above) or isn’t installed. - Run errors mentioning auth/login — the CLI isn’t logged in: run
claudein a terminal,/login, pick your subscription account. API-key auth can’t be used — the runner deletesANTHROPIC_API_KEYon purpose. - PR flow: “no worktree matching …” — the card ran without the worktree
box, or the cwd isn’t a git repo. “gh pr create failed” — check
gh auth statusand thatoriginpoints at GitHub. - Port already in use —
lsof -nP -iTCP:4747 -sTCP:LISTEN, kill the old server. - No macOS notifications — System Settings → Notifications: allow “Script Editor”/terminal; check the ⚙ Settings toggle.
- Hitting subscription rate limits — lower parallel, prefer haiku/sonnet + low effort for routine cards, disable the manager interval trigger, or lean on the Sensei’s frugality bias (it routes cheap by default).
- A card runs forever without finishing — the watchdog stops it after
maxRunMinutes(default 120) and marks it “stopped by watchdog” in Review; tune the value indata/settings.json, or set it to0to disable it for genuinely long-running cards. - Server won’t start: “Refusing to bind …” — you set
HOSTwithout a token. Createdata/auth-token(or unsetHOSTand use Tailscale serve, which works with loopback).
Security notes
This board executes code on your machine with whatever permission mode a card
carries — bypassPermissions means exactly that. Accordingly:
- The server never binds beyond loopback without a token, and you shouldn’t either. Tailscale serve (tailnet-only) + token is the supported remote path. No public exposure, ever.
- The manager’s permission ceiling stops it from escalating cards beyond what you allow; deleting cards is hard-blocked regardless of autonomy.
- ntfy topics are public-by-obscurity: unguessable names only, no secrets in card titles.
- The token cookie is
HttpOnly/SameSite=Lax, compared timing-safe, and lives a year; rotatedata/auth-tokento invalidate.