Guard: a safety net for AI agents
Before an AI agent changes your database, it checks that it can be restored and saves a Mark.
Coding agents run migrations and SQL, often with nobody watching. Guard makes sure you can get back to the moment before one goes wrong: the agent checks that the database can be restored, then saves a Mark (a named restore point) before any risky change.
Guard is the MCP tools, a hook (rowsafe guard) and a snippet for your agent instructions. For CI, use the GitHub Action.
The workflow
Before anything destructive:
- Check:
safety_check(orrowsafe status app). If the database can't be restored right now, the agent tells you why and waits for your OK. - Save a Mark:
create_restore_point(orrowsafe mark app before-drop-orders). It waits until the Mark is in your bucket and tells you its name. - Proceed.
- If something breaks, stop. The agent doesn't repair data or restore anything unless you say so. You decide whether to rewind, or ask the agent to start it (
request_change, as you). Not sure when the damage happened?find_momentfinds it (Find the moment).
Destructive means: migrations and schema changes, DROP, TRUNCATE, ALTER TABLE ... DROP/RENAME/TYPE, DELETE or UPDATE without a narrow WHERE, backfills, pg_restore --clean, dropdb, and resetting or re-seeding a database.
What agents can do
An agent acts as the person who connected it, with that person's dashboard rights:
- Reads everything the dashboard shows.
- Does what can't hurt production (with write access): backups, Proof, checks, safe copies, acknowledging alerts.
- Changes production when you ask: fixes, settings, restarts, restores, rewinds, upgrades, a Rowsafe Cloud server, and more. It runs right away, with the same checks and Marks as a click. The agent tells you what it will do, and the price, first.
- Or Rowsafe refuses, nothing changes, and the agent tells you why (for example: the person is a member, or there's no backup yet).
Details: How AI agents act. Every tool: MCP reference.
Need a database?
If the app has no database, the agent can create one in Rowsafe Cloud, protected from the first minute. It tells you the size and price and gets your OK first. It acts as you: right away for an owner or admin, refused for a member.
- Look first:
list_cloud_servers(one may already do) andcloud_catalog(regions, sizes, prices). It picks a nearby region and the cheapest size that fits (the smallest is about $10 a month at most). - Create:
create_cloud_serverwith a name, region, size and the app's addresses. PostgreSQL unless the app needs anotherengine. The first server needs a card, once, and only an owner pays: an owner's agent gives you a checkout link; an admin's is refused (Paying for Rowsafe Cloud). - Wait about 5 to 10 minutes.
- Let the app in (
cloud_firewall) if needed. It asks first if that cuts off an address. - Create the app's database and login (
create_app_database): a new, empty database and a user that owns it, withsslmode=require. - Store the connection string in the app's environment (like
DATABASE_URLin a git-ignored.env). Never in code, a commit or the chat.
Rowsafe never sees the password. Who makes the app's database depends on how the agent connects:
- Plugins and apps signed in with Rowsafe: you, in the dashboard (Databases & users). Your browser shows the connection string once; paste it into
.envor give it to the agent. - A local
rowsafe mcp --allow-writes(PostgreSQL): the agent, right away. The password is made on your machine. Needs Rowsafe 0.9.4 or newer on the server.
Later, when you ask, the agent can resize (resize_cloud_server, a Mark first), clone to a new server (clone_to_new_server) or delete it (delete_cloud_server). Rowsafe refuses to delete a server whose backup passphrase was never saved.
Creating needs Act for you (or --allow-writes). Without it, the agent asks you to create the server in the dashboard.
Or from your terminal
rowsafe cloud create shop-db --wait # the cheapest size free now; only this computer may connect
rowsafe env --on shop-db # the app's database and login, DATABASE_URL written to .envrowsafe cloud create shows the price and asks first. rowsafe env makes the password on your computer, writes DATABASE_URL to .env and git-ignores it. Let your app's server in with rowsafe cloud allow shop-db ADDRESS. See Rowsafe Cloud from the terminal.
Limits
- Engines and versions: see Databases. OpenSearch, Qdrant and Meilisearch restore to a snapshot, not to any second.
create_app_databaseworks only on Rowsafe Cloud servers with PostgreSQL 15 or newer, MySQL, MariaDB or ClickHouse. Elsewhere, use the dashboard orrowsafe env(PostgreSQL).- On MySQL, MariaDB and ClickHouse you always create it in the dashboard. On Valkey, OpenSearch, Qdrant and Meilisearch, make the app's login or API key in the dashboard.
- The new user owns only its database, but can still connect to the server's other databases with what's granted to everyone.
Preview migrations and test on safe copies
Two Guard tools that never touch production, so agents get them without write access:
preview_migrationruns a migration on a copy and answers safe, careful, dangerous or fails. On PostgreSQL it also catches row-level security the migration loses, such as a new table your API can read without it. See Preview a migration.create_safe_copygives the agent a masked copy to test against. See Safe copies.
Set it up
Name the project's database once, in the project directory. This writes .rowsafe.json, which the CLI, the MCP server and the hook read:
rowsafe init appThen pick one:
claude plugin marketplace add rowsafe/rowsafe
claude plugin install rowsafe@rowsafeIt bundles Rowsafe's MCP server (sign in with /mcp), three skills (rowsafe-safety for the workflow, rowsafe-health for health checks, which work on any database, and rowsafe-new-database for a new database), and the guard hook. When you sign in, choose whether it may save Marks and Act for you.
The hook, previews and safe copies need the rowsafe CLI, logged in: curl -fsSL https://rowsafe.sh/cli | sh on macOS and Linux, a download on Windows (CLI). Without it, the MCP tools still work.
Snippet for AGENTS.md or CLAUDE.md
Copy this into your agent instructions and set the database name:
## Database safety (Rowsafe)
This project's database is protected by Rowsafe (database: `app`). Before any destructive
or risky database operation (migrations, schema changes, DROP/TRUNCATE, DELETE or UPDATE
without a narrow WHERE, backfills, resets, restoring a dump):
1. Run `safety_check` for `app` (or `rowsafe status app`). If it is not protected,
tell me the reasons and wait for my OK.
2. Set a Mark (a named restore point) with a descriptive name: `create_restore_point`
(or `rowsafe mark app before-<what>`), wait until it is confirmed, and tell
me its name.
3. Then proceed.
If something goes wrong: stop, don't try to repair the data, and don't restore, rewind or
restart anything unless I ask. Tell me what happened and the Mark's name; I'll decide whether
to Rewind. If I ask you to start it, use `request_change`; if Rowsafe refuses, tell me why.The guard hook
rowsafe guard runs before every shell command the agent runs. When one looks destructive, it finds the project's database (ROWSAFE_DATABASE or .rowsafe.json), saves the Mark agent-<UTC time>, waits up to 90 seconds for it, then lets the command run and reports the Mark's name. It never approves a command.
If anything fails (no database set, not logged in, Rowsafe unreachable, Mark not confirmed), it only warns.
Without the dashboard: on a server where Rowsafe runs on its own, the hook saves the Mark through the agent, the same way, when Claude Code runs there as a user the agent answers (root, or the agent's user or group), and from your computer too when the project's database is that server saved over SSH (rowsafe add shop ssh://root@db1, then rowsafe use shop). Elsewhere, when the project's database is one the CLI reaches directly (saved with rowsafe use, or DATABASE_URL in the environment or .env), there is no Mark to save: the hook checks the command's SQL against the database (read-only, nothing runs) and tells the agent plainly that there is no restore point, so it asks you first and suggests a backup (pg_dump). See Pulse on any database.
To block instead, set ROWSAFE_REQUIRE_PROTECTION=1 or:
{ "database": "app", "require_protection": true }The hook then blocks the command when the database isn't protected or the Mark isn't confirmed, and tells the agent to report it to you.
What it matches
| Examples | |
|---|---|
| Migration tools | prisma migrate deploy/reset/dev, prisma db push; rails/rake db:migrate, db:rollback, db:drop, db:reset, db:schema:load; alembic upgrade/downgrade; manage.py migrate/flush; knex migrate:latest, seed:run; sequelize db:migrate; typeorm migration:run, schema:sync; drizzle-kit push/migrate; goose up/down/reset; migrate ... up/down/drop; dbmate up; atlas schema apply; sqlx migrate run; diesel migration run; flyway migrate/clean; liquibase update/rollback; mix ecto.migrate; artisan migrate; supabase db reset/push; npm run db:migrate and similar script names |
| SQL sent to a SQL client | psql, pgcli, mysql, mariadb, mycli, clickhouse-client (or clickhouse client), sqlite3 and litecli with DROP ..., TRUNCATE, RENAME TABLE, ALTER TABLE ... DROP/RENAME/TYPE/MODIFY/CHANGE, ClickHouse mutations (ALTER TABLE ... DELETE/UPDATE, DROP PARTITION), DELETE or UPDATE without WHERE, in -c/-e/--query, a heredoc, a pipe, < file, or a small file passed with -f, --queries-file, sqlite3 -init or .read |
| MongoDB shell | mongosh or mongo with drop(), dropDatabase(), deleteMany({}), updateMany({}, ...), dropIndex, renameCollection, in --eval or a small script file |
| Redis and Valkey | redis-cli or valkey-cli with FLUSHALL, FLUSHDB or SWAPDB, DEL/UNLINK fed by xargs (--scan --pattern ... | xargs redis-cli del), or those commands in < file or an --eval script (CLI 0.9.2 and later) |
| Database tools | dropdb, pg_restore --clean, mysqladmin drop, mongorestore --drop, myloader --overwrite-tables, sqlite3 ... .restore |
It ignores status, dry-run and help commands (migrate status, alembic upgrade --sql, manage.py migrate --plan, --help), generating migrations, and commands that only mention a tool (git commit -m "...", grep, echo, cat).
Test a command:
rowsafe guard --check 'npx prisma migrate deploy' # destructive: ... (exit 0)
rowsafe guard --check 'npx prisma migrate status' # not destructive (exit 1)A safety net, not a sandbox
A destructive statement hidden in app code, an ORM script or an unusual wrapper won't match. The skill and MCP tools cover what the hook can't see.
Keep agents apart
Give each agent its own API key (Settings → API keys), so its actions show up separately in the audit log. Agents using it act as you.
Keys made from a terminal (rowsafe api-keys create ci-agent) are made by another key, not a person, so agents using them can't make changes. A read-only key (--read-only) can run safety_check but can't save Marks or change anything.