Cross-post of Insights #10 — canonical: https://sheikhwasim.com/insights/undo-ledger-every-step-has-a-reverse/
Stopping an agent is easy. Stopping it cleanly is the hard part.
A multi-step agent doesn't fail in one place. It fails after step three of six, with three real changes already made in three real systems. Hitting "cancel" stops step four. It doesn't take back steps one through three.
Most agent stacks treat cancel like closing a browser tab. In production, it should work like a database rollback: every change that went out comes back, in reverse order, and you can prove it did.
I call the fix an Undo Ledger: every step the agent takes is written down together with the step that reverses it, before it runs. Cancel, failure, or a human "no" all mean the same thing: walk the ledger backward.
Quick use case
Situation. A mid-size company's IT team runs an onboarding agent. When HR marks a hire as confirmed, the agent creates the identity account, assigns software licenses, orders a laptop, adds the person to team channels and groups, and creates the payroll profile. It cut onboarding from days of tickets to about an hour of review.
What broke. An offer was rescinded the same afternoon the agent started. HR cancelled the run while it was on step four. The agent stopped, as designed. But the identity account was already active, two paid licenses were assigned, and a laptop order had gone to the vendor. Nobody knew exactly which steps had finished. IT found the live account eleven days later during an access review. The licenses had been billed for the month, and the laptop showed up at the front desk.
The fix (Undo Ledger).
- No step without a reverse. Each tool the agent can call declares its undo: create account pairs with disable account, assign license with release license, place order with cancel order.
- Write the entry first. Before a step runs, the ledger records what's about to happen and how to undo it. After it runs, it records the result and the IDs it created.
- Cancel walks backward. Cancel, a failed step, or a rejected approval triggers the undo for each completed step, newest first.
- Undo gets checked too. Each reverse step reports success or failure. Anything that can't be undone automatically (a shipped laptop) becomes a named task for a person, not a silent leftover.
- The run closes with a receipt. "Created 3, reversed 2, 1 handed to IT" is the last line of every cancelled run.
Same agent. Same tools. The difference was that "cancelled" finally meant "back to where we started."
Why "just stop it" isn't enough
Agents act through tools, and tools change real systems: accounts, money, orders, records. Those systems don't know the run was cancelled. They only know what they were told to do.
So a stop button without a reverse path leaves half-done work. It looks fine in the agent's log and sits there in everyone else's systems. The longer the chain of steps, the bigger the mess a mid-run failure leaves.
The Undo Ledger (five layers)
1. Pair every action with its reverse
When you register a tool, register its undo. If a step has no clean undo, say so up front and mark it as one that needs a human to confirm before it runs. That one decision changes which steps you let an agent take on its own.
2. Order steps so the risky ones go last
Do the easy-to-reverse steps first (draft, reserve, stage) and the hard-to-reverse ones last (send, pay, ship). If the run dies early, there's less to unwind. Ordering is the cheapest undo you'll ever build.
3. Write before you act
The ledger entry exists before the tool call. If the process crashes mid-step, you still know what was in flight. Pair this with a Receipt Gate so a retried undo never reverses something twice.
4. Make undo results boring
Undo steps return the same plain outcomes as any tool under a Boundary Contract: reversed, already reversed, or can't reverse plus a reason. "Can't reverse" goes to a named person with the IDs attached.
5. Close every run with a receipt
Every run ends with a count of what it created, what it reversed, and what it handed off. If the counts don't line up, the run isn't closed.
How this maps to what you already have
- 5-layer agent stack. The ledger sits in the Tools layer and writes into Traces, so every reverse step is visible next to the step it undid.
- Permission Envelope. When consent expires or a human says no, the envelope closes and the ledger unwinds what ran under it.
- Receipt Gate. Receipts stop duplicate writes. The ledger stops orphaned ones.
- Release Pin. The tool versions in the manifest include their undo, so changing a reverse step is a release.
- Ship Gate. Add one check before deploy: cancel a run halfway in staging and confirm the ledger puts everything back.
The test
Pick your longest-running agent and ask:
If you cancelled it right after its third step, could you list every change it made and reverse all of them without opening a single admin console by hand?
Then do it in staging. Start a run, cancel it mid-way, and check each system it touched. Anything still sitting there is a step without a reverse.
Closing
Agents will fail mid-run. Offers get rescinded, approvals get denied, APIs time out. That's normal. What isn't acceptable is not knowing what got left behind. Write the reverse before you write the action, and cancel stops being the scariest button in your product.
I'm Wasim Sheikh, AI Architect. I build systems teams trust and organizations depend on: not demos, not proofs of concept, production.
Follow for practical AI architecture that ships.
Connect on LinkedIn: Wasim Sheikh · Site: sheikhwasim.com · Notes: Practical AI Notes · X: @anciwasim
Top comments (1)
The step that worries me most in the backward walk is the one marked in flight. Writing the entry first tells you a call went out, but if it timed out you never got the account ID or order number it created, so there is nothing to aim the undo at.
One fix: mint the ID yourself and record it in the ledger entry before the call, then pass it as the vendor's idempotency key or as a tag on the object. Undo can first ask whether entry 4 actually landed by that ID, and only reverse what it finds.
The other trap is an undo that targets state instead of the object. "Remove license from user" can take away a license IT assigned by hand after the run. "Release assignment X, created by entry 2" can't.
When a cancel arrives while a step is still in flight, do you wait for it to settle before unwinding, or reverse speculatively and reconcile afterward?