Skip to main content
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. 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.
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.

Handles: a claim ticket for work in progress

To wait for something, the agent needs to name it — and that name is a 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.
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:
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, naming the paused run and what it is waiting on. Wait for it to finish, then start your next run.
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.

Limits

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

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.