Essay · AI Stack

Can an Agent Drive It?

I think every capability we build now needs to pass one additional architecture test: can an agent drive it?

I don't mean "does it have an API?" or "can we put MCP in front of it?" I mean something broader. Today a business capability might be initiated by a user in a UI, a batch job, an event, an asynchronous process, another application, a partner API, or a completely headless workflow. Increasingly, an agent can become the driver of any of those paths.

← Back to essays

That changes how I think about designing new capabilities. This post is the full version of that argument: what the problem actually is, what a process API is and why it is the answer, and how to build one so that agents, and everything else, can drive your capabilities safely.

The problem: agents are stuck between demo and production

The urgency is not hypothetical. A Camunda-commissioned survey of 1,150 senior IT and business leaders and enterprise architects found that 71% of organizations say they are using AI agents, but only 11% of agentic use cases reached production over the previous year. That gap is not only a model problem. I think a big part of it is architectural, and specifically about where we put the orchestration.

For years, we made APIs increasingly granular so consumers could compose them. That works well when developers encode the composition into software, test it, and ship it. The composition is encoded, reviewed and tested, and subsequent executions follow that defined path unless the implementation changes.

But when the consumer doing the composition is an agent, we have another choice to make. Do we give the agent eleven operations and let it reconstruct a well-known transaction at runtime? Or do we expose the business capability and let deterministic software own how that transaction executes?

Walk through what the first option actually means. Say the transaction is cancelling a policy. Granularly, that is something like: check eligibility, check for open claims, calculate the refund, reverse pending billing, update the policy state, release any linked coverages, notify the customer, notify the agent of record, write the audit events, and handle whatever regulatory notice the jurisdiction requires. Ten or eleven calls, in an order that matters, with rules about what happens when step seven fails after step five succeeded.

When we ask a model to compose that transaction at runtime, three new costs appear. The sequence is re-derived on every request, probabilistically, so two identical cancellations can take different paths. If recovery and compensation are not encoded below the agent, a mid-sequence failure turns recovery itself into another runtime reasoning problem. And you pay for all of it per call: every model-directed step adds latency, tokens, and another failure surface, to re-plan something that never needed re-planning. The point is not that agents are incapable. It is that business invariants should not depend on whether the model remembers them correctly this time.

Two ways to cancel a policy Left: an agent plans and calls ten granular operations one at a time; step seven fails after steps one to six have committed, leaving the transaction half-done. Right: the agent makes one cancel_policy call and a deterministic capability runs the same ten steps in fixed order with sequencing, authorization, idempotency, compensation, audit and named errors built in. A · The agent composes it at runtime Agent plan → call → observe → re-plan 1 · Check eligibility 2 · Check open claims 3 · Calculate refund 4 · Reverse pending billing 5 · Update policy state 6 · Release linked coverages committed 7 · Notify customer ✕ fails 8 · Notify agent of record 9 · Write audit events 10 · Regulatory notice never reached Step 7 fails after 1–6 committed. Who unwinds them? Sequence re-derived per request · ten model round-trips B · The capability executes it deterministically Agent intent outcome, or a named error cancel_policy(policy_id, effective_date, reason, idempotency_key) 1 · Eligibility 2 · Open claims 3 · Refund 4 · Reverse billing 5 · Policy state 6 · Release coverages 7 · Notify customer 8 · Notify agent 9 · Audit events 10 · Regulatory notice Sequencing Authorization Idempotency Compensation Audit trail Named errors Sequence is code, tested once, same for every caller. Compensation built in · one round-trip
The same cancellation, driven two ways. On the left the agent is the orchestrator, and the failure at step seven is now the agent's problem to unwind. On the right the agent expresses intent once, and the guarantees, including what happens when step seven fails, are code that was tested before it ever ran in production.

The economics of the alternative have changed too. The old objection to building capability-level APIs was proliferation: every use case wanted another orchestration layer, and all of it had to be built and maintained. AI changes that tradeoff. Coding agents have lowered the cost of writing that deterministic layer. They do not remove the cost of owning it, but they change the tradeoff. I would rather spend intelligence at build time encoding and testing a repeatable path than buy the same reasoning again on every production call.

What a process API actually is

The term comes from API-led architecture, which layered APIs into three tiers: system APIs that expose individual systems of record, process APIs that compose them around a business purpose, and experience APIs that shape them for a particular channel. The middle layer is the one that matters here.

In API-led architecture, process APIs sit between channels and systems of record, composing underlying capabilities around a business purpose. For the high-consequence, agent-driven use cases I am describing here, I think that layer should also own the deterministic transaction. One request expresses the intent ("cancel this policy, effective this date, for this reason") and everything required to honor that intent correctly lives behind the interface:

  • Sequencing. The steps run in the right order, every time, because the order is code, not a plan a model produced this morning.
  • Authorization and policy checks. Who may cancel, under what conditions, enforced at the capability, not left to the caller's judgment.
  • Idempotency. The same request submitted twice, because an agent retried or a user double-clicked, produces one cancellation, not two.
  • Compensation. When step seven fails after step five succeeded, the capability owns how to compensate, retry, or park the transaction safely. Recovery is not delegated back to the caller.
  • Auditability. Every execution leaves the same trail regardless of who initiated it, which is what your regulators and your incident reviews actually need.

The contract also carries deterministic error semantics. Not "something went wrong" but "ineligible: open claim exists," "refund requires supervisor approval," "already cancelled on this date." An agent can act sensibly on errors like that. It cannot act sensibly on a stack trace.

Notice what the interface does to the intent problem. The tool description, the input schema, the eligibility rules and the error vocabulary are the specification of what this capability means. A UI absorbs ambiguity because a human clicks around until the system does what they meant. An API consumed by an agent has no absorbing layer, so the contract has to carry the intent explicitly. Designing a process API is intent capture, formalized.

Agent-ready does not mean agent-controlled

This is the distinction the whole design hangs on. An agent may determine that a policy needs to be cancelled. A scheduled job might reach the same conclusion, or a customer might request it through a UI. The driver can change. But eligibility checks, authorization, billing adjustments, state transitions, notifications, idempotency, compensation and audit requirements should not change depending on who initiated the action.

So the split is: the agent decides which capability it needs, and the platform guarantees how that capability executes. The agent's job is intent: understanding that the customer wants out, determining that cancel_policy is the right capability, gathering the parameters, choosing what to do with the outcome. The platform's job is execution: everything in the list above.

The driver is replaceable. The capability is not. Five drivers, a user in a UI, a batch job, an event or async process, a partner API and an agent, all invoke the same cancel_policy process API. A dashed boundary between them marks where the driver's decisions stop and the platform's guarantees begin. The process API carries sequencing, authorization, idempotency, compensation, audit trail and named errors, and calls the system APIs for policy admin, billing, claims, notifications and the audit log beneath it. Drivers · replaceable adapters User in a UI Batch job Event / async Partner API Agent new driver, same contract ↑ the driver decides: which capability, with what parameters ↓ the platform guarantees: how it executes, for every driver Process API · the business capability · not replaceable cancel_policy(policy_id, effective_date, reason, idempotency_key) The intent arrives as one request. Everything needed to honor it lives here. Sequencing Authorization & policy Idempotency Compensation Audit trail Named errors Fix a guarantee here once and every driver inherits it. Policy admin Billing Claims Notifications Audit log System APIs · systems of record
Where probabilistic reasoning stops and deterministic enforcement begins. Everything above the dashed line is a driver, and drivers are adapters. Everything below it is the capability, and the capability does not care who called.

Anthropic draws a similar line between agents and predefined workflows: use dynamic reasoning where the problem requires it, and predictable code paths where the task is well defined. A process API is that principle expressed as an interface.

How to do it

The practice, as I think about it, in five moves.

  1. Pick the candidates by blast radius. Not every operation needs a process API, and building them indiscriminately recreates the proliferation problem. My rule: anything touching money, contracts, compliance, or irreversible state changes goes behind a deterministic capability. Reversible, low-stakes work (searching, drafting, reading, comparing) can stay granular and let the agent compose freely. If you want a sharper test: would you let a new hire improvise this sequence on their first day without review? If not, do not let a model improvise it either.

  2. Define the contract from the intent, not the data model. The request should read like the business intent: cancel_policy(policy_id, effective_date, reason, idempotency_key). If your draft contract mirrors your database tables or forces the caller to know your internal sequence, you have written a system API with a long name. A good check: a Product Owner should be able to describe the capability without describing a screen, and the contract should match that description.

  3. Encode the guarantees inside, then write the error vocabulary. Sequencing, checks, compensation and audit live behind the interface as tested code. Then spend real design effort on the failure modes, because that is most of what the caller experiences: enumerate the legitimate reasons this capability can decline or partially proceed, and give each one a stable, named, machine-actionable response. This is also where testing gets cheap. A deterministic capability can be tested with the conventional unit, integration and contract tests we already trust. Prompt-directed orchestration can be evaluated too, but its behavior cannot be asserted in quite the same way.

    Concretely, the surface an agent sees ends up looking something like this. Notice that every error tells the caller what to do next, and that a replayed request is a success, not a failure:

    {
      "name": "cancel_policy",
      "description": "Cancel an in-force policy as of an effective date.",
      "use_when": "The customer has confirmed they want coverage to end.",
      "not_for": "Mid-term coverage changes.",
      "input": {
        "policy_id": "string",
        "effective_date": "date",
        "reason": "customer_request | non_payment | replaced",
        "idempotency_key": "string"
      },
      "errors": {
        "INELIGIBLE_OPEN_CLAIM": "Resolve the open claim first.",
        "REFUND_REQUIRES_APPROVAL": "Route to a supervisor.",
        "ALREADY_CANCELLED": "Same request replayed. Treat as success.",
        "EFFECTIVE_DATE_OUT_OF_RANGE": "Date outside the permitted window."
      }
    }
  4. Expose it as a tool, thinly. This is where MCP comes in, and it is deliberately the last step rather than the first. MCP is emerging as a common way for agents to discover and invoke capabilities, not as a replacement for APIs, and both AWS and Azure now ship gateways that expose existing APIs as MCP tools. The protocol is the easy part. The tool description should say what the capability does, when to use it, and what its errors mean, and nothing else, because everything else is enforced behind it. Curate the surface: a handful of capability-level tools beats an export of your entire API catalog.

  5. Keep the agent layer light. Frameworks and managed runtimes handle context, memory, tool selection and dynamic reasoning. Resist the temptation to encode business guarantees there, even when the framework makes it easy, and here is why it matters: a guarantee implemented in one agent harness protects that execution path only. A guarantee implemented behind the capability travels with the capability. The UI gets it. The batch job gets it. The async process gets it. The partner integration gets it. And the agent gets it. Every caller inherits the same business guarantee.

The test, applied

None of this is a new idea. Hexagonal architecture told us decades ago that the driver is an adapter. Agents just raised the price of ignoring it.

Which is why the test works role by role. For a Product Owner: can I describe this capability without describing the screen? For an architect: can another trusted consumer invoke it without depending on our UI, and where exactly does probabilistic reasoning stop and deterministic enforcement begin? For a developer: is the business behavior actually behind the capability, or is some of it still trapped in frontend code and human procedure?

So when Product Owners, architects and developers design a new feature, I think the question should increasingly be: if an agent became the driver of this tomorrow, would we have to redesign the capability? If the answer is yes, we may have coupled too much of the business behavior to today's execution path.

The driver should be replaceable. The business capability should not be.

And then comes the harder architecture question: what can the driver decide, and what must the platform always guarantee?