You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is solid engineering, but it's reimplementing primitives that beads provides natively.
The Mapping
TaskPlane concept
Current implementation
Beads equivalent
Task definition
Directory + PROMPT.md + STATUS.md
bd create with description/metadata
Dependencies
dependencies.json + PROMPT.md parsing
--deps blocks:bd-123 (native)
"What's ready to run?"
computeWaves() — topological sort, 200+ lines
bd ready --json — one call
Task status
batch-state.json field per task
bd update --status / bd close
Dependency graph validation
validateGraph() — cycle detection, orphan check
Built into beads' blocker resolution
Resume after crash
resume.ts — 2,878 lines reconciling stale state
bd ready --json — just ask again
Task metadata
Parsed from PROMPT.md headers (size, review level, repo)
--metadata '{"size":"M","reviewLevel":2}'
Discovered work during execution
Not supported
bd create --deps discovered-from:bd-123
What Changes
Phase 1: Beads as task registry (additive, non-breaking)
TaskPlane keeps PROMPT.md files for the rich task spec (steps, checkboxes, file scope, context docs). But instead of scanning directories and parsing headers for the task lifecycle data, it reads from beads:
# Discovery becomes:
bd ready --json # → [{id, title, deps, priority, metadata: {size, reviewLevel, taskFolder}}]# Wave planning becomes:# beads already computed the unblocked set. TaskPlane just needs to# bin them into lanes based on size/repo — the graph work is done.# Status updates become:
bd update $TASK_ID --status in_progress # worker starts
bd close $TASK_ID --reason "All steps complete"# worker finishes
bd update $TASK_ID --status blocked --note "merge conflict"# failure
Phase 2: Replace persistence layer
batch-state.json shrinks dramatically. Runtime state (lane assignments, worktree paths, telemetry) stays in batch-state. But task status, dependency resolution, and completion tracking move to beads. The 2,878-line resume.ts simplifies to "ask beads what's still open."
Phase 3: Agent-driven task creation
Workers that discover new work during execution can:
This slots into the dependency graph automatically. Next bd ready reflects it. No file scaffolding needed.
What Stays the Same
PROMPT.md — the rich task specification with steps, checkboxes, file scope, context docs. Beads doesn't replace this; it just tracks the lifecycle around it.
Merge agent — unchanged, it works on git branches not task records.
What Gets Deleted
dependencies.json files and the cache layer
computeWaves() graph construction (1,548 lines of waves.ts significantly simplified)
Most of resume.ts — crash recovery becomes "query beads for current state"
PROMPT.md header parsing for lifecycle fields (deps, status) — only content parsing remains
Half of persistence.ts — task state serialization moves to beads
Why Dolt
Beads uses Dolt, a MySQL-compatible database with git-style version control. This is unusually well-suited for TaskPlane's needs:
Time-travel debugging
Every beads write auto-commits to Dolt history. When a batch crashes at 2:30 AM and you're debugging at 9 AM, you can query the exact state at crash time:
-- What was the task state when wave 2 started?SELECT id, title, status FROM issues AS OF 'abc123def'WHERE status ='open';
Or via the CLI:
bd show TP-004 --as-of abc123 # Show task as it was at that commit
bd diff HEAD~5 HEAD # What changed in the last 5 operations?
bd history TP-004 # Full audit trail for one task
TaskPlane currently has no equivalent. When batch-state.json gets corrupted or has a stale write, the state is gone. The 2,878-line resume.ts exists largely to reconstruct what should have been recorded. With Dolt, the history is immutable.
Crash recovery becomes a non-problem
The current resume.ts does heroic work reconciling .DONE files, STATUS.md checkboxes, lane snapshots, and batch-state.json after a crash. It has to because the source of truth is a single JSON file that can be mid-write when the process dies.
Dolt is ACID. Writes either commit or they don't. After a crash, bd ready --json returns the correct unblocked set without reconciliation. The entire category of "stale state" bugs disappears.
Branching for speculative planning
Dolt supports branches. TaskPlane could use this for dry-run wave planning:
bd dolt branch planning-run
# Simulate task completions, test wave ordering
bd dolt branch -d planning-run # Throw away if not needed
Multi-repo sync
Dolt has native push/pull to remotes. In polyrepo workspaces (TaskPlane's active development focus), task state can be shared across repos without file-based coordination:
bd dolt push # Push task state to remote
bd dolt pull # Pull task state from another machine
This is more robust than the current approach of committing batch-state.json to git branches and hoping merge conflicts don't corrupt it.
SQL as the query layer
Dolt exposes a MySQL wire protocol server. The ready-work computation that TaskPlane reimplemented in 1,548 lines of TypeScript is a SQL query in Dolt:
-- Tasks with no unresolved blockers (simplified)SELECTi.id, i.title, i.metadataFROM issues i
LEFT JOIN dependencies d ONd.issue_id=i.idLEFT JOIN issues blocker ONd.depends_on_id=blocker.idANDblocker.status!='closed'WHEREi.status='open'ANDblocker.id IS NULL;
The ready_issues materialized view in beads already does this. It's tested, fast, and handles edge cases that TaskPlane's graph code has had repeated bugs with (look at #462, #479, #508 — all dependency/state resolution bugs).
TaskPlane currently stores this partially in STATUS.md (if the worker bothers to update it — see #510) and partially in batch-state.json (which gets cleaned up after integration). With Dolt, the full lifecycle is preserved and queryable.
Integration Challenges: Go CLI ↔ TypeScript
This is the real tension. Beads is a Go binary (bd). TaskPlane is TypeScript. There are three integration paths, each with tradeoffs:
Path A: Shell out to bd CLI (simplest, proven pattern)
TaskPlane already shells out to git via execFileSync — 85 call sites across the codebase using a runGit() wrapper. The same pattern works for beads:
// Analogous to the existing runGit() pattern in git.tsfunctionrunBd(args: string[],cwd?: string): {ok: boolean;data: unknown;stderr: string}{try{conststdout=execFileSync("bd",[...args,"--json"],{encoding: "utf-8",timeout: 10_000,cwd: cwd||process.cwd(),stdio: ["pipe","pipe","pipe"],}).trim();return{ok: true,data: JSON.parse(stdout),stderr: ""};}catch(err){/* same error handling as runGit */}}// Discovery:const{data: ready}=runBd(["ready"]);// Status update:runBd(["update",taskId,"--status","in_progress"]);// Close:runBd(["close",taskId,"--reason",reason]);
Pros:
Identical to the existing git integration pattern — no new paradigm
bd --json returns structured data — no stdout parsing needed
Beads handles its own Dolt server lifecycle (auto-start, connection pooling)
Zero new dependencies in package.json
Cons:
Process spawn overhead per call (~400ms for bd ready, ~1.4s for bd create — measured on this machine)
Synchronous execFileSync blocks the event loop (but TaskPlane already does this for git)
Error handling is string-based (stderr parsing)
Mitigation: Batch operations help. Instead of calling bd update per task, beads supports bulk operations and file-based creation (bd create --file batch.md). The hot path (bd ready) is ~400ms — comparable to the git operations TaskPlane already runs.
Path B: Direct MySQL connection to Dolt (fastest reads)
Dolt runs a MySQL-compatible server (already running on the machine — port auto-assigned). TypeScript can connect via mysql2:
importmysqlfrom'mysql2/promise';constconn=awaitmysql.createConnection({host: '127.0.0.1',port: parseInt(readFileSync('.beads/dolt-server.port','utf-8')),user: 'root',database: 'wiki',// or project-specific DB name});// Direct query — microseconds, not millisecondsconst[ready]=awaitconn.execute('SELECT * FROM ready_issues ORDER BY priority');// Time-travel queryconst[snapshot]=awaitconn.execute('SELECT * FROM issues AS OF ? WHERE id = ?',[commitHash,taskId]);
Pros:
Sub-millisecond reads — no process spawn overhead
Full SQL query power (joins, aggregations, time-travel)
Async/non-blocking — doesn't hold up the event loop
Can use connection pooling for concurrent lane operations
Cons:
Adds mysql2 dependency to TaskPlane's package.json (currently only depends on yaml)
Must handle Dolt server lifecycle (what if it's not running? beads auto-starts it, but direct connections skip that)
Writes should still go through bd CLI to preserve beads' business logic (validation, audit events, auto-commit policies)
Two integration paths to maintain (SQL for reads, CLI for writes)
Mitigation: Use Path B for hot-path reads (discovery, status checks, dashboard polling) and Path A for writes. This is a common pattern — read replica via SQL, writes via the application layer.
Path C: Hybrid with JSONL interchange (most portable)
Beads exports to .beads/backup/issues.jsonl and .beads/backup/dependencies.jsonl. TaskPlane could read these directly as a lightweight integration:
// Read the JSONL backup — no bd process, no SQL connectionconstissues=readFileSync('.beads/backup/issues.jsonl','utf-8').split('\n').filter(Boolean).map(JSON.parse);
Pros:
Zero runtime dependencies — just file reads
Portable across machines (JSONL files can be committed to git)
Works even if Dolt server is down
Cons:
Stale reads — backup is periodic, not real-time
No write path — still need CLI for mutations
Loses the dependency resolution logic (must reimplement blocker checks)
Defeats the purpose of the integration
Verdict: Path C is a non-starter for the main integration but useful as a fallback/export format.
Recommended approach: Path A with selective Path B
Start with Path A (CLI shelling) because:
It mirrors TaskPlane's existing runGit() pattern — minimal paradigm shift
The performance is adequate for batch orchestration (tasks run for minutes; 400ms overhead is noise)
Beads handles all its own complexity (server lifecycle, migrations, validation)
It's the smallest diff and easiest to review/merge
Graduate to Path B (direct SQL) only for the dashboard server, which polls every 5 seconds and would benefit from sub-millisecond reads. The dashboard already runs a Node.js HTTP server (dashboard/server.cjs) — adding a mysql2 connection there is natural.
Other integration considerations
Beads as a dependency: Users would need bd installed. This is a Go binary distributed via Homebrew (brew install beads) or direct download. TaskPlane could:
Make it optional (feature flag: taskplane.backend: "beads" | "files")
Auto-detect (which bd succeeds → use beads; fails → fall back to files)
Add a taskplane doctor check for bd availability
Dolt server lifecycle: Beads auto-starts the Dolt server on first use and auto-stops it after inactivity. TaskPlane doesn't need to manage this. But in long-running batches, the server must stay alive. Beads' .beads/dolt-server.activity heartbeat file handles this — TaskPlane would touch it periodically during batch execution.
Concurrent access: Multiple lanes writing status updates simultaneously. The CLI approach serializes through beads' own locking. The SQL approach needs transaction isolation. Dolt supports SERIALIZABLE isolation, so this works but needs explicit transaction boundaries for multi-statement updates.
ID scheme: Beads uses content-addressed short IDs (e.g., wiki-fle). TaskPlane uses sequential IDs (e.g., TP-004). Beads supports --prefix to control the prefix and --id for explicit IDs, so TaskPlane's existing naming convention can be preserved:
~4,000+ lines deleted from the most bug-prone parts of the codebase (look at the issue tracker — most bugs are in dependency resolution, resume, and state persistence)
Crash recovery becomes trivial — Dolt is ACID, beads is the source of truth, not a JSON file that can get corrupted mid-write
bd ready replaces the wave planner's dependency resolution — tested, proven, fast
Agent-discovered work is a new capability that's hard to add with the file-based system
Dolt time-travel gives you historical state queries for free — "what was happening when it crashed?" becomes a one-liner instead of log archaeology
Non-breaking. Add beads as an optional backend behind a config flag. Existing file-based system stays as default. Users opt in with taskplane.backend: "beads" in settings. Once proven, flip the default.
Step 1: bd adapter module
Create extensions/taskplane/beads.ts — thin wrapper around bd CLI (modeled on git.ts). Exports: bdReady(), bdCreate(), bdUpdate(), bdClose(), bdQuery().
Step 2: Discovery adapter
In discovery.ts, add a code path: if beads backend is enabled, call bdReady() instead of scanning directories. Map beads issue fields to ParsedTask interface. PROMPT.md content is still read from the file path stored in beads metadata.
Step 3: Engine integration
In engine.ts and execution.ts, status updates write to beads in addition to (or instead of) batch-state.json. Resume reads from beads instead of reconciling stale JSON.
Step 4: Dashboard integration
In dashboard/server.cjs, optionally read task titles and status from beads (solving #485 immediately). Consider direct SQL connection here for polling performance.
The Problem
TaskPlane hand-rolled a dependency-aware task queue across ~8,000 lines of TypeScript:
discovery.tswaves.tspersistence.tsresume.tstypes.tsThis is solid engineering, but it's reimplementing primitives that beads provides natively.
The Mapping
bd createwith description/metadatadependencies.json+ PROMPT.md parsing--deps blocks:bd-123(native)computeWaves()— topological sort, 200+ linesbd ready --json— one callbatch-state.jsonfield per taskbd update --status/bd closevalidateGraph()— cycle detection, orphan checkresume.ts— 2,878 lines reconciling stale statebd ready --json— just ask again--metadata '{"size":"M","reviewLevel":2}'bd create --deps discovered-from:bd-123What Changes
Phase 1: Beads as task registry (additive, non-breaking)
TaskPlane keeps PROMPT.md files for the rich task spec (steps, checkboxes, file scope, context docs). But instead of scanning directories and parsing headers for the task lifecycle data, it reads from beads:
Phase 2: Replace persistence layer
batch-state.jsonshrinks dramatically. Runtime state (lane assignments, worktree paths, telemetry) stays in batch-state. But task status, dependency resolution, and completion tracking move to beads. The 2,878-lineresume.tssimplifies to "ask beads what's still open."Phase 3: Agent-driven task creation
Workers that discover new work during execution can:
This slots into the dependency graph automatically. Next
bd readyreflects it. No file scaffolding needed.What Stays the Same
What Gets Deleted
dependencies.jsonfiles and the cache layercomputeWaves()graph construction (1,548 lines ofwaves.tssignificantly simplified)resume.ts— crash recovery becomes "query beads for current state"persistence.ts— task state serialization moves to beadsWhy Dolt
Beads uses Dolt, a MySQL-compatible database with git-style version control. This is unusually well-suited for TaskPlane's needs:
Time-travel debugging
Every beads write auto-commits to Dolt history. When a batch crashes at 2:30 AM and you're debugging at 9 AM, you can query the exact state at crash time:
Or via the CLI:
TaskPlane currently has no equivalent. When
batch-state.jsongets corrupted or has a stale write, the state is gone. The 2,878-lineresume.tsexists largely to reconstruct what should have been recorded. With Dolt, the history is immutable.Crash recovery becomes a non-problem
The current
resume.tsdoes heroic work reconciling.DONEfiles, STATUS.md checkboxes, lane snapshots, and batch-state.json after a crash. It has to because the source of truth is a single JSON file that can be mid-write when the process dies.Dolt is ACID. Writes either commit or they don't. After a crash,
bd ready --jsonreturns the correct unblocked set without reconciliation. The entire category of "stale state" bugs disappears.Branching for speculative planning
Dolt supports branches. TaskPlane could use this for dry-run wave planning:
Multi-repo sync
Dolt has native push/pull to remotes. In polyrepo workspaces (TaskPlane's active development focus), task state can be shared across repos without file-based coordination:
This is more robust than the current approach of committing
batch-state.jsonto git branches and hoping merge conflicts don't corrupt it.SQL as the query layer
Dolt exposes a MySQL wire protocol server. The ready-work computation that TaskPlane reimplemented in 1,548 lines of TypeScript is a SQL query in Dolt:
The
ready_issuesmaterialized view in beads already does this. It's tested, fast, and handles edge cases that TaskPlane's graph code has had repeated bugs with (look at #462, #479, #508 — all dependency/state resolution bugs).Built-in audit trail
Every mutation is recorded:
TaskPlane currently stores this partially in STATUS.md (if the worker bothers to update it — see #510) and partially in batch-state.json (which gets cleaned up after integration). With Dolt, the full lifecycle is preserved and queryable.
Integration Challenges: Go CLI ↔ TypeScript
This is the real tension. Beads is a Go binary (
bd). TaskPlane is TypeScript. There are three integration paths, each with tradeoffs:Path A: Shell out to
bdCLI (simplest, proven pattern)TaskPlane already shells out to
gitviaexecFileSync— 85 call sites across the codebase using arunGit()wrapper. The same pattern works for beads:Pros:
gitintegration pattern — no new paradigmbd --jsonreturns structured data — no stdout parsing neededpackage.jsonCons:
bd ready, ~1.4s forbd create— measured on this machine)execFileSyncblocks the event loop (but TaskPlane already does this for git)Mitigation: Batch operations help. Instead of calling
bd updateper task, beads supports bulk operations and file-based creation (bd create --file batch.md). The hot path (bd ready) is ~400ms — comparable to the git operations TaskPlane already runs.Path B: Direct MySQL connection to Dolt (fastest reads)
Dolt runs a MySQL-compatible server (already running on the machine — port auto-assigned). TypeScript can connect via
mysql2:Pros:
Cons:
mysql2dependency to TaskPlane'spackage.json(currently only depends onyaml)bdCLI to preserve beads' business logic (validation, audit events, auto-commit policies)Mitigation: Use Path B for hot-path reads (discovery, status checks, dashboard polling) and Path A for writes. This is a common pattern — read replica via SQL, writes via the application layer.
Path C: Hybrid with JSONL interchange (most portable)
Beads exports to
.beads/backup/issues.jsonland.beads/backup/dependencies.jsonl. TaskPlane could read these directly as a lightweight integration:Pros:
Cons:
Verdict: Path C is a non-starter for the main integration but useful as a fallback/export format.
Recommended approach: Path A with selective Path B
Start with Path A (CLI shelling) because:
runGit()pattern — minimal paradigm shiftGraduate to Path B (direct SQL) only for the dashboard server, which polls every 5 seconds and would benefit from sub-millisecond reads. The dashboard already runs a Node.js HTTP server (
dashboard/server.cjs) — adding a mysql2 connection there is natural.Other integration considerations
Beads as a dependency: Users would need
bdinstalled. This is a Go binary distributed via Homebrew (brew install beads) or direct download. TaskPlane could:taskplane.backend: "beads" | "files")which bdsucceeds → use beads; fails → fall back to files)taskplane doctorcheck for bd availabilityDolt server lifecycle: Beads auto-starts the Dolt server on first use and auto-stops it after inactivity. TaskPlane doesn't need to manage this. But in long-running batches, the server must stay alive. Beads'
.beads/dolt-server.activityheartbeat file handles this — TaskPlane would touch it periodically during batch execution.Concurrent access: Multiple lanes writing status updates simultaneously. The CLI approach serializes through beads' own locking. The SQL approach needs transaction isolation. Dolt supports
SERIALIZABLEisolation, so this works but needs explicit transaction boundaries for multi-statement updates.ID scheme: Beads uses content-addressed short IDs (e.g.,
wiki-fle). TaskPlane uses sequential IDs (e.g.,TP-004). Beads supports--prefixto control the prefix and--idfor explicit IDs, so TaskPlane's existing naming convention can be preserved:bd create "Port RPI commands" --prefix TP --id TP-004 --jsonWhy It's Worth It
bd readyreplaces the wave planner's dependency resolution — tested, proven, fastMigration Path
Non-breaking. Add beads as an optional backend behind a config flag. Existing file-based system stays as default. Users opt in with
taskplane.backend: "beads"in settings. Once proven, flip the default.Step 1:
bdadapter moduleCreate
extensions/taskplane/beads.ts— thin wrapper aroundbdCLI (modeled ongit.ts). Exports:bdReady(),bdCreate(),bdUpdate(),bdClose(),bdQuery().Step 2: Discovery adapter
In
discovery.ts, add a code path: if beads backend is enabled, callbdReady()instead of scanning directories. Map beads issue fields toParsedTaskinterface. PROMPT.md content is still read from the file path stored in beads metadata.Step 3: Engine integration
In
engine.tsandexecution.ts, status updates write to beads in addition to (or instead of) batch-state.json. Resume reads from beads instead of reconciling stale JSON.Step 4: Dashboard integration
In
dashboard/server.cjs, optionally read task titles and status from beads (solving #485 immediately). Consider direct SQL connection here for polling performance.