> ## 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.

# Events Reference

> Complete reference for user, agent, and session event payloads.

Events are the session protocol between your application and a managed Herm
agent.

Use [`POST /v1/sessions/{sessionId}/events`](/api-reference/send-message) to send
inbound prompt, control, and result events. Use [`GET /v1/sessions/{sessionId}/events`](/api-reference/stream-events#list-events)
and [`GET /v1/sessions/{sessionId}/events/stream`](/api-reference/stream-events#stream-events)
to receive `agent.*` and `session.*` events.

## Event envelope

Every listed or streamed event is wrapped in an envelope:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "evt_123",
  "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!" }]
  },
  "processedAt": "2026-06-20T23:28:02.772Z",
  "createdAt": "2026-06-20T23:28:02.773Z"
}
```

| Field         | Type           | Description                                                                                                    |
| ------------- | -------------- | -------------------------------------------------------------------------------------------------------------- |
| `id`          | string         | Event ID.                                                                                                      |
| `type`        | string         | Event type, duplicated from `payload.type`.                                                                    |
| `sessionId`   | string         | Session ID.                                                                                                    |
| `sequence`    | integer        | Monotonic sequence number within the session.                                                                  |
| `turnId`      | string or null | Identifier shared by events from the same turn.                                                                |
| `status`      | string or null | Event processing status, when applicable.                                                                      |
| `usage`       | object         | Optional per-turn usage snapshot, including token usage, context percentage, estimated cost, and turn latency. |
| `payload`     | object         | Event-specific payload.                                                                                        |
| `processedAt` | string or null | ISO timestamp when processing completed, when available.                                                       |
| `createdAt`   | string         | ISO timestamp when the event was created.                                                                      |

## Shared types

### Text content block

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "type": "text", "text": "Hello" }
```

Text content arrays contain 1 to 100 text blocks. Each `text` value must be
non-empty and can be up to 20,000 characters. Exceeding either limit rejects the
whole request with `400 validation_error`; nothing is truncated. The message
names the failing field and limit, for example:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "error": "validation_error",
  "message": "events[0].content[0].text: Expected a string at most 20000 character(s) long"
}
```

### Message attachment

`user.message.attachments` contain 0 to 10 turn-scoped attachments. Raw bytes are
sent as a base64 data URL. The complete accepted attachment source is retained in
session event history.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "file",
  "filename": "notes.txt",
  "mediaType": "text/plain",
  "sizeBytes": 37,
  "source": {
    "type": "data_url",
    "dataUrl": "data:text/plain;base64,VGhlIGN1c3RvbWVyIGFza2VkIGZvciBhIFFCUiBwbGFuLg=="
  }
}
```

Supported `mediaType` values: `application/pdf`, `text/csv`, `application/csv`,
`text/plain`, `text/markdown`, `text/x-markdown`,
`application/vnd.openxmlformats-officedocument.wordprocessingml.document`,
`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,
`image/png`, `image/jpeg`, `image/webp`, `image/gif`, and `image/bmp`.

Set `analysisMode: "visual"` only for PDFs that should be rendered as page
images. Otherwise PDFs and documents are attached as files, and images are queued
as visual context for the next prompt.

### Stop reason

`session.status_idle.stop_reason` is one of:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "type": "end_turn" }
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "type": "error", "message": "The agent failed." }
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "type": "requires_action", "event_ids": ["custom_toolu_123"] }
```

When `type` is `requires_action`, send the matching `user.*` response event.

### Usage snapshot

`usage` is an optional event-envelope field. On terminal turn events, it describes
that completed turn and is suitable for forwarding to Langfuse or another LLM
observability backend. Session responses expose a separate aggregate `usage`
object for lifetime session totals.

| Field                  | Type   | Description                                                                   |
| ---------------------- | ------ | ----------------------------------------------------------------------------- |
| `model`                | string | Model name, when known.                                                       |
| `input`                | number | Input tokens for the completed turn.                                          |
| `output`               | number | Output tokens for the completed turn.                                         |
| `cache_read`           | number | Cache-read tokens for the completed turn.                                     |
| `cache_write`          | number | Cache-write tokens for the completed turn.                                    |
| `reasoning`            | number | Reasoning tokens for the completed turn, when reported by the model provider. |
| `total`                | number | Total tokens for the completed turn.                                          |
| `calls`                | number | Model API calls for the completed turn.                                       |
| `context_used`         | number | Latest context-window usage in tokens.                                        |
| `context_max`          | number | Model context-window size in tokens, when known.                              |
| `context_percent`      | number | Latest context-window percentage.                                             |
| `cost_usd`             | number | Estimated session cost in USD, omitted when unavailable.                      |
| `cost_status`          | string | Cost state, such as `estimated`, `included`, or `unknown`.                    |
| `last_turn_latency_ms` | number | Wall-clock latency for the completed turn.                                    |
| `latency_ms`           | number | Alias for the latest reported turn latency.                                   |

## Inbound events

Inbound events are accepted by `POST /v1/sessions/{sessionId}/events`.

### `user.message`

Starts a new agent turn.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "user.message",
  "content": [{ "type": "text", "text": "Say hello in one sentence." }],
  "metadata": { "source": "dashboard", "accountId": "acct_123" },
  "attachments": []
}
```

| Field         | Type                  | Required | Description                                                                                                                                                  |
| ------------- | --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`        | string                | Yes      | Must be `user.message`.                                                                                                                                      |
| `content`     | text block\[]         | Yes      | User message content.                                                                                                                                        |
| `metadata`    | object                | No       | Turn-scoped application context. Values must be strings, numbers, booleans, or `null`. Persisted on `payload.metadata` and supplied to the agent as context. |
| `attachments` | message attachment\[] | No       | Files or images to attach to this turn.                                                                                                                      |

Only one prompt-starting `user.message` event is allowed per request. If another
turn is running, it is persisted with `status: "queued"` and started
automatically when that turn ends. See
[Queue a message while a turn is running](/api-reference/send-message#queue-a-message-while-a-turn-is-running).

### `user.steer`

Adds steering text to the current session without starting a normal prompt.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "user.steer",
  "message": "Keep the answer short and cite files you change."
}
```

| Field     | Type   | Required | Description                                               |
| --------- | ------ | -------- | --------------------------------------------------------- |
| `type`    | string | Yes      | Must be `user.steer`.                                     |
| `message` | string | Yes      | Steering instruction. Non-empty, up to 20,000 characters. |

### `user.interrupt`

Interrupts the active turn.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "user.interrupt",
  "message": "Stop this run."
}
```

| Field     | Type   | Required | Description                                                     |
| --------- | ------ | -------- | --------------------------------------------------------------- |
| `type`    | string | Yes      | Must be `user.interrupt`.                                       |
| `message` | string | No       | Optional reason or UI note. Non-empty, up to 20,000 characters. |

### `user.approval_response`

Allows or denies a tool call that requires approval before execution.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "user.approval_response",
  "approval_id": "approval_123",
  "result": "allow",
  "scope": "once"
}
```

| Field         | Type   | Required | Description                                                      |
| ------------- | ------ | -------- | ---------------------------------------------------------------- |
| `type`        | string | Yes      | Must be `user.approval_response`.                                |
| `approval_id` | string | Yes      | `id` from `agent.approval_request`.                              |
| `result`      | string | Yes      | `allow` or `deny`.                                               |
| `scope`       | string | No       | `once`, `session`, or `always`. Defaults to `once` when omitted. |

### `user.custom_tool_result`

Returns the result of an application-owned custom tool.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "user.custom_tool_result",
  "tool_use_id": "custom_toolu_123",
  "content": [{ "type": "text", "text": "Order #123 ships tomorrow." }],
  "is_error": false
}
```

| Field         | Type          | Required | Description                                                                                                                                                                                       |
| ------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`        | string        | Yes      | Must be `user.custom_tool_result`.                                                                                                                                                                |
| `tool_use_id` | string        | Yes      | `id` from `agent.custom_tool_use`.                                                                                                                                                                |
| `content`     | text block\[] | Yes      | Tool result content shown to the model.                                                                                                                                                           |
| `is_error`    | boolean       | No       | Defaults to `false`. Set to `true` when your tool failed.                                                                                                                                         |
| `status`      | string        | No       | `running`, `completed`, or `failed`. Omit for ordinary immediate results. Use `running` for a long-running workflow kickoff, then send `completed` or `failed` later with the same `tool_use_id`. |

### `user.clarify_result`

Answers an agent clarification request.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "user.clarify_result",
  "request_id": "clarify_123",
  "answer": "Use the production environment."
}
```

| Field        | Type   | Required | Description                                                    |
| ------------ | ------ | -------- | -------------------------------------------------------------- |
| `type`       | string | Yes      | Must be `user.clarify_result`.                                 |
| `request_id` | string | Yes      | `request_id` from `agent.clarify_request`.                     |
| `answer`     | string | Yes      | User answer, up to 20,000 characters. Empty string means skip. |

## Session events

Session events describe turn status and session metadata changes.

### `session.status_running`

The agent turn is running.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "type": "session.status_running" }
```

### `session.status_idle`

The agent turn ended, errored, or paused for required action.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "session.status_idle",
  "stop_reason": { "type": "end_turn" }
}
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "session.status_idle",
  "stop_reason": {
    "type": "requires_action",
    "event_ids": ["custom_toolu_123"]
  }
}
```

| Field         | Type   | Description            |
| ------------- | ------ | ---------------------- |
| `type`        | string | `session.status_idle`. |
| `stop_reason` | object | Why the turn is idle.  |

### `session.compacted`

Emitted after Hermes successfully compacts the conversation context. Compaction
summarizes earlier conversation history so the agent can continue within its
model context window.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "session.compacted",
  "compression_count": 2
}
```

| Field               | Type   | Required | Description                                                                     |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------------- |
| `type`              | string | Yes      | Must be `session.compacted`.                                                    |
| `compression_count` | number | No       | Number of successful context compactions in the current Hermes session lineage. |

This event is persisted and replayed by the session events API. It is emitted
only after a successful automatic or manual compaction; a started or aborted
compaction does not produce this event. The public Prism session ID remains
unchanged.

### `session.title_updated`

Emitted when an untitled session receives its automatically generated title.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "session.title_updated",
  "title": "Holiday campaign planning"
}
```

The event is stored in persisted event history and updates the session's `title`
field. Explicitly provided or manually updated titles are not overwritten.

### `session.tool_updated`

Ephemeral stream event emitted when a final long-running custom tool result
replaces a prior running result in event history. This event is not retained;
live clients use it to patch the event named by `event_id`.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "session.tool_updated",
  "tool_use_id": "custom_toolu_123",
  "event_id": "evt_running_result",
  "payload": {
    "type": "user.custom_tool_result",
    "tool_use_id": "custom_toolu_123",
    "content": [{ "type": "text", "text": "Workflow finished." }],
    "status": "completed",
    "is_error": false
  }
}
```

| Field         | Type   | Description                                        |
| ------------- | ------ | -------------------------------------------------- |
| `tool_use_id` | string | Custom tool use ID being updated.                  |
| `event_id`    | string | Existing `user.custom_tool_result` event to patch. |
| `payload`     | object | Updated `user.custom_tool_result` payload.         |

See [Long-running custom tools](/api-reference/long-running-tools) for completion
and reconciliation behavior.

## Agent events

Agent events describe model output, tool activity, and required actions.

### `agent.message`

Assistant response content.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.message",
  "content": [{ "type": "text", "text": "Hello!" }],
  "delta": false
}
```

| Field     | Type          | Description                                                         |
| --------- | ------------- | ------------------------------------------------------------------- |
| `content` | text block\[] | Assistant text.                                                     |
| `delta`   | boolean       | `true` for streaming chunks, `false` for complete durable messages. |

### `agent.thinking`

Reasoning content emitted by the model.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.thinking",
  "content": [{ "type": "text", "text": "I need to inspect the repo." }],
  "delta": true
}
```

| Field     | Type          | Description                                                           |
| --------- | ------------- | --------------------------------------------------------------------- |
| `content` | text block\[] | Reasoning text.                                                       |
| `delta`   | boolean       | `true` for streaming chunks, `false` for finalized durable reasoning. |

### `agent.approval_request`

Tool call waiting for approval before execution.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.approval_request",
  "id": "approval_123",
  "tool": "terminal",
  "input": { "command": "pwd" },
  "preview": "terminal({'command': 'pwd'})"
}
```

| Field     | Type   | Description                                                                |
| --------- | ------ | -------------------------------------------------------------------------- |
| `id`      | string | Approval request ID. Send it back as `user.approval_response.approval_id`. |
| `tool`    | string | Tool requiring approval.                                                   |
| `input`   | object | Proposed tool input.                                                       |
| `preview` | string | Optional human-readable preview.                                           |

After approval, the stream emits the actual `agent.tool_use`,
`agent.mcp_tool_use`, or `agent.custom_tool_use` event.

### `agent.tool_use`

Built-in tool call.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.tool_use",
  "id": "toolu_123",
  "tool": "terminal",
  "input": { "command": "pwd" },
  "status": "running",
  "preview": "pwd"
}
```

| Field     | Type   | Description                      |
| --------- | ------ | -------------------------------- |
| `id`      | string | Tool use ID.                     |
| `tool`    | string | Tool name.                       |
| `input`   | object | Tool input.                      |
| `status`  | string | Currently `running`.             |
| `preview` | string | Optional human-readable preview. |

An event-envelope status of `streaming` marks a live, provisional preparing
state. Its ID and input are not authoritative, and it is omitted from history.

### `agent.tool_result`

Built-in tool result.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.tool_result",
  "tool_use_id": "toolu_123",
  "tool": "terminal",
  "status": "completed",
  "content": [{ "type": "text", "text": "/workspace" }],
  "is_error": false
}
```

| Field         | Type          | Description                          |
| ------------- | ------------- | ------------------------------------ |
| `tool_use_id` | string        | ID of the matching `agent.tool_use`. |
| `tool`        | string        | Tool name.                           |
| `status`      | string        | `completed` or `failed`.             |
| `content`     | text block\[] | Tool result text.                    |
| `is_error`    | boolean       | Whether the tool result is an error. |

### `agent.mcp_tool_use`

MCP tool call. Shape matches `agent.tool_use`.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.mcp_tool_use",
  "id": "mcptoolu_123",
  "tool": "mcp_github_create_issue",
  "input": { "title": "Bug" },
  "status": "running"
}
```

As with `agent.tool_use`, envelope `status: "streaming"` marks a provisional
preparing state that is omitted from history.

### `agent.mcp_tool_result`

MCP tool result. Shape matches `agent.tool_result`.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.mcp_tool_result",
  "tool_use_id": "mcptoolu_123",
  "tool": "mcp_github_create_issue",
  "status": "completed",
  "content": [{ "type": "text", "text": "Created issue #42." }],
  "is_error": false
}
```

### `agent.custom_tool_use`

Application-owned custom tool request. Your application must execute the tool and
return `user.custom_tool_result`.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.custom_tool_use",
  "id": "custom_toolu_123",
  "tool": "check_order_status",
  "input": { "order_id": "123" }
}
```

| Field   | Type   | Description                                                     |
| ------- | ------ | --------------------------------------------------------------- |
| `id`    | string | Tool use ID to return as `user.custom_tool_result.tool_use_id`. |
| `tool`  | string | Custom tool name from the agent configuration.                  |
| `input` | object | Model-supplied tool input.                                      |

### `agent.clarify_request`

Blocking clarification question from the agent.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.clarify_request",
  "request_id": "clarify_123",
  "question": "Which environment should I deploy to?",
  "choices": ["Staging", "Production"]
}
```

| Field        | Type              | Description                                                |
| ------------ | ----------------- | ---------------------------------------------------------- |
| `request_id` | string            | ID to return as `user.clarify_result.request_id`.          |
| `question`   | string            | Question to display to the user.                           |
| `choices`    | string\[] or null | Multiple-choice options, or `null` for open-ended answers. |

### `agent.thread_message_sent`

Parent agent delegated work to a subagent or thread.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.thread_message_sent",
  "thread_id": "thread_123",
  "content": [{ "type": "text", "text": "Research deployment options." }]
}
```

### `agent.thread_message_received`

Parent agent received a subagent or thread result.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "agent.thread_message_received",
  "thread_id": "thread_123",
  "content": [{ "type": "text", "text": "Use the existing deployment." }]
}
```

## Custom tool handling

Your application executes custom tools and returns their results through the
session events API.

1. Declare a `custom_tool` in the agent's `tools` array.
2. The model calls the tool during a turn.
3. The stream emits `agent.custom_tool_use` with the tool `id`, `tool` name, and `input`.
4. The stream emits `session.status_idle` with `stop_reason.type: "requires_action"` and the custom tool ID.
5. Your application executes the tool.
6. Your application posts `user.custom_tool_result` with `tool_use_id` equal to the `agent.custom_tool_use.id`.
7. The paused turn receives the result and continues streaming.

For long-running application workflows, see
[Long-running custom tools](/api-reference/long-running-tools). The kickoff must
resolve the blocked tool call within approximately five minutes. A later final
result starts a new turn and therefore requires the session to be idle.

Custom tools are governed by Herm permission policies. Your application is also
responsible for authorization, validation, idempotency, retries, and side-effect
safety.
