Your first agent in 10 minutes

Create an application tool, publish an agent, and complete a tool call.

At a glance

Base URL
https://pantheon-todo.alethiconsulting.com/pantheon-api
Sandbox
Not required
Stream
event_deltas=true

Prepare

Create an API key. Install curl and jq.

Terminal
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:nitro"
export BUILD_SUFFIX="$(date +%s)"

Create a tool

Register an echo application tool.

Terminal
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 '.tool.id')
export TOOL_DEFINITION_ID=$(printf '%s' "$TOOL_JSON" | jq -r '.definition.id')

Create an agent

Set requires_sandbox to false. This tool runs in your application.

Terminal
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

Bind the agent version to the tool definition.

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

Terminal
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}'
Selected JSON
{
  "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.

Terminal
export PANTHEON_AGENT_SLUG="$AGENT_SLUG"
Terminal, TypeScript
npm install @alethi/pantheon-chat
Terminal, Python
pip install pantheon-chat
app.mjs
import { 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;
}
app.py
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.

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