Handle a tool call
Track the tool
tool.started identifies the call.
Receive the pause
agent.custom_tool_use contains the application tool input.
Wait for idle
session.status_idle contains requires_action.
Resume
Send the result. Watch for tool.completed and run.completed.
Correlate the call
Use tool_call_id to update one UI item through the tool lifecycle.
Use execution_idempotency_key to execute the application tool once.
Use the durable SSE id as in_reply_to_event_id.
event: agent.custom_tool_use
id: evt_custom_tool_123
data: {"id":"evt_custom_tool_123","sequence":3,"type":"agent.custom_tool_use","workspace_id":"workspace_123","session_id":"session_123","run_id":"run_123","created_at":"2026-09-03T12:00:02Z","content":{"tool_call_id":"call_123","pending_input_id":"pending_123","execution_idempotency_key":"evt_custom_tool_123","name":"echo_tool","input":{"message":"hello"}},"data":{"tool_call_id":"call_123","pending_input_id":"pending_123","execution_idempotency_key":"evt_custom_tool_123","name":"echo_tool"}}
Resume the run
Reuse the same Idempotency-Key when retrying this result.
curl -sS -X POST "$PANTHEON_BASE_URL/v1/sessions/$SESSION_ID/runs/$RUN_ID/resume" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Idempotency-Key: resume-evt-custom-tool-123" \
-H "Content-Type: application/json" \
-d '{
"events": [{
"type": "user.custom_tool_result",
"in_reply_to_event_id": "evt_custom_tool_123",
"content": {"echoed": "hello"}
}]
}'
A waiting run stays resumable for 30 minutes by default.
Per-operation result schemas
Register an output_schema with top-level oneOf or anyOf branches.
Each branch must be an object schema. Nested combinators are not supported.
Give each operation a distinct operation value with const and its own required fields.
This example accepts separate create and delete results:
{
"oneOf": [
{
"title": "Create result",
"type": "object",
"properties": {
"operation": {
"const": "create"
},
"id": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"operation",
"id",
"title"
],
"additionalProperties": false
},
{
"title": "Delete result",
"type": "object",
"properties": {
"operation": {
"const": "delete"
},
"id": {
"type": "string"
},
"deleted": {
"type": "boolean"
}
},
"required": [
"operation",
"id",
"deleted"
],
"additionalProperties": false
}
]
}
A create result contains operation, id, and title.
A delete result contains operation, id, and deleted.
Send the matching object as the result's content. Publish a new deployment after changing a tool version.
When no branch matches, resume returns HTTP 422 with tool_result_invalid.
Each error.details entry includes a path JSON Pointer and a reason.
The reason names the closest branch, its index, and the failed rule.
For example, a missing title points to /events/0/content/title and identifies Create result at oneOf[0].
The run remains waiting. Correct the result and retry before its deadline.
Decline a call
Return a structured tool error. The error code must use uppercase letters, digits, or underscores.
{
"events": [{
"type": "user.custom_tool_result",
"in_reply_to_event_id": "evt_custom_tool_123",
"error": {
"code": "USER_CANCELLED",
"message": "The user declined this action.",
"retryable": false
}
}]
}
Reconnect
Save the last durable SSE id. Reconnect with Last-Event-ID.
If you receive stream.resync, reconnect with that id.
Pantheon replays persisted events before it resumes live output.
Pitfalls
- Resume returns event_expired: The wait deadline passed. Start a new run.
- Resume returns event_already_resolved: Treat the original result as accepted. Do not execute the tool again.
- Resume returns tool_result_invalid: Match the tool's output schema. Keep each operation's shape unambiguous.
- A reconnect misses live tokens: Ephemeral deltas are not replayed. Rebuild final text from durable message events.