> ## Documentation Index
> Fetch the complete documentation index at: https://docs.narrative.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Run Already In Progress

> Another run on this conversation is still pending or running.

**HTTP status:** 409

## Error message

```
conversation already has an active run {run_id} ({what it is doing}): poll GET /agents/runs/{run_id} until it reaches a terminal state, then retry
```

Example: `conversation already has an active run 82eb9cb7-619e-46bb-ac1c-8a32b0112c1c (waiting on job
9f3c1a20-1f1e-4e6e-b0f4-2e0b6a71cc55): poll GET /agents/runs/82eb9cb7-619e-46bb-ac1c-8a32b0112c1c until
it reaches a terminal state, then retry`.

The parenthesised part is the active run's most recent live turn — for a run parked on a job or a
`sleep`, this is what it is waiting for. It is omitted when the run hasn't produced one yet.

## When this error occurs

`POST /agents/conversations/{id}/runs` was called while a run on that conversation was still
`pending` or `running`. A conversation runs one turn at a time: its message history is appended
under an optimistic-concurrency check, so a second concurrent run would burn a workflow and its
inference calls only to lose that check at finalize with
[Concurrent Conversation Append](./concurrent-conversation-append). The API refuses it up front
instead.

The three terminal states never trigger this — including `requires_action`, so the run that resumes
a paused one with `tool_outputs` is accepted as normal.

An idempotent retry is never refused either: re-posting a `client_op_id` that already has a run on
this conversation returns that run's row, even while a different run is active.

## How to fix

1. Read the active run id out of `detail` and poll `GET /agents/runs/{active_run_id}` until its
   `status` is `completed`, `requires_action`, or `failed`.
2. Re-read the conversation's `version` via `GET /agents/conversations/{id}` — the finished run
   advanced it.
3. Post your run with the fresh `expected_version`.

A run that is waiting on a Narrative job — or parked on `sleep` or `wait_for` — can stay
non-terminal for a while. `live.messages` on the run response shows what it is waiting for, so you
can tell a working run from a stuck one.

## Best-effort guard

The pre-flight check is best-effort. Under a tight race the pre-flight can miss a run that is just
starting; the finalize-time CAS ([Concurrent Conversation Append](./concurrent-conversation-append))
stays the authority for simultaneous posts.
