Agent-era architecture · Practical technical guide · 18 October 2026
How an existing saga orchestrator should admit plans proposed by an AI agent: the agent selects steps from a catalogue, the orchestrator validates the plan, owns every compensation and the pivot step, and asks a human before anything irreversible runs.
What changes when the planner is a model
A saga orchestrator in production already runs multi-step business transactions without a distributed lock: it executes steps in order, records what happened, and when a later step fails it runs compensations for the earlier ones. The sagas it runs are fixed; a developer wrote each definition. The pattern itself, with its compensable, pivot and retriable steps, is covered in the saga pattern in depth.
An agent changes one thing: the plan is no longer fixed. A customer email asks to split a shipment and apologise with a small credit, and the agent assembles a sequence of steps that no developer wrote in advance. The tempting design is to let the agent call the services directly and clean up if something fails. That throws away the orchestrator, the one component that already knows how to clean up. The better design treats the agent as a plan proposer and keeps the orchestrator as the executor; the plan becomes data that is validated before anything runs.
What stays deterministic
| Concern | Who decides | What the agent contributes |
|---|---|---|
| Which steps exist, their kind and their compensation | A step catalogue in code | Nothing; it can only choose from the catalogue |
| Whether a proposed plan is well formed | A plan validator | A plan, re-planned when the validator returns errors |
| Execution order, retries and timeouts | The orchestrator | Nothing |
| Running compensations after a failure | The orchestrator, in reverse order, with idempotency keys | Nothing; the agent never calls a compensation |
| Approval before the pivot step | Policy code, by amount or by customer tier | The explanation shown to the approver |
| Provenance of the run | Saga log with run id, prompt version and source document | The free-text rationale |
| Choosing and arguing the steps | The agent | Interpretation of the request and the arguments for each step |
Every row except the last is enforced by code that already exists or is a small addition to it. The agent owns exactly one decision: which catalogue steps, with which arguments, in which order. The design assumes that decision will sometimes be wrong: the validator catches malformed plans, the pivot approval catches expensive ones, and the orchestrator guarantees that a failed plan leaves no half-finished effects behind.
The plan is data, selected from a catalogue
Define the catalogue as the orchestrator sees it: each step has a name, an argument schema, a kind and, for compensable steps, the compensation action. The three kinds matter for validation. Compensable steps can be undone by a business action such as releasing a reservation. The pivot step is the point of no return; after it succeeds the saga must complete. Retriable steps come after the pivot and are retried until they succeed, because there is nothing to compensate any more.
The validator enforces four rules: every step name exists in the catalogue, arguments satisfy the schema, at most one pivot is present with compensable steps before it and retriable steps after it, and preconditions that need no execution, such as the order not being shipped yet, hold now. A plan that fails goes back to the agent with the errors, and the agent may re-plan a bounded number of times before the request is handed to a person.
Worked example: a fulfilment change asked for in free text
Order 4471 has one line item worth 180 and is paid by card. The customer writes: ship from the closer warehouse, and I expect something for the delay. The agent proposes six steps: reserve stock at warehouse B, authorise the payment of 180, apply a goodwill credit of 20, capture the payment, create the shipment and send the confirmation.
Validation rejects the plan once: apply a goodwill credit is not in the fulfilment catalogue. The agent re-plans without it and adds a note for a human to consider a credit through the normal support process. The orchestrator reserves stock and authorises the payment. Capturing 180 is the pivot, and the policy says captures above 150 need approval, so the saga pauses with the agent rationale attached. Once approved, the capture runs, the shipment is created and the confirmation is sent.
Now change one thing: the payment authorisation fails permanently. The orchestrator stops, releases the reservation with a compensation key derived from the saga id and step index, and records the saga as compensated; if the first release call times out and is retried, the key makes the retry a no-op. The agent sees one outcome, compensated, with the reason. The lab linked below runs exactly these variants.
Validating and executing the plan in code
Illustrative python, checked for syntax; adapt names and storage to your system.
CATALOGUE = {
"reserve_stock": {"kind": "compensable", "undo": "release_stock"},
"authorize_payment": {"kind": "compensable", "undo": "void_authorization"},
"capture_payment": {"kind": "pivot"},
"create_shipment": {"kind": "retriable"},
"send_confirmation": {"kind": "retriable"},
}
ORDER = ["compensable", "pivot", "retriable"]
def validate_plan(plan):
errors, seen_kinds, pivots = [], [], 0
for i, step in enumerate(plan):
entry = CATALOGUE.get(step["name"])
if entry is None:
errors.append(f"step {i}: {step['name']} is not in the catalogue")
continue
kind = entry["kind"]
pivots += kind == "pivot"
if seen_kinds and ORDER.index(kind) < ORDER.index(seen_kinds[-1]):
errors.append(f"step {i}: {kind} step after a {seen_kinds[-1]} step")
seen_kinds.append(kind)
if pivots > 1:
errors.append("more than one pivot step")
return errors
def run_saga(saga_id, plan, execute, compensate, approve, retries=2):
"""execute/compensate/approve are injected service calls. Returns the outcome."""
done = [] # compensable steps that took effect
for i, step in enumerate(plan):
kind = CATALOGUE[step["name"]]["kind"]
if kind == "pivot" and not approve(saga_id, step):
return "awaiting_approval"
for attempt in range(retries + 1):
ok = execute(saga_id, i, step) # idempotent on (saga_id, i)
if ok:
break
if not ok and kind == "retriable":
return "needs_human" # past the pivot: forward recovery only
if not ok:
for j, earlier in reversed(done):
undo = CATALOGUE[earlier["name"]]["undo"]
compensate(f"{saga_id}:{j}:undo", undo, earlier)
return "compensated"
if kind == "compensable":
done.append((i, step))
return "completed"Three properties carry the safety. The validator runs before any effect, so an unknown step or a misplaced pivot never executes. Compensation is driven from the orchestrator log of what took effect, not from the agent memory of what it asked for. And every compensation call carries a key derived from the saga id and step index, so a retried compensation does not release stock twice. The agent is not a parameter of this function; it produced the plan earlier and receives the outcome later.
Compensation belongs to the orchestrator
Giving the agent a release-stock tool and asking it to clean up on failure fails in three predictable ways. It forgets: after a failure three steps deep it compensates the most recent step and moves on. It duplicates: a compensation that timed out is retried as a fresh call with no key, the same bug that produces double refunds. And it improvises: after the pivot, where no compensation exists, it reaches for the nearest tool that looks like an undo, such as a refund, and creates a business event nobody planned.
Keep compensations out of the agent toolset entirely. The agent can observe that a saga was compensated and explain it, but cannot trigger an undo. For failures after the pivot, the orchestrator records the saga as needing a human and attaches the agent rationale. Compensation is also semantic rather than a rollback: releasing a reservation after someone else took the last unit is a business situation, and the orchestrator should emit an event for it rather than fail silently.
Failure modes worth testing
- Pivot in the middle. A plan that captures payment and then reserves stock cannot be compensated if the reservation fails. The validator must reject it; test that it does.
- Re-plan loops. An agent can propose the same bad plan again. Cap re-plans at two or three and escalate.
- Stale approval. A human approves a capture, but the order changed while the request waited. Re-check preconditions at execution time, as human approval gates describes.
- Arguments shaped by the request text. A customer who writes credit me 500 is data, not authority. Amount limits live in the catalogue schema and in the pivot policy.
- Compensation without idempotency. A timed-out undo retried without a key releases twice. Test two calls with one key produce one effect.
- Agent-owned cleanup. If any compensation tool is reachable by the agent, the three failures above return. Test the toolset, not just the prompt.
Migration steps for an existing orchestrator
- Turn the existing saga definitions into a catalogue with kinds, argument schemas and compensation actions. Nothing changes at runtime yet.
- Add the plan validator and a plan-as-data entry point next to the fixed sagas. Submit existing sagas through it to prove it accepts them.
- Extend the saga log with actor type, run id, prompt version and the source document id, and copy them into every event the saga emits.
- Add the pivot approval policy as code, with thresholds by amount and customer tier, and a queue for approvers that shows the agent rationale.
- Run the agent in shadow mode: it proposes plans for real requests, the validator scores them, humans still execute. Measure rejection reasons and fix the catalogue descriptions the agent misreads.
- Enable execution for low-value sagas first, keep compensations out of the agent toolset, and review compensated and needs-human sagas weekly.
When an agent is unnecessary
If the plan can be derived from a form, a rule table or a fixed decision tree, use that; a fixed saga with validated input is cheaper, faster and fully testable. An agent earns its place only when the plan genuinely varies with unstructured input. Even then, if most requests map to three plan shapes, encode those as fixed sagas and send only the remainder to the agent. The orchestrator, the catalogue and the compensation log are the same in both cases: the agent is a client of the saga system, not a replacement for it.
Practice and related reading
Hands-on: Agent-planned saga: catalogue checks, the pivot and who compensates, an interactive lab on synthetic data.
Further reading:
- Saga pattern, in depth: compensations, the pivot step and a durable orchestrator
- Saga: choreography versus orchestration
- Sequential agents in ADK Java: rollback and compensation when a later stage fails
- Human approval gates in autonomous agent systems
- Agents as command senders in CQRS and event-sourced systems
Sources and scope
Checked against these sources on 18 October 2026. Examples and figures are illustrative, not measurements.