Subject: kanban (
github.com/cline/kanban), Apache-2.0, this analysis is based on v0.1.70 (checked out 2026-07-12; the npm package name is alsokanban). Code size: 561 files / 9.1MB.src/92 TS files, 30,402 lines (local runtime);web-ui/src240 ts/tsx files, 58,039 lines (React frontend; including config and styles, the wholeweb-ui/tree is 269 files);packages/desktop/Electron shell, 11 TS files, 2,327 lines;test/59 test files (143 in the whole repo). Runtime dependency@clinebot/core ^0.0.38(the npm-published Cline engine). One-line position: Cline’s own “IDE replacement”—one board, each card = an independent git worktree + an independent terminal session, so many coding agents work in parallel without colliding. It does not write the agent main loop. It is an “agent orchestration layer”: it loads 7 CLI agents (Claude Code / Codex / Cline / Droid / Kiro, plus the Cline native SDK) into cards, uses four board columns (backlog→in_progress→review→trash) to run the process, uses card links as a dependency chain, and uses hooks to inject live status back into the board. Audience: readers who have already read at least one analysis in this series (especially the Cline source analysis). This lecture uses the same frame and citesfile:lineso you can check the source.
Contents
- Project overview: an IDE replacement—a multi-agent parallel board
- Panorama: browser control plane + local runtime as source of truth + two agent paths
- Startup: the CLI brings up a local server; the browser is the only UI
- Board data model: cards, four columns, dependency chains, session state machine
- git worktree isolation: one worktree per card + symlink-shared deps + checkpoint
- Terminal subsystem: node-pty + dual WebSocket + backpressure and protocol filtering
- Multi-agent adapter layer: 7 CLI agents + reverse-injection hooks
- Cline SDK integration: ClineCore multi-session host + event translation + context compaction
- Provider and MCP: model config and tool injection
- tRPC API layer and auth: four layers of defense in depth
- Frontend board UI: React state-driven + persistent terminals + optimistic concurrency
- Desktop Electron: a pure process wrapper + OAuth relay
- Engineering and release: dogfood, CI/CD, GritQL static checks
- Cross-cut: vs the Cline engine / Claude Code / task-management products
- Summary: three most distinctive designs and their trade-offs
Chapter 1 · Project overview: an IDE replacement—a multi-agent parallel board
What is kanban? The first line of the README: A replacement for your IDE better suited for running many agents in parallel and reviewing diffs. It solves a pain the Cline team actually had: once you have tens or hundreds of coding agents running, the IDE cannot hold them—every agent is editing the same tree, stepping on each other’s diffs, and you cannot watch all of their output at once.
kanban’s answer: turn “an agent session” into a draggable card.
- Each card has its own independent git worktree (
git worktree add --detach). The agent works in an isolated tree; conflicts are naturally zero. - Each card has its own independent terminal (a node-pty process). The agent’s TUI renders directly in the browser.
- Four board columns run the process: backlog (todo) → in_progress (running) → review (awaiting review) → trash (done / discarded).
- Card links are the dependency chain:
⌘+click links two cards. When the upstream card finishes and goes to trash, the downstream card starts automatically. Combined with auto-commit, that is a fully autonomous pipeline. - Hooks reverse-inject status: every utterance, every tool call, callbacks through a hook into kanban’s own CLI and pushes state onto the card in real time—you can scan hundreds of cards without opening any terminal.
It is part of the Cline ecology (github.com/cline/ org). The core dependency is @clinebot/core (the npm-published Cline engine)—but it does not lock you to Cline: by default it detects which CLI agents are installed locally (claude / codex / cline / droid / kiro) and takes all of them.
[!WARNING] The README itself says this is a Research Preview, depending on experimental CLI-agent features (bypass permissions, runtime hooks)—the autonomous launch flags in
agent-catalog.tsare blunt:--permission-mode auto,--dangerously-bypass-approvals-and-sandbox,--auto-approve-all.
Digitized project shape
| Dimension | Number | Note |
|---|---|---|
| Version | v0.1.70 (2026-07) | From v0.1.4 to v0.1.70 took about 5 months (CHANGELOG 565 lines) |
| Backend | src/ 92 files / 30,402 lines | Local runtime: server, PTY, git, tRPC, cline-sdk |
| Frontend | web-ui/src 240 ts/tsx files / 58,039 lines (web-ui/ whole tree 269 files) | React + Vite + Tailwind v4, single page, no router |
| Desktop | packages/desktop/src 2,327 lines TS | Electron shell, a pure process wrapper |
| Dependency | @clinebot/core ^0.0.38 | Cline engine npm release (stateful session model) |
| Supported agents | 7 kinds | claude/codex/cline/opencode/droid/kiro/gemini (5 enabled) |
| Storage | JSON files + git refs | ~/.cline/kanban/ (board state) + ~/.cline/worktrees/ (worktrees) + git refs (checkpoint) |
| License | Apache-2.0 | Commercially friendly |
How it relates to Cline / Claude Code
- vs Cline: Cline (the previous analysis) is an “engine + UI” monorepo whose core is a single-agent
AgentRuntime. kanban directly imports@clinebot/coreand runs the Cline engine inside a card. Cline is the single-agent engine; kanban is the scheduling board for N agents—engine vs orchestration layer. - vs Claude Code: Claude Code is a single-session TUI (Ink). kanban runs Claude Code as a subprocess (
--settingsinjects hooks), one per task. The README is blunt: the IDE (where Claude Code lives) no longer holds parallel agents.
Chapter 2 · Panorama: browser control plane + local runtime as source of truth + two agent paths
docs/architecture.md gives three core ideas. They are the key to the whole architecture:
- The browser is only the control plane: render, send commands, receive live updates. It stores no state and runs no business logic.
- The local runtime is the only source of truth: projects / worktrees / sessions / git / streaming state all live in the local Node process.
- Two agent execution paths: most agents are PTY processes (the CLI binary runs directly inside node-pty); Cline takes a native SDK session (
ClineCorein-process host, session-oriented).
Panorama layers
┌─────────────────────────────────────────────────────────┐
│ Browser (control plane) React SPA: board / xterm / git │
│ tRPC httpBatchLink ── WS state stream ── WS terminal io ── WS control │
└──────────────┬──────────────────────────────────────────┘
│ localhost HTTP + WebSocket
┌──────────────▼──────────────────────────────────────────┐
│ Local runtime (only source of truth) src/ ~30k lines │
│ ┌──────────┬─────────────┬──────────────┬─────────────┐ │
│ │ tRPC │ state-stream│ terminal mgr │ git layer │ │
│ │ 4 router │ runtime- │ session- │ task- │ │
│ │ 60+ proc │ state-hub │ manager │ worktree │ │
│ └──────────┴──────┬──────┴──────┬───────┴──────┬──────┘ │
│ ┌───────▼──────┐ ┌────▼─────┐ ┌───────▼─────┐ │
│ │ Cline SDK │ │ PTY proc │ │ 7 Agent │ │
│ │ ClineCore │ │ node-pty │ │ CLI adapters│ │
│ │ (in-process) │ │ │ │ (subprocess)│ │
│ └──────────────┘ └──────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────┘
The two execution paths in detail
Path A: PTY subprocess (claude / codex / droid / kiro / opencode / gemini)
session-manager.ts spawns a node-pty process per card; the agent TUI runs inside it. Agent hook config (claude’s --settings, codex’s -c hooks.*, kiro’s ~/.kiro/agents/kanban.json, etc.) callbacks every event as kanban hooks notify—this is “reverse injection”: while the agent is working, it actively calls kanban’s CLI.
Path B: Cline native SDK (built-in cline)
The cline-sdk/ layer imports @clinebot/core’s ClineCore class in the same process, ClineCore.create({backendMode:"auto"}) starts a multi-session host, and kanban wraps it with ClineSessionRuntime to map “task ↔ session”. Events are subscribed directly; no hooks needed.
Why two paths?
The Cline SDK gives the deepest integration (provider catalog, OAuth, MCP tool injection, account balance) but can only run Cline. The PTY path can run anything, but can only scrape status via hooks. kanban’s strategy: Cline by default (deep integration); every other agent automatically degrades to PTY + hooks.
Chapter 3 · Startup: the CLI brings up a local server; the browser is the only UI
Entry (src/cli.ts, 755 lines)
src/index.ts is only a re-export. The real entry cli.ts uses commander:
- Main command
kanban: start the local server and auto-open a browser.shouldAutoOpenBrowserTabForInvocationdecides which invocation styles should open a browser (direct npx opens; being called by another process does not). - Port reuse:
tryOpenExistingServerprobes whether the port is already occupied by kanban—if so, reuse the existing instance, only open the browser, do not start a second server. - Options:
--host/--port/--no-open/--skip-shutdown-cleanup/--https/--cert/--key/--update/--no-passcode. - Subcommands (
src/commands/):task(list/create/update/trash/delete/link/unlink/start),hooks(ingest/notify/gemini-hook/codex-hook/codex-wrapper),update;mcpis deprecated. - Lazy load: the server stack (tRPC, terminal, git) is all dynamically imported. Running a
tasksubcommand takes the light path and starts in a second.
HTTP server (src/server/runtime-server.ts, 519 lines)
No express—native node:http/node:https. Request path:
middleware (Host allowlist + CORS gate)
→ passcode gate (remote mode only)
→ MCP OAuth callback (/kanban-mcp/mcp-oauth-callback)
→ tRPC handler (createHTTPHandler, basePath /api/trpc)
→ static assets (assets.ts: multi-path probe of web-ui artifacts, path-traversal guard, SPA fallback)
Live push uses WebSocket (the ws library), not SSE: /api/runtime/ws is fanout by runtime-state-hub.ts (604 lines)—first connection gets a full snapshot, then incremental workspace_state_updated / task_sessions_updated (150ms batch merge) / task_chat_message / task_ready_for_review and 11 message kinds in all. The terminal uses two independent WS channels, io / control (see Chapter 6).
The browser is opened with the open package (browser.ts).
Chapter 4 · Board data model: cards, four columns, dependency chains, session state machine
API contract (src/core/api-contract.ts, 1277 lines)
The entire API contract is defined in zod, shared by frontend, backend, and CLI (web-ui imports the same copy via alias @runtime-trpc). This is the most stable foundation in the project: one z type defines the whole API. Server validation, CLI construction, and frontend rendering all read the same schema.
Card model (RuntimeBoardCard)
| Field | Note |
|---|---|
id |
5-character short id (task-id.ts, readable and memorable) |
title |
Derived from the prompt (task-title.ts truncates to 80 characters) |
prompt |
Task description sent to the agent |
startInPlanMode |
Whether to start in plan mode |
autoReviewEnabled / autoReviewMode |
Auto review (commit / pr) |
images |
Attached screenshots |
agentId |
Which agent (claude/codex/cline/...) |
clineSettings |
Cline-session-specific settings |
baseRef |
Which git ref the worktree is based on |
createdAt / updatedAt |
Timestamps |
Four columns + dependency chain
The board is fixed at 4 columns: backlog → in_progress → review → trash (“done” is compat-mapped to trash).
dependencies (fromTaskId→toTaskId) is the automation core:
- Only backlog tasks can be linked (as a dependency or a waiter).
- When a review card is trashed, its backlog dependents are unlocked and auto-started—that is the “dependency-chain auto-pipeline”: task A finishes and goes to trash → task B starts automatically.
Session state machine (independent of column moves)
session-state-machine.ts (78 lines) is a pure function reduceSessionTransition:
idle → running → awaiting_review (reviewReason: attention|exit|error|interrupted|hook)
→ interrupted → running
hook.to_review: running → awaiting_review (the agent requests human intervention)hook.to_in_progress/agent.prompt-ready: back to runningprocess.exit: → interrupted or awaiting_review
The status-dot colors on the card UI and the Waiting for review label all come from this state machine.
Persistence and concurrency (src/state/workspace-state.ts, 745 lines)
- Storage:
~/.cline/kanban/workspaces/<id>/{board.json, sessions.json, meta.json}. - revision optimistic concurrency:
saveWorkspaceStatetakesexpectedRevision; meta.json increments; conflict throwsWorkspaceStateConflictErrorand surfacescurrentRevision—the frontend syncs and retries. - All mutations (
task-board-mutations.ts, 657 lines) are purely functional: add/move/update/trash/delete/link each return a new board; the caller persists. Board-state change is the same idea as React’s immutable updates.
Chapter 5 · git worktree isolation: one worktree per card + symlink-shared deps + checkpoint
This is kanban’s most load-bearing engineering design—using git’s own mechanism as resource isolation.
One worktree per card (src/workspace/task-worktree.ts, 695 lines)
git worktree add --detach <baseCommit>
path: ~/.cline/worktrees/<taskId>/<workspaceLabel>
Key decisions:
- Detached HEAD, no branch. Branches are created dynamically only at Commit / Open PR (the commit template cherry-picks back into the base worktree).
- Once a worktree exists, it is authoritative; it is not rebuilt because the base advanced (to avoid accidentally deleting task progress).
- On trash, capture a diff patch into trashed-task-patches/<taskId>.<commit>.patch, then git worktree remove --force; on resume, git apply restores—cards can “die and come back”.
- The whole create path is serialized under .git/kanban-task-worktree-setup.lock.
checkpoint (src/workspace/turn-checkpoints.ts, 92 lines)
Unrelated to the Cline engine’s checkpoint; this is kanban’s own: after each message round ends, in the worktree, using a temporary index (GIT_INDEX_FILE points at a /tmp tempfile → read-tree HEAD → add -A → write-tree → commit-tree) create a real commit (author kanban-checkpoint), then update-ref refs/kanban/checkpoints/<base64(taskId)>/turn/<N>.
Key: does not pollute the working tree, does not touch branches, pure refs. Rollback = switch to the corresponding ref. Call site is a best-effort capture at the end of each session round in runtime-api.ts.
git UI data (git-history.ts, 478 lines)
The commit graph in the browser: git log --topo-order --date-order, fields delimited by \x1f/\x1e (hash/author/date/subject/parentHashes), with rev-list --count for paging; branches/upstream use for-each-ref + rev-list --not to compute each commit’s relation (selected/upstream/shared) for commit-graph coloring.
Merge strategy (git-sync.ts, 385 lines)
kanban does not auto-commit/merge—runGitSyncAction only supports fetch / pull --ff-only / push / checkout / discard. auto-commit / auto-open-pr is implemented by injecting a prompt so the agent itself git add/commit/push/opens a PR (see Chapter 10, append-system-prompt). This is a clever trade-off: git-conflict handling is left to the agent (it understands the context); kanban only owns the process.
File locks (src/fs/locked-file-system.ts, 165 lines)
Based on proper-lockfile: withLocks sorts lockfile paths then takes them one by one (deadlock prevention); writeTextFileAtomic writes a tempfile + rename for atomic replace. Worktree creation, patch writes, and info/exclude updates all go through it.
Chapter 6 · Terminal subsystem: node-pty + dual WebSocket + backpressure and protocol filtering
PTY (src/terminal/pty-session.ts, 161 lines)
Uses node-pty: PtySession.spawn() creates a real PTY process; the agent CLI runs directly in it. onData hands output byte chunks to the manager; write() writes browser input back; on stop(), kill() then process.kill(-pid, SIGTERM) kills the whole process group (POSIX side).
Data path: PTY → onData → protocol filter → TerminalStateMirror (headless xterm mirror) → broadcast to all WS listeners → browser xterm render.
Dual WebSocket (src/terminal/ws-server.ts, 591 lines)
Two WebSocketServer({noServer:true}), demuxed on server.on("upgrade") by pathname; clientId query distinguishes tabs:
- io stream: the server
ws.send(chunk)s raw Buffer bytes (the browser xterm is fed directly); a browsermessageis keyboard bytes passthroughwriteInput. - control stream: JSON messages
{type:"state", summary}/{type:"exit", code}/{type:"restore", snapshot, cols, rows}; the browser sendsresize/stop/output_ack/restore_complete.
Multi-tab sharing: many viewers of the same task share one PTY; TerminalStateMirror (xterm headless + SerializeAddon) gives a new viewer offline scroll-buffer snapshot restore.
Backpressure (VS Code-style): track each viewer’s bufferedAmount + unacked bytes (high-water 100KB). If any viewer falls behind, pauseOutput pauses the shared PTY; only when all catch up does resumeOutput—so a slow browser cannot stall the agent process.
Protocol filter (terminal-protocol-filter.ts)
Byte-by-byte parse of ESC/OSC/CSI sequences: intercept OSC 10;?/11;? (when the browser is not yet connected, the server answers fg/bg color), drop Droid’s DA (Device Attributes) query (CSI c)—so the TUI does not wait forever for a browser that is not yet connected; half-cut sequences across chunks are held in pendingChunk. ANSI strip uses a homemade stripAnsi() (output-utils.ts, no third-party library).
workspace-trust auto-confirm (claude-workspace-trust.ts / codex-workspace-trust.ts)
Detect trust-prompt text in PTY output (claude regex "trust this folder"; codex ordered tokens "do you trust the contents of this directory"), rolling buffer 16KB, delay 100ms, auto-send Enter to confirm—but only for worktrees kanban itself created. During startup it also intercepts the TUI’s OSC 10/11 color queries and synthesizes replies.
Process management (session-manager.ts, 1040 lines)
TerminalSessionManager uses Map<taskId, SessionEntry> for concurrent sessions; crash recovery shouldAutoRestart (at most 3 times in a 5-second window) → scheduleAutoRestart reruns from the saved restartRequest; markInterruptedAndStopAll stops everything; hydrateFromRecord restores the summary from disk after restart. Process-tree cleanup is dual insurance: PTY kills the process group + server/process-termination.ts tree-kill (Windows SIGTERM tree kill).
Chapter 7 · Multi-agent adapter layer: 7 CLI agents + reverse-injection hooks
Command discovery (command-discovery.ts, 78 lines)
Does not which (avoids the side effects of spawning a shell/conda/nvm). Directly accessSync(X_OK) scans process.env.PATH (Windows uses PATHEXT). Detects 7 binaries: claude/codex/cline/opencode/droid/kiro-cli/gemini; RUNTIME_LAUNCH_SUPPORTED_AGENT_IDS only enables cline/claude/codex/droid/kiro (opencode/gemini are commented out). Default cline (always installed:true).
7 adapters (agent-session-adapters.ts, 1451 lines)
prepareAgentLaunch() dispatches by agentId:
| Agent | Injection | Mechanism |
|---|---|---|
| claude | --settings <hooks dir>/settings.json |
Stop/SubagentStop/PreToolUse/PermissionRequest/PostToolUse/Notification/UserPromptSubmit—7 hook commands all point at kanban hooks notify --event ...; autonomous adds --permission-mode auto |
| codex | -c features.hooks=true + -c hooks.Stop=... and 5 config overrides |
Compute a hook-config sha256 trust hash into hooks.state so Codex does not pop a trust confirm; output parsing relies on the TUI session log |
| gemini | GEMINI_CLI_SYSTEM_SETTINGS_PATH |
Points at a generated settings.json (BeforeTool/AfterTool/BeforeAgent/AfterAgent/Notification) |
| opencode | dynamically generate a JS plugin | Listen message.updated/session.status/tool.execute.before/permission.ask, base64-encode the payload then $ shell-call kanban hooks notify --metadata-base64; parse preferred model from config/auth/model state files to prevent provider drift |
| droid | settings.json (autonomyMode: spec/auto-high/normal) | PreToolUse matcher distinguishes active tools |
| kiro | write ~/.kiro/agents/kanban.json (tools:["*"] + hooks) |
start with --agent kanban |
| cline | generate bash/PowerShell scripts under --hooks-dir |
read JSON from stdin, grep to decide user_attention, then turn to_review |
Hooks reverse injection (src/commands/hooks.ts, 820 lines)
The core mechanism in one sentence: while the agent is working, it actively calls kanban’s own CLI to report status.
Agent event → hook command (kanban hooks notify --event ...)
→ subprocess reads JSON payload from stdin (or --metadata-base64)
→ parse toolName/activityText/finalMessage
→ tRPC hooks.ingest.mutate back to the runtime server
→ state-machine transition + broadcast workspace_state_updated / task_ready_for_review
ingestis synchronously blocking (waits for ack);notifyis aspawnBackgroundKanban()detached background process (best-effort, does not block the agent).- Codex special case:
hooks codex-wrapperwraps the real codex, setsCODEX_TUI_RECORD_SESSION=1so it writes a session log + rollout jsonl, thenstartCodexSessionWatcher()polls file increments every 200ms;parseCodexEventLinemapstask_started/agent_message/task_complete/exec_command_begininto activity events (dedup by fingerprint). - Status attribution:
KANBAN_HOOK_TASK_ID/WS_IDenv vars (hook-runtime-context.ts) pass task ownership between adapter ↔ hook subprocess.
Why not poll terminal output? Because each agent’s TUI output format is completely unreliable; a hook is the agent’s officially supported “event exit,” with clean semantics (“this tool finished,” “this message is for the user”).
Chapter 8 · Cline SDK integration: ClineCore multi-session host + event translation + context compaction
src/cline-sdk/ 17 files / 7,094 lines—kanban’s deep-integration layer with the Cline engine. This is the chapter with the most contrast value against the previous Cline analysis.
Not import SessionRuntime, but ClineCore (sdk-runtime-boundary.ts, 126 lines)
The only import boundary; re-exports ClineCore.create({backendMode:"auto"}). The load-bearing fact: npm-published @clinebot/core@0.0.38 exposes a stateful multi-session host—ClineCore has a sessions Map (start/send/stop/abort/delete/get/list/update/readMessages/subscribe). Sessions are first-class; send() incrementally continues the chat; subscribe() pushes streaming events; SessionHistoryRecord lands on disk.
This is completely different from the stateless AgentRuntime on Cline’s main branch (new AgentRuntime() + run("") re-entry)—the npm-published @clinebot/core and cline’s current main-branch API are evolving in different directions (comments on the 0.0.36 compatibility branch also confirm the API layer is still moving fast). kanban depends on a “stateful session” model because it naturally needs “one task = one continuable session.”
Task ↔ session mapping (cline-session-runtime.ts, 580 lines)
ClineSessionRuntime interface + InMemoryClineSessionRuntime implementation:
- Bidirectional Map bind: sessionIdByTaskId / taskIdBySessionId.
- createSessionId(taskId) = "<sanitized taskId>-<ts>-<rand>"; after restart, recover by prefix from host.list().
- send() takes delivery: "queue"|"steer"—queue queues; steer interrupts the current turn.
Event translation (cline-event-adapter.ts, 816 lines)
applyClineSessionEvent(input) parses events pushed by subscribe() into 5 classes (agent_event / chunk / hook / status / ended). Each AgentEvent maps to a summary patch + message mutation:
| SDK event | Card effect |
|---|---|
assistant-text-delta / content_start / content_end |
assistant message |
tool-started / tool-finished |
tool message (Tool: xxx / Input: ...) |
run-finished / done |
awaiting_review (hook/error/interrupted) |
error / run-failed |
awaiting_review(error) |
assistant-reasoning-delta |
reasoning message |
ask_followup_question / plan_mode_respond |
needs human attention → awaiting_review(attention) |
Message repository (cline-message-repository.ts, 364 lines)
SDK-side messages live in session storage under ~/.cline (host.readMessages(sessionId) reads them back); kanban only keeps an in-memory ClineTaskMessage view. After a page refresh, hydrateTaskMessages hydrates from the SDK’s persisted session. kanban does not re-persist Cline sessions—it trusts the engine’s own storage.
Context-overflow compaction (cline-context-overflow-compaction.ts, 125 lines)
After send/start errors, use 32 regexes to decide “prompt/context too long,” then stop the old session; compactPersistedMessagesForContextOverflow takes the latter half of messages (cut at a user-message boundary), embeds a preview of the first user message into a Cline-official-style compaction note ([Previous conversation history was removed...]), restartTaskSession(initialMessages=compacted) reruns. Comments state this is a temporary scheme, waiting for a pluggable compaction API from the SDK.
Tool-call display (cline-tool-call-display.ts, 352 lines)
getClineToolCallDisplay(toolName, input) compresses a tool call into a one-line summary (read_files(a.ts:1-20, b.ts)), specialized for read_files/run_commands/search_codebase/editor/fetch_web_content/skills/ask_question; resolveKanbanCommandDisplay recognizes kanban task <create|link|...> and turns it into human speech (“Creating task”)—so the card can show “the agent is creating a task.”
Chapter 9 · Provider and MCP: model config and tool injection
Provider (cline-provider-service.ts, 1294 lines)
UI-facing provider facade createClineProviderService():
- Provider catalog: Llms.getAllProviders() (the full Cline ecology).
- Model list: local + remote catalog + LiteLLM /models probe.
- Login methods: three managed OAuth kinds (cline / oca=Oracle Code Assist / openai-codex, tokens prefixed workos:), device auth, manual apiKey, env vars CLINE_API_KEY/OCA_API_KEY.
- Account: ClineAccountService looks up profile/balance/org; OAuth expiry auto-refresh.
- resolveLaunchConfig() assembles {providerId, modelId, apiKey, baseUrl, reasoningEffort} and injects it into start.
Default cline provider, default model anthropic/claude-sonnet-4.6.
MCP (cline-mcp-settings-service.ts 216 lines + cline-mcp-runtime-service.ts 879 lines)
- settings-service reads/writes the Cline standard config file
~/.cline/data/settings/cline_mcp_settings.json—the same MCP config as the Cline extension (zod validation, compatible with stdio/sse/streamableHttp). - runtime-service, on every
startTaskSession,createToolBundle(): read servers from settings → register withInMemoryMcpManager→createMcpToolsgenerates AgentTool → inject into ClineCore vialocalRuntime.extraTools, and setdisableMcpSettingsTools:trueto dedup. - Ships a full MCP OAuth flow:
@modelcontextprotocol/sdkStdio/SSE/StreamableHTTP clients + a homemadeOAuthClientProvider(persisted tocline_mcp_oauth_settings.json); callback goes through kanban’s own HTTP endpoint/kanban-mcp/mcp-oauth-callback.
Provider on the CLI path
PTY-path agents use their own config (claude’s ~/.claude/settings.json, codex’s ~/.codex/, etc.)—kanban does not touch it, because that is the agent’s own business. Only the Cline SDK path needs kanban to manage providers, which is why the provider service lives entirely in the cline-sdk layer.
Chapter 10 · tRPC API layer and auth: four layers of defense in depth
tRPC boundary (src/trpc/app-router.ts, 731 lines)
runtimeAppRouter exposes 4 routers: runtime (~40 procedures), workspace (14), projects (5), hooks (1). The pattern is “thin routing + dependency injection”—each procedure only pairs input/output schema then delegates to a service injected on the context.
workspaceProceduremiddleware forces workspace scope: parse{workspaceId, workspacePath}from thex-kanban-workspace-idheader; missing throws BAD_REQUEST.errorFormattersurfaces CONFLICT’scurrentRevisionto the frontend for optimistic-concurrency retry.- Transport is pure HTTP fetch (
httpBatchLink). There are three clients: web-ui’sgetRuntimeTrpcClient(cached by workspaceId), CLItask.ts/hooks.ts(custom fetch that auto-attaches an internal Bearer token). No tRPC ws adapter—WS is only for the state stream and the terminal.
Core runtime API procedures
startTaskSession: agentId priority = last session > card override > config default;clinetakes the SDK path, otherwise the PTY path; on trash-restore, probe a persisted Cline session; best-effort capture of a turn checkpoint.stopTaskSession/sendTaskSessionInput(try Cline first, then the terminal) /getTaskChatMessages/sendTaskChatMessage(recognizes/clear) /reloadTaskChatSession/abortTaskChatTurn/cancelTaskChatTurn.startShellSession/runCommand(shortcut buttons runnpm run dev).- Cline config domain: provider CRUD, account profile/balance/org/switch, OAuth login/device code, MCP settings/OAuth.
resetAllState(delete~/.cline/data|kanban|worktreesone-click reset).
Board instructions injected into the home agent (src/prompts/append-system-prompt.ts, 316 lines)
The sidebar-chat agent that “helps me split work into tasks” is injected a system instruction:
- Role: “Kanban board management helper,” explicitly forbidden from coding / editing files—implementation requests are always turned into creating tasks.
- Full CLI reference: parameters and semantics of task list/create/update/done/delete/link/unlink/start.
- Linking and auto-review pipeline: review complete → auto-start linked backlog tasks; auto-commit/auto-open-pr combined yield fully autonomous chained execution.
- Workspace identification: if cwd is under .cline/worktrees/, force --project-path (from the worktree back to the main workspace).
Auth: four layers of defense in depth
- Default bind 127.0.0.1 only—other machines cannot reach it (
runtime-server.ts). - Host allowlist (
middleware.ts)—anti DNS rebinding: non-allowlisted Host is refused outright. - CORS exact Origin allowlist—stops other pages on the same machine from cross-origin reading (a malicious page cannot steal your API through the browser).
- Remote mode adds a passcode (
src/security/passcode-manager.ts, 247 lines): enabled when--hostbinds non-loopback. 8-digit random code (randomBytesrejection sampling, memory only),timingSafeEqualcompare, 5 failures per IP lock 30 seconds, after verify issue a 32-byte random token (HttpOnly + SameSite=Strict Cookie, 24h TTL). AlsoKANBAN_INTERNAL_AUTH_TOKENbearer token for CLI subprocesses (hooks ingest / task).
Threat model: when bound remotely, the local runtime exposes arbitrary code-execution capability (git, shell, filesystem). The passcode stops unauthorized browser visitors (and brute force)—static assets are allowed through (so React’s PasscodeGate can render first); the API is hard-gated.
Chapter 11 · Frontend board UI: React state-driven + persistent terminals + optimistic concurrency
Entry and layout (web-ui/src/App.tsx, 1199 lines)
A single root component, no React Router, purely state-driven view switching. Provider stack: PasscodeGateProvider → TelemetryProvider → AppErrorBoundary → TooltipProvider.
Layout = left ProjectNavigationPanel (draggable/collapsible) + right TopBar + main area. Main area has three shapes:
- Home: KanbanBoard or GitHistoryView (isGitHistoryOpen toggles) + bottom ResizableBottomPane (home terminal—a shell you can run without creating a card).
- CardDetailView: after selecting a card, absolute inset-0 covers Home; inside is Chat panel + diff/file tree + bottom AgentTerminalPanel (URL query records the task id).
- Fallback: disconnected → RuntimeDisconnectedFallback; no permission → KanbanAccessBlockedFallback.
Board state machine (state/board-state.ts, ~21k lines)
Purely functional board state machine: normalizeBoardData (schema validate/clean WS raw data), addTaskToColumn, applyDragResult, moveTaskToColumn, trashTaskAndGetReadyLinkedTaskIds (cascading deps). drag-rules.ts defines drag legality (review column cannot be dropped into by hand; trash can only enter, not leave).
Data-sync trio (the consistency core)
- use-runtime-state-stream.ts (~15k lines): connect
ws://host/api/runtime/ws,useReducermaintains the store, exponential-backoff reconnect (500ms→5s). Onsnapshot, fill in full; incremental messages merge byupdatedAt; chat upserts by id. - use-workspace-sync.ts: stream’s workspaceState →
normalizeBoardData→ setBoard, dedup byrevision(old revision discarded); when the page becomes visible again, refetch full to calibrate. - use-workspace-persistence.ts: 120ms debounce
saveWorkspaceState({board, sessions, expectedRevision}); on CONFLICT, update local with returnedcurrentRevision, toast "Synced latest state", and refetch to overwrite.
Strategy in one sentence: local optimistic update (drag moves instantly, fail rolls back) → debounce save + expectedRevision optimistic lock → WS as authority + revision monotonicity so old writes cannot overwrite new writes.
Persistent terminal (terminal/persistent-terminal-manager.ts, ~21k lines)
xterm@6 wrapper + dual WebSocket (io binary + control JSON). clientId (randomUUID) identifies the browser tab; the backend shares one PTY per taskId and isolates socket state per clientId.
What “persistent” means: DOM nodes can be “parked” into a hidden #kb-persistent-terminal-parking-root—switching cards does not destroy the terminal; on restore the scroll-buffer snapshot is written back (restore/snapshot in the control protocol). Addons: Fit, WebGL (auto-degrade on context loss), Clipboard, WebLinks, Unicode11; ResizeObserver 50ms debounce reports cols/rows.
git UI and commit/PR (git-actions/build-task-git-action-prompt.ts)
commit/PR prompt = template interpolation, four-layer fallback: user template → user default template → server default → fallback sentence; the only variable is {{base_ref}}. use-git-actions.ts’s handleCommitTask → assemble prompt → sendTaskSessionInput (paste mode writes into the terminal) so the agent itself runs git—again “give the work to the agent.”
Component library and experience
- Not shadcn:
@radix-ui/react-*primitives + Tailwind CSS v4 +lucide-reacticons +@hello-pangea/dnd(board drag) +react-virtuoso(virtual lists) +sonner(toast) +motion.components/ui/is a handwritten thin wrap. - PWA:
sw.jsonly intercepts navigation requests; on failure returns a branded "Waiting for Cline" fallback page and HEADs/every 2s to auto-refresh (server crash auto-recovery). - onboarding: first-run guide (pick Agent / configure Cline provider);
isSelectedAgentAuthenticatedis the gate. - telemetry: sentry (release
kanban@version) + posthog (only 4 manual events: task_created / task_dependency_created / tasks_auto_started_from_dependency / task_resumed_from_trash—only the load-bearing board behaviors). - open-targets: generate open commands by platform (
open -a "VS Code"/code <path>), supporting vscode/cursor/windsurf/zed/xcode/ghostty etc.—click a card and open the worktree in an IDE.
Chapter 12 · Desktop Electron: a pure process wrapper + OAuth relay
Position (packages/desktop/, src 11 TS files 2,327 lines + disconnected.html 152 lines + 13 tests)
The desktop app is a pure “process wrapper”; it imports no code from the root package. The CLI is spawned as a subprocess (spawn("kanban", ["--no-open","--port",3484,...])); after HTTP health probe (checking <title>Kanban</title>) confirms ready, BrowserWindow loadURLs http://127.0.0.1:3484.
Module split:
| Module | Lines | Duty |
|---|---|---|
main.ts |
311 | single-instance lock, kanban:// protocol deep link, window-state persist, before-quit force-sync shutdown |
runtime-orchestrator.ts |
604 | lifecycle core: connect (first probe an existing runtime, else startOwnRuntime) / restart / shutdown / dispose; dual probes (attached 500ms detect external-runtime crash, recovery 2s auto-reconnect); powerSaveBlocker against App Nap |
runtime-child.ts |
334 | spawn + 30s ready poll + treeKill graceful close, 4GB V8 heap cap |
window-registry.ts / window-state.ts |
298 / 178 | multi-window management, atomic write of state file, offscreen-window clamp |
window-factory.ts |
221 | renderer crash recovery (did-fail-load + render-process-gone → health probe → disconnected screen / retry) |
oauth-relay.ts |
46 | see below |
OAuth relay
The agent’s OAuth callback arrives at the desktop as a kanban://oauth/callback?... deep link; the main process parses it and forwards params to http://127.0.0.1:3484/kanban-mcp/mcp-oauth-callback, retry 3 times then popup on failure—pure web cannot intercept a system-level custom protocol, which is one of the hard reasons the desktop app exists.
Why a desktop app?
- Resident process: native menus, multi-window, system tray.
- Ships a Node runtime: the shim uses
ELECTRON_RUN_AS_NODE=1to reuse the Electron binary; the user does not install Node. - GUI-launch PATH enhancement (
runtime-child-env.ts): fill in Homebrew/nvm etc. paths—pure CLI never has this problem; double-click-to-open does. - Crash auto-bring-up: recovery probe 2s auto-reconnect.
Packaging path: root build.mjs esbuild produces dist/cli.js+dist/web-ui/ → stage-cli.mjs copies into packages/desktop/cli/ → electron-builder extraResources + asarUnpack (only unpack cli/node-pty/*.node) → patch-node-pty.mjs postinstall fixes the app.asar→unpacked dual-suffix bug.
Chapter 13 · Engineering and release: dogfood, CI/CD, GritQL static checks
dogfood: using kanban to develop kanban
scripts/dogfood.mjs(470 lines): firstnpm run build, then spawndist/cli.jsin an independent process group running the production build;--skip-shutdown-cleanupis handed to the lockfile-elected “cleanup owner” (prevents multi-instance double cleanup); strip the repo’snode_modules/.bin(avoid shadowing global Agent CLIs); 10s SIGKILL fallback.- The repo ships
.claude/,.cline/kanban/config.json(shortcuts run dogfood/dev-full),.clinerules/workflows/release.md,.codex/environments/environment.toml(setup script auto-symlinks node_modules to the main worktree),.factory/settings.json—the same repo is a real usage scene for four agents at once: Claude Code / Cline / Codex / Factory Droid; the worktree+symlink trick is reused directly by the .codex config.
Dev norms (AGENTS.md, 8KB of “tribal knowledge”)
- No
any, no inline import, upgrade dependencies rather than downgrade. - web-ui uses Tailwind v4 + Radix + Lucide.
- Prefer direct PATH detection over launching the agent through an interactive shell (conda/nvm will freeze the runtime).
- For Cline behavior, look at
@clinebot/core+src/cline-sdk/.
Static checks and CI
- GritQL (
grit/, 4 .grit files): ban console.*, ban__home_agent__:literals, ban process.env destructuring, ban process.exit (except CLI entry)—structural constraints stronger than lint. .husky/pre-commit: biome check staged → typecheck →test:fast..github/:ci.yml(push/PR) +test.yml(3-platform matrix: ubuntu20/22 + macos22) +publish.yml: manual workflow_dispatch with a tag → version-consistency check →npm run prepublishOnly→ OIDC trusted publishingnpm publish→ auto-extract changelog, create GitHub Release → Slack.
Version evolution (CHANGELOG, v0.1.4 → v0.1.70)
| Version | Milestone |
|---|---|
| 0.1.4 | Start: git worktree parallel tasks, diff review, linked tasks, MCP integration |
| 0.1.5 | Droid agent, dogfood, xterm + node-pty terminal |
| 0.1.11 | Remove MCP, switch to skill (evidence of an architecture turn) |
| 0.1.12 | UI redesign |
| 0.1.15 | Cross-platform Windows/Linux |
| 0.1.65+ | runtime child manager, new-version notification, Electron desktop-app scaffold |
5 months from 0.1.4 to 0.1.70—this is exactly the speed a “board + agent pipeline” produces when it runs itself.
Chapter 14 · Cross-cut: vs the Cline engine / Claude Code / task-management products
14.1 vs the Cline engine (previous analysis)
| Dimension | Cline (engine) | kanban (orchestration layer) |
|---|---|---|
| Position | single-agent engine (main loop + tools + Provider) | scheduling board for N agents |
| Core abstraction | AgentRuntime stateless loop | card = worktree + session |
| Session model | stateless run("") re-entry |
stateful ClineCore session (npm 0.0.38) |
| Status display | TUI / WebView, one at a time | board batch + hook live |
| Resource isolation | single workspace | git worktree, one per card |
| Relationship | kanban depends on it (@clinebot/core) | engine-neutral (7 agents all supported) |
Essential difference: Cline answers “how does one agent work smartly”; kanban answers “how do a pile of agents get organized to work.” kanban’s orchestration (dependency chains, auto-start, batch monitoring) is what the Cline engine does not have; Cline’s depth (Provider/OAuth/MCP) kanban takes for free via the SDK.
14.2 vs Claude Code
| Dimension | Claude Code | kanban |
|---|---|---|
| Interaction | single-session Ink TUI | board + xterm (multi-session parallel) |
| Multi-task | --resume/--fork-session by hand |
cards + dependency chain automatic |
| Parallelism | single process (can spawn subagent) | independent worktree + process per card |
| Review | none built-in | review column + inline comments fed back |
| Relationship | kanban runs it as a subprocess (injects hooks) | Claude Code is one of kanban’s 7 agents |
14.3 vs task-decomposition multi-agent (OpenWorker / Manus lineage)
Those are “multi-agent inside an Agent” (coordinator splits work → delegates to specialists, sharing one session context); kanban is “dev-process-level multi-agent” (each agent independent session + independent worktree + a human review gate). The former is reasoning division of labor; the latter is engineering parallelism—one at the thinking layer, one at the repo layer.
14.4 vs project-management tools (Linear / Jira / GitHub Projects)
kanban’s cards are alive: they have a terminal, a worktree, they can start an agent, they can be linked into a pipeline. Traditional board cards are “a requirement description”; kanban’s cards are “a process that is working”—the object of task management moved from a document to an execution unit.
14.5 One-sentence division of labor
- Cline engine: one agent’s thinking and execution.
- Claude Code: one agent’s interactive terminal.
- kanban: multi-agent organization, isolation, monitoring, review, and automated pipeline.
- Claude Code / Codex / Cline / Droid: kanban’s executors (pluggable).
Chapter 15 · Summary: three most distinctive designs and their trade-offs
Three things (if you remember only three designs)
1. git worktree is resource isolation; symlink is cost optimization
No container, no sandbox. git worktree add --detach in one command gives each agent an isolated working tree, zero-conflict parallel; gitignored directories (node_modules) symlink back to the main repo, saving one npm install per card. Combined with .git/info/exclude managing the symlink itself. This is a textbook case of “use git’s mechanism to solve an engineering problem”—the cost: when the agent modifies gitignored files such as node_modules, the share will leak across (the README is explicit: "don't use Kanban"); Turbopack cache will trip over the symlink, so they added a Turbopack-detection exception.
2. Hooks reverse injection: the agent actively calls kanban’s CLI to report status
Do not poll terminal output (format unreliable). Instead inject kanban hooks notify into each agent’s official hook mechanism—every utterance, every tool call, actively callbacks kanban. That makes live status scanning of hundreds of cards possible, and naturally fits 7 agents (each generates a different hook config). The cost: every agent needs an adapter; Codex even needs a wrapper + polling rollout jsonl (its hooks are not even reliable); it depends on experimental features (bypass permissions, runtime hooks); the README itself marks Research Preview.
3. Dual path: engine-neutral and deep integration coexist
Default Cline takes the native SDK (same process, Provider/OAuth/MCP all deeply integrated); other agents take the PTY subprocess (hooks only). One path gets the deepest integration; one path gets the widest compatibility. The cost: two execution paths, two status sources (SDK events vs hook events); sendTaskSessionInput must try Cline first then the terminal; @clinebot/core 0.0.38’s API is still moving fast (the 0.0.36 compatibility branch is still kept).
Trade-offs and “who it is for”
- For: teams doing multi-agent parallel development, individuals who want to build autonomous pipelines for agents, developers studying agent orchestration.
- Not for: single-agent users (Claude Code itself is enough), enterprise scenes that need strong audit/permission control (default bypass permissions), scenes that modify gitignored files (symlink share will leak).
- Relation to the ecology: it is the Cline ecology’s “command stand”—engine (Cline), executors (each CLI agent), orchestration layer (kanban), three layers distinct.
🔍 Source pointers (go check the source)
| Want to know | Look here |
|---|---|
| Card model / four columns / deps | src/core/api-contract.ts (1277 lines) + task-board-mutations.ts |
| Session state machine | src/terminal/session-state-machine.ts (78 lines) |
| worktree + symlink | src/workspace/task-worktree.ts (695 lines, L358 symlink) |
| checkpoint | src/workspace/turn-checkpoints.ts (92 lines) |
| Terminal + dual WS + backpressure | src/terminal/pty-session.ts + ws-server.ts (591 lines) |
| 7 agent adapters | src/terminal/agent-session-adapters.ts (1451 lines) |
| Hooks reverse injection | src/commands/hooks.ts (820 lines) |
| Cline integration boundary | src/cline-sdk/sdk-runtime-boundary.ts (126 lines) |
| Event translation | src/cline-sdk/cline-event-adapter.ts (816 lines) |
| tRPC + auth | src/trpc/app-router.ts + src/server/middleware.ts + passcode-manager.ts |
| Frontend sync trio | web-ui/src/runtime/use-runtime-state-stream.ts + use-workspace-persistence.ts |
| Persistent terminal | web-ui/src/terminal/persistent-terminal-manager.ts |
| Desktop | packages/desktop/src/runtime-orchestrator.ts (604 lines) |
| Board-injected instructions | src/prompts/append-system-prompt.ts (316 lines) |