Shared build backlog for humans and Claude Code sessions
Collapsed four writers – the operators and each operator's Claude Code sessions – onto one Postgres-backed backlog with atomic claims, provenance per write, and three equivalent surfaces (dashboard, CLI, remote MCP server), so a card can go from "we should build this" to "an agent is on it" with one clipboard paste.
My contribution
Defined the collaboration need, designed the shared backlog model and the human-plus-agent concurrency primitives, and implemented the three write surfaces: the Dev tab in the fleet console, the CLI, and the remote MCP server. Shipped and iterated through daily use.
Situation
Six months into the Agent Fleet Dashboard, cost and health were solved. What are we building next, and who is building it was not. Across a two-founder shop running thirteen AI products, build intent lived in four places that disagreed with each other within a day:
- Chat scrollback between the operators.
- Per-product markdown files (
TODO.md,last_changes.md), updated at commit time at best. - The running state of each operator's Claude Code sessions – effectively the "working set" of what each agent was doing, invisible to the other operator.
- The dashboard's per-agent task list (
fleet_agent_tasks), tag + text only, no claim model, no epic structure.
Within one working day the four sources would drift. The operators would start work on the same idea, or an agent would silently finish something the human then re-did. The friction didn't scale – but neither did reaching for a project-management tool, because the users are two humans and n AI sessions, and no PM tool models that cleanly.
Task
Collapse the four writers onto one backlog, in the same cockpit that already carries cost and health, without the coordination overhead that normally forces a team of four onto a tool like Linear.
The decision we owned: whether to add a backlog surface inside the existing dashboard – one Postgres table, one validation library, three write paths (UI, CLI, remote MCP) – or to accept the friction and let the fleet keep using chat + markdown + agent working-sets as its coordination mechanism.
Action
Architecture
One table, three surfaces, one shared library. fleet_backlog is a single Postgres table with Supabase realtime enabled. DevView.jsx renders four groupings (Projects / Priority / Status / Epic) across three densities. scripts/backlog.js is a 360-line CLI exposing the same operations from any terminal. /api/mcp is a remote MCP server (mcp-handler 2.x, Streamable HTTP, 13 tools) exposing those same operations to Claude Code sessions in any repo and to the Claude.ai chat window. All three paths share one validation / grouping / momentum / stale / epic-progress library (src/lib/backlog.js) so the rules never drift between surfaces.
Alternatives considered and rejected:
- Linear / Height / Shortcut – model a team of humans, not a team of humans-plus-agents. No concept of claim provenance, no way for an agent in a repo to pick up the next item without a human-mediated step.
- Keep using chat + per-product markdown – the status quo, already failing within a working day.
- A thin Google Sheet with a Claude Code hook – tempting, but would have put the claim-concurrency problem in app code instead of in SQL, where it belongs.
- Build the MCP connector first, UI second – the better order in hindsight; see "What we would do differently" below.
Implementation
- Atomic claim RPC (
fleet_backlog_claim(), migration 0015) assigns an item to a named claimer in one transaction, so two agents cannot pick up the same item. The RPC returns the row only if the claim succeeded; refused claims are a hint, not an error. - Holder guard (migration 0020) – only the current holder can release, change status, or mark done. Closes the common Claude-session failure mode of a parallel session closing someone else's work.
- Provenance per write –
claimed_viarecordsdashboard|mcp|cli,claimed_sessioncarries the Claude session name. Combined withfleet_backlog_events, every state transition is attributable to a surface and a session. - Overlap warnings –
touches[]on each in-flight item + acheck_overlapMCP tool. Two agents touching the same files get warned at claim time rather than at merge time. - Two-level epic hierarchy enforced in SQL (
fleet_backlog_hierarchy_guard, migration 0016; delete-sets-null on parent). No infinite trees, no sub-sub-stories – the shape of the backlog itself resists over-structuring. - Push-to-Claude-Code (PR #155, 11 unit tests on the formatter) – tapping the Claude icon on a card claims the item, formats a self-contained prompt including the short id, kind, title, project, body, and the
mcp__fleet-backlog__mark_done(id=…)call, and copies it to the clipboard. The operator pastes it into a fresh Claude Code session in whatever repo the work touches; the session closes the loop by callingmark_donethrough the MCP connector when done. Deliberately clipboard-only – a true deep-link into a running session is flagged "do not retry without first verifying the URL surface" after an earlier attempt failed on an unverifiedclaude.ai/codepattern. - Live-session indicator (migrations 0021 + PR #171/#173) – the Claude logo on a card is still if the holding session has reported alive in the last five minutes, bouncing if it has reported in the last 30 seconds. The signal makes the backlog honest about whether a parked item is still being worked on or has been orphaned by a crashed session.
- Dev cost project, kept out of the fleet rollup. PR #121 added a dedicated
'dev-products'→LANGFUSE_*_DEVkeypair inapi/langfuse.jsand hardenedresolveKeypairso a known-but-unconfigured project reportsunconfiguredinstead of silently falling back to the default pair. PR #122 then removed the Dev row from the main fleet cost view – Fran's decision: the fleet dashboard should show real product/client API cost only, with the dev-session project reviewable separately.
Verification
- The backlog itself is the test. On the day the Dev tab shipped, the operators and both their Claude Code sessions put a day of work through it. Within one session the backlog became the source of truth for what was in flight. If any of the three surfaces had disagreed on a claim's state, the Supabase realtime subscription would have surfaced it inside a second.
- 11 unit tests on the push-to-Claude formatter cover prompt shape, id injection, and the
mark_donepointer. - Operator token auth with
crypto.timingSafeEqualand a 60-writes-per-minute budget on the MCP endpoint, so a leaked token has a bounded blast radius. - Zero accidental double-claims observed since ship – the atomic RPC was the first guarantee we checked for, and it holds.
Result
Shipped 2026-09-24 in PR #130 (21 files / 2,191 insertions / 338 deletions, including migration 0015 at 173 lines) and iterated through PR #173 within the first fortnight. The dashboard moved from reporting on the fleet to running the fleet in that one session. Every product change since has been claimed from, released to, or closed through the Dev tab.
Second-order effects:
- Four-writer collaboration without a project-management tool. The operators and their Claude Code sessions coordinate through one Postgres table. The row is the source of truth; the dashboard, CLI, and MCP connector are three UIs on top of it.
- Backlog items carry their own kick-off prompt. Push-to-Claude turns a card into a self-contained Claude Code brief, which collapses the gap between "we should build this" and "an agent is working on it" to one clipboard paste.
- The claim graph is itself the activity feed.
fleet_backlog_eventsrecords every claim / release / status change with provenance. The audit question ("who touched this item and from which surface?") is answered by the same table that holds the work. - "Does it have a backlog item?" became a shipping condition. Within two weeks of the Dev tab shipping, the operators had internalised the rule that work without a
fleet_backlogrow doesn't ship.
What we would do differently: build the MCP connector before the UI, not after. The Dev tab shipped first, which meant the second week's work was retrofitting hooks into tool handlers that would have been easier to design the other way around. The lib-shared-across-surfaces pattern (src/lib/backlog.js) now works precisely because that mistake forced the extraction.
Skills evidenced
- MCP server design for shared-state collaboration – one Postgres table, three write surfaces, one shared validation library; atomic-claim RPC + holder guard; provenance recorded per write; token-gated with constant-time compare and a per-minute write budget.
- Human-plus-agent concurrency primitives in SQL –
claimed_via+claimed_sessioncolumns, five-minute liveness pings, overlap warnings ontouches[], holder-only state transitions enforced by trigger. - Supabase realtime for live UI state –
postgres_changessubscription powering optimistic writes without a separate push channel. - Clipboard-first integration under uncertainty – Push-to-Claude is a clipboard MVP because the deeper deep-link surface was not yet verified. Shipping the honest version beat waiting for the fancier version.
- Separation of concerns in cost telemetry – the Dev / Claude Code Langfuse project exists and is wired, but is deliberately kept out of the fleet cost rollup. Correct boundary; the kind of design call that only shows up when the operators actually use the thing.
Portable lessons
- If humans and agents share state, the row has to carry provenance.
claimed_via+claimed_session+ a holder guard are the three cheapest primitives that keep four writers (two humans, two Claude Code sessions) from stepping on each other without reaching for a workflow engine. - Build the shared library before the second surface, not between the second and the third. The CLI and MCP connector only work because
src/lib/backlog.jsis the single source for grouping, momentum, stale, and progress – a lesson learned from shipping the UI first and dragging rules out of components by hand. - Clipboard-first beats deep-link-eventually. The real cost of agent-in-the-loop UIs is the integration surface. A copy-to-clipboard button that formats a self-contained brief is one order of magnitude cheaper to ship than a URL scheme, and gets you 80% of the value on day one.
Want to discuss the decisions behind it?
Over fifteen years of consulting and growth, and hands-on AI delivery from discovery to daily operations. I can share my CV and talk through how I connect business outcomes with systems that run.
Available for a chat. In person in Stavanger, or remotely worldwide.