Docs
Async jobs
Some operations cannot finish inside the call that starts them. Those hand back a job id you poll, instead of holding a connection open.
The job handle
An operation that cannot finish inside its own request returns a small handle instead of a result:
{jobId, kind, status, pollWith}. pollWith is always
"get_job" — it names the tool to call next, so a model never has to construct a path
or guess at a convention.
Over REST the same handle is polled at GET /jobs/{jobId}. Job ids look like
job_ followed by 26 characters. Never invent one — an id you were not handed is a
job_not_found.
Polling a job
Polling returns the full row: jobId, kind, status,
progress, meta, result, error,
isTerminal, createdAt and resolvedAt. Stop when
isTerminal is true; that is the one field to branch on, and it saves you
enumerating the terminal states yourself.
progress is an integer from 0 to 100, or null when the operation does not
report one. It is a hint for a progress bar, not a schedule.
Reading a job requires read:account, and a job belonging to another account is a
job_not_found rather than a denial. The poll only ever reads a row — it never performs
or advances the work — so it is safe to call on a loop. Poll every few seconds and respect the
rate limit: a polling loop is still traffic.
Job states
queued— accepted, not started.running— in flight, withprogresswhen the operation reports it.succeeded—resultis populated,progressis 100.failed—erroris populated.cancelled— stopped before it finished; neitherresultnorerror.
The last three are terminal: isTerminal is true, resolvedAt
is stamped, and nothing more happens to the row.
When a job fails
A failed job's error is a {code, message} object describing what
went wrong inside the work. It is not the Problem Details envelope —
that envelope belongs to the HTTP call, and the poll that reports a failed job is itself a
perfectly successful 200. Check status, not the status code.