A sandbox is an isolated, disposable test database holding your organisation’s schema, and only the rows you put in it. It never contains production data. Closing a sandbox destroys its state, and idle sandboxes are evicted after 30 minutes.
A sandbox proves your code works against this schema. It proves nothing about whether a migration survives your real rows: that is a rehearsal, which executes the migration against a disposable clone of the real database and reports what Postgres did. See Change approval.
Two backends
| In-memory | Postgres | |
|---|---|---|
| Implementation | Pure-Go simulator, in process memory | Real Postgres process with a data directory |
| Boot | Sub-millisecond | A few seconds |
| SQL coverage | The subset the executor models | Postgres SQL |
| State ops (checkpoint, restore, fork, diff, seed, snapshot, inspect) | Native | Not supported |
| Determinism (fixed clock) | Honoured | Ignored; now() returns wall-time |
Pick in-memory for fast-iteration work: agent loops, “try N then
rewind,” schema exploration, anywhere checkpoint and fork matter. Pick
Postgres when SQL fidelity matters: custom queries, triggers, plpgsql,
multi-table joins, complex check constraints. On the Postgres backend,
vector(N) columns become BYTEA, and HNSW indexes and hypertable
conversion are stripped from the DDL before it applies, so vector distance
operators fail at query time there.
The state model (in-memory only)
The simulator uses copy-on-write row maps: a capture records pointers to
every table’s current rows and marks them shared, and a later write clones
only the affected map. Checkpoint, restore, and fork therefore cost
O(tables) rather than O(rows): row data is never copied at capture time.
Four primitives:
- Checkpoint — save the current state.
- Restore — rewind to a saved state; intermediate writes are dropped.
- Fork — clone the current state into N independent sandboxes, each with its own checkpoint history.
- Diff — added, removed, and modified row counts per table between two checkpoints.
Over HTTP, a state-op call against a Postgres-backed sandbox returns
400 with <feature> is in-memory only; boot a Sim sandbox to use it.
Supported SQL
The simulator’s SQL grammar is supplied by pg_query_go, the Postgres
parser packaged for Go. Every Postgres syntactic construct parses; what
runs is what the in-memory executor models: single-table SELECT with
WHERE / ORDER BY / LIMIT / OFFSET, INSERT (with RETURNING),
UPDATE and DELETE (without RETURNING), ON CONFLICT, JSON arrow
extraction, vector distance operators, = ANY($N), and the window-total
form COUNT(*) OVER () AS alias. Multi-table joins, CTEs, GROUP BY, and
every other window form are outside the surface.
Sandbox SQL coverage is the complete list.
Surfaces
The console’s Sandbox page is the interactive surface: boot, seed, capture,
compare, restore, fork, all in the UI. The HTTP control plane at
/api/sandbox/* is the programmatic surface for scripts and agent loops;
both share one runtime. tide sandbox
runs the same in-memory runtime locally. Booting requires the developer
or admin role, and each user holds up to 100 sandboxes at a time.
What the sandbox does not model
- Production rows — every sandbox starts empty.
- Row-level tenant isolation: the in-memory backend does not enforce
partition bypolicies, so it proves nothing about them. - Migration safety against real data — rehearse the plan instead.
Related
- Use the sandbox — walkthrough of the console flow.
- Sandbox SQL coverage — the SQL the in-memory backend accepts.
- Sandbox HTTP API — programmatic surface.
- Change approval — rehearsal, the real-data proof.