https://pantheon-todo.alethiconsulting.com/docs/index.html # Pantheon developer docs > Build a stateful agent with streaming and application tools. ## Pantheon today Pantheon runs stateful agents behind a REST API. Your application owns its UI and business data. Pantheon stores session events, streams live output, and pauses runs for application tools. > **Base URL** > `https://pantheon-todo.alethiconsulting.com/pantheon-api` ## Building blocks | Object | What you use it for | | --- | --- | | Agent | Set instructions, model, tool requirements, and sandbox policy. | | Tool | Describe an application tool with input and output schemas. | | Deployment | Publish one agent version with its tool definitions. | | Session | Keep conversation state and a pinned deployment. | | Run | Process one turn. A session allows one active run. | | Event | Report durable state or ephemeral live output. | ## Start [Create an API key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html). Then set the two required variables. ```bash export PANTHEON_BASE_URL="https://pantheon-todo.alethiconsulting.com/pantheon-api" export PANTHEON_API_KEY="PANTHEON_API_KEY" ``` Follow [Your first agent in 10 minutes](https://pantheon-todo.alethiconsulting.com/docs/quickstart.html). ## Using an AI coding agent Give your agent the docs link: `https://pantheon-todo.alethiconsulting.com/docs/`. Include [llms-full.txt](https://pantheon-todo.alethiconsulting.com/docs/llms-full.txt) for all page content. Use [llms.txt](https://pantheon-todo.alethiconsulting.com/docs/llms.txt) for the page index. Install the [SDK](https://pantheon-todo.alethiconsulting.com/docs/chat-components.html#install). Copy a starter file from the [chat components page](https://pantheon-todo.alethiconsulting.com/docs/chat-components.html#react). Follow [Build on Pantheon](https://pantheon-todo.alethiconsulting.com/docs/onboarding.html) to publish your agent and connect your application. ## Choose a guide | Goal | Guide | | --- | --- | | Build the complete first flow | [Quickstart](https://pantheon-todo.alethiconsulting.com/docs/quickstart.html) | | Create and revoke an API key | [Get an API key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html) | | Run Pantheon on your infrastructure | [Self-host](https://pantheon-todo.alethiconsulting.com/docs/self-host.html) | | Add a ready-made chat surface | [Chat components](https://pantheon-todo.alethiconsulting.com/docs/chat-components.html) | | Execute tools in your application | [Application tools](https://pantheon-todo.alethiconsulting.com/docs/application-tools.html) | | Manage state and concurrency | [Sessions and runs](https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html) | | Render and reconnect streams | [Events](https://pantheon-todo.alethiconsulting.com/docs/events.html) | | Recover from failures | [Errors](https://pantheon-todo.alethiconsulting.com/docs/errors.html) | | Check current limitations | [Known issues](https://pantheon-todo.alethiconsulting.com/docs/known-issues.html) | | Inspect routes and schemas | [API reference](https://pantheon-todo.alethiconsulting.com/docs/api-reference.html) | ## Pitfalls > - **MCP tools do not run:** Use application tools. Stored MCP server settings are not dispatched by the runtime. > - **A session rejects a second run:** Finish, resume, or cancel the active run before sending another. > - **New contract examples are thin:** Use the [API reference](https://pantheon-todo.alethiconsulting.com/docs/api-reference.html) with the task guides. https://pantheon-todo.alethiconsulting.com/docs/onboarding.html # Build on Pantheon > Take an agent from account setup to a running application. 1 ## Get in Sign up in the dashboard. Create a key and copy its secret immediately. | Method | Route | | --- | --- | | `POST` | `/v1/auth/signup` | | `POST` | `/v1/auth/keys` | **Pantheon guarantees:** Pantheon shows the key secret once and stores its hash. Read [Get an API key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html). 2 ## Model the work Register tool definitions with input and output schemas. Create a version for each contract change. | Method | Route | | --- | --- | | `POST` | `/v1/tools` | | `POST` | `/v1/tools/{tool_id}/versions` | Register tools and manage versions from the dashboard [Tools](https://pantheon-todo.alethiconsulting.com/dashboard/tools.html) page. **Pantheon guarantees:** Pantheon keeps versioned tool contracts for deployment snapshots. Your application executes application tools. Read [Application tools](https://pantheon-todo.alethiconsulting.com/docs/application-tools.html). 3 ## Define the agent Choose a slug and a model from the models list. Set the system prompt, tool ids, skills, and sandbox policy. | Method | Route | | --- | --- | | `GET` | `/v1/models` | | `POST` | `/v1/agents` | Create and edit agents, publish deployments, and review deployment history on the dashboard [Agents](https://pantheon-todo.alethiconsulting.com/dashboard/agents.html) page. **Pantheon guarantees:** Pantheon stores the agent definition. Publish it before creating sessions by slug. Read [Quickstart](https://pantheon-todo.alethiconsulting.com/docs/quickstart.html). 4 ## Publish Create a deployment for your agent. Activate it to select it for new slug sessions. | Method | Route | | --- | --- | | `POST` | `/v1/deployments` | | `POST` | `/v1/deployments/{id}/activate` | See [Roll back a deployment](https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html#rollback) for activation behavior. **Pantheon guarantees:** New slug sessions use the active deployment, or the newest publication when none is active. Existing sessions stay pinned. Read [Core concepts](https://pantheon-todo.alethiconsulting.com/docs/concepts.html). 5 ## Wire it in Use the chat components or create a session by agent slug through the API. Start a run with your message and stream events. | Method | Route | | --- | --- | | `POST` | `/v1/sessions` | | `POST` | `/v1/sessions/{session_id}/runs` | | `GET` | `/v1/sessions/{session_id}/events/stream` | | `POST` | `/v1/sessions/{session_id}/runs/{run_id}/resume` | | `POST` | `/v1/sessions/{session_id}/runs/{run_id}/cancel` | Enable `event_deltas` for live output. See [Event stream](https://pantheon-todo.alethiconsulting.com/docs/events.html). Handle each tool pause in your application. Resume with the matching `tool_call_id` and result. See [Application tools](https://pantheon-todo.alethiconsulting.com/docs/application-tools.html) for the pause and resume contract. On `SESSION_BUSY`, inspect the active run. Wait, resume pending tools, or cancel before sending another message. Use [Chat components](https://pantheon-todo.alethiconsulting.com/docs/chat-components.html) for a ready-made chat UI. **Pantheon guarantees:** Pantheon allows one active run per session. Runs pause for application tools and resume with their results. Read [Sessions and runs](https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html). 6 ## Run it Read error codes and request ids. Revoke exposed keys from the dashboard. Review audit records. | Method | Route | | --- | --- | | `DELETE` | `/v1/auth/keys/{key_id}` | | `GET` | `/v1/audit/records` | Use a signed-in dashboard session to revoke keys. API keys cannot manage other keys. See [Manage keys](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html#manage) and [Audit routes](https://pantheon-todo.alethiconsulting.com/docs/api-reference.html). Filter sessions, inspect usage and events, and archive sessions from the dashboard [Sessions](https://pantheon-todo.alethiconsulting.com/dashboard/sessions.html) page. **Pantheon guarantees:** Errors include codes and request ids. Revoked keys stop working. Audit records track tenant actions. Read [Error handling](https://pantheon-todo.alethiconsulting.com/docs/errors.html). 7 ## Iterate Edit your agent. Create a new tool version when its contract changes. Publish and activate a new deployment. To roll back, activate an earlier published deployment. | Method | Route | | --- | --- | | `PUT` | `/v1/agents/{agent_id}` | | `POST` | `/v1/tools/{tool_id}/versions` | | `POST` | `/v1/deployments` | | `POST` | `/v1/deployments/{id}/activate` | See [Roll back a deployment](https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html#rollback) for activation behavior. **Pantheon guarantees:** New slug sessions receive the active deployment. Existing sessions keep their pinned deployment. Read [Endpoints and schemas](https://pantheon-todo.alethiconsulting.com/docs/api-reference.html). https://pantheon-todo.alethiconsulting.com/docs/concepts.html # Core concepts > Learn how Pantheon objects fit together. ## Agent An agent stores instructions, a model, tool requirements, and `requires_sandbox`. Updating an agent creates a new version. Existing sessions do not change. ## Tool A tool has a stable identity and immutable definition versions. An application tool runs in your application. Its definition declares JSON input and output schemas. ## Deployment A deployment pins one agent version and its tool definitions. A session created by agent slug uses the active deployment. Without one, it uses the newest published deployment. It uses a legacy deployment snapshot only as a fallback. ## Session A session keeps conversation state and the deployment snapshot chosen at creation. `deployment_snapshot_id` identifies the pinned deployment snapshot. | deployment_source | Meaning | | --- | --- | | `published` | The slug selected the active deployment, or the newest published deployment when none is active. | | `legacy` | Pantheon used a legacy deployment snapshot. | ### Sandbox status | Value | Meaning | | --- | --- | | `ready` | The sandbox is available. | | `stopped` | The sandbox exists but is stopped. | | `missing` | The recorded sandbox no longer exists. | | `none` | This session does not require a sandbox. | | `unknown` | Pantheon could not determine availability. | ## Run A run processes one turn in a session. | Value | Meaning | | --- | --- | | `queued` | The run is waiting for a worker. | | `running` | The agent is processing the turn. | | `waiting_for_input` | The run needs an application tool result. | | `completed` | The run finished successfully. | | `failed` | The run stopped with an error. | | `cancelled` | A client cancelled the run. | | `expired` | The run passed its wait deadline. | Only one run can be active. A second run returns `SESSION_BUSY`. ## Event Durable events have an SSE id and sequence. Pantheon stores and replays them. Ephemeral deltas have no SSE id. Pantheon does not replay them. Event `content` is a block list or JSON object. ## Application tool 1 ### Receive the call Wait for `agent.custom_tool_use`. 2 ### Execute the tool Validate input, ask for approval when needed, then run the application tool. 3 ### Resume the run Send the result with the event id and a stable idempotency key. ## Sandbox Set `requires_sandbox` to `false` when every tool runs in your application. If Pantheon replaces a missing sandbox, it emits `sandbox.recreated`. Unpublished files from the old workspace are lost. ## Pitfalls > - **Agent edits do not update existing sessions:** Create a new deployment and a new session. > - **A waiting tool blocks the session:** Resume or cancel its run before starting another. > - **MCP configuration does not run:** Pantheon stores MCP configuration but does not dispatch it. Use an application tool. https://pantheon-todo.alethiconsulting.com/docs/quickstart.html # Your first agent in 10 minutes > Create an application tool, publish an agent, and complete a tool call. ## Prepare [Create an API key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html). Install `curl` and `jq`. ```bash 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. ```bash 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. ```bash 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. ```bash 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. ```bash 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}' ``` ```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. ```bash export PANTHEON_AGENT_SLUG="$AGENT_SLUG" ``` ```typescript npm install @alethi/pantheon-chat ``` ```bash pip install pantheon-chat ``` ```javascript 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; } ``` ```python 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](https://pantheon-todo.alethiconsulting.com/docs/application-tools.html) for approval and retry behavior. ### Raw HTTP Use the wait endpoint when you do not need live output. ```bash 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. https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html # Get an API key > Create an account, copy a key once, and revoke it when needed. ## Create an account 1 ### Sign up Open `/dashboard/signup.html`. Enter your email, password, and organization name. Your password must contain at least 10 characters. Pantheon Create an account form in the dark theme Create an account with your email, password, and organization name. 2 ### Sign in Use `/dashboard/index.html` when you return. Pantheon opens the keys page after sign in. Pantheon Sign in form in the dark theme Sign in with the email and password for your Pantheon account. 3 ### Create a key Enter a name on the keys page. Pantheon uses the full tenant builder scopes by default. Pantheon keys page showing a newly created one time secret Create the key to reveal its secret one time. 4 ### Copy the secret Copy the secret immediately. Pantheon shows it once and stores only its hash. Pantheon keys page confirming the secret was copied to the clipboard Copy the secret and keep it in a secure location. ## Use the key Set the production base URL and the one-time secret. ```bash export PANTHEON_BASE_URL="https://pantheon-todo.alethiconsulting.com/pantheon-api" export PANTHEON_API_KEY="PANTHEON_API_KEY" ``` Send the key as a bearer token. API keys cannot list, create, or revoke other keys. ## Manage keys The keys page lists each key without its secret. Pantheon keys page listing a key without its secret Review key details without exposing the secret. | Action | Result | | --- | --- | | Create | Pantheon shows the new secret once. | | Review | You see the name, prefix, scopes, creation time, last use, and revocation state. | | Revoke | The key stops working and returns `KEY_REVOKED`. | Revoke a key from the same page. Create a replacement before revoking an active key. ## Auth release routes | Method | Path | Use | | --- | --- | --- | | `POST` | `/v1/auth/signup` | Create a user, tenant, and session. | | `POST` | `/v1/auth/login` | Start a session. | | `POST` | `/v1/auth/logout` | Revoke the current session. | | `GET` | `/v1/auth/me` | Read the current user, tenant, and session expiry. | | `GET` | `/v1/auth/keys` | List keys without secrets. | | `POST` | `/v1/auth/keys` | Create a key and return its secret once. | | `DELETE` | `/v1/auth/keys/{id}` | Revoke a key. | | `POST` | `/v1/auth/password/change` | Change the signed-in user's password. | ### Signup response Send `email`, `password`, and `organization_name`. A successful request returns HTTP 201. ```json { "user": {"id": "user_123", "email": "you@example.com"}, "tenant": {"id": "tenant_123", "name": "Acme", "slug": "acme"}, "session": {"token": "ps_SESSION_TOKEN", "expires_at": "2026-10-04T12:00:00Z"} } ``` ### Key response Send `name` and optional `scopes`. A successful request returns HTTP 201. ```json { "id": "key_123", "name": "Development", "prefix": "pk_example", "scopes": ["..."], "created_at": "2026-09-04T12:00:00Z", "secret": "PANTHEON_API_KEY" } ``` ### Limits and sessions | Value | Meaning | | --- | --- | | 5 signups per IP per hour | Further signup requests wait until the rate window resets. | | 10 login attempts per email per 15 minutes | Further attempts return HTTP 429 with `retry_after`. | | 30 day session expiry | Valid session use extends the expiry. | | `ps_` prefix | Identifies an opaque session token. | The dashboard stores the session token in `localStorage` under `pantheon.session`. ## Manage your application Create and edit agents, publish deployments, and review deployment history on the dashboard [Agents](https://pantheon-todo.alethiconsulting.com/dashboard/agents.html) page. Register tools, create versions, and deprecate versions on the dashboard [Tools](https://pantheon-todo.alethiconsulting.com/dashboard/tools.html) page. Filter sessions, inspect usage and events, and archive sessions on the dashboard [Sessions](https://pantheon-todo.alethiconsulting.com/dashboard/sessions.html) page. ## Reset a password Change a known password from the account page. Email delivery is not available yet. An admin resets a forgotten password with `POST /v1/admin/users/{id}/password`. ## Pitfalls > - **The secret is missing from the key list:** This is expected. Pantheon returns the secret only when you create the key. > - **Sign in returns INVALID_CREDENTIALS:** Check both fields. `INVALID_CREDENTIALS` does not identify which value is wrong. > - **Sign in returns RATE_LIMITED:** Wait for the retry_after interval after `RATE_LIMITED`. > - **You forgot your password:** Ask an administrator to reset it. Email reset is not available yet. https://pantheon-todo.alethiconsulting.com/docs/self-host.html # Self-host Pantheon > Run the API, worker, migrations, and PostgreSQL with Docker Compose. ## Prerequisites - Install Docker Engine with Docker Compose. - Clone Pantheon and open the `api` directory. - Create an OpenRouter API key for model calls. - Create a Daytona API key only when an agent requires a sandbox. ## Create the Compose file Save this file as `docker-compose.yaml` in the `api` directory. ```yaml services: postgres: image: pgvector/pgvector:pg15 environment: POSTGRES_USER: ai_user POSTGRES_PASSWORD: secret POSTGRES_DB: pantheon_db volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ai_user -d pantheon_db"] interval: 2s timeout: 3s retries: 15 migrate: build: context: . target: runtime image: pantheon:latest env_file: [.env] environment: POSTGRES_DATABASE_URL: postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db depends_on: postgres: condition: service_healthy command: alembic upgrade head restart: "no" api: build: context: . target: runtime image: pantheon:latest env_file: [.env] environment: POSTGRES_DATABASE_URL: postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db ports: - "8000:8000" depends_on: migrate: condition: service_completed_successfully command: uvicorn app.main:app --host 0.0.0.0 --port 8000 restart: unless-stopped worker: image: pantheon:latest env_file: [.env] environment: POSTGRES_DATABASE_URL: postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db depends_on: migrate: condition: service_completed_successfully command: python -m app.modules.workers.runtime_worker restart: unless-stopped volumes: pgdata: ``` | Service | Purpose | | --- | --- | | postgres | Stores Pantheon data in the `pgdata` volume. | | migrate | Applies Alembic migrations before the API or worker starts. | | api | Serves the REST API on host port 8000. | | worker | Claims queued runs and executes agents. | ## Set the environment Save the required values in `api/.env`. Restrict access to this file. ```bash OPENROUTER_API_KEY=replace-with-your-openrouter-secret DAYTONA_API_KEY= POSTGRES_DATABASE_URL=postgresql+psycopg2://ai_user:secret@postgres:5432/pantheon_db PANTHON_BOOTSTRAP_ADMIN_KEY=ak_replace-with-a-long-random-secret ``` | Variable | Purpose | Secret | | --- | --- | --- | | `OPENROUTER_API_KEY` | Authorizes model calls. | Yes | | `DAYTONA_API_KEY` | Creates sandbox environments. Leave it empty when `requires_sandbox` is `false`. | Yes | | `POSTGRES_DATABASE_URL` | Connects each Pantheon service to PostgreSQL. | Yes | | `PANTHON_BOOTSTRAP_ADMIN_KEY` | Seeds the first admin key when the API starts. | Yes | ## Start Pantheon 1 ### Build and start Compose waits for PostgreSQL, runs migrations, then starts the API and worker. 2 ### Check each service The migration service should exit successfully. The API and worker should remain running. 3 ### Protect the admin key The API stores its hash on first start. Keep the raw bootstrap value in a secret manager. ```bash cd /path/to/pantheon/api docker compose up --build -d docker compose ps docker compose logs migrate api worker ``` ## Create the first tenant ### Admin API Use the bootstrap admin key to create a tenant and its first API key. ```bash export PANTHEON_BASE_URL="http://localhost:8000" export PANTHEON_API_KEY="PANTHEON_API_KEY" TENANT_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/admin/tenants" \ -H "Authorization: Bearer $PANTHEON_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Acme","slug":"acme"}') TENANT_ID=$(printf '%s' "$TENANT_JSON" | jq -r '.id') KEY_JSON=$(curl -sS -X POST "$PANTHEON_BASE_URL/v1/admin/tenants/$TENANT_ID/api-keys" \ -H "Authorization: Bearer $PANTHEON_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Development","permissions":["*"]}') printf '%s' "$KEY_JSON" | jq '{id, tenant_id, name, permissions, key_preview, raw_key, created_at}' ``` The key creation response contains `raw_key` once. ```json { "id": "key_123", "tenant_id": "tenant_123", "name": "Development", "permissions": ["*"], "key_preview": "pk_example...", "raw_key": "PANTHEON_API_KEY", "created_at": "2026-09-04T12:00:00Z" } ``` ### Dashboard After the auth release, open `/dashboard/signup.html`. The dashboard talks to the API that served it, so no base URL setup is needed. Sign up with an email, password, and organization name. Then create a key on the keys page. Read [Get an API key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html) for the complete flow. ## Upgrade Back up PostgreSQL first. Then pull the source, rebuild images, migrate, and restart services. ```bash cd /path/to/pantheon/api git pull --ff-only docker compose build --pull docker compose run --rm migrate docker compose up -d --remove-orphans ``` ## Back up PostgreSQL Create a compressed dump outside the database container. ```bash cd /path/to/pantheon/api docker compose exec -T postgres \ pg_dump -U ai_user -d pantheon_db -Fc > pantheon.dump ``` Store the dump away from the Docker host. Test restoration on a separate database. ## Troubleshooting | Symptom | Cause | What to do | | --- | --- | --- | | Migrations wait for a lock. | Idle database connections still hold the migration lock. | Stop the API and worker. Close idle connections, then run the migration again. | | The API cannot bind port 8000. | Another process uses the host port. | Stop that process or change the left side of `8000:8000`. | | Sandbox creation fails. | The Daytona account reached its quota. | Increase the quota or use an agent with `requires_sandbox` set to `false`. | ## Pitfalls > - **The migration service keeps running:** Inspect its logs before starting the API manually. > - **The worker cannot call a model:** Set OPENROUTER_API_KEY in the shared .env file and restart the worker. > - **A sandbox request uses mock mode:** Set DAYTONA_API_KEY before using an agent that requires a sandbox. https://pantheon-todo.alethiconsulting.com/docs/chat-components.html # Chat components > Add streaming chat without writing an event reducer. ## Install [Create an API key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html). Install the SDK for your application. ```typescript npm install @alethi/pantheon-chat react@18 react-dom@18 ``` ```bash pip install pantheon-chat ``` ## React Render the component with an agent slug and application tool handlers. ```typescript import { PantheonChat } from "@alethi/pantheon-chat/react"; export default function App() { return ( ({ echoed: input.message }), todo_ops: { requireApproval: true, execute: async (input) => ({ applied: true, operations: input.operations }), }, }} /> ); } ``` `requireApproval` shows an in-page Approve or Decline card. ## Node client Use the core client with Node 18 or later. ```javascript import { PantheonClient } from "@alethi/pantheon-chat"; const baseUrl = process.env.PANTHEON_BASE_URL; const apiKey = process.env.PANTHEON_API_KEY; const client = new PantheonClient({ baseUrl, apiKey }); const session = await client.createSession({ agentSlug: "todo-assistant" }); const run = await client.startRun(session.id, "Hello"); for await (const event of client.streamEvents(session.id)) { if (event.type === "message.delta") process.stdout.write(event.delta.text || ""); if (event.type === "run.completed") break; } ``` ## Props | Prop | Use | | --- | --- | | `baseUrl` | Pantheon API base URL. | | `apiKey` | Bearer API key. | | `agentSlug` | Agent for a new session. | | `deploymentId` | Explicit deployment instead of a slug. | | `tools` | Application tool handlers and approval rules. | | `theme` | `light`, `dark`, or `auto`. | | `title` | Session title. | ## Python Use the async client directly. ```python 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="todo-assistant") run = await client.start_run(session.id, "What is 15 + 15?") async for event in client.stream_events(session.id, run_id=run.run_id, stop_on_terminal=True): print(event.type, event.content or event.data) asyncio.run(main()) ``` ### Terminal chat ```bash export PANTHEON_BASE_URL="https://pantheon-todo.alethiconsulting.com/pantheon-api" export PANTHEON_API_KEY="PANTHEON_API_KEY" export PANTHEON_AGENT_SLUG="todo-assistant" python -m pantheon_chat ``` Application tools ask for approval. Pass `--auto-approve` only for trusted tools. ### Streamlit ```bash pip install "pantheon-chat[streamlit]" export PANTHEON_BASE_URL="https://pantheon-todo.alethiconsulting.com/pantheon-api" export PANTHEON_API_KEY="PANTHEON_API_KEY" export PANTHEON_AGENT_SLUG="todo-assistant" pantheon-chat-streamlit ``` ## Pitfalls > - **Queued text can wait behind approval:** Resolve the approval before expecting the queued message to run. > - **A network reconnect repeats output:** Persist only durable SSE ids. Never use an ephemeral delta's JSON id as the cursor. > - **A Stop request fails:** Keep the run active and show the error until cancel returns HTTP 202. https://pantheon-todo.alethiconsulting.com/docs/application-tools.html # Application tools > Execute a tool in your application, then resume the waiting run. ## 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`. ```yaml 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. ```bash 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: ```bash { "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. https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html # Sessions and runs > Manage deployment pinning, sandbox state, concurrency, and cleanup. ## Sessions Create a session with `agent_slug` or `deployment_id`. The session stays pinned to its selected deployment snapshot. | deployment_source | Meaning | | --- | --- | | `published` | The slug selected the active deployment, or the newest published deployment when none is active. | | `legacy` | Pantheon used a legacy deployment snapshot. | ## Sandbox status | Value | Meaning | | --- | --- | | `ready` | The sandbox is available. | | `stopped` | The sandbox exists but is stopped. | | `missing` | The recorded sandbox no longer exists. | | `none` | This session does not require a sandbox. | | `unknown` | Pantheon could not determine availability. | ## Runs Start one run at a time. Stream until the run becomes terminal or waits for input. | Value | Meaning | | --- | --- | | `queued` | The run is waiting for a worker. | | `running` | The agent is processing the turn. | | `waiting_for_input` | The run needs an application tool result. | | `completed` | The run finished successfully. | | `failed` | The run stopped with an error. | | `cancelled` | A client cancelled the run. | | `expired` | The run passed its wait deadline. | ## Recover from SESSION_BUSY A second run returns HTTP 409 with `SESSION_BUSY`. ```json { "error": { "code": "SESSION_BUSY", "message": "Session 'session_123' has an active run 'run_123' in status 'waiting_for_input'.", "request_id": "request_123", "active_run_id": "run_123", "active_run_status": "waiting_for_input", "recovery": [ "cancel the active run via POST /v1/sessions/session_123/runs/run_123/cancel", "or resume it via POST /v1/sessions/session_123/runs/run_123/resume" ] } } ``` Resume the active run when it waits for a tool. Otherwise wait or cancel it. ```bash curl -sS -X POST \ "$PANTHEON_BASE_URL/v1/sessions/$SESSION_ID/runs/$ACTIVE_RUN_ID/cancel" \ -H "Authorization: Bearer $PANTHEON_API_KEY" ``` ## Archive a session Archive finished sessions. Pantheon then destroys the attached sandbox. ```bash curl -sS -X POST "$PANTHEON_BASE_URL/v1/sessions/$SESSION_ID/archive" \ -H "Authorization: Bearer $PANTHEON_API_KEY" ``` ## Publish updates 1 ### Update the agent Changing an agent creates a new version. 2 ### Publish again Create a deployment for that version and its tool definitions. 3 ### Create a session New slug sessions select the active deployment, or the newest published deployment when none is active. Existing sessions stay pinned. ## Roll back a deployment Send `POST` to `/v1/deployments/{id}/activate` with the earlier published deployment id. This makes that deployment active for new sessions created by agent slug. Existing pinned sessions stay unchanged. Only published deployments can be activated. Legacy snapshots return HTTP 409. An active deployment takes precedence over newer publications until you activate another deployment. Use `agent_id` to filter `GET` `/v1/deployments`. Each returned deployment includes an `active` boolean. Activation returns the deployment with this flag set to `true`. ## Pitfalls > - **Session creation can take about 2.5 seconds:** Sandbox provisioning happens during creation when required. > - **Run creation can take about 2.7 seconds:** Show a pending state before streaming begins. > - **Pantheon recreates a missing sandbox:** Treat sandbox.recreated as data loss for unpublished workspace files. https://pantheon-todo.alethiconsulting.com/docs/events.html # Events > Render live output and replay durable session state. ## Connect Set `event_deltas` to `true` for live text and reasoning deltas. ```bash curl -N -sS \ "$PANTHEON_BASE_URL/v1/sessions/$SESSION_ID/events/stream?event_deltas=true" \ -H "Authorization: Bearer $PANTHEON_API_KEY" \ -H "Last-Event-ID: $LAST_EVENT_ID" ``` The server sends a keep-alive comment every 15 seconds. ## Durable events Durable events have an SSE `id` and JSON `sequence`. Save the SSE id as your replay cursor. ```yaml event: tool.started id: evt_101 data: {"id":"evt_101","sequence":7,"type":"tool.started","workspace_id":"workspace_123","session_id":"session_123","run_id":"run_123","created_at":"2026-09-03T12:00:01Z","content":{"tool_call_id":"call_123","name":"echo_tool","input":{"message":"hello"}},"data":{"tool_call_id":"call_123","name":"echo_tool"}} ``` Text event content is a list of blocks. Structured event content is a JSON object. ## Ephemeral events Ephemeral events have no SSE id. ```yaml event: message.delta data: {"type":"message.delta","run_id":"run_123","delta":{"text":"Hello"}} ``` Pantheon does not persist or replay these events. ## Event catalog | Value | Meaning | | --- | --- | | `run.started` | A worker started the run. | | `run.completed` | The run finished successfully. | | `run.failed` | The run stopped with an error. | | `run.cancelled` | A client cancelled the run. | | `message.started` | Assistant message generation started. | | `message.delta` | A live assistant text fragment arrived. | | `message.completed` | Pantheon stored the final assistant message. | | `reasoning.delta` | A live reasoning fragment arrived. | | `reasoning.message` | Pantheon stored a durable reasoning message. | | `intermediate.delta` | A live intermediate output fragment arrived. | | `tool.started` | The agent started a tool call. | | `agent.custom_tool_use` | Your application must execute an application tool. | | `tool.completed` | A tool call completed. | | `tool.failed` | A tool call failed. | | `session.status_idle` | The session returned to idle. | | `session.error` | A session error occurred. | | `sandbox.recreated` | Pantheon replaced a missing sandbox. | | `stream.resync` | The client must replay and reconnect. | The application tool lifecycle events share one `tool_call_id`. Read [Application tools](https://pantheon-todo.alethiconsulting.com/docs/application-tools.html) for correlation and resume steps. ## Handle stream.resync 1 ### Save the cursor Keep the last durable SSE id. 2 ### Reconnect Send that id in `Last-Event-ID`. 3 ### Read the replay Pantheon replays persisted events before live output resumes. ## Pitfalls > - **Deltas repeat after reconnect:** Use only the SSE id from durable events as the cursor. > - **Live text is missing:** Include event_deltas=true. Final durable message events still arrive. > - **Two tool pills appear:** Merge every tool lifecycle event by tool_call_id. https://pantheon-todo.alethiconsulting.com/docs/errors.html # Errors > Read the error code, then take the matching recovery action. ## Error envelope Read `error.code` for recovery logic. Show `error.message` to a developer. ```json { "error": { "code": "tool_result_invalid", "message": "Tool result failed schema validation.", "request_id": "request_123", "details": [{"path": "content.title", "reason": "minLength"}] } } ``` ## Error codes | Error code | Meaning | | --- | --- | | `invalid_request` | HTTP 400. Fix the request body or parameter. | | `invalid_cursor` | HTTP 400. Restart replay without the bad cursor. | | `authentication_required` | HTTP 401. Send a valid bearer key. | | `permission_denied` | HTTP 403. [Create a key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html) with the required scope. | | `resource_not_found` | HTTP 404. Check the id and workspace. | | `idempotency_conflict` | HTTP 409. Reuse a key only with the same payload. | | `event_already_resolved` | HTTP 409. Do not execute the tool again. | | `event_expired` | HTTP 409. Start a new run. | | `run_not_resumable` | HTTP 409. Read the run status. Start a new run if it is terminal. | | `SESSION_BUSY` | HTTP 409. Wait, cancel, or resume the active run. | | `RUN_NOT_CANCELLABLE` | HTTP 409. Treat the run as already terminal. | | `tool_input_invalid` | HTTP 422. Fix the tool input schema or prompt. | | `tool_result_invalid` | HTTP 422. Return an object matching the tool output schema. | | `RATE_LIMITED` | HTTP 429. Wait for Retry-After before retrying. See [Usage and limits](https://pantheon-todo.alethiconsulting.com/docs/usage.html). | | `runtime_resume_unavailable` | HTTP 503. Retry with the same idempotency key. | | `SANDBOX_PROVISION_FAILED` | HTTP 503. Retry later or remove an unnecessary sandbox requirement. | ## SESSION_BUSY ```json { "error": { "code": "SESSION_BUSY", "message": "Session 'session_123' has an active run 'run_123' in status 'waiting_for_input'.", "request_id": "request_123", "active_run_id": "run_123", "active_run_status": "waiting_for_input", "recovery": [ "cancel the active run via POST /v1/sessions/session_123/runs/run_123/cancel", "or resume it via POST /v1/sessions/session_123/runs/run_123/resume" ] } } ``` Read [Sessions and runs](https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html) for the recovery flow. ## SANDBOX_PROVISION_FAILED ```json { "error": { "code": "SANDBOX_PROVISION_FAILED", "message": "Sandbox provider quota is exhausted. Please retry later.", "request_id": "request_123" } } ``` ## RATE_LIMITED Tenant requests share three independent 60-second limits: 30 sessions, 60 runs, and 120 events. HTTP 429 includes a `Retry-After` header with the wait in seconds. ```json { "error": { "code": "RATE_LIMITED", "message": "Tenant rate limit reached.", "request_id": "request_123", "details": {"limit": 30, "window_seconds": 60, "retry_after": 12} } } ``` `details.limit` is the configured request limit. `details.window_seconds` is the window length in seconds. `details.retry_after` is the wait before retrying, matching the header. See [Usage and limits](https://pantheon-todo.alethiconsulting.com/docs/usage.html#limits) for affected operations. Request a higher limit through [Support](https://pantheon-todo.alethiconsulting.com/docs/support.html#limits). ## Recovery actions | Symptom | Action | | --- | --- | | 409 busy | Use `active_run_id`. Resume or cancel that run. | | 503 sandbox | Back off. Check quota. Disable sandboxing only when safe. | | 401 | Send `Authorization: Bearer PANTHEON_API_KEY`. | | 422 tool result | Validate the result against the registered output schema. | | Stream drops | Reconnect with the last durable SSE id. Handle `stream.resync`. | ## Pitfalls > - **Error code casing differs:** Use the exact code returned by the API. The contract contains lowercase and uppercase codes. > - **A 422 leaves a run waiting:** Correct the result and retry with the same idempotency key before the deadline. https://pantheon-todo.alethiconsulting.com/docs/known-issues.html # Known issues > Plan for current API limits and their workarounds. ## Open issues | Issue | Impact | Workaround | | --- | --- | --- | | **Run admission latency** | A run takes about 2.7 seconds to create. | Show a pending state and open the stream immediately. | | **Sandbox session latency** | A sandbox-backed session takes about 2.5 seconds to create. | Create sessions before the user needs them when appropriate. | | **Coarse live-bus resync** | One publisher failure can resync every subscriber on a session. | Handle stream.resync and replay durable events. | | **Strict tool output schema** | One shared schema can reject valid operation-specific results. | Use oneOf or omit fields that do not apply. | | **MCP servers are not dispatched, 4 September 2026** | Agents accept MCP server configuration, but the runtime does not dispatch it. | Use application tools. | | **OpenAPI prose gaps** | Some new fields and routes have limited contract examples. | Use these guides with `api/openapi/openapi.yaml` and the smoke flow. | | **Claim fairness** | A stuck older run can delay another tenant. | Use client timeouts and retry only safe requests. | | **Compensation claim race** | Two workers can race expired-sandbox cleanup. | Treat cleanup errors as transient and inspect final session state. | | **Admission finalization race** | A stale worker can race failure finalization. | Use terminal events as the final client state. | ## Recently fixed - Slug sessions prefer the active deployment, or the newest published deployment when none is active. - Tool lifecycle events now share one `tool_call_id`. - New sessions can use refreshed legacy deployment snapshots. - Streams now deliver live deltas and structured content. - Waiting application tools now have a 30 minute default deadline. ## Pitfalls > - **This page is date-bound:** Check the [Changelog](https://pantheon-todo.alethiconsulting.com/docs/changelog.html) before assuming an item is still open. > - **Todo UI issues are separate:** Do not infer an API failure from a tenant rendering defect. https://pantheon-todo.alethiconsulting.com/docs/api-reference.html # API reference > Inspect the current OpenAPI routes and shared schemas. ## Conventions | Base URL | `https://pantheon-todo.alethiconsulting.com/pantheon-api` | | --- | --- | | Authentication | `Authorization: Bearer PANTHEON_API_KEY` | | JSON | Send `Content-Type: application/json`. | | Streaming | Request `text/event-stream`. | | Idempotency | Use an 8 to 255 character key where required. | ## Endpoints | Method | Path | Purpose | Success | Errors | | --- | --- | --- | --- | --- | | `POST` | `/v1/auth/signup` | Signup | 201 | 422 | | `POST` | `/v1/auth/login` | Login | 200 | 422 | | `POST` | `/v1/auth/logout` | Logout | 204 | None | | `GET` | `/v1/auth/me` | Me | 200 | None | | `GET` | `/v1/auth/keys` | List Keys | 200 | None | | `POST` | `/v1/auth/keys` | Create Key | 201 | 422 | | `DELETE` | `/v1/auth/keys/{key_id}` | Revoke Key | 204 | 422 | | `POST` | `/v1/auth/password/change` | Change Password | 204 | 422 | | `POST` | `/v1/auth/email/verify/request` | Request Verification | 202 | 401, 409, 429 | | `POST` | `/v1/auth/email/verify/confirm` | Confirm Verification | 200 | 400, 422 | | `POST` | `/v1/auth/password/reset/request` | Request Password Reset | 202 | 422, 429 | | `POST` | `/v1/auth/password/reset/confirm` | Confirm Password Reset | 204 | 400, 422 | | `POST` | `/v1/admin/users/{user_id}/password` | Admin Reset Password | 204 | 422 | | `GET` | `/v1/admin/api-keys` | List Admin Api Keys | 200 | None | | `POST` | `/v1/admin/api-keys` | Create Admin Api Key | 201 | 422 | | `DELETE` | `/v1/admin/api-keys/{key_id}` | Delete Admin Api Key | 204 | 422 | | `POST` | `/v1/agents` | Create Agent | 201 | 422 | | `GET` | `/v1/agents` | List Agents | 200 | 422 | | `GET` | `/v1/agents/{agent_id}` | Get Agent | 200 | 422 | | `PUT` | `/v1/agents/{agent_id}` | Update Agent | 200 | 422 | | `DELETE` | `/v1/agents/{agent_id}` | Delete Agent | 204 | 422 | | `GET` | `/v1/agents/slug/{slug}` | Get Agent By Slug | 200 | 422 | | `GET` | `/v1/audit/records` | List Audit Records | 200 | 422 | | `POST` | `/v1/deployments` | Create a deployment | 201 | 422, 401, 403, 404 | | `GET` | `/v1/deployments` | List deployments | 200 | 422, 401, 403 | | `GET` | `/v1/deployments/{deployment_id}` | Get a deployment | 200 | 422, 401, 403, 404 | | `POST` | `/v1/deployments/{deployment_id}/activate` | Activate Deployment | 200 | 404, 409, 422 | | `POST` | `/v1/admin/environments` | Create Environment | 201 | 422 | | `GET` | `/v1/admin/environments` | List Environments Admin | 200 | 422 | | `GET` | `/v1/admin/environments/{env_id}` | Get Environment Admin | 200 | 422 | | `PUT` | `/v1/admin/environments/{env_id}` | Update Environment | 200 | 422 | | `DELETE` | `/v1/admin/environments/{env_id}` | Delete Environment | 204 | 422 | | `GET` | `/v1/environments` | List Environments | 200 | 422 | | `GET` | `/v1/environments/{env_id}` | Get Environment | 200 | 422 | | `POST` | `/v1/sessions/{session_id}/events` | Append session events | 200 | 429, 422, 401, 403, 404 | | `GET` | `/v1/sessions/{session_id}/events` | List session events | 200 | 422, 400, 401, 403, 404 | | `GET` | `/v1/sessions/{session_id}/events/stream` | Stream session events | 200 | 422, 400, 401, 403, 404 | | `GET` | `/health/components` | Component health | 200 | None | | `GET` | `/v1/sessions/{session_id}/messages` | List Messages | 200 | 422 | | `GET` | `/v1/models` | List Models | 200 | None | | `POST` | `/v1/sessions/{session_id}/runs/{run_id}/resume` | Resume a run | 202 | 429, 422, 400, 401, 403, 404, 409, 503 | | `POST` | `/v1/sessions/{session_id}/runs` | Start a run | 200 | 409, 429, 422, 400, 401, 403, 404, 503 | | `GET` | `/v1/sessions/{session_id}/runs/{run_id}/stream` | Stream run events | 200 | 422, 401, 403, 404 | | `POST` | `/v1/sessions/{session_id}/runs/wait` | Start a run and wait | 200 | 409, 429, 422, 401, 403, 404, 503 | | `POST` | `/v1/sessions/{session_id}/runs/{run_id}/stop` | Stop a run | 200 | 422, 401, 403, 404 | | `POST` | `/v1/sessions/{session_id}/runs/{run_id}/cancel` | Cancel a run | 202 | 409, 422, 401, 403, 404 | | `POST` | `/v1/sessions/{session_id}/sandbox` | Provision Sandbox | 201 | 422 | | `DELETE` | `/v1/sessions/{session_id}/sandbox` | Destroy Sandbox | 204 | 422 | | `POST` | `/v1/sessions/{session_id}/sandbox/sync` | Sync Sandbox | 200 | 422 | | `POST` | `/v1/sessions/{session_id}/sandbox/extend-ttl` | Extend Sandbox Ttl | 200 | 422 | | `POST` | `/v1/sessions/{session_id}/files/upload` | Upload File | 200 | 422 | | `GET` | `/v1/sessions/{session_id}/files/{path}` | Download File | 200 | 422 | | `GET` | `/v1/sessions/{session_id}/files` | List Files | 200 | 422 | | `POST` | `/v1/sessions` | Create Session | 201 | 429, 422 | | `GET` | `/v1/sessions` | List Sessions | 200 | 422 | | `GET` | `/v1/sessions/{session_id}` | Get Session | 200 | 422 | | `POST` | `/v1/sessions/{session_id}` | Update Session | 200 | 422 | | `DELETE` | `/v1/sessions/{session_id}` | Delete Session | 204 | 422 | | `POST` | `/v1/sessions/{session_id}/archive` | Archive Session | 200 | 422 | | `GET` | `/v1/skills` | List Tenant Skills | 200 | None | | `POST` | `/v1/skills` | Create Tenant Skill | 201 | 422 | | `GET` | `/v1/skills/{name}` | Get Tenant Skill | 200 | 422 | | `PUT` | `/v1/skills/{name}` | Update Tenant Skill | 200 | 422 | | `DELETE` | `/v1/skills/{name}` | Delete Tenant Skill | 204 | 422 | | `POST` | `/v1/skills/import` | Import Tenant Skill Md | 201 | 422 | | `POST` | `/v1/skills/validate` | Validate Skill | 200 | 422 | | `POST` | `/v1/skills/upload` | Import Tenant Skill File | 201 | 422 | | `GET` | `/v1/skills/{name}/download` | Download Tenant Skill | 200 | 422 | | `GET` | `/v1/skills/{name}/files/content` | Get Tenant Skill File Content | 200 | 422 | | `POST` | `/v1/skills/{name}/files` | Upload Tenant Skill File | 200 | 422 | | `POST` | `/v1/admin/tenants/{tenant_id}/skills` | Create Org Skill | 201 | 422 | | `POST` | `/v1/admin/skills/builtin` | Create Builtin Skill | 201 | 422 | | `GET` | `/v1/sessions/{session_id}/skills/catalog` | Get Skill Catalog | 200 | 422 | | `PUT` | `/v1/sessions/{session_id}/skills/enabled` | Set Enabled Skills | 200 | 422 | | `GET` | `/v1/sessions/{session_id}/artifacts` | List Artifacts | 200 | 422 | | `GET` | `/v1/sessions/{session_id}/artifacts/{artifact_id}` | Get Artifact | 200 | 422 | | `DELETE` | `/v1/sessions/{session_id}/artifacts/{artifact_id}` | Delete Artifact | 200 | 422 | | `GET` | `/v1/sessions/{session_id}/artifacts/{artifact_id}/download` | Download Artifact | 200 | 422 | | `GET` | `/v1/sessions/{session_id}/artifacts/{artifact_id}/files/{path}` | Serve Artifact Subfile | 200 | 422 | | `GET` | `/v1/api-keys` | List Tenant Api Keys | 200 | None | | `POST` | `/v1/api-keys` | Create Tenant Api Key | 201 | 422 | | `DELETE` | `/v1/api-keys/{key_id}` | Delete Tenant Api Key | 204 | 422 | | `POST` | `/v1/admin/tenants/{tenant_id}/api-keys` | Admin Create Tenant Api Key | 201 | 422 | | `GET` | `/v1/admin/tenants/{tenant_id}/api-keys` | Admin List Tenant Api Keys | 200 | 422 | | `DELETE` | `/v1/admin/tenants/{tenant_id}/api-keys/{key_id}` | Admin Delete Tenant Api Key | 204 | 422 | | `POST` | `/v1/users` | Create User | 201 | 422 | | `GET` | `/v1/users` | List Users | 200 | 422 | | `GET` | `/v1/users/{user_id}` | Get User | 200 | 422 | | `PUT` | `/v1/users/{user_id}` | Update User | 200 | 422 | | `DELETE` | `/v1/users/{user_id}` | Delete User | 204 | 422 | | `GET` | `/v1/admin/users` | Admin List Users | 200 | 422 | | `GET` | `/v1/admin/users/{user_id}` | Admin Get User | 200 | 422 | | `PUT` | `/v1/admin/users/{user_id}` | Admin Update User | 200 | 422 | | `POST` | `/v1/admin/tenants` | Create Tenant | 201 | 422 | | `GET` | `/v1/admin/tenants` | List Tenants | 200 | 422 | | `GET` | `/v1/admin/tenants/{tenant_id}` | Get Tenant | 200 | 422 | | `PUT` | `/v1/admin/tenants/{tenant_id}` | Update Tenant | 200 | 422 | | `PATCH` | `/v1/admin/tenants/{tenant_id}` | Update Tenant | 200 | 422 | | `DELETE` | `/v1/admin/tenants/{tenant_id}` | Delete Tenant | 204 | 422 | | `POST` | `/v1/tools` | Create a tool | 201 | 422, 400, 401, 403, 409 | | `GET` | `/v1/tools` | List tools | 200 | 422, 401, 403 | | `GET` | `/v1/tools/{tool_id}` | Get tool by ID | 200 | 422, 401, 403, 404 | | `POST` | `/v1/tools/{tool_id}/versions` | Create new tool definition version | 201 | 422, 400, 401, 403, 404, 409 | | `GET` | `/v1/tools/{tool_id}/versions` | List tool definition versions | 200 | 422, 401, 403, 404 | | `GET` | `/v1/tools/{tool_id}/versions/{version}` | Get specific tool definition version | 200 | 422, 401, 403, 404 | | `POST` | `/v1/tools/{tool_id}/versions/{version}/deprecate` | Deprecate a tool definition version | 200 | 422, 401, 403, 404 | | `GET` | `/v1/usage` | Get Usage | 200 | 422 | | `GET` | `/v1/admin/tenants/{tenant_id}/usage` | Get Admin Usage | 200 | 422 | | `GET` | `/health` | Healthcheck | 200 | None | | `GET` | `/` | Root Healthcheck | 200 | None | ## Schemas The checked-in OpenAPI file is the field-level source of truth. | Schema | Required fields | Properties | | --- | --- | --- | | `CreateToolRequest` | description, input_schema, output_schema, name, display_name | description, executor, input_schema, output_schema, side_effect, confirmation, name, display_name | | `ToolDetail` | tool, definition | tool, definition | | `CreateAgentRequest` | name, slug | name, slug, model, system_prompt, tools, tool_requirements, mcp_servers, skills, environment_id, requires_sandbox, metadata | | `AgentResponse` | id, name, slug, model | id, name, slug, model, system_prompt, tools, tool_requirements, mcp_servers, skills, environment_id, requires_sandbox, metadata, status, version, created_at, updated_at | | `CreateDeploymentRequest` | agent_id | agent_id, agent_version, tool_definition_ids, model, environment_id, application_tool_response_deadline_seconds | | `DeploymentResponse` | id, agent_id, agent_revision_id, agent_version, model, requires_sandbox, application_tool_response_deadline_seconds, prompt_policy_version, prompt_policy_hash, content_hash, source, status, active, created_at | id, agent_id, agent_revision_id, agent_version, model, environment_id, requires_sandbox, application_tool_response_deadline_seconds, prompt_policy_version, prompt_policy_hash, content_hash, source, status, active, tool_bindings, created_at | | `CreateSessionRequest` | None | agent_slug, deployment_id, prefer_latest_agent_version, user_id, environment_id, model, title, metadata | | `SessionResponse` | id, model | id, user_id, agent, agent_id, agent_slug, deployment, deployment_snapshot_id, deployment_source, environment_id, requires_sandbox, model, status, title, metadata, sandbox_id, sandbox_status, usage, created_at, updated_at, archived_at | | `CreateRunRequest` | None | events, model | | `RunResponse` | run_id, status | run_id, status, model_used, artifacts, required_event_ids | | `ResumeRequest` | events | events | | `ResumeAccepted` | run_id, status, accepted_events, remaining_event_ids | run_id, status, accepted_events, remaining_event_ids | | `EventResponse` | id, type | id, type, content, parent_event_id, run_id, model_used, seq, metadata, created_at, sequence, workspace_id, session_id, in_reply_to_event_id, data | | `ErrorEnvelope` | error | error | | `SessionBusyErrorEnvelope` | error | error | | `UsageResponse` | from, to, totals, daily | sandbox_seconds_estimated, from, to, totals, daily | | `UsageTotals` | None | sessions_created, runs_total, input_tokens, output_tokens, sandbox_seconds, tool_calls, runs_by_status | | `DailyUsage` | date | sessions_created, runs_total, input_tokens, output_tokens, sandbox_seconds, tool_calls, date | | `TenantRateLimitEnvelope` | error | error | | `TenantRateLimitDetails` | limit, window_seconds, retry_after | limit, window_seconds, retry_after | ## Use the contract Generate clients from `api/openapi/openapi.yaml`. Read [Get an API key](https://pantheon-todo.alethiconsulting.com/docs/get-a-key.html) for account setup and [Usage and limits](https://pantheon-todo.alethiconsulting.com/docs/usage.html) for tenant reporting. Use the [Quickstart](https://pantheon-todo.alethiconsulting.com/docs/quickstart.html) to test the generated client against a complete flow. ## Pitfalls > - **New route examples are thin:** Read [Events](https://pantheon-todo.alethiconsulting.com/docs/events.html), [Errors](https://pantheon-todo.alethiconsulting.com/docs/errors.html), and [Sessions and runs](https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html). > - **A schema changes:** Regenerate both SDKs from the same checked-in OpenAPI contract. https://pantheon-todo.alethiconsulting.com/docs/usage.html # Usage and limits > Read tenant usage and plan requests around the shared limits. ## Read usage Call `GET` `/v1/usage` with a tenant key carrying `usage:read`. Tenant keys receive this scope by default. A signed-in dashboard session token also works. The response covers only your tenant. Omit the dates for the last 30 days, including today. Set `from` and `to` to inclusive UTC dates. The maximum range is 92 days. ```bash curl -sS --get "$PANTHEON_BASE_URL/v1/usage" \ -H "Authorization: Bearer $PANTHEON_API_KEY" \ --data-urlencode "from=2026-09-06" \ --data-urlencode "to=2026-09-06" ``` ```json { "sandbox_seconds_estimated": false, "from": "2026-09-06", "to": "2026-09-06", "totals": { "sessions_created": 2, "runs_total": 3, "input_tokens": 1200, "output_tokens": 450, "sandbox_seconds": 180.0, "tool_calls": 2, "runs_by_status": { "completed": 2, "failed": 1 } }, "daily": [ { "date": "2026-09-06", "sessions_created": 2, "runs_total": 3, "input_tokens": 1200, "output_tokens": 450, "sandbox_seconds": 180.0, "tool_calls": 2 } ] } ``` | Value | Meaning | | --- | --- | | `from`, `to` | Inclusive UTC dates for the report. | | `totals` | Combined metrics across the selected dates. | | `totals.runs_by_status` | Run counts keyed by status. | | `daily` | One row per UTC date, including days with zero usage. | | `sessions_created` | Sessions created during the period. | | `runs_total` | Runs created during the period. | | `input_tokens`, `output_tokens` | Recorded tokens for those runs. | | `sandbox_seconds` | Sandbox lifetime within the selected period, in seconds. | | `tool_calls` | Number of tool.started events. | | `sandbox_seconds_estimated` | True when sandbox time includes an estimated end. | ## Per-tenant limits Requests from keys and dashboard sessions share the same tenant limits. Each limit has its own 60-second window. | Value | Meaning | | --- | --- | | `sessions_per_minute` | 30 session creation requests per 60 seconds. | | `runs_per_minute` | 60 run requests per 60 seconds. | | `events_per_minute` | 120 event requests per 60 seconds. | Session creation uses `/v1/sessions`. Run creation uses `/v1/sessions/{id}/runs`. Posting events uses `/v1/sessions/{id}/events`. Resume requests share the event limit. Operations that resume execution also use the run limit. HTTP 429 returns `RATE_LIMITED` with limit details and a `Retry-After` header. Wait for the specified seconds before retrying. See [the error response](https://pantheon-todo.alethiconsulting.com/docs/errors.html#rate-limited). Tenant overrides can change these defaults. Ask for a higher limit through [Support](https://pantheon-todo.alethiconsulting.com/docs/support.html#limits). ## Sandbox seconds Time is summed across sandbox instances, including recorded earlier instances after recreation. Each lifetime is clipped to your date range and split across UTC days. Concurrent sandbox lifetimes add together. Open sandbox time counts up to now, subject to the observation cap below. Missing or stopped resources use the provider end timestamp when available. Otherwise, Pantheon uses the observation time. Unobserved open resources are capped at their last observation plus the configured provider idle timeout. The Daytona default is 10 minutes to stop plus 5 minutes to delete. Legacy records without an observation use the instance start. Estimated ends set `sandbox_seconds_estimated` to `true`. ## Pitfalls > - **Usage returns HTTP 422:** The date range must contain 1 to 92 inclusive UTC days. Correct the dates. > - **Requests return HTTP 429:** Your tenant exhausted a shared limit. Wait for Retry-After before retrying. > - **Sandbox seconds are estimated:** Read sandbox_seconds_estimated before using these totals for precise accounting. https://pantheon-todo.alethiconsulting.com/docs/support.html # Support > Get help with a Pantheon integration. > **Contact to be confirmed** > > `support@alethiconsulting.com` is a placeholder. Confirm the address before sending a report. ## Report a problem Check [Known issues](https://pantheon-todo.alethiconsulting.com/docs/known-issues.html) first. Include: - The `request_id` from the failed request. - Your tenant slug. - The time in UTC. - The error code, steps to reproduce, and expected result. Remove API keys, passwords, and private message content from the report. ## MCP status MCP server settings are stored. The runtime does not dispatch MCP tools. Dispatch is planned. Use [Application tools](https://pantheon-todo.alethiconsulting.com/docs/application-tools.html) for execution today. ## Service status Check the [status page](https://pantheon-todo.alethiconsulting.com/status/) for API, database, worker, and sandbox provider health. ## Request a higher limit Ask your Pantheon administrator or confirmed support contact for a tenant limit increase. Include your tenant slug, the affected operation, the requested requests per minute, and your expected traffic. An administrator can override each tenant limit. See [default limits](https://pantheon-todo.alethiconsulting.com/docs/usage.html#limits). https://pantheon-todo.alethiconsulting.com/docs/terms.html # Terms of use (draft) > Proposed terms for using Pantheon. > **Draft, not yet reviewed** > > This draft does not describe final approved terms. ## Use the service Use Pantheon for lawful applications. Do not access other tenants, disrupt the service, or bypass access controls. You manage your account, API keys, application tools, and the data you submit. ## Review agent actions Check generated output before relying on it. Add approval checks before tools perform sensitive actions. ## Availability Pantheon is a builder preview. Features and limits may change. This draft promises no uptime or support response time. ## Questions See [Support](https://pantheon-todo.alethiconsulting.com/docs/support.html) for contact status and [Data handling](https://pantheon-todo.alethiconsulting.com/docs/data.html) for storage details. https://pantheon-todo.alethiconsulting.com/docs/data.html # Data handling (draft) > What Pantheon stores and who can access it. > **Draft, not yet reviewed** > > These handling details need review before this policy is final. ## Stored data - Accounts and hashed passwords. - Hashed session tokens and API key hashes. - Agent definitions. - Session events, including user messages and tool inputs and outputs. - Audit rows. ## Storage location Pantheon stores this data in PostgreSQL on Alethi infrastructure. ## Retention Agent and session data are retained until the tenant deletes the agent or session. Backups are kept per release. Retention periods for accounts, credentials, audit rows, and release backups remain to be confirmed. ## Access Alethi operators can access stored data for support. Limit the private data you send in messages and tool results. See [Support](https://pantheon-todo.alethiconsulting.com/docs/support.html) for questions about stored data. https://pantheon-todo.alethiconsulting.com/docs/changelog.html # Changelog > Track builder-facing contract changes. ## 6 September 2026 - Dashboard Agents, Tools, and Sessions pages manage agents, tool versions, deployments, and sessions. - [Usage and limits](https://pantheon-todo.alethiconsulting.com/docs/usage.html) documents GET /v1/usage and the three per-tenant rate limits. - The [status page](https://pantheon-todo.alethiconsulting.com/status/) reports component health. - [Deployment rollback](https://pantheon-todo.alethiconsulting.com/docs/sessions-and-runs.html#rollback) selects an active deployment for new slug sessions. - [Per-operation tool result schemas](https://pantheon-todo.alethiconsulting.com/docs/application-tools.html#schemas) support top-level oneOf and anyOf branches. - SDKs published: `@alethi/pantheon-chat` 0.1.0 on npm, `pantheon-chat` 0.1.0 on PyPI. - Email verification and password reset. - Build on Pantheon page. - `llms.txt`. ## 4 September 2026 - Slug sessions now prefer the newest published deployment. - Tool lifecycle events now share one `tool_call_id`. ## 3 September 2026 - Session streams now deliver live ephemeral deltas with `event_deltas=true`. - `SESSION_BUSY` now includes the active run and recovery routes. - Run cancellation now releases a busy session. - Sandbox failures now return HTTP 503 with a safe message. - Agents can opt out of sandbox creation. - Sessions now report `sandbox_status`. - Application tool waits now default to 30 minutes. - Run claims now use owner tokens, leases, and reconciliation. ## Contract hardening - The checked-in OpenAPI contract now covers every served route. - Contract tests now require exact route parity. - Worker tests can limit claims to one tenant. ## Open See [Known issues](https://pantheon-todo.alethiconsulting.com/docs/known-issues.html) for current impact and workarounds. ## Pitfalls > - **A commit is not deployment proof:** Use the deployment date and verify the live contract before rollout. > - **The todo tenant is a probe:** Do not treat tenant UI behavior as the API contract.