Runs

The lifecycle of an agent run, from the handle you get first to following, steering, stopping and continuing it.

A run is one piece of work an agent does: a conversation turn it plans and carries out, or a direct tool call that halted for approval. It is a durable, recorded object with a name, the runUid, and you receive that name before any work starts. Everything else (following it, sending it a new instruction, stopping it, having it approved) is keyed by that one handle.

Nothing about a run depends on your connection staying open. Close the tab, lose the network, restart your process: the run carries on, and you pick it back up by its runUid.

Statuses

statusMeaningWhat can happen next
runningIn flight.Poll, send a message, stop
waitingParked on slow work it started itself. wait says what for and until when.Resumes on its own; you can also message or stop it
pending_approvalHalted on a step that needs a person. pendingSteps says which.An approver decides; a message supersedes the step; stop
budget_pausedStopped with work left. resumable: true.An approver continues it; stop
completedFinished. message is the answer.Nothing
canceledSomeone stopped it.Nothing
failedIt could not finish. detail says why.Nothing

The difference between waiting and budget_paused is deliberate. A waiting run is blocked on something it already started at your request, so it picks up again without anyone pressing anything. A paused run is a checkpoint for a person, so it never continues by itself.

Every response carries actions: exactly the moves available for the run's current status, each with its method and href. Read them rather than building URLs.

Following a run

Poll GET /{appUid}/agent/runs/{runUid} every few seconds, or start a short run with ?wait=true to hold the connection until it ends (see Waiting for short calls).

{
  "ok": true,
  "status": "running",
  "runUid": "0199f1d2-4b6a-7c3e-8a1f-2d9e6b0c4a77",
  "message": "",
  "resumable": false,
  "pendingSteps": [],
  "steps": [
    { "stepIndex": 0, "tool": "listRecords", "status": "executed", "output": { "meta": { "matched": 3, "returned": 3 } } }
  ],
  "progress": {
    "stepsCompleted": 1,
    "seq": 2,
    "currentStep": { "stepIndex": 1, "tool": "refund_order", "startedAt": "2026-10-29T14:02:11Z" },
    "heartbeatAt": "2026-10-29T14:02:14Z",
    "stale": false
  },
  "activity": { "seq": 7, "text": "Checking the order history", "step": 1, "at": "2026-10-29T14:02:12Z" },
  "actions": {
    "poll": { "method": "GET", "href": "/{appUid}/agent/runs/0199f1d2-4b6a-7c3e-8a1f-2d9e6b0c4a77" },
    "cancel": { "method": "POST", "href": "/{appUid}/agent/runs/0199f1d2-4b6a-7c3e-8a1f-2d9e6b0c4a77/abort" },
    "message": { "method": "POST", "href": "/{appUid}/agent/runs/0199f1d2-4b6a-7c3e-8a1f-2d9e6b0c4a77/messages", "body": { "message": "" } }
  }
}

steps lists finished steps only. progress is how you tell a slow step from a dead run:

  • currentStep is what it is doing right now, and since when.
  • seq increases at every step boundary and never resets, even across a resume. If it moved between two polls, the run is progressing.
  • heartbeatAt is the last time the process driving the run proved it was alive.
  • stale: true means nothing is driving it any more. It will be settled into an explicit outcome shortly; you do not need to keep waiting.

activity is the run's current status line, the same one the console shows, with a counter that moves on every visible change.

A step that did not run carries a detail saying why. On a step an approver rejected, that is their reason.

What a run produced

When a run made something, the poll carries an assets block alongside actions. It lists the charts the run made: each one with its uid and title, and links to view or share it, emitted only when the agent's roles actually grant them. A share link appears there only while it is live; revoke it and the next poll stops offering it.

Messages to a live run

A conversation has one run going at a time. A message to it does not start a second run beside it: it is delivered into the one that is going.

curl -X POST "$AUTHORIC_URL/$APP_UID/agent/runs/$RUN_UID/messages" \
  -H "Authorization: Bearer $AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Only the 2024 invoices, please.", "seenSeq": 7 }'
The run isYour message
runningIs read at its next checkpoint: before its next model turn, or after the model decided on a call but before it ran (that call is then not made). A turn still being generated is redone with your message in it. A tool already running is never interrupted.
waitingWakes it now. The wait ends early and the agent reads your message.
pending_approvalSupersedes the halted step. It can never run, the record says it was superseded rather than rejected, and the agent re-plans.

The response is 202 with the run's poll payload and steered: true. Pass seenSeq, the activity.seq you last saw, and the agent is told which steps finished after you wrote, so "don't touch the vendors" is reconciled with a step that already did.

A message to a run that has finished is 409 AGENT_RUN_NOT_ACTIVE. To carry on the same conversation, invoke again with the run's sessionUid. A sessionUid whose run is still going does the same as this route. Only the caller that started a run can message it (409 AGENT_SESSION_BUSY otherwise), and a message never approves anything.

Waiting on slow work

Some actions start work that finishes later, such as provisioning or restoring an app. An agent waits for that work instead of polling it: the run reports waiting, with wait.label and wait.until, and resumes on its own when the work completes, fails, or the deadline passes. Every risky step after that still halts for approval as usual.

Stopping a run

curl -X POST "$AUTHORIC_URL/$APP_UID/agent/runs/$RUN_UID/abort" \
  -H "Authorization: Bearer $AGENT_KEY"
  • Only this ends a run. Disconnecting does not.
  • It is recorded, on the run, rather than being an accident of a dropped connection.
  • It is idempotent. Stopping a run that already finished, or is already stopping, returns 200 and says so (alreadyFinished, alreadyRequested).
  • A running run stops at its next checkpoint. A tool call already in flight is allowed to finish, never abandoned half-way. Until then it still reports running. A paused or waiting run is settled at once.

An API key may stop its own runs because stopping only ever reduces what an agent does. In the console, Stop run sits beside Approve and Reject.

Paused runs

A run reports budget_paused with resumable: true when it stopped with work left for a reason a person should look at:

  • It reached its effort checkpoint. Effort (Brief, Short, Moderate, Long, Max) sets how many steps it runs before checking back in.
  • The process driving it stopped between steps. Its record is intact, so continuing replays it and carries on.
  • An AI spend limit you set was reached. Raise the limit, then continue.
  • Your plan's included AI credits ran out and the agent has no AI connection of its own to fall back to.

None of these continues by itself. Continuing (actions.continue) takes an approver's credential, like approving, so a script cannot loop past a checkpoint on its own. An agent set to auto-approve Budget checkpoints runs past its checkpoints without pausing, up to an absolute ceiling that no setting lifts; a run that hits the ceiling stops and is never continued automatically.

If the process stopped inside a step, so that a call may already have taken effect, the run is failed with detail naming the step instead. It is never replayed automatically: repeating a charge is worse than asking you.

Sub-agents

An agent with sub-agents can hand a subtask to one. The sub-agent runs as itself, under its own roles, reach and approval settings, in a run of its own that belongs to the coordinator's run. If it halts or pauses, the coordinator stops with it, so the whole tree waits together rather than one branch stalling quietly.

A key bound to a coordinator can read and stop the sub-agent runs its own runs started. GET /agent/runs lists only the runs you started; add ?includeDelegated=1 to include sub-agent runs, flagged "delegated": true.

Undoing a write

Every row an agent writes during a run is recorded, and the step's output carries a revert: { changeSet, rows, revertible } handle (never the values). Ask the agent to undo it and it uses its revertChanges tool; a member can do the same from the Arbiter conversation. A revert writes the recorded rows back as the same agent, through the same policy and approval rules, and skips any row that has changed since rather than overwriting it. A redacted value is kept out of the change record, so a revert leaves that column as it is, and the step says so.

How many at once

Each organization may have a limited number of agent runs in flight in each app, set by its plan. A run and every sub-agent run it starts count as one. Over the limit, starting a run returns:

HTTP/1.1 429 Too Many Requests
Retry-After: 15

{ "error": { "code": "AGENT_RUN_CONCURRENCY_LIMIT", "details": { "limit": 5, "retryAfterSeconds": 15 } } }

This is separate from rate limiting, which caps requests per window. Wait for Retry-After, or stop a run, and retry. A refused run never started and leaves no record. Direct tool calls and MCP calls count against the same limit while they execute.

What's next

On this page