Agent reliability | September 2, 2026

Design the undo path before an AI agent gets write access

Approval answers whether an action may start. A reversibility contract answers what happens after the external system has changed. Classify each side effect, bind execution to current state, declare the undo or compensation, verify the repaired world, and escalate when no truthful rollback exists.

Primary keyword: AI agent rollback Saga and compensation design Evidence checked: Sep 2, 2026
AI agent action flowing through approval, execution, compensation, reconciliation, and a human repair gate

An approval prompt is not a recovery design

The most dangerous agent failure is not always an unauthorized action. It is a fully authorized action that commits, changes the world, and then proves difficult or impossible to repair. Once an email is sent, a payment clears, a ticket closes, a secret leaves a boundary, or another automation consumes a webhook, “stop the agent” protects only the future.

Teams often collapse three controls into one. Authorization decides whether the proposed action is allowed. Execution evidence records what request and result were observed. Recovery changes or compensates for the state left behind. These controls cooperate, but none substitutes for another. A narrow permission can still produce a harmful result. A complete trace can faithfully document damage. A successful compensation can still leave a visible correction, fee, delay, or downstream copy.

A reversibility contract makes that separation explicit. Before the model can call a state-changing tool, the host runtime knows the action class, exact preconditions, idempotency boundary, approval requirement, undo window, point of no return, compensation function, expected evidence, reconciliation query, and repair owner. The model may propose the action and explain intent. It cannot invent whether the world is reversible.

Reversibility is a property of the tool and target state, not a promise the model makes in natural language.

This extends two other controls. A permission kernel ensures the model proposes rather than authorizes. An execution receipt binds a run to observed evidence. The reversibility layer decides whether to use a safe inverse, launch compensation, reconcile uncertainty, or transfer repair to a named human.

Recent failures make the missing layer visible

An August 31 Hacker News discussion about a Meta security researcher's agent deleting email reached 59 points and 61 comments. The incident itself dates to February and should not be presented as a new experiment. Its resurfacing matters because the failure combines several production conditions: a toy test did not match the real inbox, the larger workload triggered context compaction, an approval instruction was reportedly lost, stop messages did not halt the active work, and the connected mailbox still permitted destructive actions.

OpenClaw's current documentation explains the underlying boundary without confirming every causal claim about the incident. Compaction summarizes older turns into a smaller entry while retaining recent context; the complete transcript can remain on disk even though the model no longer sees all original turns. Current help documentation also lists standalone stop phrases. That is useful runtime behavior, but a safety invariant cannot depend on a natural-language rule surviving summarization or on a stop message reaching every worker before the next commit.

A separate August 28 HN discussion titled “AI Agent Has Root” drew 42 points and 68 comments. Root access is the extreme version of a broader engineering smell: one tool surface exposes reads, ordinary writes, bulk writes, and permanent destructive operations under the same identity. The model does not need to escape a sandbox if an approved API already contains a one-way door.

The developer signal is real but not uniform. The focused last30days scan returned 47 items across Reddit, HN, and GitHub, yet most high-engagement social results were about adjacent agent and model topics. That noisy corpus does not justify calling rollback a broad trend. The stronger basis is architectural: current Gmail APIs, Microsoft's published reversibility schema and saga examples, established compensating-transaction guidance, and a concrete failure that exposes what prompt-only control misses.

Use six action classes, not one “undoable” flag

A Boolean reversible: true hides the details operators need. Classify actions according to what the target system can actually guarantee. Assign the class in a reviewed tool registry, then tighten execution policy according to the hardest action in a workflow.

ClassExampleRequired runtime behavior
Read onlyList messages or fetch order statusEnforce scope and privacy; no compensation path
Directly reversibleMove a message to Trash, then untrash itRecord inverse API, resource ID, undo window, and verification query
Reversible with costCancel a reservation with a feeExpose cost and deadline before approval; record residual effect
CompensatableSend a correction after a wrong emailCreate a new repair action; never claim the original event disappeared
ReconcilableTimeout after a payment request with unknown commit stateQuery receiver state before retrying or compensating
IrreversiblePermanent deletion, public disclosure, cleared wireStage first, require exact human approval, and place last in the workflow

Class can change with state. An order may be directly cancellable before shipment, reversible with a fee after fulfillment begins, and effectively irreversible after consumption. A message in Trash is directly reversible; a permanently deleted message is not. A draft is reversible; a delivered email is at best compensatable. Therefore the contract needs both time-based and state-based undo conditions.

Do not let the model select the class. It can report that an order appears unshipped, but the target-system status and policy determine the available transition. When the state cannot be proven, use the more restrictive class or pause. “Unknown” is a first-class outcome, not permission to retry.

Put the recovery facts in the tool contract

A production tool definition should carry more than name, description, and JSON Schema. The following non-normative YAML shows the minimum fields for a reversible mailbox action. The host validates them before the model's arguments reach Gmail.

tool: mail.trash_message
contract_version: reversibility/v1
effect: state_change
reversibility:
  class: direct
  inverse_tool: mail.untrash_message
  undo_window_seconds: 2592000
  state_condition: "message.labelIds contains TRASH"
preconditions:
  - message_etag equals proposed_etag
  - permanent_delete is false
approval:
  required: true
  binds: [tool, user_id, message_id, message_etag, policy_version]
  expires_after_seconds: 300
execution:
  idempotency_key: required
  max_batch_size: 10
  rate_limit_per_minute: 10
evidence:
  receiver_fields: [message_id, label_ids, history_id]
  outcome_query: mail.get_message
compensation:
  owner: messaging-operations
  timeout_seconds: 120
  on_failure: escalate_and_revoke
prohibited_alternatives:
  - mail.delete_message
  - mail.batch_delete

The approval binds the exact action digest and current resource version. It cannot be replayed for another message, expanded into a bulk call, or consumed after the message changes. For externally visible or irreversible actions, consume approval inside the same idempotency boundary that starts execution. Otherwise two workers can race to reuse one approval.

The contract also makes safer API design observable. Gmail exposes trash and untrash separately from delete, which immediately and permanently deletes a message. An email agent intended for cleanup should not receive the permanent-delete tool at all. If long-term deletion is required, a separate retention service can empty Trash under a delayed policy after backups, holds, and notices are satisfied.

Every compensation needs its own authorization and evidence. A refund, rollback deploy, reopened ticket, or untrash call is another state change, not a magical exception. Bind it to the original action receipt, record why it started, and verify the result against the target system.

Separate proposal, commit, recovery, and reconciliation

Proposal planeThe model selects a registered tool and arguments. The runtime normalizes targets and resolves the reviewed reversibility contract.
Decision planePolicy checks identity, state, risk class, blast radius, approval digest, expiry, and point-of-no-return rules.
Commit planeAn executor sends one idempotent request and stores receiver evidence before reporting success to the agent.
Recovery planeA durable saga service runs inverse or compensating actions without asking the model to remember what to undo.
Reconciliation planeAn independent query checks world state, downstream copies, residual effects, and whether manual repair is still required.

The recovery service must outlive the chat turn. Put saga state in durable storage with explicit transitions such as proposed, approved, executing, committed, compensating, compensated, reconciled, and manual_repair. A model process crash cannot erase a pending compensation.

Keep a control channel independent from the agent's conversational queue. A stop request should revoke new permits and signal active workers. It cannot assume a third-party API call can be cancelled after commit. The executor records whether cancellation occurred before dispatch, during an abortable request, after an unknown timeout, or after confirmed commit. Each state leads to a different recovery action.

Use receiver-side evidence where consequences matter. An agent log saying “message trashed” proves only what the agent reported. Gmail's message ID, label state, and history identifier provide stronger confirmation. For a payment, use provider transaction state and ledger reconciliation. For a deploy, use the orchestrator revision and health checks. Recovery ends when world state is verified, not when the compensation function returns 200.

Worked example: make inbox cleanup fail safely

Suppose an agent must review 4,000 messages and propose cleanup. The naive design streams message content into a long conversation, keeps “confirm before deleting” in the prompt, and gives the agent a bulk delete tool. The larger real mailbox can change context behavior, a stale instruction can disappear from the model's active view, and one tool call can create a large blast radius.

The safer design separates classification from execution. The agent writes a proposed set containing message IDs, reasons, confidence, and immutable message versions. A deterministic service checks exclusions such as legal holds, starred mail, protected senders, recent messages, and retention rules. The user approves an exact digest. A worker moves at most ten messages per batch to Trash, captures the new label and history state, and stops between batches when the approval expires or policy changes.

If the worker receives a timeout, it does not repeat the trash request blindly. It queries each message. Items already in Trash are recorded as committed. Items still in the inbox remain pending. Unknown items move to reconciliation. If the user withdraws approval, new batches stop and already trashed messages are untrashed from the receiver records. If untrash fails for three messages, the saga enters manual_repair, revokes mailbox write access, and creates an operator packet with exact IDs and failure responses.

Naive controlFailureReversibility control
“Confirm first” in promptCan be lost, reinterpreted, or bypassedAction-bound permit outside model context
Bulk permanent deleteNo reliable inverseTrash only, small batches, delayed retention deletion
Retry on timeoutDuplicate or expanded side effectReceiver query plus idempotency and reconciliation
Stop chat messageMay arrive after commitIndependent permit revocation and worker cancellation
Agent says “restored”Self-reported, incomplete outcomeReceiver state check and exception register

This architecture does not claim zero risk. Messages in Trash may have triggered rules, notifications, or downstream synchronization. The reconciliation query should include those known consumers where possible. If the original effect crossed a boundary the system cannot inspect, the receipt must say so.

Run compensation as deterministic workflow code

Do not ask the model to improvise the inverse sequence. The model can suggest a repair plan, but reviewed orchestration code owns execution. The simplified TypeScript below shows the control shape.

async function executeWithRecovery(action: ProposedAction) {
  const contract = registry.resolve(action.tool, action.target);
  const snapshot = await contract.readState(action.target);
  const digest = hash({ action, snapshot, contract: contract.version });

  const permit = await policy.consumePermit({
    digest,
    idempotencyKey: action.idempotencyKey,
    expiresAt: action.approvalExpiresAt
  });
  if (!permit.allowed) throw new Denied(permit.reason);

  const saga = await sagas.begin({ digest, contract, snapshot });
  try {
    const result = await contract.execute(action, permit);
    await saga.recordCommit(result.receiverReceipt);
    const observed = await contract.reconcile(action.target);
    if (!contract.accepts(observed)) throw new OutcomeMismatch(observed);
    return await saga.markReconciled(observed);
  } catch (error) {
    const commitState = await contract.detectCommit(action.target, saga);
    if (commitState === "not_committed") return saga.markAborted(error);
    if (contract.reversibility.class === "irreversible") {
      await access.revoke(action.principal);
      return saga.escalate("irreversible_side_effect", error);
    }
    const repair = await contract.compensate(saga.originalReceipt);
    const repairedState = await contract.reconcile(action.target);
    if (!contract.compensationAccepts(repairedState, repair)) {
      await access.revoke(action.principal);
      return saga.escalate("compensation_incomplete", { error, repair });
    }
    return saga.markCompensated(repairedState);
  }
}

Real workflows often have several committed steps. The compensating transaction pattern warns that repair does not always run in literal reverse order. Undo the most sensitive inconsistency first. A payment workflow might release an inventory reservation before sending a customer correction, or restore account access before fixing an analytics copy. Encode dependency and risk priority in the saga definition.

Compensation must be idempotent too. The orchestrator can crash after the inverse commits but before it records success. Give each repair an idempotency key derived from the original receipt and compensation version. On retry, query receiver state and continue from proven facts.

Failure modes a rollback demo usually misses

Failure modeWhat goes wrongRequired defense
Hidden permanent pathTool wrapper exposes delete behind a generic “clean up” operationCapability inventory, distinct tool IDs, deny permanent path by default
Approval driftResource or policy changes after approvalBind version and state digest; expire and re-request
Permit replayTwo workers consume one approvalOne-time permit consumed atomically with idempotency boundary
Unknown commitTimeout occurs after receiver changes stateQuery receiver before retry or compensation
Partial compensationSome resources restore while others failPer-item receipts, exception register, access revocation, named repair owner
False rollbackCorrection is described as erasing a sent or public eventRecord residual effect and use honest “compensated” status
Context-dependent invariantSafety rule disappears after compactionHost policy and reviewed tool contract outside model context
Stop-path illusionChat stop cannot reach queued or remote workIndependent cancellation, permit revocation, worker heartbeat
Self-attested recoveryAgent declares success without checking world stateReceiver evidence and independent reconciliation query
Ownerless repairManual step exists but nobody is paged or authorizedOn-call owner, runbook, access, deadline, closure evidence

Start with ten tools and run a failure drill

Inventory the ten tools with the highest combination of impact, frequency, blast radius, and weak recovery. For each, list the target system, operation, principal, reversible class, point of no return, inverse or compensation, undo window, downstream consumers, receiver evidence, reconciliation query, and manual owner. Remove any operation whose contract cannot be completed.

Prefer staging primitivesDraft before send, Trash before permanent delete, preview before publish, reserve before charge, and canary before broad deploy.
Bind exact approvalHash normalized tool, arguments, target version, policy, scope, expiry, and idempotency key.
Limit each commitCap batch size, rate, value, recipients, targets, and total side effects under one permit.
Store receiver evidenceCapture provider identifiers, resource versions, result state, and a controlled reference to the original request.
Make compensation durableRun recovery from a persisted saga service that survives model, worker, and chat failures.
Verify the repaired worldCheck target and known downstream state; record residual effects and unknowns.
Revoke on uncertaintyStop new writes when commit or compensation state cannot be proven.
Name manual ownershipGive the repair team a pager, permissions, evidence packet, deadline, and closure gate.

Then run a game day. Inject a timeout after the receiver commits. Duplicate a queue delivery. Expire approval between two batch items. Change resource version after approval. Make the third compensation fail. Drop the model's original instruction through context compaction. Send stop while two workers are active. Verify that the system contains blast radius, records uncertainty, revokes permits, produces a repair packet, and refuses to mark the workflow restored until reconciliation passes.

Measure median time to detect unknown state, percentage of writes with complete reversibility contracts, compensation success by class, unreconciled side effects, manual repair time, repeat side effects prevented by idempotency, and actions removed because no safe recovery existed. A lower autonomy rate after the inventory can be a success: the system learned which one-way doors should remain human-controlled.

Frequently asked questions

What is an AI agent reversibility contract?

It is a runtime-enforced description of how one state-changing tool action can be undone, compensated, reconciled, or escalated. It includes class, preconditions, approval binding, idempotency, undo windows, evidence, receiver checks, and repair ownership.

Is a compensating transaction the same as rollback?

No. Database rollback prevents or removes changes inside a transaction boundary. Compensation happens after one or more effects commit. It creates new actions that offset or repair the outcome, and the original event may remain visible.

Does human approval solve irreversible actions?

It helps only when bound to the exact current action. Approval does not make a public message unread, a secret undisclosed, or a cleared transfer reversible. Use staging, small scope, stronger validation, and a final point-of-no-return gate.

Should the model choose the compensation?

The model may propose or explain a repair, but reviewed runtime code should select and execute registered compensation. The target system's verified state, not the model's confidence, decides whether the repair succeeded.

What is the first production change to make?

Remove permanent and bulk-destructive APIs from general agent toolsets. Replace them with staging or soft-delete operations, small batch limits, exact approvals, durable receipts, and a tested manual repair path.

Sources and further reading

Current documentation and recent community pages were checked on September 2, 2026. The Summer Yue event is a reported February incident resurfaced in late-August discussion, not a newly reproduced test.