Agent Experiences

The Delegation Cycle

Delegate, attempt, feedback — and what each turn of the loop needs from your service.

In short

Agents work in a loop: a person states an intent, the agent decomposes it into steps, attempts a step against some service, interprets whatever came back, and then retries, adapts, or escalates to the human. Your service participates in that loop only through what it returns. Every response is simultaneously a result and an instruction — it tells the agent what happened and, implicitly, what to do next. A service is agent-ready when its responses answer the second question as clearly as the first: what failed, why, whether retrying could help, and what state the world is in now.

This is a different contract from the one an API has with a human developer. A developer reads your docs once, writes code against the happy path, and debugs failures with a stack trace and a search engine. An agent has no build step. It discovers, attempts, and recovers at runtime, on every request, with no prior familiarity and no ability to file a bug report. Everything it knows about your service it learned in the last few seconds.

The five stages

1. Intent

A person says what they want, usually underspecified: “renew the annual plan on the work account,” “find out why the last invoice is higher.” The agent holds an outcome, not a procedure. It also holds a budget — of time, of tokens, of the user’s patience — that determines how much failure it will absorb before giving up on your service and trying another route.

2. Decomposition

The agent turns the outcome into candidate steps, which requires knowing what your service can actually do. It builds that picture from whatever is machine-readable at that moment: a tool list from an MCP server, an OpenAPI description, a documentation page it can fetch as text, or the affordances it can see in the rendered page. If none of that is available, it guesses — and a guessing agent produces exactly the malformed traffic you were worried about.

3. Attempt

It executes a step. This is the only stage most API design accounts for, and it is the least interesting one, because the happy path is easy. What matters is that attempts are made speculatively: the agent will often try a call it is not certain is correct, because trying is cheaper than being sure.

4. Interpretation

The agent reads the response and decides what it means. This is where most agent–service interactions actually break. A 400 with the body "Invalid request" gives it nothing to act on: it does not know which field, whether the value was malformed or merely not permitted for this account, or whether a different approach exists. So it retries the same call with a trivial variation, or invents a parameter, or tells the user your service is unavailable.

5. Retry, adapt, or escalate

Based on that interpretation the agent repeats the step, changes approach, or hands control back to the person. Escalation is a success condition, not a failure — an agent that stops and says “this needs your card details, here is the link” has done the right thing. What you want to avoid is the silent middle case: an agent that keeps retrying a call that can never succeed, or that reports success for something that half-happened.

What the loop needs from your service

Discoverable capabilities

The agent has to learn what is possible before it can plan. Give it a machine-readable inventory — a tool list, an OpenAPI document, a plain-text or markdown docs endpoint — that states operations, required parameters, and preconditions. Two properties matter more than completeness: the description must be reachable without authentication where possible, so the agent can plan before it has credentials, and it must be honest about what the current caller may do. A capability the agent will be denied is worse than a capability it never saw.

Unambiguous, machine-readable errors

An agent cannot read “Oops! Something went wrong.” It needs a stable error code it can branch on, the specific field or resource at fault, and — the field almost nobody ships — whether a retry could ever succeed. Retryability is not inferable from the status code alone: a 429 is retryable, a 400 is not, and a 409 depends entirely on why.

Unusable

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "success": false,
  "message": "Oops! Something went wrong."
}

Nothing here is actionable. The agent cannot tell what to change, whether the failure is permanent, or whether the operation partially applied. Its best available move is to retry identically — which is how a single bad response becomes a traffic spike.

Usable

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.example.com/errors/invalid-currency",
  "title": "Unsupported currency for this account",
  "status": 422,
  "detail": "Account acct_18f settles in USD. GBP is not enabled.",
  "instance": "/v1/orders",
  "errors": [
    {
      "field": "currency",
      "code": "unsupported_value",
      "allowed": ["USD", "CAD"]
    }
  ],
  "retryable": false,
  "docs": "https://docs.example.com/orders#currency"
}

Now the agent can act: switch to an allowed value, or stop and tell the user their account is not enabled for GBP. retryable: false ends the loop cleanly. Theapplication/problem+json shape is RFC 9457, which means the envelope is already familiar to models and to libraries.

Two rules keep this useful over time. Error codes are API surface: once unsupported_value means something, it has to keep meaning it, because agents branch on the string. And never put information in the human-readable detail that is not also expressed structurally — prose is the fallback, not the channel.

Idempotency, because agents retry

Retrying is the agent’s primary recovery strategy, and it retries under exactly the conditions where you least want it to: timeouts, dropped connections, ambiguous 500s. In every one of those cases the agent does not know whether the first request took effect. If your write endpoints are not idempotent, that uncertainty becomes a double charge, a duplicate booking, or two support tickets — and it becomes your problem, not the agent vendor’s.

  • Accept a client-supplied idempotency key on every state-changing request and return the original response for a repeat of the same key, rather than executing again.
  • Scope keys to the authenticated principal and store them long enough to outlive a plausible retry window — a key that expires in thirty seconds does not protect against an agent that resumed after an escalation.
  • Say so in your capability description. An agent that knows an endpoint is idempotent can retry confidently; one that does not must either risk duplication or give up.
  • Where a key is impractical, offer a way to check: a lookup by client reference that lets the agent ask “did my order actually go through?” before trying again.

Partial-failure semantics

Agents naturally batch — “update all of these,” “cancel the ones from March.” When a batch half-succeeds, a single overall status code is a lie in both directions. A 200 hides the failures; a 500 implies nothing happened and invites a retry that duplicates the part that worked.

  • Return per-item outcomes with stable identifiers, so the agent can retry precisely the items that failed.
  • State the transaction model explicitly: all-or-nothing, or best-effort. The agent cannot guess it, and the recovery strategy is completely different for each.
  • If an operation cannot be atomic, expose the compensating action — the cancel, the refund, the rollback — as a first-class capability rather than a support process.

Progress and state for long operations

Anything that takes longer than a request timeout needs a state machine the agent can observe. Accept the work, return an identifier and a status resource immediately, and let the agent poll or subscribe. The status should carry an explicit phase, a terminal-or-not flag, and a suggested next poll interval so the agent does not choose one for you. Where the operation might need human input to continue — a confirmation, a payment step — that should be a distinct state with a URL the agent can hand to its user, which is the cleanest possible escalation.

The same reasoning applies to reads that change: an agent working over minutes needs to know whether what it fetched is still current. Conditional requests and clear resource versioning let it re-check cheaply instead of refetching everything.

Preference forms from the loop

The cycle does not just resolve one task. Agents accumulate evidence about which services are worth attempting — through explicit memory, through the tools a user keeps connected, and through the plain fact that a route which worked before gets tried first. A service that fails legibly and recovers cleanly gets attempted again. A service that returns opaque errors gets routed around, and nobody tells you.

The principles page turns these requirements into design rules, and implementation covers the specific protocols to build them on.

Stay Updated

Analysis of AI search, crawler policy and agent standards — sent when there is something worth reading, roughly twice a month. Unsubscribe anytime.

We store your email address only to send you this newsletter. See our privacy policy.