spine

What spine does

Spine is the ledger that agent sessions file into. A session is a process that appears, edits a shared tree and vanishes, and git never records that it existed. Spine records who is working, on what, since when, holding which files, and what happened. It records; it does not act.

How the pieces connect

spine: how the pieces connect Claude Code sessions, windowed, terminal or headless, call mcp.mjs over stdio. mcp.mjs resolves ids through identity.mjs, which reads the presence files and IDE locks Claude Code writes about itself, and dispatches to db.mjs, which reads and writes ~/.substrate/tasks.db. The command-line tools reap.mjs, snapshot.mjs, gate-route.mjs and board.mjs all go through db.mjs. Built on the ledger: the substrate server imports db.mjs and order.mjs, the Rust engine reads the store through a proxy and writes only task status, and the vscode/ extension runs board.mjs for its board tab, opens an existing session tab by transcript id, and opens new tabs under a profile through the spine-qwen/ launcher. SPINE · HOW THE PIECES CONNECT it records what happened; it runs no command and writes no repository AGENT SESSIONS Claude Code in VS Codea tab in an editor window Claude Code in a terminalthe claude CLI Headless runclaude -p from an orchestrator WRITTEN BY CLAUDE CODE ABOUT ITSELF ~/.claudesessions/<pid>.jsontranscript id, name, cwdide/<port>.lockwindow and workspaceprojects/…/<id>.jsonlthe conversation writes tools/call over stdio, one server process per session SPINE · THE LEDGER mcp.mjsthe MCP server, JSON-RPC over stdiodispatches each tool to db.mjs identity.mjsresolves every id at openand records how each was known at open_session reads presence and IDE locks db.mjsthe schema and every queryone active session per owner and agent order.mjsdependency order, pureready, blocked, cyclic COMMAND LINE · EACH GOES THROUGH db.mjs reap.mjsretires the dead snapshot.mjsverified copy gate-route.mjsred gate → task board.mjswho holds what reads presence and transcripts ~/.substrate/tasks.db plans tasks file_claims sessions overlook reads and writes ~/.spine/tasks.db the same file, moved by migrate-store.mjs once nothing holds the old path proposed OTHER READERS · OUTSIDE THIS REPOSITORY EXTENSIONS · SHIPPED FROM THIS REPOSITORY substrate serverimports db.mjs and order.mjsas the sibling checkout Rust enginesrc/spine.rs, a read-mostly proxyfails closed when the store is absent reads, and writes task status only spine-qwen/launches claude, qwen or codex,each profile in its own config dir vscode/the Spine extension: board taband new tabs under a profile node board.mjs --json new tab opens a session tab: an existing one by transcript id, or a new one under a profile calls, or writes the storereadsopens a session tab built, and shipped from here proposed, not built yet

Solid lines are calls, or writes to the store. Dashed lines are reads. Boxes with a gold edge ship from this repository. The gold line is where spine leads back to a session. The Spine extension opens an existing session's tab by its transcript id, whether that session runs in a window, in a terminal or headless under claude -p. It also opens a new tab under a launch profile through spine-qwen/.

A gold edge means a box ships from this repository, and the two at the bottom right are its extensions rather than the ledger: the VS Code extension and the launcher. A dashed gold edge is proposed and not built yet. The store is drawn twice for that reason. It is ~/.substrate/tasks.db today, named for the repository spine used to live in, and its home is ~/.spine/tasks.db. migrate-store.mjs moves it and refuses while any process still holds the old path, because SQLite names the write-ahead log after the path a connection was opened with, and two paths in use at once is two logs over one database.

Every write this repository makes goes through db.mjs. Two things outside it touch the store: substrate's server, which imports db.mjs itself, and the Rust engine, whose proxy reads the store and writes one column, a task's status.

A session, from open to receipt

A spine session, from open to receipt Five stages on a rail: open_session resolves ids and records the pid, and the session is active; create_plan and create_task lay out a tree of tasks gated by dependsOn, and the plan reads ready; claim_files and set_status record the work, and the first activation reads the plan active; set_plan_status moves it complete (refused while tasks are open), reviewed with its review text, then archived; close_session writes a receipt of counts and duration, and the session reads completed. If the process dies without closing, reap.mjs sees the pid gone and marks the session reaped, never completed or abandoned. A SESSION, FROM OPEN TO RECEIPT closing a session transitions none of its tasks 1 · OPEN open_sessionids resolved while the chain is alivepid and pid_start recorded active 2 · PLAN create_plan, create_tasktree addresses 1, 1.2, 1.2.3dependsOn gates activation 3 · WORK claim_files, set_statusa claim declares, it does not lockabandoned needs a reason 4 · FINISH set_plan_statusready and active follow the taskscomplete → reviewed → archived 5 · CLOSE close_sessionreceipt: counts and durationanything still active stays active completed the process dies without closing reap.mjs sees the pid goneonly ESRCH counts as dead reaped One active session per owner and agent, enforced by a unique index, not by convention. A reap records that a holder died. It never writes completed or abandoned, because it saw no outcome.

Closing a session writes a receipt and changes nothing else. A task still active when its session closes stays active, because the ledger records what happened and decides nothing on anyone's behalf.

How a session row knows its transcript and its window

How a session row knows its transcript and its window At open_session, identity.mjs walks up from the MCP server (evidence: self) to its parent, the claude CLI (ppid-walk), and reads that pid's presence file for the transcript id, fleet name and cwd (presence). It keeps walking up to the VS Code main process (ppid-walk) and matches its pid, then the cwd, against the IDE lock files for the window and workspace (lockfile+cwd). A headless claude -p run has no VS Code above it, so the chain stops at presence and the board lists it by transcript id. HOW A SESSION ROW KNOWS ITS TRANSCRIPT AND ITS WINDOW resolved once, at open_session, while every process in the chain is alive UP THE PROCESS TREE MCP servermcp.mjs, the spine processthat opened the sessionself claude CLIthe parent of the MCP serverppid-walk VS Code main processup past Code Helper (Plugin),matched on a path suffixppid-walk ppid walks up presence file~/.claude/sessions/<pid>.jsontranscript id, fleet name, cwdpresence IDE lock~/.claude/ide/<port>.lockwindow pid and workspacelockfile+cwd by the CLI pid by window pid, then cwd Headless run, claude -pno VS Code above it, so the chainstops at presence; the board stilllists it by transcript id The badge on each box is the evidence class stored beside the id it yields. A file Claude Code wrote about itself and a process-tree inference that will not exist tomorrow are different kinds of fact. They are never stored as one, and an IDE lock’s auth token is never read into anything spine returns.

At open, identity.mjs walks the process tree from the MCP server up to the VS Code main process. It matches that pid against the IDE lock files, preferring the lock whose workspace contains the session's cwd. The presence file Claude Code wrote about itself gives the transcript id. A headless run has no window above it, so its row carries a transcript and no window. The board still lists it, by joining the presence files on that transcript id.

The five tables

tableone row isthe rule that shapes it
plansnamed work with a cwd and a project slugcreated → ready → active → complete → reviewed → archived, never deleted; review is written at reviewed; dormancy is computed, never stored
tasksa tree address inside a planresult is terminal, notes always mutable, abandon_reason visible to siblings
file_claimsa task saying it is editing a patha declaration, not a lock: it makes a collision visible and stops no write
sessionsa process that opened the ledgerevery id carries how it was known: self, presence, ppid-walk, lockfile
overlooka posture reading against a sessionrefuses an unknown session rather than writing an orphan

The surfaces spine ships

  • Commands, skills and hooks under .claude/, linked into ~/.claude so every window has them. None of the hooks reads or writes the store.
    • Sweeps: reap.mjs retires sessions whose process is gone, and snapshot.mjs copies the store and refuses to claim a copy it cannot reopen.
  • The board: board.mjs joins live sessions to the tasks they hold, with the evidence for each attribution.
    • The extension: vscode/ renders the board as its own tab and opens new session tabs under a chosen profile, without touching the default Claude settings.
  • Launch profiles: spine-qwen/ runs Claude Code or Codex against a local model in a config dir that belongs to that profile alone. The claude profile is a passthrough.
    • The site: built from this tree, with the tool reference generated from a live tools/list.
  • The Rust engine: evaluation and posture tooling that reads the same store through a proxy which fails closed when the store is absent.

    Regenerating the figures

    The SVGs are generated by docs/diagrams.mjs, never edited by hand. Run the first command after changing it, and the second to check that the committed files still match. The site build inlines the generator's output directly, so a stale committed file cannot reach the website.

    node docs/diagrams.mjs
    node docs/diagrams.mjs --check