# Authentication
> Create an API key and authenticate requests.
Source: https://www.ellipsis.dev/docs/api/authentication
Create a key under **API > API keys** in the dashboard. Copy the secret when it is shown; it cannot be retrieved later.
## Bearer token
Store the key in `ELLIPSIS_API_TOKEN`:
```bash
export ELLIPSIS_API_TOKEN="ellipsis_key_..."
```
Send it on each request:
```bash
curl https://api.ellipsis.dev/v1/identity -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"
```
The identity response identifies the account and credential. An API key acts for its account, with no individual user identity.
## Permissions
An invalid or revoked token returns `401`. A valid credential without access returns `403`.
Account financial settings require an owner or organization administrator's user credential. API keys and sandbox credentials cannot change them.
Revoke a key from the dashboard when it is no longer needed. Keep keys on your server; do not embed them in browser code or committed configuration.
---
# API overview
> Start agent sessions and manage agents with the CLI, Python, TypeScript, or curl.
Source: https://www.ellipsis.dev/docs/api
## Guides
- [Quick start](/docs/api/quick-start): Start your first agent session
- [Authentication](/docs/api/authentication): Create and use an API key
- [Python SDK](/docs/api/python-sdk): Handles, streaming, and typed errors
- [TypeScript SDK](/docs/api/typescript-sdk): Handles, streaming, and typed errors
- [CLI](https://github.com/ellipsis-dev/cli#readme): Commands and flags
- [Session events](/docs/api/session-events): Every event a session emits
## Reference
- [Agents](/docs/api/agents): Create, edit, and start agents
- [Sessions](/docs/api/sessions): Follow sessions and read their output
- [Environments](/docs/api/environments): Define where agents run
- [Secrets](/docs/api/secrets): Store credentials for environments
- [Account](/docs/api/account): Check budgets, usage, and models
- [Integrations](/docs/api/integrations): List connected services and repositories
- [OpenAPI](/openapi.v1.json): Full schema, including dashboard endpoints
---
# Python SDK
> Start sessions, stream records, and invoke agents from Python.
Source: https://www.ellipsis.dev/docs/api/python-sdk
The Python SDK is a client for the `/v1` API with session handles, streaming, pagination, and typed errors.
## Install
Python 3.10 or newer. The `stream` extra enables live session streaming.
```bash
pip install 'ellipsis-dev[stream]'
```
## Start a session
```python
import os
from ellipsis import Ellipsis
client = Ellipsis(api_key=os.environ["ELLIPSIS_API_TOKEN"])
handle = client.sessions.run(
claude_code={
"model": "claude-opus-5-5",
"prompt": "Run the tests and report failures.",
},
environment="api-environment",
conversation={"interactive": False},
budget={"session": 3},
)
session = handle.wait(timeout=900)
print(session.turn.status, session.turn.reason, session.turn.detail)
```
`run` returns a handle while work continues. `wait` polls until the turn that answers the prompt reaches a final status (`completed`, `failed`, `stopped`, or `cancelled`) and returns the session; `session.turn` is that turn. A timeout stops waiting and leaves the session running.
Use `sessions.handle(session_id)` to attach to an existing session.
## Continue a conversation
Leave `conversation.interactive` enabled to accept messages:
```python
handle = client.sessions.run(
claude_code={
"model": "claude-opus-5-5",
"prompt": "Investigate the failing validation test.",
},
environment="api-environment",
budget={"session": 5},
)
handle.wait(timeout=900)
message = handle.send("Add a regression test.", idempotency_key="add-test")
session = handle.wait(timeout=900)
print(message.turn_id, session.turn.status)
```
`send` returns the message with the `turn_id` of the turn that answers it, and `wait` then waits on that turn. The same message key is accepted once per session. A message sent while a turn is running is answered after it. `handle.stop()` ends the running turn with `stopped`; it fails with `409` when no turn is pending or running.
## Start an agent session
Start an agent session as written:
```python
response = client.agents.start("test-repair")
session = client.sessions.handle(response.session.id).wait()
print(session.turn.status, session.turn.detail)
```
Send any field of a session start to replace the agent's value for that session:
```python
response = client.agents.start("test-repair", budget={"session": 5})
```
For an agent with an input schema, pass `input`:
```python
response = client.agents.start(
"classify-change",
input={"description": "Reject expired reset tokens"},
)
```
## Stream a session
See [Session events](/docs/api/session-events) for every modeled event and complete JSON examples.
```python
import asyncio
import os
from ellipsis import AsyncEllipsis
async def main():
async with AsyncEllipsis(
api_key=os.environ["ELLIPSIS_API_TOKEN"]
) as client:
handle = await client.sessions.run(
claude_code={
"model": "claude-opus-5-5",
"prompt": "Explain how to test a request validator.",
},
conversation={"interactive": False},
)
outcome = await handle.stream(lambda frame: print(frame.type))
print(outcome.type)
asyncio.run(main())
```
Use `AsyncEllipsis` for asynchronous requests and streaming. Complete records persist; partial text deltas are live-only.
## Read results
```python
turns = client.sessions.turns.list(handle.id)
for turn in turns.turns:
print(turn.index, turn.status, turn.reason, turn.cost.total)
for message in turns.messages:
print(message.turn_id, message.status, message.body)
for session in client.sessions.list(days=7):
print(session.id, session.conversation.state)
```
`turns.list` returns every turn and message of a session, oldest first; `turns.get(session_id, turn_id)` returns one turn. SDK page iterators fetch every page. For manual pagination, use `items`, `has_more`, and `next_cursor`.
## Errors
```python
from ellipsis import APIError
try:
client.sessions.get("session_missing")
except APIError as error:
print(error.status, error.code)
```
The client defaults to a 60-second request timeout and 2 retries for transport errors, `429`, `502`, `503`, and `504`. Configure `timeout` and `max_retries` when creating it.
---
# Quick start
> Start your first agent session from the CLI, Python, TypeScript, or curl.
Source: https://www.ellipsis.dev/docs/api/quick-start
## Get started
Install the CLI or an SDK:
```bash title="CLI"
curl -fsSL https://ellipsis.dev/install.sh | sh
```
```bash title="Python"
pip install ellipsis-dev
```
```bash title="TypeScript"
npm install @ellipsis-dev/sdk
```
Sign in with device auth. The CLI opens your browser to approve the login:
```bash
el auth login
```
For CI, the SDKs, and curl, export an API key from **API > API keys** in the dashboard:
```bash
export ELLIPSIS_API_TOKEN="ellipsis_key_..."
```
## Start an agent session
Start the `classify-change` agent:
```bash title="CLI"
el agent start classify-change --input '{"description": "Reject expired reset tokens"}'
```
```python
import os
from ellipsis import Ellipsis
client = Ellipsis(api_key=os.environ["ELLIPSIS_API_TOKEN"])
change = {"description": "Reject expired reset tokens"}
response = client.agents.start("classify-change", input=change)
print(response.session.id)
```
```typescript
import { Ellipsis } from '@ellipsis-dev/sdk';
const apiKey = process.env.ELLIPSIS_API_TOKEN!;
const client = new Ellipsis({ apiKey });
const input = { description: 'Reject expired reset tokens' };
const { session } = await client.agents.start('classify-change', { input });
console.log(session.id);
```
```bash title="cURL"
curl https://api.ellipsis.dev/v1/agents/classify-change/sessions -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" -H "Content-Type: application/json" -d '{"input": {"description": "Reject expired reset tokens"}}'
```
[Typed input and output](/docs/agents/schema#typed-input-and-output) defines it. An agent with no input schema takes `prompt` instead of `input`.
Wait for it to finish:
```bash title="CLI"
el session get --watch
```
```python
handle = client.sessions.handle("your-session-id")
session = handle.wait()
print(session.turn.status)
```
```typescript
const handle = await client.sessions.handle('your-session-id');
const turn = await handle.wait();
console.log(turn?.status);
```
```bash title="cURL"
# Repeat until session.turn.status is final
curl https://api.ellipsis.dev/v1/sessions/your-session-id -H "Authorization: Bearer $ELLIPSIS_API_TOKEN"
```
`wait` and `--watch` return when `status` is `completed`, `failed`, `stopped`, or `cancelled`. `--watch` exits `0` only for `completed`.
Read the output:
```python
print(client.sessions.get("your-session-id").session.output.result)
```
```typescript
console.log((await client.sessions.get('your-session-id')).session.output?.result);
```
```bash title="cURL"
curl https://api.ellipsis.dev/v1/sessions/your-session-id -H "Authorization: Bearer $ELLIPSIS_API_TOKEN" | jq .session.output.result
```
It matches the agent's `output.json_schema` and is `null` until the turn ends:
```json
{
"category": "bugfix",
"reason": "Rejecting expired reset tokens closes a validation gap."
}
```
## Errors and retries
- Errors return `{"error": {"code": "...", "message": "..."}}`. Branch on `code`, not `message`.
- Retry `429` after `Retry-After`, and `5xx` with backoff.
- A start that times out can still succeed. Tag sessions with `metadata` and list recent sessions before you retry.
## Pagination
- Paginated routes take `limit` and `cursor`.
- Pass `next_cursor` until `has_more` is `false`.
- SDK iterators fetch every page.
---
# Session events
> Every modeled session event, with JSON examples for platform, harness, and streaming consumers.
Source: https://www.ellipsis.dev/docs/api/session-events
Read session records through [List records](/docs/api/sessions/get-sessions-id-records) or receive them in a live stream's `records_append` frames. Open an event to see its example JSON and detailed field specification. Events are grouped by producer and ordered roughly from a message's receipt through environment preparation to the end of its turn.
The session's own `event` field identifies the external event that started it. The records on this page describe activity throughout that session. See [Sessions](/docs/sessions#inspect-the-result) for `source` and `event` fields.
Examples are illustrative, independent messages, not a consecutive transcript. Optional native fields vary by harness version. Platform and transport types are listed in full; native harnesses can also send additional types through [unknown records](#unknown-records).
- [Platform](#platform): message, environment, turn, and conversation events.
- [Claude Code](#claude-code): native messages and turn results.
- [Codex](#codex): native app-server notifications.
- [WebSocket frames](#websocket-frames): delivery, live output, and connection state.
- [Historical formats](#historical-formats): records retained from older sessions.
For how the conversation and its turns report progress, and how to know when your message is answered, start with [Lifecycle](/docs/sessions/lifecycle).
## Read an event
| Field | How to use it |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `kind` | Select the typed record variant: `platform`, `claude_code`, `codex_app_server`, `claude_sdk`, `codex`, or `unknown`. |
| `source` | Identify the producer: `lifecycle`, `claude_code`, or `codex`. |
| `record_format` | Select the payload version; existing history retains its original format. |
| `record_type` | Identify the event within its format. |
| `payload` | Read the platform fields or the unchanged native message. |
| `feed_seq` | Order records within a session and resume the stream after this position. |
| `turn_id` | Correlate records with the turn they belong to; environment records carry the turn they prepare for. |
| `session_message_id` | Correlate message events and native user echoes so a message can render once. |
Use `kind` and `record_type` to narrow platform records. For native records, narrow `kind` first, then `payload.type` (Claude and historical Codex) or `payload.method` (current Codex). Native payloads can have their own `kind` field, such as `"push"` in a Claude Git notification; it is event data, not the envelope's record variant. Claude rate limits are the exception: `record_type` is `rate_limit`, while `payload.type` is `rate_limit_event`.
The field specifications below come from the SDK schema. **Required** means the field must be present; `null` in its type means its value can be null. Expand nested fields to inspect their properties and variants. Required fields within a variant apply when that variant is used. Examples show one possible payload; optional native fields may be absent.
Both SDKs expose `SessionRecord` and `StreamFrame`:
```python
from ellipsis.models import SessionRecord
from ellipsis.frames import StreamFrame
```
```typescript
import type { SessionRecord, StreamFrame } from '@ellipsis-dev/sdk';
```
## Platform
These records use `kind: "platform"`, `source: "lifecycle"`, and `record_format: "ellipsis_lifecycle@1"`. Events describe individual changes; not every session produces every type. Every record that belongs to a turn carries its `turn_id`; environment records carry the turn they prepare for. Older sessions can contain platform record types that are no longer produced; they arrive as [unknown records](#unknown-records).
### message_received
A message entered the session and its turn was created; `turn_id` is the turn that will answer it, and `closes_session` marks a final message.
Use `session_message_id` to match the message with its native user echo so it renders once. The message is answered when its turn reaches a final status; see [Lifecycle](/docs/sessions/lifecycle#wait-for-the-answer).
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "message_received",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": "message_example",
"feed_seq": 1,
"payload": {
"message_id": "message_example",
"turn_id": "turn_example",
"body": "Run the tests and report failures.",
"author": "priya-shah",
"sender_attribution_type": "github_user",
"sender_attribution_id": "12345",
"closes_session": false
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:00Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "message_received". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.author` | `string \| null` | Optional | |
| `payload.body` | `string` | Required | |
| `payload.closes_session` | `boolean` | Optional | |
| `payload.message_id` | `string` | Required | |
| `payload.sender_attribution_id` | `string \| null` | Optional | |
| `payload.sender_attribution_type` | `string \| null` | Optional | |
| `payload.turn_id` | `string \| null` | Optional | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### environment_phase
A phase of environment preparation started, completed, or failed, with an optional `step`, timing, and details.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "environment_phase",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 2,
"payload": {
"phase": "setup",
"status": "completed",
"step": "after_checkout",
"duration_ms": 2400,
"detail": null
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:03Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "environment_phase". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.detail` | `object \| null` | Optional | Additional properties are allowed. |
| `payload.duration_ms` | `integer \| null` | Optional | |
| `payload.phase` | `string` | Required | |
| `payload.status` | `string` | Required | |
| `payload.step` | `string \| null` | Optional | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### environment_output
A chunk of output from environment preparation, including your hooks; many arrive per turn, and `chunk` orders them within a step.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "environment_output",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 3,
"payload": {
"phase": "clone",
"step": "your-org/api-repo",
"stream": "stdout",
"chunk": 0,
"lines": ["Cloning into 'api-repo'..."]
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:01Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "environment_output". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.chunk` | `integer` | Required | |
| `payload.lines` | `array` | Required | |
| `payload.lines.[]` | `string` | | |
| `payload.phase` | `string` | Required | |
| `payload.step` | `string \| null` | Optional | |
| `payload.stream` | `string` | Optional | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### environment_ready
The environment is prepared for the listed repositories and the agent is about to start.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "environment_ready",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 4,
"payload": {
"repositories": ["your-org/api-repo"],
"duration_ms": 8400
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:08Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "environment_ready". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.duration_ms` | `integer \| null` | Optional | |
| `payload.repositories` | `array` | Required | |
| `payload.repositories.[]` | `string` | | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### turn_queued
The turn waits because your account already has `limit` sessions alive. It starts when one of them ends.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "turn_queued",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 2,
"payload": {
"turn_id": "turn_example",
"sessions_alive": 32,
"limit": 32
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:01Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "turn_queued". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.limit` | `integer` | Required | |
| `payload.sessions_alive` | `integer` | Required | |
| `payload.turn_id` | `string` | Required | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### turn_started
The agent started on the turn's message; the turn is now `running`.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "turn_started",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 5,
"payload": {
"turn_id": "turn_example",
"turn_index": 0
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:09Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "turn_started". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.turn_id` | `string` | Required | |
| `payload.turn_index` | `integer` | Required | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### message_delivered
The agent started on the message, on the identified turn.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "message_delivered",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": "message_example",
"feed_seq": 6,
"payload": {
"message_id": "message_example",
"turn_id": "turn_example"
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:09Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "message_delivered". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.message_id` | `string` | Required | |
| `payload.turn_id` | `string` | Required | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### turn_ended
The turn reached its final status; `status`, `reason`, and `detail` match the turn on the session object.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "turn_ended",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 7,
"payload": {
"turn_id": "turn_example",
"turn_index": 0,
"status": "completed",
"reason": null,
"detail": null,
"duration_ms": 4200
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:13Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "turn_ended". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.detail` | `string \| null` | Optional | |
| `payload.duration_ms` | `integer \| null` | Optional | |
| `payload.reason` | `string \| null` | Optional | |
| `payload.status` | `string` | Required | |
| `payload.turn_id` | `string` | Required | |
| `payload.turn_index` | `integer` | Required | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### message_requeued
The turn ended before answering the message, so the message was queued again with a new queued turn, `requeued_turn_id`.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "message_requeued",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_requeued_example",
"session_message_id": "message_example",
"feed_seq": 8,
"payload": {
"message_id": "message_example",
"turn_id": "turn_example",
"requeued_turn_id": "turn_requeued_example"
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:13Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "message_requeued". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.message_id` | `string` | Required | |
| `payload.requeued_turn_id` | `string \| null` | Optional | |
| `payload.turn_id` | `string` | Required | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### session_closed
The conversation closed permanently, after the turn of a session that runs once or after a closing message's turn.
**Example JSON**
```json
{
"kind": "platform",
"source": "lifecycle",
"record_format": "ellipsis_lifecycle@1",
"record_type": "session_closed",
"id": "record_example",
"session_id": "session_example",
"turn_id": null,
"session_message_id": null,
"feed_seq": 10,
"payload": {},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:14Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "platform". |
| `source` | `string` | Required | Must be "lifecycle". |
| `record_format` | `string` | Required | Must be "ellipsis_lifecycle@1". |
| `record_type` | `string` | Required | Must be "session_closed". |
| `payload` | `object` | Required | Additional properties are allowed. |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
`environment_phase.phase` names a stage of preparation: `image` (the base image), `clone` (the repositories), `setup` (the environment's `build_base` and `after_checkout` hooks, named in `step`), `snapshot` (saving the prepared environment for reuse), `restore` (bringing a saved environment back for a later turn), and `hooks` (the `before_start` hook, which runs each time an environment is prepared, before the agent starts). `status` is `started`, `completed`, or `failed`. Phases, steps, and statuses are open vocabularies, so render values you don't recognize as they are. `environment_output` carries the same `phase` and `step`; a `clone` chunk names the repository in `step`. A reused environment skips its build steps.
A turn's `status`, `reason`, and `detail` also arrive on the [`session` frame](#session); `turn_ended` is the record of the same change. There are no separate session-level success, failure, or stop records: the conversation reports only `session_closed`.
## Claude Code
Current Claude records use `kind: "claude_code"`, `source: "claude_code"`, and `record_format: "claude_jsonl@1"`. A native `result` ends a harness turn, not necessarily the whole session.
### system
Reports harness metadata, including initialization, status, compaction, hooks, and background-task activity through `subtype`.
**Example JSON**
```json
{
"kind": "claude_code",
"source": "claude_code",
"record_format": "claude_jsonl@1",
"record_type": "system",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 12,
"payload": {
"type": "system",
"subtype": "init",
"model": "claude-opus-5-5",
"tools": ["Bash", "Read"],
"cwd": "/workspace",
"session_id": "11111111-1111-4111-8111-111111111111",
"uuid": "22222222-2222-4222-8222-222222222222"
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:00Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "claude_code". |
| `source` | `string` | Required | Must be "claude_code". |
| `record_format` | `string` | Required | Must be "claude_jsonl@1". |
| `record_type` | `string` | Required | Original record type within its format. |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.type` | `string` | Required | Must be "system". |
| `payload.session_id` | `string \| null` | Optional | |
| `payload.subtype` | `string` | Required | |
| `payload.uuid` | `string \| null` | Optional | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### system
Reports a Git state change through `subtype: vcs_state_changed`; `payload.kind` identifies the operation, such as a push.
This notification is part of the current turn's activity. It does not complete or fail the turn. Its native `kind`, `cwd`, and other fields are preserved in `payload`; `record_type` remains `system`.
**Example JSON**
```json
{
"kind": "claude_code",
"source": "claude_code",
"record_format": "claude_jsonl@1",
"record_type": "system",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 12,
"payload": {
"type": "system",
"subtype": "vcs_state_changed",
"kind": "push",
"cwd": "/workspace",
"session_id": "11111111-1111-4111-8111-111111111111",
"uuid": "22222222-2222-4222-8222-222222222222"
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:00Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "claude_code". |
| `source` | `string` | Required | Must be "claude_code". |
| `record_format` | `string` | Required | Must be "claude_jsonl@1". |
| `record_type` | `string` | Required | Original record type within its format. |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.type` | `string` | Required | Must be "system". |
| `payload.session_id` | `string \| null` | Optional | |
| `payload.subtype` | `string` | Required | |
| `payload.uuid` | `string \| null` | Optional | |
| `cost` | `integer \| null` | Required | |
| `created_at` | `string` | Required | Format: date-time. |
| `duration` | `integer \| null` | Required | |
| `feed_seq` | `integer` | Required | Position in the session feed and the stream's resume cursor. |
| `id` | `string` | Required | Unique identifier of the record. |
| `model` | `string \| null` | Required | |
| `session_id` | `string` | Required | Session containing this record. |
| `session_message_id` | `string \| null` | Required | Message this record receives, delivers, requeues, or echoes. |
| `tokens_info` | `object \| null` | Required | Additional properties are not allowed. |
| `tokens_info.cache_creation_input_tokens` | `integer` | Optional | |
| `tokens_info.cache_read_input_tokens` | `integer` | Optional | |
| `tokens_info.cost_usd` | `number` | Optional | |
| `tokens_info.input_tokens` | `integer` | Optional | |
| `tokens_info.num_turns` | `integer` | Optional | |
| `tokens_info.output_tokens` | `integer` | Optional | |
| `tools` | `array \| null` | Required | |
| `tools.[]` | `string` | | |
| `turn_id` | `string \| null` | Required | Turn containing this record, when applicable. |
### user
Carries a user-message echo or tool results supplied back to Claude; this example is a tool result.
**Example JSON**
```json
{
"kind": "claude_code",
"source": "claude_code",
"record_format": "claude_jsonl@1",
"record_type": "user",
"id": "record_example",
"session_id": "session_example",
"turn_id": "turn_example",
"session_message_id": null,
"feed_seq": 12,
"payload": {
"type": "user",
"message": {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "tool_example",
"content": "12 passed",
"is_error": false
}
]
},
"parent_tool_use_id": null,
"session_id": "11111111-1111-4111-8111-111111111111",
"uuid": "22222222-2222-4222-8222-222222222222"
},
"tools": null,
"tokens_info": null,
"cost": null,
"duration": null,
"model": null,
"created_at": "2026-09-10T14:00:00Z"
}
```
**Field specification**
| Field | Type | Presence | Details |
| --- | --- | --- | --- |
| `kind` | `string` | Required | Must be "claude_code". |
| `source` | `string` | Required | Must be "claude_code". |
| `record_format` | `string` | Required | Must be "claude_jsonl@1". |
| `record_type` | `string` | Required | Original record type within its format. |
| `payload` | `object` | Required | Additional properties are allowed. |
| `payload.type` | `string` | Required | Must be "user". |
| `payload.isReplay` | `boolean \| null` | Optional | |
| `payload.message` | `object` | Required | Additional properties are allowed. |
| `payload.message.content` | `string \| array