Runs and approvals
A run is one task for an agent: an input in, a model’s answer out, with any tool calls in between.
const run = await zupflow.runs.create(workspaceId, agentId, { input: "What's due this week?" });A run’s status moves through:
stateDiagram-v2 direction LR [*] --> queued queued --> running running --> waiting_for_approval: a call needs approval waiting_for_approval --> running: decided running --> completed running --> failed queued --> cancelled running --> cancelled waiting_for_approval --> cancelled completed --> [*] failed --> [*] cancelled --> [*]The run records its model and connection when it starts, and checks before every model call that both are still there. With neither an agent model nor an environment default, starting a run is refused (422 model_not_configured).
Events
Section titled “Events”Every step is an event with a sequence number. Read them all, or only those after a sequence you already have:
const events = await zupflow.runs.events(workspaceId, run.id, lastSequence);| Event | When |
|---|---|
run.queued, run.started |
Admitted, then taken up with its model. |
message.assistant |
The model said something. |
tool_call.started, tool_call.completed, tool_call.failed |
A call was sent and answered. |
approval.requested, approval.resolved |
A call waits for a decision, then gets one. |
run.completed, run.failed, run.cancelled |
The run ended. |
- 1
run.queuedAdmitted for agent Planner0.0s - 2
run.startedOn claude-sonnet-4-50.1s - 3
tool_call.startedlinear_k7m2qd_list_issues1.6s - 4
tool_call.completed14 issues2.2s - 5
approval.requestedupdate_issue · move ENG-212 to next sprint4.0s - 6
approval.resolvedApproved by the person, in your product3m 12s - 7
tool_call.startedlinear_k7m2qd_update_issue3m 12s - 8
tool_call.completedENG-212 moved3m 13s - 9
message.assistant“Three issues are due this week…”3m 15s - 10
run.completedOutput saved on the run3m 15s
sequence. The amber stretch is the pause: the run waits for a decision for as long as it takes, holding nothing open.For anything you need to react to, prefer webhooks over polling events.
Approvals
Section titled “Approvals”When a call needs approval, the run pauses with waiting_for_approval, records approval.requested with the call’s ID and arguments, and Zupflow sends the run.waiting_for_approval webhook. Your product shows the person what the agent wants to do; your backend sends the decision:
await zupflow.runs.resolveApproval(workspaceId, runId, { call_id: callId, decision: "denied", reason: "Not this week.", // optional, goes back to the model});- Only an API key can decide. A Console session gets
403 approval_requires_api_key: the Console observes approvals, the person decides in your product. - A denial’s
reasongoes back to the model as the call’s result, so it can carry on without the call. - Deleting or disconnecting the installation refuses its waiting calls, so the run goes on without anyone deciding. A decision sent after that answers
409 not_waiting.
Cancelling
Section titled “Cancelling”POST /v1/workspaces/{ws}/runs/{run}/cancel stops a run and closes every call not yet sent.
Durability
Section titled “Durability”Runs survive interruptions. Each step is saved before it’s taken, and a call whose outcome was lost mid-send is settled as unknown, never sent again. A run nothing can take forward any more fails with run_unavailable.