> ## 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.

# Waiting for Work to Finish

> How an agent waits for a long-running job instead of asking again and again — and what the handle it holds while waiting actually represents.

Some of the work an agent starts does not finish while it is talking to you. Sampling a dataset,
refreshing a materialized view or running a query all return in a moment, but the work itself takes
minutes.

Without a way to wait, an agent asked to "run this and tell me when it's done" can only keep asking
"is it done yet?" — and every question costs a turn, so it runs out of turns long before the work
finishes.

Agents on Narrative have two ways to wait properly.

|                | Use it for                                           | What happens                                                                    |
| -------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------- |
| **`wait_for`** | work Narrative runs — today, jobs                    | The conversation is **paused** until the work finishes, then handed the outcome |
| **`sleep`**    | work Narrative cannot see, like a third-party ticket | The conversation pauses for a set time, then the agent checks again itself      |

The difference is whether the platform can watch the work to completion. For a Narrative job it can,
so nothing needs to be checked at all — the run simply wakes up when the job is done. For anything
outside Narrative it cannot, so the best it can offer is a pause between checks.

<Info>
  Waiting costs nothing while it lasts. A paused run holds no compute and makes no model calls, so a
  job that takes four hours costs the same as one that takes four seconds.
</Info>

## Handles: a claim ticket for work in progress

To wait for something, the agent needs to name it — and that name is a **[handle](/reference/glossary#handle)**.

A handle is a **claim ticket for work that outlived the call that started it**. When the agent
registers a job with `job_monitor`, it gets back a handle, and later hands that handle to `wait_for`
to collect the outcome. Like a coat-check ticket: the ticket is not the coat, and it is not a
description of the coat — it is what lets you claim it later.

What a handle **is not** is where most confusion starts:

* **Not a job id.** A job id names a job. A handle names *your conversation's claim on it*, which is
  a different thing with a different lifetime.
* **Not something to read.** It looks like `wt_9d06ad7bc3674fe1aa789ec2eda55c3c` and carries no
  meaning inside the string. What kind of work it is, which job sits behind it, and how it is going
  are all recorded by the platform, not encoded in the text. The agent passes it back exactly as
  received.
* **Not transferable.** A handle works only in the conversation it was issued in. Presented anywhere
  else it is refused, even while the job behind it is running perfectly well.
* **Not permanent.** A handle stays usable for 24 hours, comfortably longer than the longest wait a
  single run can ask for. After that it reads as unknown.

The point of the indirection is that waiting does not have to care what it is waiting for. Today a
handle stands for a job; the same ticket can later stand for a whole workflow or another agent, and
`wait_for` will not change.

## What it looks like in a conversation

Three steps show up in the message stream: the work starts, the agent takes a ticket, the agent
waits.

```text theme={null}
// 1. The agent starts a job through a Narrative tool, which returns a job id
{ "job_id": "e0f58bb8-b64f-4dad-b262-3f8728b447b3", "state": "pending" }

// 2. It registers that job and gets a handle back
tool_use:    job_monitor { "job_id": "e0f58bb8-b64f-4dad-b262-3f8728b447b3" }
tool_result: { "waitable": { "handle": "wt_548aaf8b1dfc4a3d921b39e1566d5da8",
                             "kind": "job", "status": "running" } }

// 3. It waits on the handle. The conversation pauses here, then wakes with the outcome
tool_use:    wait_for { "handles": ["wt_548aaf8b1dfc4a3d921b39e1566d5da8"], "timeout_seconds": 1800 }
tool_result: { "timed_out": false,
               "results": [ { "handle": "wt_548aaf8b1dfc4a3d921b39e1566d5da8",
                              "status": "completed", "result": { "…": "…" } } ] }
```

Each `status` is one of `completed`, `failed`, `cancelled`, `running`, or `not_found` for a handle
this conversation cannot claim. A job that **failed** is still a finished wait: the agent is told
what went wrong and can react, rather than the run collapsing.

## While a run is waiting

A paused run is a healthy run, and it tells you what it is doing. `GET /agents/runs/{id}` shows a
live line for the pause:

```text theme={null}
waiting on 1 task [wt_548aaf8b1dfc4a3d921b39e1566d5da8], giving up at 2026-08-14T12:31:07Z
```

or, for a plain pause, `sleeping for 60s`.

A conversation runs one turn at a time, so while a run is paused a second run on the same
conversation is refused with [Run Already In Progress](./errors/run-in-progress),
naming the paused run and what it is waiting on. Wait for it to finish, then start your next run.

<Warning>
  A run that is already waiting cannot be interrupted in this version. A new message cannot reach it,
  and there is no way to cut a wait short — so prefer a timeout you are willing to sit through.
</Warning>

## Limits

|                              | Value                                                                         |
| ---------------------------- | ----------------------------------------------------------------------------- |
| `sleep` duration             | 1 second to 1 hour                                                            |
| `wait_for` timeout           | 1 second to 12 hours; 1 hour if not given                                     |
| Handles in one `wait_for`    | 8                                                                             |
| How long a handle lasts      | 24 hours                                                                      |
| Size of a result handed back | 16 KB; past that the agent is told the size and that the payload is elsewhere |

A duration outside its range is quietly brought inside it rather than rejected — the agent asked to
wait, and waiting a slightly different amount is closer to that than an error.

## When a wait gives up

A timeout ends the **waiting**, never the work. The job carries on exactly as before, and the agent
is told the task was still running. It can wait again on the same handle, report back and let you
decide, or get on with something else.

<Note>
  The same holds if the run itself ends. A run that fails, or is stopped by an operator, does not stop
  the work it started — the job finishes on its own and its result is where it always was.
</Note>

## What is not covered yet

* **Only jobs can be waited on.** Work that is not a Narrative job — including an NQL statement
  followed through its workflow run — has no handle, so the agent has to check on it and `sleep`
  between checks.
* **No interruption**, as above: a waiting run cannot be reached or cut short.
