Giving the agent an undo button for your data
An AI agent that builds your app doesn't just write code — it runs
migrations, seeds tables, and issues bulk UPDATEs. Code has a great undo:
it's called git, and every generation is a commit. Data doesn't. An agent that
runs a migration dropping the wrong column, or a bulk reprice with an off-by-one
WHERE, has done something git can't touch. For a fast, autonomous agent, data
is the unprotected surface.
We already gave the platform's build agent a commit-tied code+data undo (time-travel for code and data). This post is about the active half: handing the agent a snapshot/restore it wields as a tool, so it can protect itself during a run — snapshot before a risky operation, roll back if the operation breaks the data — and so you can ask, in plain language, to undo what happened. The trick that makes it affordable everywhere is the same one that lets these apps sleep for free.
Snapshots are markers, not copies
The naive way to make a destructive operation safe is "copy the rows first." On a normal database that's real storage and real time — absurd to do reflexively before every risky step, across thousands of small apps.
Adorable runs a self-hosted Neon: storage (the pageserver)
is separated from compute (a stateless Postgres), and history is a timeline —
the database reconstructed from versioned pages + WAL up to an LSN. Branching a
timeline is copy-on-write and O(1), independent of database size. So a snapshot
isn't a copy of your data; it's a two-field marker — (timeline_id, lsn) — that
costs effectively nothing until you actually restore.
When a snapshot is a row, not a copy, the default can flip: snapshot before every risky operation, reflexively. That's exactly what the agent now does.
The agent brackets its own risky operations
The producer — the agent that generates and iterates your app — is the thing that runs the scary operations. So it's the thing that snapshots. Two paths:
- Automatic, at the clearest boundary.
apply_migrationstakes a safety snapshot before it runs. A migration that drops a column or backfills wrong can't be undone by editing code — only by a data restore — so the restore point is captured without the agent having to remember. - Deliberate, by judgment. A
snapshot_datatool lets the agent snapshot before anything else it judges risky — a bulk rewrite, a large seed, a data-munging step. Cheap enough that "when in doubt, snapshot" is the right habit.
If a later step breaks the data, the agent calls restore_data and rolls back —
recovering from damage it can't fix by editing code. This is the capability the
commit-time checkpoint can't provide: it catches damage done mid-run, before any
commit. The agent can attempt the bold migration precisely because it can take
it back.
A guardrail keeps this safe: snapshot and restore are not symmetric. A snapshot is cheap and non-destructive, so the agent takes it freely. A restore reverts data and restarts the compute, so the agent only uses it to undo its own in-run damage — reverting your historical data is a decision you make, not one the agent makes for you (more below).
Restore in place: the connection string never moves
For this to be usable mid-run — and mid-conversation — restore has to be stable.
If it handed back a new database endpoint, every restore would mean
reconfiguring the running app. So restore is in place: fork the timeline at
the snapshot's LSN, then repoint the app's compute at the fork — same database
name, same DATABASE_URL. The app doesn't reconnect to a new place; the data
under it rolls back. It reuses the platform's existing fork-at-LSN +
proxy-owned repoint path — the same primitive deploy and rollback ride on.
Undo you can ask for
The agent protecting itself ships first — that's live today. The same snapshots are meant to be yours, too, and that's the direction from here: because restore is stable and project-wide, it surfaces where you already are — the conversation. Say "that import wrecked everything, roll it back," and the chat agent proposes the restore ("I can roll your data back to the 2:14pm snapshot — want me to?") and you confirm; it never repoints your database on its own. A dashboard panel lists the restore points and is where that confirmation happens. The disruptive direction always stays behind a human yes.
And it sleeps for free
Why this belongs in every app, not just the busy ones: an Adorable app's database is scale-to-zero. Idle → the compute scales to zero replicas and costs nothing; the next request wakes it in seconds (see scale-to-zero Postgres). The snapshots are markers, so an app with 200 of them and no traffic is still ~free. A wedding RSVP tool, a seasonal storefront, an internal tool used twice a week — each can carry full snapshot/undo and contribute ~$0 while idle. It's the property that makes AI-app fleets viable at all (app.build, QwikBuild, Atoms run thousands of scale-to-zero databases for exactly this reason); here we spend that headroom on a safety net rather than just on idle savings.
One restore point for the whole application
Restore rolls back the whole project's database — and that's the right grain, not a limitation. On Adorable a project is one application, and its apps are microservices that deliberately share a database and reference each other's data. Rolling one service's data back without the others would break those cross-service references, so a consistent undo has to cover the whole application at once — the same reason you'd never point-in-time-restore half of a system. The finer grain — independent undo for each end user of a multi-tenant app you've built — is a separate shape (a branch per tenant) and is deferred.
Why it matters
Data-undo turns destructive automation from a liability into a capability. The agent can run the scary migration, because the floor is a one-tool-call restore to a state that actually existed; and you can undo a bad day's data by asking. Git gave code that property a long time ago. The database just needed the same primitive — a branch at an LSN — and an agent that knows to reach for it before it does something it might regret.