Skip to navigation

Runs and tool requests

Read a run's status and events, stream them, resolve pending tool actions, handle failures and refused requests, cancel work, and read retained evidence.
View as Markdown

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.

ObservationWhat your application should do
Work admitted or runningDisplay progress and continue observing.
waiting_for_toolRead pending actions and resolve the required approval or result.
pausedInspect the retained state and pending actions before deciding how to resume.
completedDrain remaining events and present the final output.
failedRead error on the run. Retry when error.recoverable is true; otherwise follow error.message. See Handle failures.
cancelledStop presenting the run as active; reconcile already dispatched external actions.
current = client.runs.get(project, run_id)
page = client.runs.events(project, run_id, after="0")
for event in page.events:
print(event.event_type, event.payload)
cursor = str(page.next_after)
  • Pass cursor as after on 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 exposes next_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 returns an event iterator:

cursor = "0" # Restore the last processed sequence when reconnecting.
for event in client.runs.stream_events(project, run_id, after=cursor):
print(event.event_type, event.payload)
cursor = str(event.sequence)

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

actions = client.runs.pending_actions(project, run_id)

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.

opt [Approved product-owned action] Pending action and identities Request approval when required Decision Record decision Execute admitted action Outcome Submit retained result receipt Further run events Sikaru Your backend User Your tool

Record an approval decision

When the user has approved the specific action, record that decision:

approval = client.runs.decide_approval(
project, run_id, tool_call_id,
decision="approved",
idempotency_key=approval_request_id,
)
  • tool_call_id comes from the pending action.
  • approval_request_id is 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:

receipt = client.runs.submit_tool_result(
project, run_id,
tool_call_id=tool_call_id,
tool_provider_id=tool_provider_id,
capability_name=capability_name,
idempotency_key=result_request_id,
status="completed",
payload=tool_result,
)
  • The identities above come from the pending action.
  • tool_result is the actual result dictionary.
  • result_request_id is 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:

FieldMeaning
codeA stable value to branch on.
messageAn explanation you can show to a user, with the next step.
recoverableWhether retry can run the failed turn again.

error is null for any other status. The same object is the error field of the run’s run.failed event.

current = client.runs.get(project, run_id)
if current.status == "failed" and current.error.recoverable:
retried = client.runs.retry(project, run_id)
run_id = retried.run.id
coderecoverableMeaning
model_timeoutyesThe model did not respond in time.
model_unavailableyesThe model was temporarily unavailable.
evidence_unavailableyesThe turn’s record could not be saved, so the turn did not finish.
interruptedyesExecution stopped before it finished.
capacity_unavailableyesNo execution capacity became available to start the turn.
execution_failedyesThe agent stopped because of an execution problem. Contact support if retries keep failing.
insufficient_creditsnoCredits or the agent’s spending cap ran out. Add credits or raise the cap, then send the message again.
setup_failednoThe agent could not finish setup. Review its instructions and connected tools, then start a new session.
tool_not_allowednoThe agent requested a tool it is not allowed to use. Review its tools, then start a new session.

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.failed may carry error with code and message. 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.paused carries reason:
    • 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) or service_unavailable (503).
  • A 429 or 503 refusal with a specific code includes Retry-After in seconds.
  • A refused submission starts no new work.
  • Request validation errors (422) list each invalid field in detail instead.
StatuscodeWhat to do
402insufficient_creditsBilling does not allow the request. Check your plan, credits and the agent’s spending cap in Billing, then send it again.
409idempotency_conflictThe idempotency key was already used for a different request, or for a file that was deleted. Use a new key for new content.
409concurrent_requestAnother request is changing the same resource. Send the same request again.
409agent_disabledThe agent is turned off. Turn it on, then send the request again.
409release_unavailableThe agent has no release that can run. Ask an agent administrator to publish or upgrade it.
409action_not_pendingThe tool call or approval is not waiting for this input. It already has an outcome, its run finished, it is handled by Sikaru, or it doesn’t match a pending request. Read pending actions.
409not_retryableThe run cannot be retried. Read its error and send a new turn instead.
409branch_unavailableThe session cannot be branched at that run yet. Wait until the turn has finished and its workspace is saved.
409record_invalidThe retained record cannot be read. Contact support if this continues.
409state_conflictThe request conflicts with the resource’s current state. Read it before trying again.
503storage_unavailableThe storage behind this resource is unavailable right now. Try again after Retry-After.
429agent_capacity_fullThe agent’s turn capacity is full. Try again after Retry-After.
503agent_unavailableExecution is switched off for this agent. Try again after Retry-After; contact support if it persists.
503turn_delivery_pendingThe message was recorded but has not started. Send it again with the same idempotency key.
503execution_storage_unavailableSession, file, or workspace storage is unavailable right now. Try again after Retry-After.
503trajectory_storage_unavailableRetained records cannot be read right now. Read again after Retry-After.

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

cancelled = client.runs.cancel(project, run_id)

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.

Next steps