SDK workflow helpers
Wait for indexed documents, observe agent outcomes and recover existing runs from saved receipts.
SDK workflow helpers
The Python and TypeScript SDKs provide bounded, read-only lifecycle helpers. They use your client's configured API origin and credentials. They never start replacement runs, approve actions, submit tool results, or cancel server work automatically.
These helpers are available in the repository SDK source and qualified archives. Check SDK support for public package availability before relying on a registry installation.
Wait for indexed knowledge
After ingestion accepts a document, wait for a fresh indexed version before retrieving from it:
ready = await q.knowledge.wait_until_ready(
document_id,
minimum_document_version=2,
timeout=120,
poll_interval=1,
)const ready = await q.knowledge.waitUntilReady(documentId, {
minimumDocumentVersion: 2,
timeoutMs: 120_000,
pollIntervalMs: 1_000,
signal: controller.signal,
});Use the document version returned by your ingestion/update operation, or omit the minimum to accept any fresh ready version. A ready state with fresh: false continues polling. Failed/deleted ingestion raises IngestionFailedError, with the last ingestion receipt in snapshot. Waiting does not upload, reindex or retry failed ingestion. An unknown ingestion state remains pending until the deadline; an unrecognised future state or mismatched document ID fails validation.
Observe a run
Persist your operation's idempotency key before starting a run. Once q.agents.runs.start(...) returns, save its receipt before waiting. The receipt contains agent_id, run_id and stream_key; it contains no API key. Treat stream keys and run results as sensitive application data and keep them in your backend's authorised storage.
# receipt was durably saved after runs.start().
outcome = await q.agents.runs.recover(receipt, timeout=120, poll_interval=1)
if outcome["kind"] == "completed":
result = outcome["run"]["result_json"]
elif outcome["kind"] == "requires_action":
# Show the pending approval/input to an authorised user.
pending_status = outcome["run"]["status"]// receipt was durably saved after runs.start().
const outcome = await q.agents.runs.recover(receipt, {
timeoutMs: 120_000,
pollIntervalMs: 1_000,
signal: controller.signal,
});
if (outcome.kind === 'completed') {
const result = outcome.run.result_json;
} else if (outcome.kind === 'requires_action') {
// Show the pending approval/input to an authorised user.
const pendingStatus = outcome.run.status;
}If you already have both IDs, call q.agents.runs.wait_for_completion(agent_id, run_id, ...) or q.agents.runs.waitForCompletion(agentId, runId, options) directly. Recovery does the same observation from a saved receipt. Use the same environment and tenant credentials that created the run; a receipt is not an authentication credential and does not select an API host.
| Outcome kind | Meaning | Next action |
|---|---|---|
completed | Execution completed | Inspect result_json and result_access_error; completion does not guarantee access to every result |
failed | Execution failed | Inspect the run error; do not assume a replacement is safe |
cancelled | Server confirms cancellation | Inspect retained results and side-effect receipts |
expired | Execution deadline expired | Reconcile completed side effects before any new submission |
requires_action | awaiting_input or awaiting_approval | Obtain an explicit decision; use the existing input/approval APIs, then observe again |
unknown | Server returned an unfamiliar run state | Preserve the snapshot and investigate; never treat it as success or auto-resubmit |
RunStatus, RunReceipt, RunRecord and RunOutcome are exported by both SDKs. Python run records remain ordinary dictionaries with type hints; TypeScript results expose typed fields and retain unknown extension fields. Existing high-level AgentRun.wait_for_completion() / waitForCompletion() methods now use bounded polling and retain their run-record return shape. Inspect that record's status: they can return when intervention is required as well as when execution ends.
Deadlines, cancellation and persistence
Waits default to 60 seconds, with one second between completed polls. Python durations are seconds; TypeScript durations are milliseconds. The overall deadline covers requests, transport retries, polling delays and progress callbacks. There are no overlapping polls. Nonpositive or nonfinite durations fail before issuing a request.
- On deadline, catch
WorkflowTimeoutError; inspectlast_snapshotin Python orlastSnapshotin TypeScript. The last snapshot is absent if no read completed. - To stop observing, cancel the Python task or abort the TypeScript
AbortSignal. This does not cancel the remote run. Use the existing explicitruns.cancelAPI when you intend remote cancellation, then observe its actual status. - HTTP errors, authentication failures, expired retention, transport failures and callback exceptions propagate. The helper does not add mutation retries. The root client's existing read-retry policy still applies within the overall deadline.
- Pass an awaited
on_update(snapshot)Python callback oronUpdate(snapshot)TypeScript callback to persist progress in your own store. Avoid logging result bodies. Use idempotent writes because repeated snapshots are possible. Callbacks should be bounded; a TypeScript callback already running cannot be forcibly undone when the wait is aborted. - Polling snapshots do not replace the event journal. For event-based consumers, persist
event.cursorafter applying each event, then useruns.streamwithlast_event_id/lastEventId. See streaming.
If submission failed before you received a valid receipt, there is nothing for recover to identify. Retain the original idempotency key and request body, follow retry safety, and reconcile with the server. An invalid legacy stream receipt raises WorkflowProtocolError rather than returning missing IDs. Do not start a replacement with a fresh key to resolve an uncertain outcome.