Skip to main content

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

Register a tool​

Look up the exact name first with GET /v1/tools?name=<name>.

Terminal
curl -sS --get "$PANTHEON_BASE_URL/v1/tools" \
  -H "Authorization: Bearer $PANTHEON_API_KEY" \
  --data-urlencode "name=echo_tool"

Read the matches from data. If none exist, create the tool with POST /v1/tools.

Otherwise, use the matching tool's id to add a version.

Creating an existing name returns HTTP 409 idempotency_conflict. The error details carry tool_id.

Tool creation returns ToolResponse with these fields. Additional definition fields are omitted here.

Selected JSON response fields
{
  "id": "tool_123",
  "name": "echo_tool",
  "display_name": "Echo tool",
  "latest_version": 1,
  "status": "active",
  "created_at": "2026-09-08T12:00:00Z",
  "updated_at": "2026-09-08T12:00:00Z",
  "latest_definition": {
    "id": "tool_definition_123",
    "tool_id": "tool_123",
    "name": "echo_tool",
    "version": 1
  }
}

Deployments bind latest_definition.id through tool_definition_ids.

Tool options​

Set side_effect and confirmation when creating a tool or version.

ValueMeaning
noneAccepted for side_effect. Default.
workspace_writeAccepted for side_effect.
external_writeAccepted for side_effect.
neverAccepted for confirmation. Default.
policyAccepted for confirmation.

Add a version​

POST /v1/tools/{{id}}/versions accepts only these fields:

FieldMeaning
descriptionAccepted request field.
executorAccepted request field.
input_schemaAccepted request field.
output_schemaAccepted request field.
side_effectAccepted request field.
confirmationAccepted request field.

Omit name and display_name. Both are rejected with "Extra inputs are not permitted".

Set TOOL_ID to the matching tool's id before running this request.

Terminal
curl -sS -X POST "$PANTHEON_BASE_URL/v1/tools/$TOOL_ID/versions" \
  -H "Authorization: Bearer $PANTHEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "description": "Echo a message.",
  "executor": "application",
  "input_schema": {
    "type": "object",
    "properties": {
      "message": {
        "type": "string"
      }
    },
    "required": [
      "message"
    ],
    "additionalProperties": false
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "echoed": {
        "type": "string"
      }
    },
    "required": [
      "echoed"
    ],
    "additionalProperties": false
  },
  "side_effect": "none",
  "confirmation": "never"
}'

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":"0123456789abcdef0123456789abcdef","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 live startup fails, the stream stays open with durable events and X-Pantheon-Live-Events: unavailable. Otherwise that header is live. If you receive stream.resync after a mid-stream live failure, reconnect with that id, with backoff if resyncs repeat within seconds of connecting.

Pantheon replays persisted events before it resumes live output.

Pitfalls​

  • Tool creation returns idempotency_conflict: The name already exists. Read tool_id from error details, then add a version.
  • Confirmation always is rejected: This release does not accept always. Gate the call in your application.
  • 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.

Search docs

Esc

Ask Bami

Bami answers inside your signed-in dashboard. Write your question here, then paste it into Bami with Cmd/Ctrl+/.