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 Kungfu Kanban master — a cardboard box robot held together with duct tape
fig. 1 — the master. cardboard, tape, runs the dojo.

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.

Backlog2
Summarize the week’s PR review comments
haiku⏱ mon 09:00
Draft onboarding email sequence
sonnet
Queued1
Summarize competitor pricing pages
haiku
Running1
Fix flaky auth test in CI
opusrunning
Review2
Add healthcheck endpoint
sonnetPR #142
Migrate icons to SVG sprites
fablefailed
Done2
Shipped
Refactor webhook retries
opusPR #138
Shipped
Draft changelog for v0.4
haiku

The discipline, itemized.

Subscription

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.

no token billing. ever.
Models

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.

Skill

“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.
Manager

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.

Worktrees

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.

Chains

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.

Groups

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.

Cooldown

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.

Import

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.

Phone

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.

Local

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.

your machine. your files. obviously.
Frugal

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.

Suggest
every action waits for your approval
Semi
creates and runs cards; verdicts need your sign-off
Auto
full autopilot, inside your guardrails

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.

§ 01

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
§ 02

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).

§ 03

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.

§ 04

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.

§ 05

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:

  1. The worktree is located by asking git for the branch named kanban-<card-id> — wherever the CLI put it.
  2. Anything the agent left uncommitted is committed.
  3. If the branch has no commits beyond the base, the flow stops — no empty PRs.
  4. The branch is pushed to origin and gh pr create opens a PR: title = card title, body = prompt + acceptance criteria.
  5. 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.
Keep local main pushed. Agent worktrees branch from the default branch, so an unpushed main is how you get avoidable conflicts in the first place.
§ 06

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 →

§ 07

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 .md files into data/inbox/ — cards appear automatically and the file is archived to data/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.

The import format, as its own reference page →

§ 08

Scheduled cards

Give a card a Repeat value in the editor to run it on a schedule:

  • 6h (or 6) — every 6 hours (fractional hours like 0.5h are 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.

§ 09

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)

  1. Install the ntfy app (iOS / Android) — no account needed.
  2. 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).
  3. Put it in ⚙ Settings → ntfy topic on the board.
  4. 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.

§ 10

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_TOKEN env var (overrides the file).
  • Rotate it by regenerating data/auth-token (old cookies stop working).
  • tailscale serve is tailnet-only. Never use tailscale funnel or a public port-forward for this app.
  • To stop sharing: tailscale serve --https=443 off (or tailscale serve reset).
§ 11

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.

§ 12

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
§ 13

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).

§ 14

Troubleshooting

  • Card fails instantly with “Failed to launch claude CLI”claude isn’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 claude in a terminal, /login, pick your subscription account. API-key auth can’t be used — the runner deletes ANTHROPIC_API_KEY on 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 status and that origin points at GitHub.
  • Port already in uselsof -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 in data/settings.json, or set it to 0 to disable it for genuinely long-running cards.
  • Server won’t start: “Refusing to bind …” — you set HOST without a token. Create data/auth-token (or unset HOST and use Tailscale serve, which works with loopback).
§ 15

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; rotate data/auth-token to invalidate.