MCP, A2A, and AP2 standardized how agents find each other and speak. None of them standardize what happens when step four fails and steps one through three already happened.
Here is a design question that keeps surfacing as teams move agents into real workflows. You have microservices coordinated by a saga. Push it one step further: what if the services were AI agents, and the API between them was disposable: generated on demand, shaped to the task, never written down as a permanent contract? Agents negotiating their own interface, executing the work, unwinding it if something fails. Is there a standard for that yet?
The short answer is no. The longer answer is the interesting one, because the entire agent protocol stack standardizes speech and none of it standardizes commitment. Once you see that, the architecture that fixes it is forced.
Why This Matters #
Take a concrete case: an agent that onboards a new supplier. Six steps: create the vendor record, reserve a budget line, provision a sandbox tenant, run a compliance check against an external registry, capture a setup fee, email the supplier their credentials.
Step four fails. The external registry times out after the agent has already retried it three times.
Now answer these questions about your system, today:
- Which of the first three steps actually took effect, and which are safe to leave in place?
- The budget reservation has a hold on it. Does anyone release it, or does it sit there until a quarterly reconciliation finds it?
- The agent retried step four three times. Did the registry create three records, or one?
- If the agent had made it to step five, the fee is captured. There is no undo for that. Did anything in the plan know that before it started?
None of these are model-quality problems. They are all the same problem: the protocol carried the request and the response, and nothing carried the commitment. The failure mode is not a wrong answer. It’s a system that ends up in a state nobody designed, that no log can reconstruct, and that a human discovers weeks later during a reconciliation.
This gets worse, not better, as agents improve. A more capable agent attempts longer chains with more side effects before it stops.
What the Stack Standardizes Today #
By mid-2026 the layering has settled, and it’s narrower than the press releases suggest.
MCP governs the vertical: how one agent reaches tools, data, and services. Anthropic donated it to the Linux Foundation in December 2025 as the founding project of the Agentic AI Foundation.
A2A governs the horizontal: how agents discover and delegate to each other. Google handed it to the Linux Foundation in June 2025, IBM’s competing ACP merged into it in August 2025, and v1.0 landed in April 2026 with multi-transport support, multi-tenancy, and modernized security flows.
AP2 governs money: a chain of signed mandates (intent, cart, payment) carrying a user’s authorization through a purchase they aren’t present for.
AGNTCY, out of Cisco, fills in identity, messaging, and observability around all of it.
That is a genuinely impressive amount of standardization in two years. It is also, without exception, standardization of speech: discovery, addressing, framing, streaming, authentication, delegation. How agents find each other and what a well-formed utterance looks like.
The closest thing in the entire stack to transactional semantics is the A2A task lifecycle: submitted
, working
, input-required
, completed
, failed
, canceled
. It isn’t close at all. ** canceled is not compensated.** Cancelling a task means you stopped it. It says nothing about the four side effects it already produced. There is no compensating action in the spec, no pivot transaction, no saga context that propagates across a delegation chain, and no way for an agent to advertise
“this operation of mine can be undone, and here is how.”
This is not a niche complaint. A mid-2026 gap analysis of MCP, A2A, ACP, ANP, and ERC-8004 reaches the same structural conclusion from the governance angle: the missing capabilities are not features inside these protocols, they are a missing architectural layer above them. The transaction layer is the same shape of gap.
The “Obvious” Solution #
The obvious answer to that question is that the disposable API is the hard part, and the industry has already solved it. That’s half right, and the half that’s right is worth understanding, because it’s genuinely good engineering.
The pattern is code mode, and several organizations arrived at it independently. The problem it solves is context economics. In the naive design, every tool an agent might use is loaded into context as a JSON schema before it reads the request. Anthropic reported a Google Drive to Salesforce workflow dropping from roughly 150,000 tokens to 2,000, a 98.7% cut, once tools were exposed as discoverable code rather than preloaded definitions. Cloudflare had a harder version of the same problem: over 2,500 API endpoints, which would exceed a million tokens as native tool definitions, collapsed to roughly 1,000 tokens behind two tools, search
and execute
, backed by a sandboxed V8 isolate.
That is exactly the disposable API. The agent discovers what exists, pulls the signature of the one thing it needs, writes code against a generated stub, and the stub evaporates. Ephemeral, task-shaped, never versioned. It works.
So the ergonomic surface is already disposable. The mistake is assuming the commitment layer can go in the same bin: if the interface is generated on demand, the rollback can be too. The interface is a convenience. The commitment is a promise, and a promise you improvise at rollback time is not a promise.
Why a Saga Refuses to Become Disposable #
Here is the thing I’d want a designer to internalize before writing a line of code: a saga is not a communication pattern, it is a commitment protocol. Every guarantee it provides comes from something being deliberately, permanently not disposable. There are four of them, and each one breaks in a specific way under a non-deterministic executor.
Compensation has to be declared in advance. The point of a saga is that at design time, calmly, you decided what “undo” means for each step. If an LLM improvises the compensating action at rollback time, you’ve introduced creativity at the exact moment in the system’s life when creativity is least welcome. Durable execution frameworks give you the hooks to run compensation; none of them can tell you what compensation means for your domain. That’s a design decision, and it cannot be deferred to inference.
Idempotency keys require stable operation identity. Retry safety depends on recognizing that two calls are the same call. If the interface is regenerated per session, per agent, per task, there’s no stable identity to hash. Two retries become two operations, and you refund twice. Agents retry far more aggressively than humans, which makes this worse rather than better.
The pivot transaction is real and it is not a metaphor. Sending the email, capturing the payment, deleting the object, publishing the post. Past that line there is no rollback. There is only apology. Any design that treats “the agent will figure out how to reverse it” as a general property will discover its irreversible steps in production.
Retry is not replay when the executor is non-deterministic. This is the one that bites hardest. Durable execution works by journaling completed steps and replaying to recover. Replay assumes the code produces the same decisions given the same inputs. An LLM does not. The rule that falls out is blunt: an LLM call must always be a journaled activity, never workflow code. Its output is an input to be recorded, not a computation to be repeated. Get this wrong and your recovery path silently takes a different branch than your original execution did, which is worse than crashing, because it looks like it worked.
What We’re Actually Building Is an Apology Protocol #
Pat Helland settled the philosophical part of this in 2007, in Life Beyond Distributed Transactions and its companion piece
Memories, Guesses, and ApologiesAgents don’t break this model. They make it literal. The guessing is now explicit, probabilistic, and sitting in the middle of your control flow.
So the standard nobody has written isn’t really a transaction protocol. It’s an apology protocol: a way for autonomous parties to make bounded commitments, discover when one can’t be honored, and unwind what’s unwindable while acknowledging what isn’t. The best description of how that works between real, autonomous, unreliable parties isn’t in a distributed systems paper. It’s Gregor Hohpe’s Your Coffee Shop Doesn’t Use Two-Phase Commit, where a barista takes your order, writes your name on a cup, and handles failure with retries, write-offs, and refunds. Written in 2004, and still the most accurate model of agent coordination in print.
The Real Solution: Split the System by Determinism #
The Decision
Stop asking whether agents can run a saga. Split the system along the only line that matters, whether the same input reliably produces the same behavior, and let the boundary between the two halves be an artifact rather than a call.
The intent plane is where agents live. A planner decomposes a goal; specialists advertise capabilities through agent cards and reach tools through MCP. They negotiate, argue, revise. This plane is non-deterministic by construction, and everything in it, including the API surface, is disposable.
Its output is not an action. Its output is a typed, signed plan: steps in order, budgets, declared compensation for each step, and an explicit marker for the pivot.
The execution plane takes that artifact and runs it. Deterministic, journaled, replayable. A durable saga engine handles retries, timeouts, and compensation in reverse order. An effect ledger tracks idempotency keys and undo handles. LLM calls appear here only as recorded activities, never as control flow.
Agents propose. The runtime disposes. This is the same boundary as keeping AI agents out of the deploy step, where the thing which reasons is not the thing which holds the credential. One word changes: the thing which reasons is not the thing which commits.
What Actually Changes
Nothing about the model changes between the two halves. The only difference is which side holds the authority to commit.
The Code
Two pieces. First, the artifact. This is the thing that doesn’t exist in any spec today:
{
"sagaId": "sg_01J8Z9",
"planVersion": 1,
"authoredBy": { "agent": "planner@acme", "model": "claude-opus-5" },
"signature": "ed25519:...",
// The pivot is an index, not a vibe. Everything before it must be
// compensable; everything at or after it is one-way. Validating this
// is a static check. It happens before a single side effect runs.
"pivotIndex": 3,
"mandate": {
// Borrowed wholesale from AP2: bounded in money and in time.
// The orchestrator refuses the plan if a step exceeds this.
"maxSpendEur": 250,
"expiresAt": "2026-08-07T18:00:00Z"
},
"steps": [
{
"id": "s0",
"tool": "vendor.create",
"class": "compensable",
// Declared at design time by whoever owns the tool, NOT generated
// by the planner. This is the load-bearing constraint of the whole
// design: the planner may choose steps, never their undo.
"compensation": { "tool": "vendor.archive", "args": { "ref": "$s0.id" } },
// Derived from (sagaId, step id), so it survives the interface
// being regenerated. This is what makes retries safe.
"idempotencyKey": "sg_01J8Z9:s0"
},
{
"id": "s1",
"tool": "budget.reserve",
"class": "compensable",
"compensation": { "tool": "budget.release", "args": { "ref": "$s1.hold" } },
// A compensable step whose undo expires is not compensable for long.
// The orchestrator checks that the worst-case time to reach the pivot
// fits inside the shortest TTL on the path.
"undoTtlSeconds": 86400,
"idempotencyKey": "sg_01J8Z9:s1"
},
// ...s2 (sandbox provisioning) elided; ids track array positions,
// so s3 really does sit at pivotIndex 3...
{
"id": "s3",
"tool": "payments.capture",
"class": "irreversible",
// No compensation key is permitted here. A tool that claims
// "irreversible" and also ships an undo is lying about one of them.
"idempotencyKey": "sg_01J8Z9:s3"
}
]
}
Second, the orchestrator. This is deliberately framework-shaped rather than framework-specific. Temporal, Restate, and DBOS all give you the durable part:
async function runPlan(plan: Plan, ctx: WorkflowContext) {
// Reject before executing. A plan that puts an irreversible step ahead
// of an unproven one is a bug you catch here, not an incident you
// catch in production.
assertPivotOrdering(plan);
assertUndoTtlCoversPath(plan);
assertWithinMandate(plan);
const done: CompletedStep[] = [];
for (const [i, step] of plan.steps.entries()) {
try {
// Every call carries the saga context. This is the piece no wire
// format has today: the callee can see what it is part of.
const result = await ctx.activity(step.tool, step.args, {
idempotencyKey: step.idempotencyKey,
sagaContext: { sagaId: plan.sagaId, stepId: step.id, causedBy: done.at(-1)?.id },
});
// Write the undo handle BEFORE anything else can fail. An undo token
// you didn't persist is an undo token you don't have.
await ctx.ledger.record(step.id, result, result.undoToken);
done.push({ id: step.id, result });
} catch (err) {
const pastPivot = i > plan.pivotIndex;
const pivotUnknown = i === plan.pivotIndex && outcomeUnknown(err);
if (pastPivot || pivotUnknown) {
// Post-pivot steps are retriable by construction, so reaching
// this branch means the durable layer already exhausted retries.
// That, or the pivot itself is in an unknown state: a capture
// that timed out may have charged. Either way, nothing here is
// safe to unwind. Stop, keep the record, escalate to a human.
// Silently "trying something" is how you double-charge.
await ctx.escalate({ sagaId: plan.sagaId, failedAt: step.id, reason: err });
throw err;
}
// Everything else unwinds automatically. That includes the pivot
// failing cleanly: a declined capture charges nothing, and paging
// a human for every declined card is not an architecture.
const toUndo = [...done].reverse();
if (step.class === "compensable") {
// The failed step unwinds too. It may have timed out in an
// unknown state (did the registry create the record?), so TCC's
// rule applies: cancel must tolerate a try that never happened.
// It returned no undo handle; its cancel locates work by saga context.
toUndo.unshift({ id: step.id, result: null });
}
// Compensation is itself a set of side effects that can fail, so it
// runs through the same durable machinery: retried, journaled, and
// dead-lettered to a human if it exhausts.
for (const c of toUndo) {
await ctx.activity(plan.compensationFor(c.id), { ref: c.result }, {
idempotencyKey: `${plan.sagaId}:${c.id}:undo`,
});
}
throw err;
}
}
}
// The one rule that is easy to state and easy to violate: the model is
// never control flow. Its output is data that gets journaled, and the
// branch is taken by deterministic code reading that journal.
async function replan(ctx: WorkflowContext, failure: Failure): Promise<Plan> {
const raw = await ctx.activity("llm.replan", failure); // journaled activity
return validatePlan(raw); // typed before it can act
}
Three properties hold regardless of what the model produces:
The planner never authors an undo. It selects from compensations declared by whoever owns the tool. Improvisation is confined towhichsteps run, never tohow they are reversed.Identity is derived from the plan, not the call site. The interface can be regenerated per session and the idempotency key does not move.The pivot is a static property. A bad ordering fails validation, not production.
What Breaks #
The undo token’s TTL is shorter than your approval queue. Payment authorizations hold for days; inventory reservations for minutes. Insert one human approval step into a saga and the compensation you planned for step one may have quietly expired by the time step four fails. This is why assertUndoTtlCoversPath
is in the code above, and why the check is genuinely hard: it needs a worst-case time estimate for every step, including the ones that wait on people.
Self-declared reversibility becomes a marketing claim. The moment class: compensable
affects whether a planner picks your tool, every tool declares itself compensable. The classification has to be curated by the platform team the way Treaty curates allowed implementations: an attested property of a capability, not a string in a manifest anyone can write.
Compensation fails too, and nobody plans for it. budget.release
gets a 503. Now you are compensating a compensation. There is no recursive solution here; there is a retry policy, a dead-letter queue, and a human. Every demo of this pattern stops one level before this, and every production system hits it in the first month.
The escape hatch reopens the hole. Someone will want the agent to handle “unexpected” failures adaptively, and will add a tool that lets it call arbitrary endpoints during execution. That single tool deletes every guarantee above. The boundary survives only if it is enforced in the runtime, where the execution plane holds credentials the intent plane cannot reach, rather than agreed in a design doc.
Plans outlive the models that wrote them. A plan authored in August and replayed in November was written by a model that no longer exists at that version. This is the design working as intended, but it means the artifact must be self-contained enough to audit without the planner, which in practice means recording the model ID, prompt hash, and the alternatives it rejected.
The Five Primitives That Are Missing #
If someone wants to write this specification, and someone should, this is the shape of it. None of these exist in MCP or A2A today.
1. Reversibility declaration. Every skill and tool declares its class (compensable
, retriable
, or irreversible
) plus whether it constitutes the pivot. Cheapest and highest-value addition by a distance, because it makes a whole category of bad plan statically rejectable. Agent cards already carry capability metadata; they should carry this.
2. The undo token. The load-bearing idea. Every side-effecting call returns an opaque handle meaning post this back before T+ttl and it never happened. We have exactly one widely deployed instance of this primitive, payment authorization’s auth/capture/void, and its microservice generalization already exists as TCC in Seata. Generalizing auth/capture/void to arbitrary agent actions is the thing nobody has standardized.
3. Effect context propagation. A saga id, step sequence, and causality chain riding every A2A message and every MCP call. Think W3C Trace Context, but carrying commitments instead of spans. Today you can trace an agent workflow perfectly well after the fact; you cannot unwind it, because nothing in the wire format says what belongs to what.
4. Idempotency discipline. The IETF has had a draft Idempotency-Key header sitting in the HTTP APIs working group for years, driven largely by payments. In human-driven systems it was a nice-to-have. In agent-driven systems it is mandatory, and it belongs in the agent transport rather than in each vendor’s application-level convention.
5. Authority mandates. AP2 already solved this for money: a signed, budget-bounded, time-bounded grant scoping what an agent may commit on your behalf. There is no reason that model should stop at payments. Every irreversible action deserves the same treatment.
Trade-offs #
| Gain | Loss |
|---|---|
| Every commitment is recorded and either reversible or explicitly not | Someone must classify every tool by hand, once, and keep it honest |
| Agents keep full freedom above the boundary | They lose the ability to improvise during execution, including when improvising would have helped |
| Rollback authority lives in exactly one place | That orchestrator is a new single point of failure and a scaling bottleneck |
| You can answer “what did the system do last Tuesday, and why” | A second artifact to type, sign, version, and store per saga |
| Swapping the planner model doesn’t invalidate execution history | Plans from a smarter model still execute at the old runtime’s capability |
This is worth it when the side effects are expensive and the audit is real. It is over-engineering when your agent’s worst failure is a wasted API call.
When to Use This #
Adopt the two-plane split when you have irreversible side effects (money, notifications, deletions, provisioning), more than a handful of agents crossing team or organizational boundaries, and someone who will be asked, in writing, what the system did. Avoid it when your agents only read, when a failed run can simply be re-run from scratch, or when you have two agents.
That last one deserves emphasis, because it’s where most teams are: don’t build a protocol for two agents. A documented message contract with explicit failure handling covers the overwhelming majority of two-agent pipelines without a protocol dependency or its operational surface. If you’re there, the honest answer is that you have a workflow, not a distributed transaction problem.
And one thing I’d hold firm on regardless of scale: never let agents choreograph. In classical microservices, choreography is a legitimate design choice with real trade-offs. With non-deterministic participants it is not: you get emergent behavior, unbounded fan-out, no single point of rollback authority, and an audit trail that reads like a transcript rather than a log. Agents negotiate the plan; one deterministic orchestrator executes it.
Operational Notes #
Monitoring: compensation success rate is the metric that matters, followed by the count of undo handles approaching TTL with their saga still open. Both are leading indicators of the reconciliation work you’re about to inherit.Reproducibility: store the plan artifact, the model ID, and a hash of the planner prompt alongside every execution. When behavior shifts you need to know whether the planner changed, the tools changed, or the world did.Permissions: the execution plane holds credentials the intent plane cannot reach. If a planner agent can callpayments.capture
directly, the architecture is decorative.Rollback: disabling the planner must leave previously issued plans executable. If removing the model breaks in-flight sagas, the model was control flow.Failure modes: expect plans that fail validation, compensations that fail, tools that misdeclare their class, and mandates that expire mid-saga. Design so all four are survivable, because none are preventable.
The Prior Art #
Read this before writing anything down as a spec. It’s older than the problem:
- Garcia-Molina & Salem, (1987) — long-lived transactions and compensating actions, done properly the first time.Sagas - Pat Helland, (2007) — entities and activities; its companion pieceLife Beyond Distributed Transactionsnames the coping strategy.Memories, Guesses, and Apologies - Gregor Hohpe, (2004) — accidentally the best description of agent coordination ever written.Your Coffee Shop Doesn’t Use Two-Phase Commit
The agentic web has spent two years teaching machines to introduce themselves. That was the easy half.
Conclusion #
A protocol that only standardizes speech has taught agents to promise anything and guarantee nothing.