Runs and tool requests
A run is one execution of work. Starting it admits the task; events and status tell you what happens next. Persist the run ID before presenting progress to a user so you can reconnect without submitting the task again.
The examples use client and project from the Quickstart. Set run_id to the run you want to inspect.
Read status and events
A run is admitted and runs. It may stop at waiting_for_tool until you resolve a pending action, or at paused until it resumes. It ends as completed, failed, or cancelled.
- Pass
cursorasafteron the next page. - Save the cursor after processing the page. Deduplicate events by their ID if a page is received again.
- In REST responses the cursor is named
nextAfter; the Python client exposesnext_after.
Use a bounded polling interval and application deadline, as shown in the quickstart.
An empty page means there are no newer events in that response. It does not mean the run has finished.
Stream events
Current SDKs expose a dedicated SSE operation.
Python
TypeScript
Python returns an event iterator:
The REST endpoint is GET /v1/projects/{project_id}/runs/{run_id}/events/stream. Reconnect using the last processed sequence in after, or an event ID in Last-Event-ID.
A terminal run event ends the stream, but idle streams may also close. Read status before interpreting a closed stream as completion.
Keep one cursor per run. If your connection drops after displaying an event but before saving its cursor, replay may include that event again.
Make event handling safe to repeat. Updating a UI twice is recoverable; executing an external tool twice may not be.
Resolve pending actions
Inspect the returned actions and approval states. Your application enforces the user’s grants and chooses how to handle product-owned actions. Reading an action or approving it does not execute your backend’s tool.
Record an approval decision
When the user has approved the specific action, record that decision:
tool_call_idcomes from the pending action.approval_request_idis a stable ID retained for that decision.- A denied decision uses
decision="denied".
Do not approve every request automatically simply to unblock the run.
Submit a tool result
After your backend executes an admitted product-owned action, submit its actual result:
- The identities above come from the pending action.
tool_resultis the actual result dictionary.result_request_idis the stable key for its submission.
Record failure as failure rather than fabricating a completed payload. Managed connection tools execute through their managed path; do not also execute them in your backend.
Recover without duplicating effects
Retain the immutable action receipt and idempotency key across uncertain acknowledgements.
For example, if a tool sent an email but the result submission timed out, reconcile the email operation and resubmit the retained result when appropriate. Do not send the email again just because Sikaru has not yet observed its receipt.
client.runs.recover(project, run_id) is an explicit recovery request. Inspect the run, actions, and retained receipts first. Recovery is separate from status reads, approval decisions, and result submission. It cannot reverse an external effect.
Handle failures
A failed run carries error with three fields:
error is null for any other status. The same object is the error field of the run’s run.failed event.
New codes may be added. Treat a code you do not recognize like execution_failed.
Retry a failed turn
client.runs.retry(project, run_id) admits the failed turn again as a new turn with its own run ID.
- The failed run stays failed.
- The retry starts from the session’s last completed turn.
- External actions the failed turn already completed are not repeated.
- Repeating the request returns the same retry.
A run that did not fail, whose failure is not recoverable, or that failed too long ago is refused with 409 and code not_retryable. In that case send the message again as a new turn.
retry and recover are different. recover asks an unfinished run to continue from its saved progress. It does nothing for a run that has already completed, failed, or been cancelled.
Failure details in events
run.tool_call.failedmay carryerrorwithcodeandmessage. In each case the action was not performed again:approval_denied: a reviewer denied the action, so it did not run.access_changed: access to the tool changed during the turn, so the action was not performed.effect_unconfirmed: an earlier attempt started but its result was never confirmed. Check whether it took effect before trying again.
run.pausedcarriesreason:credits_required: credits or the agent’s spending cap ran out. The run resumes once it is funded.admission_deferred: the run is waiting to be admitted and resumes on its own.
Refused requests
Every error response is {"detail": ..., "code": ...}. Branch on code; detail is for people.
- The codes below are specific. Any other error carries its status default, such as
unauthenticated(401),forbidden(403),not_found(404),conflict(409),rate_limited(429) orservice_unavailable(503). - A
429or503refusal with a specific code includesRetry-Afterin seconds. - A refused submission starts no new work.
- Request validation errors (
422) list each invalid field indetailinstead.
agent_disabled means someone turned the agent off, so it stays off until it is turned on. agent_unavailable is temporary, so waiting is enough. New codes may be added; handle an unrecognized code by its status.
Keep the same idempotency key when you resend a refused submission.
Cancel work
Then observe status until cancellation has been processed.
Closing the CLI, ending an HTTP wait, or disconnecting from events does not cancel hosted work. Your application must also stop or reconcile any product-owned tool operation it has already dispatched.
For machine execution, the native executor observes hosted termination and stops its local process groups. See Local compute for interruption and crash limits.
Inspect retained evidence
Call client.runs.get_trajectory(project, run_id) with runs:read or inspect managed history in the platform. The permitted trajectory includes retained execution structure and measured usage. Private messages, reasoning, tool payloads, and serving metadata are omitted.
A completed status does not guarantee a complete retained trajectory. Missing measurements remain unknown. See Trace ingestion for the response shape and distinction from traces you submit.
