retriever.mjs).
Problem: network timeouts, 429, restarts. You want zero duplicates and zero lost runs.
Idempotent creation
Generate theIdempotency-Key before the call and store it with your job. If the call fails with no response, replay with the same key: you get the same run back.
409 idempotency_conflict means a key was reused with a different body. That is a client bug: do not retry.
POST /v1/workspaces, POST /v1/workspaces/{id}/tables and POST /v1/go/* have no idempotency. Do not auto-replay them after 5xx or a timeout: check whether the resource already exists first. Two exceptions: 503 server_restarting on POST /v1/go/runs started nothing, so send it again after Retry-After; and a cancel (POST /v1/go/runs/{id}/cancel) can always be sent again, a second one does nothing more. A fast service called on POST /services/{serviceName} has none either: a replay runs it again and is billed again.
Retry only what can be retried
The
call() client applies these rules, with two exceptions. It does not handle conversation_busy: handle it per your business logic. It does not replay exclusion writes, because it never replays a POST without Idempotency-Key: rerun your exclusion sync instead.
Efficient polling
- Respect
Retry-After(15 s for an active run). Polling faster consumes yourreadsbudget without speeding up the run. - Send
If-None-Matchwith the previousETag:304has no body. It still counts toward thereadsbudget.
Concurrency
By default, a key can create 30 runs per minute. The “3 concurrent” limit applies to in-flight HTTP requests, not active runs. For a large batch, spread creations (for example one every 2 s) rather thanPromise.all on 100 items. For POST /v1/go/run (synchronous), limit to 3 parallel calls per key.
Active runs have their own cap: your organization executes at most 5 runs at the same time by default. The others stay queued and start on their own. Do not create a queued run again, and do not time out on queued time. See Runs per organization.
Recovery after crash
Storerun.id as soon as you get the 202 response. On restart, resume polling unfinished runs instead of creating new ones. A run in recovering resumes on its own on Retriever’s side, including after a deploy.
A run in failed does not resume. To continue work, create a new run on the same conversation_id with a new Idempotency-Key: the agent keeps history and tables already produced; ask it to finish what is missing (see New conversation or follow-up?). run_timeout (maximum duration exceeded, 3 hours by default) is restarted the same way.