⑂ Branch SQLite like git.
Copy-on-write forks of stock SQLite files, on your local disk or S3 bucket — a real database per agent attempt, eval run, or test; promote the winner, let the rest expire.
$ offshoot init
$ offshoot create app
$ sqlite3 "$(offshoot checkout app)" "CREATE TABLE users (name); INSERT INTO users VALUES ('ada');"
$ offshoot checkpoint app v1
$ offshoot fork app attempt-1 --ttl 2h # copy-on-write branch, near-zero added storage
$ sqlite3 "$(offshoot checkout app@attempt-1)" "DELETE FROM users;" # destructive experiment
$ offshoot rollback app@attempt-1 --to fork # it went badly — undo the world
$ offshoot promote app@attempt-1 --onto main --force # …or it went well — ship it
why branch a database?
- AI agents — fork the database per attempt — the agent writes to a real
SQLite file through plain
sqlite3or MCP tools, you promote the attempt that worked, and TTL'd branches expire instead of leaking. - Eval harnesses — seed once, fork per run — a shared fork of a 100 MB database adds 377 bytes to the store, flat from 1 to 100 forks, so isolated attempts cost near-nothing.
- Tests — real isolation without fixture rebuilds: fork a fresh branch per test via the pytest fixtures or the vitest/jest testkit, with TTL-backstopped cleanup.
- Experiments — run the destructive migration on a fork —
rollback --to forkif it goes badly,promote --onto mainif it doesn't.
New here? Read the introduction →
how it works
- O(1) forks — a fork shares the parent's durable objects through a base
pointer and writes new ones only as it diverges — N forks of a G-byte database
cost near-zero added bytes. Branches carry TTLs and reap themselves;
compactmakes a fork self-contained on demand. - Bounded replay — a branch is a lineage of immutable snapshots plus incremental segments. A read applies one snapshot and at most fifteen segments (default cadence), checksum-verified member by member — a torn chain is a loud error, never a short read.
- kill -9 durable — one writer per branch, enforced by leases and epoch fencing; every ref update is a compare-and-swap. Validated by a torture harness that kill -9's a stock sqlite3 writer mid-write on roughly half of every round and requires the replica to converge byte-identical, every round.
- Stock everything — a checkout is a real SQLite file — open it with plain
sqlite3, no forked engine, no special VFS on the read path. Snapshots and refs live in a local directory or any S3-compatible bucket with conditional writes.
for agents & harnesses
daemon · live capture, writer never pauses
$ offshoot serve &
$ P=$(offshoot session open app)
$ sqlite3 "$P" "INSERT INTO t VALUES ('x');"
$ offshoot session flush app v1
$ offshoot session status # durable txid
mcp · the agent branches on its own
$ claude mcp add offshoot -- offshoot mcp
# seven tools: list, checkout, checkpoint,
# fork, rollback, promote, destroy —
# agent forks default to a 24h TTL
The daemon captures every committed transaction while your agent keeps
writing — a checkpoint never quiesces the database, -flush-every 30s bounds
what a crash can lose, and -http adds /metrics,
/healthz, /rpc, and an SSE /events stream.
Python and TypeScript SDKs drive the same API: pytest fixtures and a
vitest/jest testkit for seed-once, fork-per-test isolation.
install
# homebrew
$ brew tap sricola/offshoot https://github.com/sricola/offshoot
$ brew install offshoot
# docker — images publish to ghcr on every tagged release; the store lives in /data
$ docker run --rm -v offshoot-data:/data ghcr.io/sricola/offshoot:latest init
# from source (go 1.25+, cgo)
$ go install github.com/sricola/offshoot/cmd/offshoot@latest
# same binary, any store
$ offshoot -store ./.offshoot init # local directory (default)
$ offshoot -store s3://my-bucket/offshoot init # S3, MinIO
Prebuilt binaries (offshoot_vX_os_arch.tar.gz +
.sha256) ship with every tagged
release. Linux and macOS;
on Windows, use WSL2. At attach time offshoot probes the store and refuses to run
if conditional writes aren't enforced — fail-closed, never silently degraded.
essential commands
| create <db> [--from f] | new database (branch main), or import a file |
| checkout <db>[@branch] | materialize a working copy; prints its path |
| checkpoint <db>[@br] <name> | snapshot the checkout as a named checkpoint |
| fork <db>[@br] <new> [--at cp] [--ttl d] | branch from head or a checkpoint; copy-on-write |
| rollback <db>[@br] --to <cp> | repoint a branch at a checkpoint |
| promote <db>@<src> --onto <target> | repoint target at src's head (--force for protected) |
| destroy <db>[@br] [--force] | delete a branch; children survive their parent |
| status | all branches: state, storage class, TTL |