---
title: Sessions and runs
description: A session is an append-only event log. A run is one pass of the engine over it. Everything you can observe about an agent is an event.
sidebar:
  order: 3
---

A **session** is one unit of work: a conversation, a ticket, a background job. It belongs to a workspace, binds an agent version at creation, and holds an append-only log of everything that happened.

A **run** is one pass of the engine over that log. Appending user input starts a run; the run appends its own events as it goes and ends in exactly one terminal event.

The distinction matters because a session outlives its runs. A run can fail, be cancelled, or lose the connection you were watching it on, and the session is still there with its log intact.

## Creating a session

```json
{
  "agent_id": "…",
  "display_name": "ticket-4192",
  "vault_ids": ["…"]
}
```

| Prop | Type | Default | Description |
| - | - | - | - |
| `agent_id` | `uuid` | - | The agent to run. Its newest live version is bound now and never re-bound. |
| `display_name?` | `string` | - | A human label, up to 200 characters. Empty when omitted. |
| `vault_ids?` | `uuid[]` | - | Vaults this session may draw tool credentials from — up to 10, each a live vault in the same workspace. |

The response carries the session and its first **session token**, shown once.

## Two credentials, two jobs

| | API key | Session token |
| --- | --- | --- |
| Scope | A workspace | One session |
| Carries | The permissions it was minted with | No permissions at all |
| Lives | On your server | Wherever you need it — a browser tab, a worker |
| Expires | When you revoke it, or at `expires_at` | At `session_token_expires_at` |

A session token can read its own session, append input to it, and register clients on it. It cannot list sessions, create one, or touch anything else in the workspace — so handing one to a front end gives that front end exactly one session and nothing more.

Mint more of them as you need them:

```http
POST /v1/sessions/{session_id}/tokens
```

## The event log

Every event carries a sequence number, and the log is the whole truth about a session. Runs are events, messages are events, tool calls and their results are events, a client joining is an event.

```http
GET  /v1/sessions/{session_id}/events          # paged, newest first by default
GET  /v1/sessions/{session_id}/events/stream   # SSE, tail and resume
POST /v1/sessions/{session_id}/events          # append user input, starting a run
POST /v1/sessions/{session_id}/cancel          # ask the current run to stop
```

See [Streaming events](/guides/streaming-events) for the tailing and resumption rules, and the [event reference](/reference/events) for every kind.

## What a run touches

When a run is claimed, the engine is handed three things, all scoped to that run alone:

1. **The committed log**

    The engine rebuilds its state from the events already stored, so a run
    that follows a failed one starts from what actually happened, not from
    what the previous attempt thought was happening.

2. **A model proxy binding**

    A URL and a run token, minted fresh at claim time. Your provider key is
    never handed to the engine — the proxy holds it. A stale attempt's token
    stops working the moment the run is re-claimed.

3. **A tool broker binding**

    A URL, a run token, and the list of server names the bound version
    grants. Never credentials, never upstream URLs, never policy — the broker
    resolves all three itself when a call arrives.

## Terminal states

| Event | What it means |
| --- | --- |
| `run_completed` | The model finished its turn with nothing left to do. |
| `run_failed` | The run stopped on an error; `error` carries the text. |
| `run_cancelled` | A cancel request was honored. |

A session with no run in flight is idle, not finished. Append more input and a new run starts against the same log.
