Application tools

Execute a tool in your application, then resume the waiting run.

At a glance

Executor
application
Wait deadline
30 minutes
Correlation
tool_call_id
Retry key
Idempotency-Key

Handle a tool call

1

Track the tool

tool.started identifies the call.

2

Receive the pause

agent.custom_tool_use contains the application tool input.

3

Wait for idle

session.status_idle contains requires_action.

4

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.

SSE
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.

Terminal
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:

output_schema
{
  "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.

JSON
{
  "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.