Agents & MCP
Fork-per-attempt is the workload offshoot is built around: give each agent attempt its own real database — a copy-on-write fork, not a mock, not a re-seed — let it write with a stock SQLite client, then keep the attempt that passed and let the rest expire. This page covers the MCP server that puts fork/checkpoint/rollback in the agent's own hands, the daemon workflow that captures the agent's writes live, and where the safety rails actually are.
One command to wire up Claude Code
claude mcp add offshoot -- offshoot -store ./.offshoot mcp
offshoot mcp speaks the Model Context Protocol on stdio — no daemon
required for the baseline. The agent gets seven tools, each described so
the model knows when to reach for it: fork before a risky migration,
checkpoint when tests pass, roll back when they don't, promote the attempt
that worked. See it work end to end in a real captured session:
the MCP walkthrough.
The seven tools
Verified against internal/mcp/tools.go; branch defaults to "main"
wherever it's optional.
| Tool | Arguments | What it does |
|---|---|---|
offshoot_list |
(none) | List every database and branch, with head txid, checkpoints, and protected flags — the orient-yourself call |
offshoot_checkout |
database, branch? |
Materialize a branch to a local SQLite file and return the path to open |
offshoot_checkpoint |
database, name, branch? |
Name the current state so it can be rolled back to or forked from |
offshoot_fork |
database, new_branch, branch?, at?, ttl? |
Create an isolated branch from head, or from a checkpoint via at |
offshoot_rollback |
database, to, branch? |
Return a branch to a named checkpoint, discarding everything since |
offshoot_promote |
database, source, target, force? |
Repoint target at source's head — ship the winning attempt |
offshoot_destroy |
database, branch, force? |
Permanently discard a branch and its checkout |
Forks expire by default
Agent-initiated forks carry a TTL: offshoot_fork applies offshoot mcp -default-ttl (default 24h) to any call that omits its own ttl, so
a branch an agent forks and forgets becomes reap-eligible a day later
instead of leaking forever. An explicit ttl:"<duration>" always wins,
ttl:"none" always yields no TTL, and -default-ttl 0/-default-ttl none disables the default entirely. The fork tool's response echoes the
applied TTL and computed expiry, so both land in the agent's transcript.
A TTL alone reaps nothing. Reaping is the janitor's job (offshoot serve), and offshoot mcp runs no daemon of its own — a daemonless setup
only sweeps expired branches when offshoot gc is run by hand.
The safety posture
Destructive tools honor the same protected-branch rules as the CLI: an
unforced offshoot_promote onto main or offshoot_destroy of main
is refused, and the refusal comes back to the agent as the tool
result — described to the model as "confirmation you need, not a bug" —
rather than a transport error. The agent forks and experiments freely;
touching the branch of record requires the explicit force step. Note
what this is and isn't: force is an argument the agent can pass, so
the protected flag is a deliberate speed bump and an auditable decision
point, not a permission boundary — if promotion must be a human/harness
decision, keep it in the harness (the pattern in the
Claude Agent SDK recipe, where the harness
checkpoints or rolls back based on your own success signal).
Also true regardless of tools: the daemon's unix socket is mode 0600,
and one leased, epoch-fenced writer per branch means concurrent attempts
get isolated forks, never interleaved writes
(architecture).
Live capture: the daemon workflow
Bare offshoot mcp runs every tool at rest — checkpoints quiesce the
checkout and write a full snapshot. For an agent writing continuously, put
a daemon session under it and the same tools ride live capture instead
(incremental flushes, no quiesce, writer never paused):
offshoot -store ./.offshoot init # once
offshoot serve -socket /tmp/o.sock & # holds leases, captures continuously
sleep 1
offshoot session open app -socket /tmp/o.sock # the harness-opened session
claude mcp add offshoot -- offshoot -store ./.offshoot mcp -socket /tmp/o.sock
# ... agent works ...
offshoot session close app -socket /tmp/o.sock
The rules, exactly as shipped:
- No MCP tool ever opens a session itself. That's a harness's job —
the SDKs,
offshoot session open, or your own loop. A bare tool call has no guaranteed teardown, and an MCP-opened session would leak its lease exactly the way TTLs exist to prevent (the design reasoning). - With a session open on the branch:
offshoot_checkpointflushes live through the daemon andoffshoot_checkoutreturns the session's live checkout path. offshoot_forkrides the daemon whenever one is reachable — session or not (an open source session is flushed first, so unflushed writes land in the child; the fork uses the daemon's-snapshot-everyshare floor and counts in its metrics).- With no reachable daemon, every tool works at rest — exactly as if the daemon didn't exist.
offshoot_rollback,offshoot_promote(itstarget), andoffshoot_destroyrefuse — even withforce, which has no effect on this particular refusal — whenever the daemon has any session open on the affected branch, because all three repoint or delete a ref out from under a session the daemon still owns. Close the session first and retry.offshoot_promote'ssourceis the one exception: an open session there doesn't block, but what gets promoted is the source's last-flushed head, not its unflushed writes.
Full flag-level detail: offshoot mcp in the
reference.
Beyond MCP
- Test/eval harnesses — the paved road for fork-per-test: pytest fixtures and a vitest/jest testkit, seed-once-fork-many, TTL hygiene, CI. The eval-harness tutorial.
- Python / TypeScript SDKs — thin clients over the daemon's lifecycle API for harnesses that open and close sessions themselves. See the Python SDK and TypeScript SDK.
- LangGraph —
ThreadForksmaps each thread to its own branch, so rewinding a conversation also rewinds the database (LangGraph adapter). - Other frameworks — the OpenAI Agents SDK, LlamaIndex, and CrewAI each get a short honest recipe: framework recipes, OpenAI Agents SDK.