Prepare
Create an API key. Install curl and jq.
export PANTHEON_BASE_URL="https://pantheon-todo.alethiconsulting.com/pantheon-api"
export PANTHEON_API_KEY="PANTHEON_API_KEY"
export PANTHEON_MODEL="deepseek/deepseek-v4-flash"
export BUILD_SUFFIX="$(date +%s)"Take model ids from GET /v1/models.
Create a tool
Register an echo application tool.
TOOL_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/tools" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "echo_tool_'"$BUILD_SUFFIX"'",
"display_name": "Echo tool",
"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"
}')
export TOOL_ID=$(printf '%s' "$TOOL_JSON" | jq -r '.id')
export TOOL_DEFINITION_ID=$(printf '%s' "$TOOL_JSON" | jq -r '.latest_definition.id'){
"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
}
}The response is a ToolResponse. Its top-level id identifies the tool.
Create an agent
Set requires_sandbox to false. This tool runs in your application.
AGENT_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/agents" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Echo agent '"$BUILD_SUFFIX"'",
"slug": "echo-agent-'"$BUILD_SUFFIX"'",
"model": "'"$PANTHEON_MODEL"'",
"system_prompt": "Call the echo tool when asked to echo text.",
"requires_sandbox": false,
"tool_requirements": [{"tool_id": "'"$TOOL_ID"'", "required": true}]
}')
export AGENT_ID=$(printf '%s' "$AGENT_JSON" | jq -r '.id')
export AGENT_VERSION=$(printf '%s' "$AGENT_JSON" | jq -r '.version')
export AGENT_SLUG="echo-agent-$BUILD_SUFFIX"Publish a deployment
Deployments bind latest_definition.id through tool_definition_ids.
DEPLOYMENT_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/deployments" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "'"$AGENT_ID"'",
"agent_version": '"$AGENT_VERSION"',
"tool_definition_ids": ["'"$TOOL_DEFINITION_ID"'"],
"model": "'"$PANTHEON_MODEL"'"
}')
export DEPLOYMENT_ID=$(printf '%s' "$DEPLOYMENT_JSON" | jq -r '.id')Create a session
The slug selects the active deployment, or the newest published deployment when none is active.
SESSION_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/sessions" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_slug": "'"$AGENT_SLUG"'", "title": "First agent"}')
export SESSION_ID=$(printf '%s' "$SESSION_JSON" | jq -r '.id')
printf '%s
' "$SESSION_JSON" | jq '{id, deployment_source, sandbox_status}'{
"id": "session_123",
"deployment_source": "published",
"sandbox_status": "none"
}Run, pause, and resume
Install an SDK client for streaming and tool correlation. Export PANTHEON_AGENT_SLUG first.
export PANTHEON_AGENT_SLUG="$AGENT_SLUG"npm install @alethi/pantheon-chatpip install pantheon-chatimport { PantheonClient } from "@alethi/pantheon-chat";
const client = new PantheonClient({
baseUrl: process.env.PANTHEON_BASE_URL,
apiKey: process.env.PANTHEON_API_KEY,
});
const session = await client.createSession({ agentSlug: process.env.PANTHEON_AGENT_SLUG });
const run = await client.startRun(session.id, "Echo hello");
for await (const event of client.streamEvents(session.id)) {
if (event.type === "message.delta") process.stdout.write(event.delta.text || "");
if (event.type === "agent.custom_tool_use") {
await client.resumeWithToolResult({
sessionId: session.id,
runId: run.run_id,
inReplyToEventId: event.id,
toolCallId: event.content.tool_call_id,
content: { echoed: event.content.input.message },
});
}
if (event.type === "run.completed") break;
}import asyncio
import os
from pantheon_chat import PantheonClient
async def main():
async with PantheonClient(
os.environ["PANTHEON_BASE_URL"],
os.environ["PANTHEON_API_KEY"],
) as client:
session = await client.create_session(agent_slug=os.environ["PANTHEON_AGENT_SLUG"])
run = await client.start_run(session.id, "Echo hello")
async for event in client.stream_events(session.id, run_id=run.run_id):
if event.type == "agent.custom_tool_use":
await client.resume_with_tool_result(
session.id,
run.run_id,
in_reply_to_event_id=event.sse_id,
content={"echoed": event.content["input"]["message"]},
)
if event.type == "run.completed":
break
asyncio.run(main())The stream emits live deltas when event_deltas=true. An application tool pauses at agent.custom_tool_use.
Resume with its SSE event id. Stop when run.completed arrives.
Read Application tools for approval and retry behavior.
Raw HTTP
Use the wait endpoint when you do not need live output.
RUN_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/sessions/$SESSION_ID/runs/wait" \
-H "Authorization: Bearer $PANTHEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{"events":[{"type":"user.message","content":[{"type":"text","text":"Echo hello"}]}]}')
export RUN_ID=$(printf '%s' "$RUN_JSON" | jq -r '.run_id')
export TOOL_EVENT_ID=$(printf '%s' "$RUN_JSON" | jq -r '.required_event_ids[0]')
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-$TOOL_EVENT_ID" \
-H "Content-Type: application/json" \
-d '{"events":[{"type":"user.custom_tool_result","in_reply_to_event_id":"'"$TOOL_EVENT_ID"'","content":{"echoed":"hello"}}]}'Pitfalls
- The first run takes a few seconds to start: Run admission currently constructs the agent inside the request.
- A tool result returns 422: Match the registered output schema. Use operation-specific output shapes in your own tool.
- The stream emits stream.resync: Reconnect with the last durable SSE id and read persisted events again.
- The session returns SESSION_BUSY: Resume or cancel the active run before sending another message.