Skip to main content
Posting a message starts a run. The response is 202 — the turn is accepted, not finished:
From there the run either finishes on its own or stops and waits for you. Knowing which is the whole job of this page.

The five states

closed and failed are terminal — the run will never change again, and the terminal field on the run object is true. Everything else can still move:
A paused run always returns to running before it can close. Nothing transitions directly out of awaiting_input to a terminal state except failed.

awaiting_input is two different situations

This is the part worth reading twice. A run pauses in awaiting_input for either of two reasons, and they need different API calls to resume. The state string is identical in both cases. Posting a message to resolve a confirmation does not work — you get 422 awaiting_confirmation telling you a decision is pending.

Telling them apart

GET /v1/loom/threads returns three fields that name exactly what each thread is waiting for. At most one is ever non-null.
object
A question is waiting. Resume by posting a message.
object
A write is held at an approval gate. Resume by approving or rejecting.
object
The agent proposed an action for you to perform. Resume by reporting the outcome.
GET /v1/loom/runs/:id returns the run’s state but not these fields. If you are polling and need to resume a paused run, poll GET /v1/loom/threads — that is where the tool_call_id lives.
If you subscribe to realtime, the same information arrives as events the moment the run pauses, and you do not need to poll at all.

Confirm versus outcome

Both pause a run, both take a tool_call_id, and they are easy to confuse. The difference is who performs the work. A confirmation asks permission before we act. An outcome asks what happened after you acted.

Watching a run finish

Three options, in order of how much work they cost you:
  1. Realtime. Subscribe to the thread channel and receive run.completed, run.failed, and the pause events as they happen.
  2. Webhooks. Register an endpoint for run.completed and run.failed — and, if you are headless, for the pause events too. A run parked at one of those waits until you answer.
  3. Polling. GET /v1/loom/runs/:id until terminal is true. Poll GET /v1/loom/threads instead if the run might pause.

What a finished run tells you

GET /v1/loom/runs/:id returns the final state, the assistant’s message, the connection used, and cost_credits for the turn. For the full picture — every model call, every tool call, arguments, results, risk, and timings — fetch GET /v1/loom/runs/:id/trace. The trace is the record of what the agent actually did, and it is the first place to look when a run did something you did not expect.