Skip to main content
Substrate splits its state into two deliberate tiers: board definitions live as JSON files in your repo — reviewable in pull requests, versioned with your code — while tasks and their history live in a local SQLite database that changes constantly and stays gitignored. Understanding that split explains almost every design decision you’ll encounter.

Substrate-as-code

Each board is a single JSON file at .substrate/boards/<id>.json. It holds the board’s groups, field schema, policies, and optional team binding. Substrate reads each file fresh on every call, so an edit — whether through a text editor, a git merge, or an MCP tool — is live on the next request. There is no migration step, no cascade, and no rebuild.
.substrate/boards/delivery.json
A board file that doesn’t match the expected shape fails the entire substrate load with substrate_corrupt — the error names the file and how to fix it. Run substrate validate to check without starting a server.

Runtime state in SQLite

Tasks, comments, and the append-only event log live in .substrate/data.sqlite — a libsql database running in WAL mode, safe for concurrent agents. This data is deliberately not substrate-as-code: tasks change constantly, are per-checkout, and would turn every board file into a merge conflict if stored there.

What gets committed and what doesn’t

substrate init writes the correct gitignore block for you, and the command is idempotent — run it again and nothing breaks.
Because the database is gitignored, a git worktree or fresh clone checks out your boards but not the database. Substrate warns on startup when it detects this condition. See Worktrees and Clones for how to handle this.

Two surfaces

Substrate exposes exactly two surfaces, with a clear separation of intent.

MCP Server

substrate mcp — the read/write surface. Agent runtimes spawn it over stdio. Provides 32 tools covering reads, task and comment writes, and workflow edits.

Web UI

substrate serve — a localhost-only, read-only inspector. Boards render as a live kanban that auto-refreshes. The UI never writes — no drag-and-drop.
substrate approve is a write channel deliberately reserved for people. That asymmetry is what makes a human_only gate real: an agent’s MCP tools structurally cannot set those fields.