Agent Engineering

Compensating Actions and Safe Undo

Reversing effects that already left your system, and admitting when you cannot.

Chapter 6 of 1211 min readOpen access

Compensating Actions and Safe Undo deal with the third kind of wrong state from Chapter 2: the effect that has already left your process. There is no rollback for those. There is only a second action, taken forward in time, that reduces the damage the first one caused.

Key takeaways

  • Compensation is a forward action, not a rewind. Refunding a payment leaves two ledger entries and a customer who saw both, which is a different end state from never having charged.
  • Every step with an external effect declares a compensation or declares explicitly that none exists. The second declaration is the useful one, because it tells you where a gate or an approval has to go instead.
  • Compensations run in reverse order and must be idempotent and retryable, because the failure that triggered them is often the same failure that will interrupt them.
  • The strongest design move is structural: split each irreversible step into a reservation and a commit, keep the commit small, and move it as late in the run as the work allows.
  • Some effects are irreversible in the only sense that matters, being observed. Design for not sending rather than for unsending.

Read this beside Chapter 5, whose gates decide when compensation is triggered, and Chapter 4, whose record of emitted effects is what the compensation acts on. Chapter 10 covers the escalation that a failed compensation demands.

At step nineteen the agent sends a price correction to four hundred customers. The gate at step twenty reads back the pricing table and finds the correction was applied against last quarter's rates.

The emails are gone. No operation makes them not have arrived. Every option from here is a choice about what to do next.

Rollback and compensation are different things

Inside your own transactional store, rollback is real: the database can restore the prior state and nobody outside ever observed the intermediate one.

Across a boundary, that guarantee disappears. The other system has committed, its observers have observed, and its clock has moved. What remains is compensation, which Garcia-Molina and Salem defined precisely in 1987 when they introduced sagas: a long-lived transaction is written as a sequence of transactions, and the system guarantees either that all of them complete or that compensating transactions amend a partial execution.

The word amend is doing careful work in that sentence. A compensation does not restore the prior state. It produces a new state that is acceptable. A refund is not the absence of a charge, a correction email is not the absence of an error, and a deleted record in a partner's system may still exist in their backups and their audit log.

That distinction is not pedantry. It changes what you promise the business. It also sets the standard for this chapter. The question is never whether you can undo. It is whether the amended end state is one you can defend.

Declare a compensation for every external step, or declare that there is none

Chapter 3 put a compensation field on every step in the plan artifact. This is the chapter that fills it in, and the discipline is that the field cannot be left blank.

For each external effect, one of three entries is required. A compensating action, named and implemented. A statement that the effect is compensable only partially, with what remains uncorrected written down. Or a statement that no compensation exists.

The third entry is the valuable one, because it converts an assumption into a design constraint. A step that cannot be undone must not run without whatever protection is appropriate: a stronger gate, a human approval, a smaller scope, or a later position in the sequence. Discovering that in the plan is cheap, and discovering it during an incident is not.

Anthropic's December 2024 guidance to add complexity only where needed applies here in an inverted form. Compensation machinery is expensive, so build it where an effect is genuinely reversible and valuable to reverse, and spend the same money on prevention where it is not.

Compensations have their own failure modes

A compensation is a step. Everything Chapter 2 says about steps applies to it, including that it can fail, and it usually runs under worse conditions than the original.

Four properties keep them trustworthy. They run in reverse order, because later steps often depend on earlier ones and undoing out of order can leave a state neither forward nor backward. They must be idempotent, since the failure that triggered the rollback is often the failure that will interrupt it, and a resumed rollback will retry them. They must be able to run against partial information, because the effect they compensate may have been emitted without its confirmation being recorded. And they need their own bounded retry policy, after which the run escalates rather than looping.

Temporal's model is useful here even conceptually: activities are the units that touch the outside world, and failed activities are retried according to configured policy. A compensation is an activity, so it inherits that treatment rather than becoming a special case in your code.

There is one rule that overrides the others. If a compensation fails after its retries, stop and escalate with the full record. A run that keeps making forward progress after a failed rollback is building on a state it has already declared wrong, and that is the worst configuration available.

Shrink the irreversible part, then move it late

The best compensation is the one you never need, and the way to get there is structural rather than procedural.

Split every irreversible operation into two steps: a reservation that is reversible, and a commitment that is not. Draft the message rather than sending it. Authorise the payment rather than capturing it. Stage the file rather than publishing it. Create the record in a pending state rather than an active one. The reservation carries the work, holds the resource, and can be released. The commitment is a single small action with almost no logic in it.

Then move every commitment as late as the work allows, ideally into one phase at the end of the run. A run structured that way has a long reversible body and a short irreversible tail, which means a failure at step nineteen of forty costs nothing outside the process.

Two consequences follow that make this worth the refactor. The gates in Chapter 5 get their strongest position, immediately before a commit phase where a single check guards everything. And the human approval in Chapter 10 becomes practical, because there is one place to approve rather than nineteen.

This is the same shape the strangler-fig and reservation patterns take in distributed systems generally, and it predates agents by decades. What is new is having a caller that will sometimes propose the wrong commitment with complete confidence, which raises the value of the split rather than changing its mechanics.

Some things cannot be unsent

A category of effect resists all of this, and pretending otherwise is how systems get into positions they cannot argue their way out of.

Anything a person has already read is irreversible: an email, a text message, a push notification, a comment on a public thread. Anything with a legal or regulatory character may be irreversible in the sense that the record persists even when the state is corrected: a filing, a disclosure, a report to a counterparty. Anything that triggers a third party's automation may set off consequences you cannot see, let alone reverse.

For those, the only real control is prevention, and it takes three forms. A tighter gate before the action. A human approval where the cost justifies the latency, which the Model Context Protocol's own security principles push toward by requiring that hosts obtain explicit user consent before invoking a tool. And a smaller blast radius by construction: send to a batch of ten and check, rather than to four hundred and hope, which turns an unbounded error into a bounded one.

Batching deserves emphasis because it is the cheapest of the three. A step that affects four hundred external parties can nearly always be expressed as forty steps affecting ten, and the gate between them turns a catastrophe into an incident.

Testing a compensation you hope never runs

Compensation code is the least exercised code in the system. It runs on the worst day, under the worst conditions, having never been tried.

Three practices keep it honest. Run every compensation in the normal test suite against a real sandbox of the provider, not a mock, because the failure you care about is the provider rejecting the undo. Run a scheduled game day in a staging environment where a run is deliberately failed after its external effects, and watch the whole reverse sequence execute. And record, per compensation, when it last ran successfully, in the same spirit as a runbook's last-executed date.

There is one test that finds the most defects for the least effort. Trigger a compensation twice. Idempotency claims are usually assumptions, and the second run is where they break: a refund issued twice, a cancellation that errors because the booking is already cancelled and takes the rollback down with it.

Then check the timing assumption. Many provider APIs allow an undo only inside a window: a payment can be voided before capture and only refunded after, a message can be recalled for minutes. Record the window in the plan artifact beside the compensation, because a compensation that expires is a compensation that does not exist, and a long run can easily outlive it.

What to do when the damage is done

Sometimes the compensation is inadequate and the effect is out. The run's job at that point is not to fix it. It is to stop making it worse and to hand a complete account to a person.

Freeze forward progress on anything that touches the affected scope, keeping the remainder of the run alive only if it is genuinely independent. Emit the record: what was intended, what happened, which external references were affected, what compensation was attempted and what it returned. That record is what Chapter 4's checkpoint exists to make possible, and it is the difference between a support team that can act within the hour and one that spends a day reconstructing events.

Then escalate as Chapter 10 describes, with the scope quantified. Four hundred customers, these identifiers, sent at this time, with this text. The 2026 diagnostic work on long-horizon failures relies on exactly this kind of trajectory record to attribute failures to steps, and the same record is what makes your own postmortem possible rather than speculative.

Chapter summary

Once an effect crosses a boundary, rollback is unavailable and compensation is a forward action that amends rather than erases, which is precisely how sagas framed it in 1987. Every step with an external effect must declare a compensation, a partial compensation with the residue written down, or the absence of one, and that third declaration is the most useful because it forces a gate, an approval, a smaller scope or a later position instead. Compensations are steps with their own failure modes, so they run in reverse order, must be idempotent and tolerant of partial information, carry a bounded retry policy, and stop the run when they fail rather than letting it continue on a state already declared wrong. The structural move that beats all of this is splitting each irreversible operation into a reversible reservation and a small commit, then moving the commits as late as possible, which gives a run a long reversible body and a short irreversible tail, puts the strongest gate in one place and makes human approval practical. Some effects cannot be unsent at all, particularly anything a person has read or anything with a regulatory record, and for those the only controls are a stronger gate, an explicit approval of the kind the Model Context Protocol's security principles require, and a smaller blast radius by construction, with batching the cheapest of the three. When the damage is done, the run stops touching the affected scope, emits the full record of intent, effect, references and attempted compensation, and escalates with the scope quantified.

Everything in the last three chapters assumes the process survives long enough to run its own recovery. Chapter 7 is Durable Execution: Workflows, Retries, Idempotency, which is about where the state lives when the process does not survive, why determinism is the price of replay, and how idempotency keys stop a resumed run from doing everything twice.

Sources

  1. SagasHector Garcia-Molina and Kenneth Salem, ACM SIGMOD 1987 · 1987 · Research paper · reported
  2. Understanding TemporalTemporal · Official documentation · verified
  3. SpecificationModel Context Protocol, version 2025-11-25 · 2025-11-25 · Standard · verified
  4. Building Effective AgentsAnthropic · 2024-12-19 · Vendor engineering · verified
  5. The Long-Horizon Task Mirage? Diagnosing Where and Why Agentic Systems BreakarXiv · 2026-04-13 · Research paper · reported