Undo Database Changes Made by an AI Agent, Step by Step
To undo database changes an AI agent made, you need a record of exactly what that agent changed, with the old values, kept separately from everyone else's writes. Postgres itself only offers transactions (gone once committed) and point-in-time recovery (which rewinds every user's work too). StitchDB records every row and schema change an agent key makes per session, so you can preview and revert one agent session in Studio or with one API call, and leave all other writes alone. This guide walks through that, plus the other undo tools for changes that did not come from an agent.
#Why "just restore a backup" is the wrong tool
Say an agent ran a cleanup at 14:02 that set status = 'archived' on 3,000 orders it should not have touched. By the time you notice at 14:40, customers have placed new orders and support has edited others. Your options in plain Postgres:
| Option | Problem |
|---|---|
| Point-in-time recovery to 14:01 | Throws away 38 minutes of everyone's writes |
| Restore a copy and diff by hand | Slow, error-prone, and you must know which columns changed |
| Ask the agent to "undo it" | It guesses the previous values; it does not have them |
What you want is the inverse of exactly the agent's statements, applied only where nobody has changed the rows since. That needs recording at write time.
#How agent sessions are recorded
Every write made with an agent key belongs to a session:
- With MCP, each MCP session is an undo session.
- With the SQL API, the
X-Stitch-Sessionheader you send names it. - Without either, writes are grouped into one automatic session per hour. Direct connections also get one session per hour.
For each session StitchDB records:
- Rows: inserted, updated and deleted rows, with old and new values. Tables need a primary key so rows can be matched back.
- Schema: tables, indexes, views, functions, policies, constraints and columns the agent created or changed, together with the SQL that reverses them.
- Drops: dropped tables go to a trash schema instead of being deleted, and dropped columns keep their values, so undo can bring them back.
Recording is on only while the project has agent keys that can write, and recorded changes count toward your database size until the undo window ends.
#Name sessions after the task
The single most useful habit: give each unit of work its own session name, such as the ticket, pull request or chat it came from.
curl https://stitchdb.com/platform/v1/agent/sql \
-H "Authorization: Bearer sdb_agt_..." \
-H "X-Stitch-Session: fix-signup-bug" \
-H "Content-Type: application/json" \
-d '{"query": "update profiles set plan = 'pro' where id = 42"}'In the JavaScript SDK, pass session to sql():
import { createAgentClient } from 'stitchdb-js/management'
const agent = createAgentClient({ agentKey: process.env.STITCHDB_AGENT_KEY })
const { data, error } = await agent.sql("update orders set status = 'shipped' where id = 42", { session: 'ticket-881' })Then "undo ticket-881" means exactly that task, not "everything the bot did this hour".
#Undo a session in Studio
- Open AI agents → Sessions & undo and pick the session. The timeline shows each statement, its verdict and the rows it changed.
- Choose Undo session…. A dry run runs first and shows how many changes can be reversed and which conflict.
- Review the conflicts. A conflict is a row someone else changed after the agent. It is skipped, so a customer's later edit is never overwritten by the agent's "before" value.
- Confirm. Row changes are reversed and schema changes are undone with their recorded inverse SQL.
Use the Query log next to it to see what led there: every statement with its result (ran, blocked, needs approval, error), rows changed and duration.
#Undo with the API
The same flow, for scripts and incident runbooks:
POST /platform/v1/projects/<ref>/agent-sessions/<session>/revert {"dry_run": true}
POST /platform/v1/projects/<ref>/agent-sessions/<session>/revert {"dry_run": false}
# the agent itself, for its own sessions:
POST /platform/v1/agent/sessions/<session>/revert {"dry_run": false}All paths are under https://stitchdb.com/platform/v1. A dry run needs the developer role; the real undo needs an admin. The agent can also revert its own sessions, either with the undo_session MCP tool or with the SDK:
await agent.revert('ticket-881', { dry_run: false }) // undo everything the session changedrevert defaults to a dry run unless you pass dry_run: false, which is the right default for something an AI decides to call. A good instruction for an agent is: "If a change was wrong, call undo_session with a dry run, show me the result, and wait."
#What undo cannot reverse
Undo reverses changes inside the database. It cannot reach:
- E-mails that were sent, push notifications that went out.
- HTTP calls made from SQL with
pg_net, or webhooks already delivered. - Queue messages that a worker already consumed.
- Anything an Edge Function or external system did in response to the change.
This is a reason to keep side effects out of agent-driven writes: let triggers enqueue work, review it, then process it. The firewall also refuses HTTP from SQL for agent keys, which removes one class of side effect.
#Limits on how far back you can go
| Plan | Undo window | Rows recorded per session |
|---|---|---|
| Free | 24 hours | 10,000 |
| Pro | 7 days | 100,000 |
| Scale | 14 days | 1,000,000 |
| Enterprise | 30 days | 10,000,000 |
Values from the plans API as of November 2026 (agent limits, pricing). When a session records more rows than the plan keeps, recording stops for that session, it is marked capped, and only the changes recorded so far can be undone. That is one more reason to keep a low max rows per statement on agent keys: a statement over the limit is rolled back and sent for approval instead of running.
#When the change did not come from an agent
Agent sessions only cover agent keys. For other mistakes, StitchDB has three more tools, each with a different reach.
#Change history for one table
Turn on Track changes for a table in Database › Change history, and every insert, update and delete is recorded with who made it, the row before and the new values, whether it came from your app, Studio, an Edge Function or SQL. You can undo a single change:
select id, op, old, new, changed_at, changed_by
from _stitch_history.changes
where tbl = 'public.orders'::regclass and pk = '{"id": 42}'
order by id desc;
select _stitch_history.revert(1234); -- 'reverted', 'gone' or 'exists'An undo is itself recorded, so it can be undone too. Only changes after tracking was turned on are kept, truncate is not recorded, and history is kept for 1, 7, 30 or 90 days by plan (change history).
#The recycle bin for deleted rows
With the recycle bin on for a table, every deleted row is kept for 1, 7, 14 or 30 days by plan, with who deleted it:
select _stitch_bin.restore(array[41, 42]);#Restore one table, or point in time
For a bad bulk update on a table without tracking, or a dropped table, Restore one table loads the table as it was at a moment in your point-in-time window into the _stitch_restore schema, next to the live one. You can then copy rows back yourself, put old rows back for rows matching a condition, or replace all rows. Full point-in-time recovery rewinds the whole database to a second within the last 1, 7, 14 or 30 days by plan, and loses everything after that moment. See backups and the point-in-time recovery guide.
#Which tool to reach for
| What happened | Use |
|---|---|
| An agent key made bad changes | Agent session undo |
| One row was changed wrongly by anyone | Change history (if tracking was on) |
| Rows were deleted | Recycle bin, or change history |
| A table was dropped or mass-updated, no tracking | Restore one table |
| Everything is broken | Point-in-time recovery |
#Prevent the next one
Undo is the last layer, not the first. Combine it with:
- Read-only keys for agents that only need to look.
- Approvals for writes without
WHERE, big changes, drops and policy changes, so the dangerous statement waits for a person. - Denied tables for data the agent has no business touching.
Letting AI agents write to your database safely covers those settings.
#FAQ
#Does undo overwrite changes my users made after the agent?
No. A row someone else changed after the agent is reported as a conflict and skipped. The dry run lists them first.
#Can I undo a migration an agent applied?
Yes, if it was made with an agent key in the undo window. Schema changes are recorded with the SQL that reverses them, and dropped tables and columns are kept so they can come back.
#Are changes over a direct connection recorded?
Yes. Changes from an agent key's direct connection are recorded for undo, grouped into one session per hour, although the firewall and query log do not apply to that path.
#Does recording slow down writes?
Each recorded change is an extra write, and recorded changes use database space until they expire. Recording is on only while the project has agent keys that can write.
Give your agents a way back: create a free project and read sessions and undo.