> ## Documentation Index
> Fetch the complete documentation index at: https://docs.herm.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream session events

> List persisted session events or stream new events over Server-Sent Events.

Session events are the log of user input, agent output, tool activity, and status
changes for a session. Use the list endpoint for durable history and the stream
endpoint for live updates.

For every event shape, see the [Events Reference](/api-reference/events-reference).

## List events

```
GET /v1/sessions/{sessionId}/events
```

Returns persisted events for a session in sequence order.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.herm.run/v1/sessions/session_123/events?limit=100" \
  -H "x-api-key: $PRISM_API_KEY"
```

| Query   | Type    | Description                                          |
| ------- | ------- | ---------------------------------------------------- |
| `after` | integer | Return events with sequence greater than this value. |
| `limit` | integer | Page size, 1 to 100. Defaults to `100`.              |

Response:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "object": "list",
  "data": [
    {
      "id": "evt_123",
      "type": "agent.message",
      "sessionId": "session_123",
      "sequence": 5,
      "turnId": "turn_123",
      "status": "complete",
      "usage": {
        "total": 116200,
        "context_percent": 11,
        "cost_usd": 1.48,
        "last_turn_latency_ms": 12400,
        "cost_status": "estimated"
      },
      "payload": {
        "type": "agent.message",
        "delta": false,
        "content": [{ "type": "text", "text": "Hello!" }]
      },
      "processedAt": "2026-06-20T23:28:02.772Z",
      "createdAt": "2026-06-20T23:28:02.773Z"
    }
  ],
  "pagination": {
    "after": 5,
    "hasMore": false,
    "limit": 100
  }
}
```

## Stream events

```
GET /v1/sessions/{sessionId}/events/stream
```

Opens a Server-Sent Events stream for new events. Use `after` with the last
durable numeric `sequence` already loaded from event history to avoid replaying
older events on the initial connection.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -N "https://api.herm.run/v1/sessions/session_123/events/stream?after=5" \
  -H "x-api-key: $PRISM_API_KEY"
```

Each SSE message uses the event type as the SSE `event` name and the event
envelope as JSON `data`.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
event: agent.message
id: stream_cursor_abc123
data: {"id":"evt_456","type":"agent.message","sessionId":"session_123","sequence":6,"turnId":"turn_123","status":"complete","usage":{"total":116200,"context_percent":11,"cost_usd":1.48,"last_turn_latency_ms":12400,"cost_status":"estimated"},"payload":{"type":"agent.message","delta":false,"content":[{"type":"text","text":"Hello!"}]}}
```

### Reconnect and resume

Treat the SSE `id` as an opaque transport cursor, not as the event's numeric
`sequence`. Browsers automatically send it back as `Last-Event-ID` when an
`EventSource` reconnects to the same URL.

Some replayed history frames may not include an SSE `id`. The live stream is
therefore not the source of truth for complete history. On every new connection
or reconnect:

1. Open the SSE stream and buffer incoming frames.
2. List durable events using `GET /events?after={lastSequence}`.
3. Merge the history response and buffered frames by event `id`, then advance your numeric sequence cursor.
4. Continue consuming the live stream.
5. Let the SSE client preserve `Last-Event-ID`; do not parse or synthesize it.

Opening the stream first avoids losing events created between the history request
and the live connection.

The `after` query parameter and event `sequence` belong to durable history. The
SSE `id` and `Last-Event-ID` belong to live transport resume.

## Event envelope

List and stream responses wrap every payload in the same event envelope:

| Field         | Type           | Description                                                                                                                                         |
| ------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`          | string         | Event ID.                                                                                                                                           |
| `type`        | string         | Same value as `payload.type`, duplicated for filtering.                                                                                             |
| `sessionId`   | string         | Session that owns the event.                                                                                                                        |
| `sequence`    | integer        | Monotonic per-session sequence number. Use this to paginate and reconcile durable event history.                                                    |
| `turnId`      | string or null | Identifier shared by events from the same turn.                                                                                                     |
| `status`      | string or null | Processing state, such as `received`, `queued` (a `delivery: "when_idle"` message waiting for the running turn to end), `processed`, or `complete`. |
| `usage`       | object         | Optional per-turn usage snapshot, when available.                                                                                                   |
| `payload`     | object         | Event-specific payload.                                                                                                                             |
| `processedAt` | string or null | ISO timestamp when Herm processed the event, when available.                                                                                        |
| `createdAt`   | string         | ISO timestamp when the event was created.                                                                                                           |

For inbound `user.message` events, `payload` is the accepted event. If the event
included `metadata`, it is returned as `payload.metadata`. Metadata is persisted
turn context and is not part of the visible `payload.content` text. If the event
included `attachments`, the attachment metadata and `dataUrl` source are included
in that payload so the event is auditable.

### Usage snapshots

When available, terminal turn events include a `usage` object with the usage for
that completed turn. This is the right object to forward to observability systems
such as Langfuse for per-generation token and cost reporting. Session responses
also expose an aggregate `usage` object for lifetime session totals.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "usage": {
    "model": "claude-sonnet-4-5",
    "input": 111000,
    "output": 5200,
    "cache_read": 90000,
    "cache_write": 1200,
    "reasoning": 0,
    "total": 116200,
    "calls": 8,
    "context_used": 116200,
    "context_max": 1000000,
    "context_percent": 11,
    "cost_usd": 1.48,
    "cost_status": "estimated",
    "last_turn_latency_ms": 12400,
    "latency_ms": 12400
  }
}
```

`cost_usd` is omitted when pricing is unavailable; `cost_status` explains whether
the cost is `estimated`, `included`, or `unknown`. `last_turn_latency_ms` measures
the wall-clock time for that completed turn. `context_*` fields describe the
current context window at the end of the turn; they are not billable deltas.

## Event categories

| Event                           | Description                                                                                                               |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `user.message`                  | User text input with optional metadata and turn-scoped attachments.                                                       |
| `user.steer`                    | User steering text.                                                                                                       |
| `user.interrupt`                | User interruption request.                                                                                                |
| `user.approval_response`        | User approval or denial for a pending tool approval request.                                                              |
| `user.custom_tool_result`       | Result returned by your application for a custom tool.                                                                    |
| `user.clarify_result`           | User answer for a clarification request.                                                                                  |
| `session.status_running`        | The agent has started processing.                                                                                         |
| `session.status_idle`           | The agent turn is complete or waiting for required action.                                                                |
| `session.tool_updated`          | Ephemeral patch for a prior custom tool result. Emitted when a long-running `user.custom_tool_result` completes or fails. |
| `agent.thinking`                | Reasoning/thinking content emitted by the agent.                                                                          |
| `agent.message`                 | Assistant message content.                                                                                                |
| `agent.approval_request`        | Tool call waiting for approval before execution.                                                                          |
| `agent.tool_use`                | Built-in tool call emitted by the agent.                                                                                  |
| `agent.tool_result`             | Built-in tool result emitted by the agent.                                                                                |
| `agent.mcp_tool_use`            | MCP tool call emitted by the agent.                                                                                       |
| `agent.mcp_tool_result`         | MCP tool result emitted by the agent.                                                                                     |
| `agent.custom_tool_use`         | Application-owned custom tool request.                                                                                    |
| `agent.clarify_request`         | The agent paused to ask the user a question (the `clarify` tool). Answer it with a `user.clarify_result` event.           |
| `agent.thread_message_sent`     | Parent agent delegated work to a subagent/thread.                                                                         |
| `agent.thread_message_received` | Parent agent received a subagent/thread result.                                                                           |
| `session.title_updated`         | The session was automatically titled. `payload.title` holds the new title.                                                |

See the [Events Reference](/api-reference/events-reference) for schemas and examples.

## Required actions

When a `session.status_idle` event has `payload.stop_reason.type` set to
`requires_action`, the turn is paused until your application sends the matching
user event.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
event: session.status_idle
id: 15
data: {"id":"evt_15","type":"session.status_idle","sessionId":"session_123","sequence":15,"status":"complete","payload":{"type":"session.status_idle","stop_reason":{"type":"requires_action","event_ids":["custom_toolu_123"]}}}
```

| Required action       | Respond with              |
| --------------------- | ------------------------- |
| Tool approval         | `user.approval_response`  |
| Custom tool execution | `user.custom_tool_result` |
| Clarification answer  | `user.clarify_result`     |

## Clarify requests

When the agent uses the `clarify` tool, the stream emits an `agent.clarify_request` followed by a `session.status_idle` with `stop_reason.type: "requires_action"`. The turn is paused until you answer.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
event: agent.clarify_request
id: 14
data: {"id":"evt_14","type":"agent.clarify_request","sessionId":"session_123","sequence":14,"status":"complete","payload":{"type":"agent.clarify_request","request_id":"a1b2c3d4","question":"Which environment should I deploy to?","choices":["Staging","Production"]}}
```

`payload.choices` is a list of multiple-choice options, or `null` for an open-ended question. Answer by sending a [`user.clarify_result`](/api-reference/send-message) event with the `request_id` and the user's `answer` (an empty `answer` skips the question, letting the agent proceed with its own default). The paused turn then resumes and continues streaming.

## Custom tool requests

When the model calls one of your custom tools, the stream emits
`agent.custom_tool_use`, then `session.status_idle` with `requires_action`. Your
application should execute the tool and send `user.custom_tool_result`.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
event: agent.custom_tool_use
id: 22
data: {"id":"evt_22","type":"agent.custom_tool_use","sessionId":"session_123","sequence":22,"status":"running","payload":{"type":"agent.custom_tool_use","id":"custom_toolu_123","tool":"check_order_status","input":{"order_id":"123"}}}
```

See [Send Session Events](/api-reference/send-message#return-a-custom-tool-result)
for the response format.

For asynchronous application-owned work, see
[Long-running custom tools](/api-reference/long-running-tools). That flow defines
when `session.tool_updated` is emitted and how clients reconcile the ephemeral
patch with durable event history.

## Automatic title updates

When you create a session without a `title`, the agent generates a short, descriptive one from the first exchange (see [Sessions](/api-reference/sessions)). When the title is ready, the stream emits `session.title_updated`; update your UI from `payload.title`:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
event: session.title_updated
id: 12
data: {"id":"evt_789","type":"session.title_updated","sessionId":"session_123","sequence":12,"status":"complete","payload":{"type":"session.title_updated","title":"Holiday campaign planning"}}
```

The event is persisted in the session event log, so it is returned by `GET /events`
and replayed on reconnect. It also updates the session's `title` field. Manual
titles are authoritative: if you create or update a session with an explicit title,
the automatic title is not applied. To read the current title at any time, call
[Get a session](/api-reference/sessions).

## Send events

Use `POST /v1/sessions/{sessionId}/events` to send user input and control events. See [Send Session Events](/api-reference/send-message).

## Errors

| Status | Error              | When                    |
| ------ | ------------------ | ----------------------- |
| `400`  | `validation_error` | Invalid query.          |
| `404`  | `not_found`        | Session does not exist. |
