Skip to main content

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

Take model ids from GET /v1/models.

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

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.

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​

Deployments bind latest_definition.id through tool_definition_ids.

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.

Search docs

Esc

Ask Bami

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