Deferred tool results
A tool backend may defer the result of the current tool call without creating a user message or asking the model to call it again. Core understands only an operation id and opaque app data; login, approval, payment, device pairing, or any other workflow remains app logic.
{
"_sota": {
"deferredToolResult": {
"operationId": "app-defined-globally-unique-id",
"data": { "anyAppOwnedValue": true }
}
}
}Core registers the exact run, tool call, tool name, app installation, and environment behind that id. It streams output-pending to matching tool surfaces and adds the same item to the exact composer panel's pendingToolResults. The model has not received a tool result yet.
coreToolGrants:
- tool: core.app-operations.complete
scope: write
# Only when a longer-lived job callback token is needed:
- tool: core.tokens.issueJobCallback
scope: writeSend the callback from the app backend to the Core origin. The bearer value is the delegated capability received in the original invocation's x-sota-core-token header.
POST /v1/app-operations/app-defined-globally-unique-id/complete
Authorization: Bearer <delegated-token>
Content-Type: application/json
{
"status": "completed",
"result": { "events": [] }
}Use status: "failed" with an arbitrary error to reject it. The completion atomically becomes the result of the original tool call, and the same agent run continues. Parallel tool calls each have their own operation id; the model step continues only after all calls in that step settle.
{
"status": "failed",
"error": {
"code": "ACTION_NOT_COMPLETED",
"message": "The requested action was not completed"
}
}- Grant
core.app-operations.complete:writeto the app backend. - Operation ids are trimmed, 1–200 characters, and globally unique per tool call. Identical completion retries are idempotent.
- Registration occurs after Core receives the deferred response, so retry
app_operation_not_foundwith bounded backoff. - If completion can outlive the 60-second delegation token, exchange it for a job callback token while the original invocation is still valid, then store that token safely and send its job id in
x-sota-job-id. - Keep
datasmall and free of secrets: it is opaque to Core but deliberately client-visible. Narrow its shape in app UI before use. - The current primitive stays attached to the live run. Stopping or steering the run fails a still-pending operation with
operation_cancelled. App-owned expiry should complete the operation as failed; this is not a background job queue.