-
-
The holds: the charge and the email never ran. Each has a four-digit code for the iMessage approval.
-
The proof: expected and actual checksums for Postgres, GitHub and Notion, kept human edits included.
-
The plan: every field, the agent's value and what it goes back to. A teammate's later price edit stays.
-
How Mulligan was built: the dashboard's page on Sentry, Photon, Paper and the rest.
-
Approvals over iMessage via Photon, and the one-line summary texted when a rewind finishes.
-
The session: what the agent changed in each system, and what's waiting on a person.
-
The timeline: reads are dots, undoable changes are bars, held actions are flags. Each links to its Sentry trace.
-
GIF
The rewind: a dry-run plan, the live rewind newest first, then every system's checksum matching.
Inspiration
AI agents don't just write text anymore. They write to production databases, close tickets, edit docs, send email and move money. In July 2025, Replit's AI coding agent deleted a company's production database during a code freeze. When an agent goes wrong, the cleanup is manual and often impossible, and a backup only restores one system to a point in time, wiping out everything people did since.
We wanted the thing golfers get after a bad shot: a mulligan. Backups restore a database. Mulligan undoes an agent.
What it does
Mulligan is the layer agents act through. Agents connect to it as an MCP server, and Mulligan holds the real credentials, so there's no way around it.
- Records every action with its before-state, a SHA-256 hash of every field it changed, and a recipe for undoing it.
- Rewinds a whole session in one click across Postgres, GitHub and Notion, newest action first, with a dry-run plan before anything changes.
- Keeps a teammate's edits. If a person changed a field after the agent did, the rewind keeps their change, flags it, and restores everything around it.
- Holds what can't be undone. Emails, payments and unknown tools wait for approval. The approver gets an iMessage: "Reply YES 4821 to approve."
- Gives money an undo window. An approved Stripe charge is authorized now and captured only after the window closes. A rewind cancels the authorization, or refunds it once captured.
- Proves the restore. After every rewind, each system is checksummed and compared with how it looked before the agent, kept human edits included.
It works with any MCP agent. We protect Claude Code, Devin and a Fetch.ai Guardian in ASI:One chat: give the Guardian a task, it hands it to a worker agent that acts only through Mulligan, reports what changed and what's held (as approve/reject cards), and undoes the whole session when you say "rewind".
In the demo, a cleanup agent goes rogue: it deletes 30 customers (cascading into 90 orders, 120 order items and 10 support tickets), zeroes prices, drops a pricing table, closes GitHub issues, rewrites the Notion pricing doc, and tries to email customers and charge a card. One click puts it all back, a teammate's price fix survives, and every checksum matches.
How we built it
- Gateway: Python 3.12, FastAPI and the official MCP SDK over Streamable HTTP, with per-agent bearer tokens so every action is tied to an agent and a session. The action log lives in TimescaleDB.
- Typed tools, never raw SQL. Agents get
pg_delete_rows,pg_update_rows,pg_drop_table,github_close_issue,notion_update_pageand friends. Postgres writes take a table and a structured filter, so Mulligan knows exactly which rows a call will touch before it runs.pg_queryruns as a SELECT-only role in a read-only transaction. - Soft drop.
pg_drop_tablemoves the table into a hiddenmulligan_trashschema with its indexes and sequences. Undo moves it back with its data, primary key, indexes and every foreign key pointing at it. Cascading deletes come back parents first. - Per-field conflict detection. Every action stores a hash of each field it changed, down to
postgres:products/42/price, a GitHub issue's state or labels, a file's blob sha, or a single Notion block. At rewind time, a field whose hash no longer matches was changed by someone else, so Mulligan keeps it. No three-way merges, no silent overwrites. - Policy table and rules gate. Every built-in tool has a fixed label (
reversible,reversible_noisy,compensable,irreversible), and the gateway refuses to start if one is missing. Mulligan can also front other MCP servers; their unknown tools go through a deterministic rules gate that fails closed. - Verified rewinds. Each adapter computes a canonical checksum of its system (rows, issues and file blobs, page properties and block trees), and the dashboard shows expected against actual for each one.
- Approvals run in a TypeScript service on Photon over iMessage, which also texts a one-line summary when a rewind finishes. Payments use Stripe manual capture.
- Dashboard: React 19, Vite and TypeScript, designed in Paper first: the course, the hand-drawn system objects and the dithered textures, rendered with Paper Shaders.
- Sentry across all three services: a trace span for every agent action and every rewind, MCP server monitoring, gateway Logs, dashboard Session Replay (text masked) and error monitoring. A real startup crash and missing rewind traces we spotted in Sentry both became fixes.
- Devin helped build Mulligan. It wrote a randomized undo fuzz test: seeded sessions of inserts, updates, cascading deletes, primary-key changes and table drops, plus a teammate edit that must be kept, each required to rewind to the exact pre-session checksum. The fuzz test found three verification bugs, and Devin fixed them (PR #17). Devin also connects to Mulligan as a protected agent, so even Devin can be undone.
- Fetch.ai: a Guardian and a worker built with uAgents and the Agent Chat Protocol, registered on Agentverse and usable in ASI:One.
mulligan-guardian(agent1qveg9ca3jn06upx2ztx78k40kjm4jxg2ywuul2lmfrvc77druqwp6qae9w4) chats in ASI:One over a mailbox;mulligan-worker(agent1q2hqgd60cdsfxhda3tyesj0zmyca4mle66c6varrj6gmzrfhp88aqsrdngz) acts only through Mulligan's MCP tools. Their README, fetch-agent/README.md, carries theinnovationlabandhackathonbadges.
Challenges we ran into
- Undoing a drop without breaking foreign keys. Moving a table to the trash schema and back sounds simple until other tables point at it, Postgres truncates long names at 63 bytes, and cascaded rows have to return parents first.
- Lost replies. GitHub and Notion have no transactions, so a write whose reply is lost may or may not have happened. The action still keeps its before-state and inverse, so a rewind can undo it either way.
- Races. Agents keep working while a rewind runs, and they make parallel tool calls. A rewind undoes calls in the order they actually ran, not the order they were logged, and waits for every call still in flight, for up to 30 seconds, then fails that step with a clear reason instead of hanging. Agent calls and rewind steps share a per-system lock so they never interleave, and if the gateway stops mid-rewind, the next rewind picks up the unfinished steps.
- Money is messy. A Stripe authorization lapses after a few days, so the undo window always ends safely before Stripe's capture deadline. A rewind has to know whether to cancel or refund, a cancel that loses to a capture refunds instead, and refunds carry an idempotency key.
- Deciding what "restored" means. A correct rewind doesn't return to the old checksum when a teammate's edit was kept, so the expected state has to include the kept edits.
Accomplishments that we're proud of
- A rogue agent's session across three real systems comes back in one click, and the checksums prove it.
- The rewind keeps a teammate's edit instead of clobbering it.
- Irreversible actions never run until a person approves them, with a "YES" over iMessage or on the dashboard, and money gets a real undo window.
- One gateway protects Claude Code, Devin and a Fetch.ai Guardian in ASI:One, each connecting over plain MCP.
- Devin's randomized fuzz test requires every seeded session to rewind to the exact pre-session checksum, and a load check rewinds 200 actions in one session in under 30 seconds.
- In our final review, 40 of 40 integration tests passed, and 5 replayed and 2 live rogue-agent runs each rewound clean.
What we learned
- Undo has to be designed in before the action, not bolted on after: capture the before-state, know exactly what a call will touch, and hash what it changed.
- Typed tools beat raw SQL for safety. Constraining what an agent can say is what makes its actions reversible.
- "Restore to a backup" and "undo an agent" are different problems. The second has to respect everything people did in the meantime.
- Randomized tests find what hand-written ones don't. Devin's fuzz test caught bugs our own tests missed.
- Observability pays off during a hackathon: Sentry showed us real bugs we would otherwise have demoed.
What's next for Mulligan
- More undo adapters: Slack, Linear, Google Docs and cloud storage.
- Voice-call approvals for the riskiest actions, alongside iMessage.
- A per-session fee for the Fetch.ai Guardian via the Payment Protocol.
- Team policies: who approves what, per agent and per system.
- A hosted Mulligan, so any team can put it in front of their agents in minutes.
Built With
- agentverse
- asi-one
- claude
- devin
- docker
- fastapi
- fetch.ai
- github
- mcp
- notion
- paper
- photon
- postgresql
- python
- react
- sentry
- stripe
- timescaledb
- typescript
- uagents
- vite


Log in or sign up for Devpost to join the conversation.