202 — the turn is accepted, not finished:
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:
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.Confirm versus outcome
Both pause a run, both take atool_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:- Realtime. Subscribe to the thread channel and receive
run.completed,run.failed, and the pause events as they happen. - Webhooks. Register an endpoint for
run.completedandrun.failed— and, if you are headless, for the pause events too. A run parked at one of those waits until you answer. - Polling.
GET /v1/loom/runs/:iduntilterminalistrue. PollGET /v1/loom/threadsinstead 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.