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

# DOM events

> Every event the <mindset-agent> element fires, with payload shapes and when they fire.

The `<mindset-agent>` element is the customer-facing contract. It dispatches DOM events as the agent runs, and you listen with standard `addEventListener`. Every event name starts with `mindset:`, every event bubbles, every event is composed (it crosses shadow DOM boundaries), and every event has a documented `event.detail` payload.

You can listen on the element directly, or higher up the tree if it's easier:

```js theme={null}
const agent = document.querySelector('mindset-agent');

// Listen on the element
agent.addEventListener('mindset:text-delta', (e) => {
  appendToOutput(e.detail.content);
});

// Or listen on document, events bubble
document.addEventListener('mindset:thread-changed', (e) => {
  updateUrl(e.detail.threadUid);
});
```

Events fall into three groups: lifecycle (the agent's overall state), thread (which thread is active), and per-turn (what's happening during a single turn). Read each group below or jump to the [event sequence](#event-sequence-in-a-turn) section for the order they fire in.

***

## Lifecycle events

These tell you whether the agent is ready to take input, processing a turn, or in trouble. Use them to enable and disable your input controls, show busy indicators, and surface errors.

### `mindset:agent-registered`

Fires once when the element is added to the DOM and its `connectedCallback` runs. The agent isn't bound to a runtime yet at this point (that happens later, after `mindset.init()` completes), but the element exists and you can attach event listeners. Wait for `mindset:agent-idle` before calling element methods like `setPageTools` or `sendMessage`.

**`event.detail`:**

| Field      | Type      | Description                                         |
| ---------- | --------- | --------------------------------------------------- |
| `headless` | `boolean` | `true` if the element has the `headless` attribute. |

```js theme={null}
agent.addEventListener('mindset:agent-registered', (e) => {
  console.log('Element registered, headless mode:', e.detail.headless);
});
```

### `mindset:agent-initializing`

The agent is loading models, fetching its session envelope, and discovering tools. You can't send messages yet.

**`event.detail`:** `{}`

### `mindset:agent-idle`

The agent is ready and waiting for input. This fires on first becoming ready and again every time a turn ends.

**`event.detail`:** `{}`

### `mindset:agent-busy`

The agent is processing a turn. Sending another message during a busy state throws. Wait for `mindset:agent-idle` first, or check `agent.isAgentBusy()`.

**`event.detail`:** `{}`

### `mindset:agent-error`

Init failed, or the agent hit an unrecoverable error. The element stays in the error state until you re-initialize.

**`event.detail`:**

| Field     | Type     | Description                                   |
| --------- | -------- | --------------------------------------------- |
| `code`    | `string` | A short error code suitable for branching on. |
| `message` | `string` | Human-readable description.                   |

### State transitions

The agent moves through states in a fixed pattern:

```
init() → mindset:agent-initializing → mindset:agent-idle
                                          ↑   ↓
                                          └───┴── mindset:agent-busy ──┐
                                                                       │
                                                                       └─── (turn ends) ──→ mindset:agent-idle

(any state) → mindset:agent-error
```

Same-state transitions don't fire the event a second time. If you want to know whenever a turn finishes, listen for [`mindset:complete`](#mindset-complete). It's the canonical turn-end signal and carries the full response text.

<Note>
  **Warmup and icebreaker turns**: after init, before your first `sendMessage` runs, you'll see one or two automatic turns:

  1. **Warmup** (always): the agent does an internal context-loading pass. You'll see `agent-busy → tool-start`/`tool-end` (with `silent: true`, repeated) `→ agent-idle`, but **no `mindset:text-delta` and no `mindset:complete`**, and no thread events. Analytics or "first response ready" indicators should not key on this turn. They should key on the first `mindset:complete` after the user's first `sendMessage`.

  2. **Icebreaker** (only if configured): if your agent has an icebreaker message, you'll then see a full turn cycle (`agent-busy → text-delta → complete → agent-idle`) before your first `sendMessage` runs. The icebreaker fires automatically.

  Both apply in both modes (built-in chat UI and headless).

  If you call `sendMessage` immediately after the first `mindset:agent-idle`, the call may land while warmup or the icebreaker is in flight and throw. Use the [`sendWhenIdle` pattern](./methods.mdx#waiting-for-the-agent-to-be-ready). It re-checks busy state on each idle event, so it handles both cases transparently whether you know an icebreaker is configured or not.
</Note>

***

## Thread events

### `mindset:thread-changed`

The customer-visible thread uid changed. Fires when:

* A new thread persists server-side, shortly after the user's first `sendMessage` turn completes. Listen for this event rather than timing against `mindset:complete`.
* `agent.switchThread(uid)` succeeds
* The active thread is deleted (the next persisted thread becomes active, or the uid drops to `null`)
* A page boots with `<mindset-agent thread-uid="<existing>">` and the existing thread loads

**`event.detail`:**

| Field       | Type             | Description                                                                                                                                                 |
| ----------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `threadUid` | `string \| null` | The new active thread's UID. `null` while between threads, for example, immediately after `agent.newThread()` and before the user's first message persists. |
| `previous`  | `string \| null` | The thread UID that was active before this change.                                                                                                          |

```js theme={null}
agent.addEventListener('mindset:thread-changed', (e) => {
  // Persist the new thread in your URL or local storage
  if (e.detail.threadUid) {
    updateUrl(e.detail.threadUid);
  }
});
```

**Important: thread uids are exposed only after the user engages.** A freshly-created thread doesn't have a customer-visible uid until a user-message turn completes and the thread persists server-side. This means:

* `agent.newThread()` does NOT fire `mindset:thread-changed` immediately. It fires after the user sends a message and the thread persists.
* `agent.threadUid` returns `null` between `newThread()` and the first persisted user turn.

This deliberately matches the chat-UI thread list semantics: the headless API never exposes a uid that wouldn't also appear in the UI list, can't be loaded later, or can't be passed back to `agent.switchThread()`. See the [methods reference § Thread-uid persistence semantics](./methods.mdx#thread-uid-persistence-semantics) for the full transition matrix.

This event suppresses no-op transitions. If the underlying thread store fires for a non-UID change (for example, an `isBusy` flag toggle), the DOM event does not fire.

***

## Per-turn events

These fire during a single agent turn, in the order the runtime produces them. Most carry chunks of streaming output you'll want to render incrementally.

### `mindset:text-delta`

A streaming chunk of text from the agent. Append these to your output as they arrive. Each chunk may be a fragment, including punctuation or whitespace, not necessarily a complete word.

**`event.detail`:**

| Field     | Type     | Description              |
| --------- | -------- | ------------------------ |
| `content` | `string` | The new chunk to append. |

```js theme={null}
let output = '';
agent.addEventListener('mindset:text-delta', (e) => {
  output += e.detail.content;
  document.getElementById('reply').textContent = output;
});
```

### `mindset:stream-flush`

A buffer boundary in the runtime's stream. Adapters that buffer incoming `mindset:text-delta` content should commit their buffer when this arrives. Treat what comes next as a new contiguous chunk. Fires multiple times per turn. Consumers that don't render text incrementally can ignore it. Distinct from `mindset:complete`, which fires once at the very end of the turn.

**`event.detail`:** `{}`

### `mindset:tool-start`

The agent is about to execute a tool. Fires once per tool call, before the tool runs. The matching `mindset:tool-end` shares the same `toolCallId`.

**`event.detail`:**

| Field        | Type      | Description                                                                                                                                                                                                                                                                                                                                                                  |
| ------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `toolName`   | `string`  | The tool's name.                                                                                                                                                                                                                                                                                                                                                             |
| `toolCallId` | `string`  | Pairs with the matching `mindset:tool-end`.                                                                                                                                                                                                                                                                                                                                  |
| `runId`      | `string`  | LangGraph run ID.                                                                                                                                                                                                                                                                                                                                                            |
| `silent`     | `boolean` | Set per-invocation by the agent runtime. When `true`, the tool call is part of the agent's internal reasoning (examples: `search_information`, `recall_message`) and customer UIs typically should not surface it. The same tool can be silent on one invocation and not silent on another within a single turn. **Trust the flag, not the tool name.**                      |
| `args`       | `unknown` | The tool's input arguments as the agent runtime produced them. The shape reflects the underlying LLM's tool-call format and may include provider-specific wrapping. Parse defensively if you read it from the event, or use the `args` your `setPageTools` handler receives (the SDK extracts those before invoking your handler). Truncated to 5,000 chars when serialized. |

### `mindset:tool-end`

The tool finished. The detail payload includes the tool's output and, for tools that produce visual artifacts, a `widget` or `canvas` payload.

**`event.detail`:**

| Field           | Type                         | Description                                                                              |
| --------------- | ---------------------------- | ---------------------------------------------------------------------------------------- |
| `toolName`      | `string`                     | The tool's name.                                                                         |
| `toolCallId`    | `string`                     | The same ID as the matching `mindset:tool-start`.                                        |
| `runId`         | `string`                     | LangGraph run ID.                                                                        |
| `durationMs`    | `number`                     | How long the tool ran, in milliseconds.                                                  |
| `silent`        | `boolean`                    | Same per-invocation flag as on `tool-start`, mirrored on the closing event.              |
| `widget`        | `WidgetPayload \| undefined` | Rich-rendered payload for `show_*` tools.                                                |
| `canvas`        | `CanvasPayload \| undefined` | Canvas payload for tools that target the canvas surface.                                 |
| `llmToolCallId` | `string \| undefined`        | The LLM-emitted tool-call ID (e.g. `toolu_*` for Anthropic), distinct from `toolCallId`. |
| `output`        | `string \| undefined`        | Tool output text. Truncated to 5,000 chars.                                              |

```js theme={null}
agent.addEventListener('mindset:tool-end', (e) => {
  if (e.detail.widget) {
    renderWidget(e.detail.widget);
  }
});
```

### `mindset:citation-applied`

Fires after post-processing if citations are enabled for the agent. `enrichedText` is the agent's full reply with citation markers (e.g. `[1]`, `[2]`) inserted. If you render citations, display this in place of the concatenated `text-delta` content.

**`event.detail`:**

| Field          | Type     | Description                                            |
| -------------- | -------- | ------------------------------------------------------ |
| `enrichedText` | `string` | The agent's full reply with citation markers inserted. |

### `mindset:references`

The agent produced source references for the turn. Fires when retrieval-augmented generation (RAG) is enabled and the agent used source documents.

**`event.detail`:**

| Field     | Type                        | Description                                                                                                                                                                |
| --------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `records` | `Record<string, unknown>[]` | Source documents the response drew from. Each record carries the source's metadata; the exact field set is enrichment-dependent and not yet pinned in the public contract. |

### `mindset:quick-replies`

The agent suggested a small set of tappable next-message options (typically 2 to 4). Render whatever arrives rather than pre-allocating slots. The array length is variable and may change as the agent runtime evolves. Show them as buttons that call `agent.sendMessage(option)` when clicked.

**`event.detail`:**

| Field      | Type       | Description                                          |
| ---------- | ---------- | ---------------------------------------------------- |
| `question` | `string`   | A short framing prompt to display above the options. |
| `options`  | `string[]` | The suggested replies.                               |

### `mindset:follow-up-questions`

The agent suggested a small set of exploratory follow-on questions (typically 3 to 5). Render whatever arrives. The array length is variable. Distinct from `mindset:quick-replies`: follow-ups are open-ended prompts, quick replies are tappable canned responses.

**`event.detail`:**

| Field       | Type       | Description                        |
| ----------- | ---------- | ---------------------------------- |
| `questions` | `string[]` | The suggested follow-up questions. |

### `mindset:interrupt`

The turn paused. The agent called a UI-interrupt tool (e.g. `present_choices`, `present_quiz`) and expects the user to respond. Render UI based on `interruptType` and `args`. The turn does not produce `mindset:complete`.

**`event.detail`:**

| Field           | Type                      | Description                                                                                   |
| --------------- | ------------------------- | --------------------------------------------------------------------------------------------- |
| `interruptType` | `string`                  | The kind of interrupt. Matches the tool that paused the turn.                                 |
| `args`          | `Record<string, unknown>` | The arguments the interrupt tool was called with, typically the data your UI needs to render. |
| `toolCallId`    | `string \| undefined`     | The originating tool-call ID.                                                                 |

<Note>
  **Resuming an interrupted turn**: programmatic resume from the element is on the roadmap and not currently exposed. For now, treat interrupts as turn-ending. Handle the `args` to render your UI, and drive the next turn with `agent.sendMessage()` based on the user's selection.
</Note>

### `mindset:guard-intercept`

The input was classified as a prompt-injection or jailbreak attempt and refused. The turn ends; no `mindset:complete` fires.

**`event.detail`:**

| Field            | Type     | Description                      |
| ---------------- | -------- | -------------------------------- |
| `refusalMessage` | `string` | The text to display to the user. |

### `mindset:plan-update`

The agent's plan advanced (cursor moved to the next step, plan completed, etc.). Fires when the agent uses planning steps and the plan state changes during the turn.

**`event.detail`:**

| Field       | Type                      | Description                   |
| ----------- | ------------------------- | ----------------------------- |
| `planState` | `Record<string, unknown>` | The full plan-state snapshot. |

### `mindset:thread-title`

The agent generated a 3-5-word title for the current thread. Fires once on the first turn of a thread, after post-processing. Persists alongside the thread document.

**`event.detail`:**

| Field   | Type     | Description          |
| ------- | -------- | -------------------- |
| `title` | `string` | The generated title. |

### `mindset:complete`

The canonical turn-end signal. Fires once at the end of a normal turn with the full response text.

**`event.detail`:**

| Field      | Type     | Description                                    |
| ---------- | -------- | ---------------------------------------------- |
| `response` | `string` | The complete response text the agent produced. |
| `threadId` | `string` | The thread the turn ran in.                    |

```js theme={null}
agent.addEventListener('mindset:complete', (e) => {
  console.log(`Turn done: ${e.detail.response.length} chars in thread ${e.detail.threadId}`);
});
```

`mindset:complete` is suppressed when a turn ends with `mindset:guard-intercept`, `mindset:interrupt`, or `mindset:turn-aborted` (a user stop). Those turn-end signals are mutually exclusive. Use `mindset:complete` when you need the full response in one place; use the deltas if you're rendering progressively.

### `mindset:turn-aborted`

The turn was stopped by the user — via the visible Send→Stop control or a call to [`agent.stop()`](./methods.mdx#agent-stop). No `mindset:complete` fires. Mindset AI persists only what was already shown and keeps the thread continuable, so the next `sendMessage` starts a fresh turn.

**`event.detail`:**

| Field       | Type             | Description                                                                   |
| ----------- | ---------------- | ----------------------------------------------------------------------------- |
| `threadUid` | `string \| null` | The thread the stopped turn ran in. `null` if the thread isn't persisted yet. |

```js theme={null}
agent.addEventListener('mindset:turn-aborted', () => {
  // The user stopped the turn — re-enable your input, update state, or log it.
});
```

***

## What you'll see during a turn

A turn fires a series of events as the agent works through the user's message. The events fall into rough phases. The agent enters a busy state, runs tools and/or generates text, optionally produces post-processing artifacts (citations, references, quick replies, follow-up questions, a new thread title), and then completes.

**Events that fire on most turns:**

* `mindset:agent-busy` at the start.
* `mindset:tool-start` / `mindset:tool-end` (in pairs, one per tool call) if the agent calls tools.
* `mindset:text-delta` (repeated) as the agent streams its reply.
* `mindset:stream-flush` between text-delta bursts and around tool transitions. Commit any text buffer you're holding when this arrives.
* `mindset:complete` once, at the end of a normal turn, with the full response text.
* `mindset:agent-idle` after the turn ends.

**Optional events** (fire only when the conditions apply):

* `mindset:references`, when retrieval-augmented generation produced source documents.
* `mindset:citation-applied`, when the agent enriched its reply with citation markers.
* `mindset:quick-replies`, when the agent is configured to suggest tappable replies.
* `mindset:follow-up-questions`, when the agent is configured to suggest open-ended follow-on questions.
* `mindset:thread-title`, on the first turn of a new thread.
* `mindset:plan-update`, when the agent uses planning steps.
* `mindset:thread-changed`, fires *after* `mindset:agent-idle` when a new thread first persists server-side, not during the turn itself.

<Note>
  **Don't depend on a specific event order.** Some events are reliable: `mindset:agent-busy` is always first, `mindset:agent-idle` is always last, `mindset:tool-start` and `mindset:tool-end` always pair around their tool. Post-processing events (citations, references, quick-replies, follow-up-questions, thread-title) fire after streaming completes, but the exact internal ordering between them may shift across runtime versions. Render reactively. Handle each event as it arrives rather than waiting for a specific predecessor.
</Note>

**Turns that don't fire `mindset:complete`:**

* A turn that hits a guardrail fires `mindset:guard-intercept` instead, then `mindset:agent-idle`.
* A turn that pauses on a UI-interrupt tool fires `mindset:interrupt` instead. Handle the interrupt's `args` to render UI; drive the next turn with `agent.sendMessage()`.
* The initial warmup turn after init produces no `mindset:complete` either (see the warmup-and-icebreaker note in the [lifecycle section](#state-transitions) above).

***

## Listening on the document

Every event bubbles and is composed, so you can listen at any level. Listening on `document` is useful when you're not sure exactly where the element will be in the DOM, or when you have multiple `<mindset-agent>` elements and want one handler:

```js theme={null}
document.addEventListener('mindset:thread-changed', (e) => {
  // e.target is the <mindset-agent> that fired
  console.log('Thread changed on', e.target.getAttribute('agent-uid'), 'to', e.detail.threadUid);
});
```

When you have multiple elements on a page, use `e.target` to disambiguate.

***

## Removing listeners

Standard DOM mechanics. Keep a reference to the function so you can pass it to `removeEventListener`:

```js theme={null}
function onDelta(e) {
  appendToOutput(e.detail.content);
}

agent.addEventListener('mindset:text-delta', onDelta);

// Later
agent.removeEventListener('mindset:text-delta', onDelta);
```

In React, do this in a `useEffect` cleanup. See the [Examples page](./examples.mdx) (Headless: React tab) for the pattern.
