An agent that tests every database migration PR on a masked copy of real production data, fixes what breaks, and stops for a human before the one step you can't undo. Built on TrueForge for the TrueFoundry × Polaris "Agents That Act" hackathon.
CI said green. Prod lost 11 refunds.
Also as a standalone file: WRITEUP.md · WRITEUP.pdf (2 pages, with dashboard states and edge cases).
The problem. Migrations are tested in CI or on a UAT database, but those don't have production's years of messy history, and migrations break on real data. In our demo app, ledgerly (a UPI payments ledger), a four-line PR "Enforce ledger integrity" passes CI. Deployed the usual way, statement by statement, it deletes 18 duplicate charges, silently cascades into 11 customers' refund records (₹19,554), then crashes on statement 3 and leaves prod half-migrated. No test could see it: the bug is in the data, not the code.
What the agent reaches. Every PR runs our GitHub workflow, which starts a TrueForge session. The agent reads the PR, pulls a masked, full copy of prod into a Daytona sandbox, writes and runs its own rehearsal (applies the SQL, counts violations, replays the app's queries, checks invariants), writes a fix, rehearses again, posts the report on the PR and asks to apply.
Where it stops. Applying to prod is gated. TrueForge pauses at apply_migration for a human. Our MCP server pgwarden, the only holder of the prod credential, then:
- re-runs the exact rehearsed SQL in a transaction and commits only if the real effects equal what the human approved;
- refuses DROP / TRUNCATE / RENAME even with approval;
- refuses when prod drifted, the rehearsal is stale, or the change deletes rows of protected tables (payments, refunds).
Merging is blocked until the check is green.
How TrueForge is used.
- The agent runtime and model loop.
- The Code Mode sandbox on Daytona, holding zero credentials (proven on each run).
- MCP connections with vaulted tokens.
- The human approval gate, with durable pause and resume.
- A git-pinned skill.
- The sessions API, which drives CI status and our trace dashboard.
Real vs mocked. Everything runs for real: TrueForge, Claude Sonnet 5, Daytona, GitHub PRs, Actions, statuses and branch protection, Postgres and the applies. Only "prod" is simulated: a local Postgres with generated, deterministic data.
Known limits. Fix quality varies between runs, and the approval card, independent review and protected tables catch the unsafe ones. Effects measure rows and schema, not changed values. Full-table copies suit small and medium databases.
| Autonomous | Needs a human | Never, even with approval | |
|---|---|---|---|
| What | Reading the PR and schema, masked export, sandbox rehearsals, fixes, the PR report | apply_migration |
DROP TABLE, TRUNCATE, DROP/RENAME COLUMN, GRANT/REVOKE, DELETE without WHERE, deleting rows of protected tables, applying SQL that wasn't rehearsed byte for byte |
Threat model and every refusal code: SAFETY.md. Edge cases compared with Atlas, Squawk and Bytebase: docs/EDGE_CASES.md.
TrueForge, where the agent runs. A CI-triggered session of migration-rehearsal-ledgerly, paused at the one irreversible step. The run can't continue until a human clicks Allow or Deny:

After Allow, the agent reports the verified outcome in the same TrueForge session: migration 0007 committed, row counts unchanged, and earlier rehearsal reports in the PR comments treated as untrusted and flagged as a possible injection:

The app. ledgerly, a UPI payments ledger with a live ops dashboard (30,055 payments, 427 refunds, 18 double charges).

One real run, end to end (session 01m3ep0n5yeh…, triggered by a push to ledgerly PR #1). TrueForge ran the agent in this order:
- sandbox proof;
- masked export of 30,055 payments;
- v1 of the PR's SQL fails (37 orphan payments, 64 without a merchant, 18 payments + 11 refunds would be deleted);
- v2 fix passes with zero row changes;
- approval, and pgwarden commits 0007 with effects matching exactly.
This is the trace at the top of this page. The attempts row reads attempt 1 fail · payments -18, refunds -11 → attempt 2 pass · all 0.
Projects. Every onboarded repo with live health, plus onboarding from the UI.

| TrueForge (the harness) | Our code (the domain) |
|---|---|
| Runs the agent: model calls, tool routing, 60-step budget | skills/migration-rehearsal/: the procedure and rules, plus sandbox helpers |
Code Mode sandbox on Daytona; call_tool bridge so sandbox code reaches MCP tools without credentials |
pgwarden/: the MCP server that holds the prod credential, masks PII and gates the apply |
| MCP server registry and token vault (GitHub, pgwarden) | agent/*.agent.json: agent spec with require_approval_for_tools: ["apply_migration"] |
| Human approval: the pause, the card, and resuming with the decision | ci/rehearse-pr.mjs: starts sessions from CI and mirrors them as PR statuses and a job summary |
| Sessions and events API | viewer/: read-only trace dashboard, projects, onboarding |
- Onboard any repo with
npm run onboard -- --project <name> --repo <owner/app>, or the Projects page in the dashboard. The steps:- check the prod database;
- PII scan that suggests masking;
- register the project's own pgwarden and agent in TrueForge (the approval gate is enforced, and it fails closed);
- open a PR adding the workflow to the app repo;
- make the check required on the default branch (admins included);
- check the self-hosted runner.
- Per-project isolation: each project has its own pgwarden (port, database, masking rules, audit log, protected tables) and its own agent.
- Review assist on every approval: a plain-English reading of each statement, risk flags and a recommendation (allow / review / deny), computed from the SQL and effects rather than written by the agent.
- Dashboard (
http://localhost:8795): runs, traces, approvals and projects, with a live trace linked from every PR check.
You need:
- Node 22.14+
- the GitHub CLI (
gh, logged in) and SSH access to GitHub - Docker, or any Postgres 16
- a Daytona API key with
write:sandboxes,write:snapshotsanddelete:snapshots - one model key: Anthropic, OpenAI, or TrueFoundry AI Gateway
git clone https://github.com/<you>/truefoundry-hackathon && cd truefoundry-hackathon
cp .env.example .env # keys, PGWARDEN_TOKEN, PGWARDEN_MASK_KEY, SKILL_REPO_URL + SKILL_REF (a commit SHA)
npm install
docker run -d --name dr-pg -e POSTGRES_PASSWORD=dev -p 55432:5432 postgres:16
# terminal 1: TrueForge on :8790 (allowed to reach MCP servers on localhost)
npm run trueforge
# terminal 2: base setup: model, Daytona sandbox, GitHub MCP, skill (npm run seed + npm run pgwarden first for the bundled demo DB)
npm run setup && npm run doctorConnect the demo app, ledgerly:
git clone https://github.com/<you>/ledgerly ../ledgerly && (cd ../ledgerly && cp .env.example .env && npm install && npm run reset && npm start &)
echo 'LEDGERLY_DATABASE_URL=postgres://postgres:dev@localhost:55432/ledgerly' >> .env
npm run onboard -- --project ledgerly --repo <you>/ledgerly --db-env LEDGERLY_DATABASE_URL
npm run pgwarden:project -- ledgerly # its own pgwarden on :8788 (masking + protected tables)
npm run onboard -- --project ledgerly # re-run: every step green; merge the workflow PR it opened
node viewer/server.mjs # dashboard on :8795 (Projects page shows health)Register a self-hosted runner for the app repo with the migration-rehearsal label (onboarding prints the exact commands). Then open a PR that changes migrations/. The check turns yellow, a session appears in TrueForge, and the approval card waits for you.
Reset ledgerly's prod between runs with cd ../ledgerly && npm run reset. To see the damage without the harness: npm run deploy:unsafe -- migrations/0007_….sql in ledgerly.
-
pgwarden: 61 tests covering golden effects, masking, schema round-trip, drift, deploy lock, backups, every refusal code, and protected tables. Viewer: 23 tests (event mapping, dashboard, review assist). Run them with
npm --prefix pgwarden testandnode --test viewer/test/*.test.mjs. -
Live runs: the first demo app went 13/13 end to end (docs/demo/reliability.md). On ledgerly, every CI-triggered run reached the approval card, and the fix quality varied:
ledgerly run Agent's fix Card Outcome before protected tables deleted duplicates, orphans and no-merchant payments payments −119, refunds −11 approved in testing, applied exactly as shown before protected tables NOT VALIDconstraints, but the duplicate delete still cascadedpayments −18, refunds −11 approved in testing with protected tables NOT VALIDFK and CHECK, partial unique index; flagged old bot comments as possible injectionall 0 ✅ applied, PR mergeable -
npm run guardrails: 7 live attacks on pgwarden (lying about effects, swapped SQL, fake or failed rehearsals, DROP TABLE, prod drift), all refused, with prod proven unchanged.
| Path | What it is |
|---|---|
pgwarden/ |
TypeScript MCP server (Streamable HTTP, bearer auth): masked export, statement policy, effects engine, gated apply, drift, lock, backups, protected tables, analyze_migration |
skills/migration-rehearsal/ |
Git-backed TrueForge skill: procedure, references, sandbox helpers (pg_boot.py, effects.py, sources.py) |
agent/ |
Agent specs: migration-rehearsal and a naive control agent with no safety instructions |
projects/ |
One config per onboarded app repo |
ci/ |
rehearse-pr.mjs (CI entry) and the workflow template installed by onboarding |
scripts/ |
onboard, pgwarden:project, setup, doctor, reset, guardrails, e2e, trueforge |
viewer/ |
Dashboard: traces, runs, approvals with review assist, projects and onboarding |
seed/, fixtures/ |
The first demo app's data (shopkart) and the golden effects format |
docs/ |
Contracts, edge cases, plan, demo material |
Outbound URL blocked for host "localhost": start TrueForge withnpm run trueforge.- MCP name rejected: TrueForge accepts lowercase names with hyphens; onboarding uses
pgwarden-<project>. - Workflow PR fails with 404: writing
.github/workflowsneeds theworkflowscope through the API; onboarding pushes over SSH instead, so make sure[email protected]works. - PR check stuck at "expected": the app repo's runner is offline or busy. Jobs run one at a time per runner.
REHEARSAL_STALEafter Allow: approvals must happen within 30 minutes of the rehearsal. Push again to re-rehearse.- Numbers don't match: reset the app's prod (
npm run resetin ledgerly).
MIT. Built with Claude Code (Anthropic): the team designed the idea, architecture, safety model and contracts, and Claude Code helped write the code and docs under that direction.
