Delegate the Undoable, Guard the Irreversible — Tiering Agent Autonomy by Reversibility
When you hand production work to an Antigravity agent, the thing that bites first isn't intelligence — it's whether the operation can be undone. Here is a design that sorts every operation into three reversibility tiers and routes each to auto-execution, checkpointed execution, or a human gate, with TypeScript implementations and real numbers from running six apps in parallel.
Running six apps solo, I have an endless backlog of work I would love to hand to an agent. Dependency bumps, multi-language resource alignment, Remote Config flag tweaks, AdMob mediation cleanup. The chores I have hand-cranked since 2014 are exactly the ones I most want to automate.
Then, one Thursday night this spring, my blood ran cold. I told an Antigravity agent to "tidy up the ad settings," and it published a Remote Config flag to production. Code I can revert with git. But a production Remote Config push had already begun rolling out to devices among the 50 million installs that happened to launch in that window. Even if I reverted it, the devices that had already received the new value would not roll back. The tests had passed. The problem lived outside the tests, in whether the action could be undone at all.
That night I realized the thing that bites first in agent operations is not intelligence — it is whether you can undo the operation afterward. Even a smart agent will be wrong sometimes. An operation you can revert with git and an operation that leaves a permanent mark on the world deserve completely different levels of trust. Here is how I sort operations by reversibility and shift autonomy in stages, with the implementation.
Split Operations by "Can This Be Undone?"
In software decision-making there is a useful distinction between a "two-way door" you can walk back through and a "one-way door" you can't. Map that onto agent operations and the whole thing gets clearer.
Two-way-door operations are file edits, branch commits, draft generation, local builds. If they go wrong you revert with git, and no one outside ever sees them. Forcing a human approval on these defeats the entire point of hiring an agent.
One-way-door operations are App Store submissions, production database migrations, Remote Config publishes, billing events, outbound notifications. Once executed they leave a mark on user devices, payments, or external systems, and no single command undoes them. You must not leave these to raw intelligence.
My first attempt gated "important" operations. But "important" was vague, so a confirmation dialog popped up on practically everything and the agent could barely act on its own. Only when I swapped the criterion from "is this important?" to "can this be undone?" did operations finally start to flow.
Three Tiers Work Better Than Two
A binary split didn't fit reality. I needed a middle rung between the two doors, so I settled on three tiers.
Tier 1 is auto-reversible: revertable with git, no external trace, near-zero rollback cost. File edits, local tests, draft generation. I let the agent run these unconditionally.
Tier 2 is checkpoint-required: technically reversible, but undoing it takes effort, or forgetting to undo it is dangerous. Staging deploys, data updates without schema changes, branch merges. Before running, I always leave a rollback point (a snapshot) and only then auto-execute.
Tier 3 is irreversible: once run it leaves a permanent mark and only a human can own it. Store submissions, production migrations, billing and notification events. This tier alone makes the agent stop right before execution so a human can review the contents and let it through.
The nice property of three tiers is that the weight of the decision tracks exactly how hard the operation is to undo. Safe operations go fast, dangerous ones go slow — sorted mechanically, with no argument about intelligence.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Build a router in TypeScript that classifies every agent operation into auto-reversible / checkpoint-required / irreversible, auto-runs the safe ones, and sends only the irreversible ones to a human gate
✦Using a real incident from running six apps in parallel — an agent pushing an un-undoable config to production — see the classifier that judges reversibility and the checkpoint layer that always leaves a rollback point
✦From the measured split (about 70% of operations are reversible, only 8% need a human gate), set thresholds that stop accidents without drowning your workflow in approvals, down to the average number of daily pauses
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Classify Operations — the Reversibility Classifier
First, a classifier that sorts every operation the agent is about to run into one of the three tiers. An operation is an object with a kind and a target scope, judged by rules. I avoid machine learning here because reversibility judgments are the last place you want fuzziness.
// reversibility.ts — classify operations into three reversibility tiersexport type Tier = "auto" | "checkpoint" | "irreversible";export interface AgentOperation { kind: string; // e.g. "file.edit", "remoteConfig.publish" target: string; // e.g. "src/App.tsx", "production" reversalCostSec?: number; // estimated seconds to undo (optional)}// Manage irreversible operations as an explicit list, not an allow-list.// If a kind is missing, fall to the safe side (irreversible).const IRREVERSIBLE_KINDS = new Set([ "appstore.submit", "playstore.release", "db.migrate", "remoteConfig.publish", "billing.charge", "notification.send", "email.send",]);const AUTO_KINDS = new Set([ "file.edit", "file.create", "test.run", "build.local", "draft.generate",]);export function classify(op: AgentOperation): Tier { // 1. Anything touching production is bumped up a tier even if the kind looks safe. const hitsProduction = op.target === "production" || op.target.startsWith("prod"); if (IRREVERSIBLE_KINDS.has(op.kind)) return "irreversible"; if (hitsProduction) return "irreversible"; // touching prod = treat as irreversible if (AUTO_KINDS.has(op.kind)) return "auto"; // 2. Operations that are expensive to undo require a checkpoint. if ((op.reversalCostSec ?? 0) > 30) return "checkpoint"; // 3. Unknowns fall to the safe side (at least leave a checkpoint). return "checkpoint";}
The most important line is the final return "checkpoint". When the classifier meets a kind it does not know, dropping to auto leaves you defenseless. Unknowns fall to the safe side — this one line prevents accidents from missing list entries. I originally had it returning auto, and a newly added operation kind ran automatically before it was registered in the classifier. That gave me a scare.
A Guard Middleware to Switch Autonomy
With the classifier in hand, run every operation through this guard — a thin middleware that only changes behavior per tier.
// guard.ts — switch how things execute, per tierimport { classify, AgentOperation } from "./reversibility";import { snapshot, requireApproval } from "./checkpoint";export interface ExecResult { executed: boolean; checkpointId?: string; reason?: string;}export async function guardedExecute( op: AgentOperation, run: () => Promise<void>,): Promise<ExecResult> { const tier = classify(op); if (tier === "auto") { await run(); return { executed: true }; } if (tier === "checkpoint") { // Leave a rollback point first; if the snapshot fails, do not run. const checkpointId = await snapshot(op); if (!checkpointId) { return { executed: false, reason: "snapshot-failed" }; } await run(); return { executed: true, checkpointId }; } // irreversible: do not run until a human reviews and approves const approved = await requireApproval(op); if (!approved) { return { executed: false, reason: "rejected-by-human" }; } await run(); return { executed: true };}
The effect showed up clearly in the numbers. Before this guard, the agent attempted an un-undoable operation on its own roughly once or twice a week. In the three weeks after, those near-misses dropped to zero. Measuring the split, about 70% of operations were auto-reversible (Tier 1), about 22% were checkpoint-required (Tier 2), and irreversible operations needing a human gate were about 8%. Approval pauses averaged 3.4 per day. Out of an agent that fires dozens of times daily, stopping just the 3.4 that truly matter is plenty.
Always Leave a Rollback Point for Reversible Operations
The heart of Tier 2 is taking a snapshot before execution and guaranteeing you can always go back. How you build the rollback point depends on the target: git for code, a copy for config files, a logical snapshot for data.
// checkpoint.ts — build a rollback point appropriate to the operation kindimport { execFile } from "node:child_process";import { promisify } from "node:util";const sh = promisify(execFile);export async function snapshot(op: { kind: string; target: string }): Promise<string | null> { const id = `cp-${Date.now()}`; try { if (op.kind.startsWith("file.") || op.kind.startsWith("build.")) { // Stash the whole working tree so it can always be restored. await sh("git", ["stash", "push", "-u", "-m", id]); return id; } if (op.kind.startsWith("config.")) { // Copy config files with a timestamped backup. await sh("cp", [op.target, `${op.target}.${id}.bak`]); return id; } // Otherwise we may not be able to build a rollback point. Return null to stop. return null; } catch { return null; }}export async function rollback(checkpointId: string): Promise<void> { // Find the matching ID in the stash list and restore it. const { stdout } = await sh("git", ["stash", "list"]); const line = stdout.split("\n").find((l) => l.includes(checkpointId)); if (line) { const ref = line.split(":")[0]; // e.g. stash@{0} await sh("git", ["stash", "pop", ref]); }}
One pitfall I learned in production: when you stash code, forgetting -u (include untracked files) means files the agent newly generated are not stashed and survive the rollback. A rollback point only matters if it guarantees you can undo everything. A snapshot that only undoes half is more dangerous than none, because it makes you believe you are safe.
Snapshot overhead averaged 120 milliseconds in my six-app setup. Tier 2 operations are only about 20% of the total, so the slowdown across the whole agent is imperceptible. If that buys "you can always go back," it is cheap insurance.
A Minimal Confirmation Before Irreversible Operations
When I built the human gate for Tier 3, my first mistake was showing too much. I dumped the agent's full reasoning log, and that made me less able to decide. Standing in front of a one-way door, a human wants exactly three things.
// approval.ts — present only the minimum before an irreversible operationexport interface ApprovalCard { whatChanges: string; // what changes (one line) whoIsAffected: string; // who is affected ifWrong: string; // what happens if this is wrong}export function buildCard(op: { kind: string; target: string }): ApprovalCard { if (op.kind === "remoteConfig.publish") { return { whatChanges: `Publish Remote Config "${op.target}" to production`, whoIsAffected: "Every user on next launch (reverting won't roll back delivered devices)", ifWrong: "A wrong value caches on devices and persists until a forced update", }; } if (op.kind === "appstore.submit") { return { whatChanges: `Submit "${op.target}" to the App Store`, whoIsAffected: "Review team and existing users", ifWrong: "Rejection or an unintended release; pulling it back takes days", }; } return { whatChanges: `Run ${op.kind} on ${op.target}`, whoIsAffected: "External systems", ifWrong: "Manual rollback required", };}
What changes, who is affected, what happens if it's wrong. Put just those three on one card and the decision takes seconds. The third — "what happens if it's wrong" — matters most, because it puts the operation's irreversibility into words. If the card says recovery takes days, people naturally slow down.
What the Docs Don't Tell You
After running this for several weeks in production, here are three things the design diagram alone won't show you.
Reversibility has a shelf life. A git commit reverts cleanly right after, but once ten commits stack on top it becomes "revertable, but dangerous to revert." Assume Tier 1 operations drift into Tier 2 over time and give rollback points an expiry. I auto-promote any stash older than an hour to the review queue.
"Looks reversible but isn't" is the most dangerous class. A database UPDATE can technically be set back to its old value. But if a user acted on the new value in the meantime, restoring it doesn't restore consistency. Reversibility is decided not by the operation alone but by how it entangles with the outside world. When in doubt, I recommend treating it as irreversible.
The classifier is the code to change most carefully. Forget to register a new kind and, thanks to the safe-side default, you get no accident — just an agent that stops more than it needs to. But mis-register an irreversible operation as auto and your defense collapses in an instant. I require a human review for changes to the classifier alone. Ironically, the code that underpins autonomous execution is the one place you must least allow it.
Where to Start
You don't need to build all of this at once. First, write down the ten irreversible operations your agent touches. Store submissions, production publishes, billing, notifications. The list is probably short. Even across my six apps, only about eight were truly irreversible. List just those eight in IRREVERSIBLE_KINDS and put a human gate in the guard. That alone drops the odds of the agent doing something unrecoverable in the middle of the night to nearly zero. You can grow the checkpoint layer and the cards gradually as you go.
Delegate reversible operations boldly and keep only the irreversible ones in your own hands. Drawing the line by undoability instead of intelligence is the single change that let me sleep soundly while running six apps alongside an agent. I hope it helps anyone who has just started trusting an agent with production.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.