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
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.
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.