# The mindset-agent element

> Reference for the drop-in chat element. Attributes, configure() fields, methods, events, error codes and theming.

**Skill for your coding agent:** [`mindset-embed/SKILL.md`](https://docs.mindset.ai/skills/mindset-embed/SKILL.md)

**Skill for your coding agent:** [`mindset-page-tools/SKILL.md`](https://docs.mindset.ai/skills/mindset-page-tools/SKILL.md)

In the browser, the SDK runs the conversation with Mindset, with your page tools and widgets. It gets its session from your server, which creates it with your org API key. Mindset holds the agent and runs its model and tools. Highlighted: Browser.

For a first working setup, start with [Put an agent on your page](https://docs.mindset.ai/docs/put-an-agent-on-your-page).

## Load it

```html
<script src="https://YOUR-MINDSET-HOST/sdk/mindset-agent.js"></script>

<mindset-agent agent="billing-help"></mindset-agent>

<script>
  document.querySelector("mindset-agent").configure({
    getSession: async () => {
      const r = await fetch("/api/session");
      if (!r.ok) throw new Error("Could not start a session");
      return r.json();
    },
  });
</script>
```

The script registers the `mindset-agent` tag itself. It also sets `window.MindsetAgentSDK`, which reports `version`, `commit` and `buildTimeMs` for the build on the page. Quote those when you contact support. See [SDK versions and updates](https://docs.mindset.ai/docs/sdk-versions-and-updates).

Load the script once per page (see [one SDK per page](#one-sdk-per-page)), and never add `mindset-agent-charts.js` yourself.

## configure()

`configure()` starts the element. Until you call it, the element draws nothing and `send()` reports an error.

| Field                  | Type     | What it does                                                                                                                                                                                                                                                   |
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `getSession`           | function | Required. Returns the JSON body your backend got from the [session create call](https://docs.mindset.ai/docs/sessions-and-trust), or a promise of it. The SDK calls it straight away and again whenever it needs a new session                                                        |
| `conversationId`       | string   | Resume this conversation. Leave it out for a fresh one. See [resume a conversation](#resume-a-conversation)                                                                                                                                                    |
| `conversationList`     | boolean  | Show the user a list of their earlier conversations with this agent. Off by default                                                                                                                                                                            |
| `pageTools`            | array    | Functions in your page the agent may call. Same as `setPageTools()`. Each has `name`, `description`, `jsonSchema` and `execute`, and optionally `runningDescription` and `completedDescription`, the activity row's labels. See [Page tools](https://docs.mindset.ai/docs/page-tools) |
| `situationalAwareness` | object   | Facts about what the user is looking at. Same as `setSituationalAwareness()`                                                                                                                                                                                   |
| `passthroughParams`    | object   | Values a tool needs that the model must never see. Same as `setPassthroughParams()`                                                                                                                                                                            |
| `agent`                | string   | Overrides the `agent` attribute. The attribute is the usual way                                                                                                                                                                                                |

The session carries your org, environment and Mindset host, so there is nothing else to configure.

Calling `configure()` again stops the current conversation and starts again from the new config. It resumes the conversation named in the `conversation-id` attribute, and the element writes that attribute itself after the first completed turn. So a second `configure()` normally picks up the same conversation. To start a new one, see [start a new conversation](#start-a-new-conversation).

The new config replaces the old one completely. Anything you set with `setPageTools()`, `setSituationalAwareness()` or `setPassthroughParams()` is dropped unless the new config includes it.

Moving the element in the DOM doesn't reconfigure it. The conversation carries on.

## Attributes and properties

Each attribute mirrors a property, in both directions.

| Property                       | Attribute                        | What it is                                                                                                                  |
| ------------------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `agent`                        | `agent`                          | Which agent to run: its handle or its ID, both on the agent's Embed tab                                                     |
| `conversationId`               | `conversation-id`                | Which conversation to resume                                                                                                |
| `conversationList`             | `conversation-list`              | Whether to show the conversation list. A presence attribute: any value turns it on, and removing the attribute turns it off |
| `situationalAwareness`         | `situational-awareness`          | Situational awareness as a JSON object. Replaces the entries, like `setSituationalAwareness()`                              |
| `passthroughParams`            | `passthrough-params`             | Pass-through values as a JSON object. Replaces them, like `setPassthroughParams()`                                          |
| `extendedSituationalAwareness` | `extended-situational-awareness` | Allows situational awareness up to 20,000 characters instead of 10,000. A presence attribute                                |

HTML lowercases attribute names, which is why the attribute is `conversation-id` and the property is `conversationId`.

Changing `agent` after `configure()` stops the conversation and raises `stale-configuration`. Call `configure()` again to start one for the new agent. The other attributes apply in place, and the conversation carries on.

## Methods

| Method                                | What it does                                                                                                                              |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `configure(config)`                   | Set the element up, as above                                                                                                              |
| `send(text)`                          | Send a turn as if the user typed it. A send while the agent is answering waits its turn. Before `configure()`, it raises `not-configured` |
| `sendMessage(text, { silent: true })` | Send an instruction from the page. The agent acts on it and the user sees only the reply                                                  |
| `isAgentBusy()`                       | Whether a message sent now would wait                                                                                                     |
| `stop()`                              | Stop the turn in progress. Safe when nothing is running. Queued sends still run                                                           |
| `setPageTools(tools)`                 | Replace the set of page tools                                                                                                             |
| `setSituationalAwareness(entries)`    | Replace the situational-awareness entries                                                                                                 |
| `setPassthroughParams(params)`        | Replace the pass-through values                                                                                                           |

The three `set` methods raise `not-configured` if called before `configure()`. Each method behaves like the UI-less command of the same name. [Page tools](https://docs.mindset.ai/docs/page-tools) describes all three in full.

The element also registers one page tool of its own, `record_script_data`, which a scripted agent uses to save what the user tells it. You can't remove or replace it. If one of your tools uses that name, yours is skipped and the console warns you.

Page tool handlers run with arguments the model chose. Validate them, and put nothing behind a page tool that you wouldn't let a hostile caller do. See [page tool safety](https://docs.mindset.ai/docs/page-tools#page-tool-safety).

## Resume a conversation

By default every mount starts a fresh conversation. To let a user pick up where they left off, store the conversation ID for that user and hand it back next time.

**First visit.** Leave `conversation-id` off. After the user's first completed turn, the element writes the ID onto its own `conversation-id` attribute and fires `mindset:conversation`. Store the ID against that user, in your own database.

**Next visit.** Pass the stored ID as `conversationId` in `configure()`, or set the `conversation-id` attribute before you call `configure()`. The agent continues with its earlier history, and the element shows it.

```html
<script>
  const agent = document.querySelector("mindset-agent");

  agent.addEventListener("mindset:conversation", (e) => {
    fetch("/api/conversation-id", {
      method: "PUT",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ conversationId: e.detail.conversationId }),
    });
  });

  fetch("/api/conversation-id")
    .then((r) => r.json())
    .then(({ conversationId }) => {
      agent.configure({
        getSession: async () => {
          const r = await fetch("/api/session");
          if (!r.ok) throw new Error("Could not start a session");
          return r.json();
        },
        ...(conversationId ? { conversationId } : {}),
      });
    });
</script>
```

Here `/api/conversation-id` is an endpoint in your own backend that saves and returns the ID for the signed-in user.

### Rules to know

- **Store what the event gives you, every time.** If an ID you supplied can't be used, the element starts a fresh conversation and fires `mindset:conversation` again with the new ID, before any turn. Saving whatever arrived last keeps you correct with no cleanup code.
- **Prefer the event to the attribute.** If your framework resets the DOM to a template, it may strip the attribute, and the element doesn't write it back.
- **Clearing the attribute does nothing.** An unrelated DOM update can't cost the user their conversation.
- **Writing the same ID back does nothing.** A re-render that echoes it is safe.
- **Writing a different ID stops the conversation** and raises `stale-configuration`. To open that conversation, call `configure()` again. To start fresh instead, see [start a new conversation](#start-a-new-conversation).
- **An ID is per user.** Resolve it per request from who is signed in. Never put one in shared or cached HTML, or every visitor gets the same conversation.

The ID is a correlation key. It grants nothing without a session for the same user, and an ID that belongs to another user is refused.

## Start a new conversation

There is no "new conversation" method. Remove the `conversation-id` attribute, then call `configure()` without a `conversationId`:

```javascript
const agent = document.querySelector("mindset-agent");

function startNewConversation() {
  agent.removeAttribute("conversation-id");
  agent.configure({
    getSession: async () => {
      const r = await fetch("/api/session");
      if (!r.ok) throw new Error("Could not start a session");
      return r.json();
    },
  });
}
```

Removing the attribute on its own does nothing. The `configure()` call starts over. After the new conversation's first completed turn, `mindset:conversation` fires with its ID. Store that one.

Replacing the element with a new `<mindset-agent>` that has no `conversation-id` attribute also works.

## The conversation list

Set `conversationList: true` in `configure()` (or add the `conversation-list` attribute) and the element shows a **Conversations** button in its top corner. It opens a list of this user's earlier conversations with this agent, newest first. Picking one switches the element to it.

The list shows only this user's conversations, and only with this agent. The list only changes what is drawn. The session decides what the user may read.

When the user switches, `mindset:conversation` fires with the new ID. Store it as usual.

**Live demo:** [Conversation list](https://docs.mindset.ai/demos/orbit). A returning customer reopens an earlier conversation about the same order instead of starting again.

## Events

DOM `CustomEvent`s. They bubble and cross the shadow boundary, so a listener on any ancestor sees them.

| Event                   | `detail`                                                                                                                                                                    |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mindset:runtime-event` | One runtime event, exactly as the [events reference](https://docs.mindset.ai/docs/events-reference#runtime-events) lists. Fires for every turn, whether typed in the element or sent with `send()` |
| `mindset:error`         | `{ code, message, cause? }`. A configuration or transport problem. See error codes below                                                                                    |
| `mindset:conversation`  | `{ type: "conversation_id", conversationId, replacedConversationId?, replacedBecause? }`. Which conversation the element is having                                          |
| `mindset:busy`          | `{ busy }`. Fires when the agent starts working from idle and when its last queued turn ends                                                                                |

`mindset:conversation` fires when the conversation ID first becomes known (at configure when you supplied one, otherwise after the first completed turn). It fires again only when the element moves to another conversation:

| `replacedBecause` | Meaning                                                               | What to do                                |
| ----------------- | --------------------------------------------------------------------- | ----------------------------------------- |
| `"discarded"`     | The ID you supplied couldn't be used, so a fresh conversation started | Store the new ID. The old one is dead     |
| `"switched"`      | The user picked another conversation from the list                    | Store the new ID. The old one still works |

`replacedConversationId` names the ID left behind. The event carries nothing else, on purpose, because every script on your page can read it.

The element doesn't re-emit the SDK's `history_settled` event. It contains the user's conversation, and the element draws it for you.

## Error codes

`mindset:error` always carries a `code`. Branch on the code and treat an unknown one as a general failure. Every error is also logged to the console.

| `code`                  | Raised when                                                                                                                                                                                                                                                     |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `missing-agent`         | `configure()` ran with no agent, from either the attribute or the config                                                                                                                                                                                        |
| `invalid-auth`          | `configure()` got no `getSession`, or `getSession` returned something the SDK can't use, such as a `Response` that wasn't parsed with `.json()`, or a session that has already expired. Raised as soon as `getSession` answers. The message says what to return |
| `not-configured`        | `send()` or a `set` method was called with no live conversation: before `configure()`, or after the element was removed from the page                                                                                                                           |
| `stale-configuration`   | The `agent` or `conversation-id` attribute changed after `configure()`, so the conversation no longer matches the element. Call `configure()` again                                                                                                             |
| `turn-failed`           | A turn failed: transport, credential or the run itself. An expired session, or a `getSession` that threw, shows up here and nowhere else                                                                                                                        |
| `invalid-attribute`     | The `situational-awareness` or `passthrough-params` attribute isn't a JSON object, or situational awareness is over its size limit. Nothing changes                                                                                                             |
| `invalid-configuration` | The element can't do what it was configured to do. The message says which setting and how to fix it                                                                                                                                                             |

`stop()` raises no error. A stopped turn arrives as a `run_error` runtime event with `aborted: true`.

## Theming

Theme the element with CSS custom properties set on the element itself. A custom property sets a color everywhere at once. To style one surface, use `mindset-agent::part(<name>)` from your own stylesheet. The parts are `chat`, `message-user`, `message-agent`, `page-event`, `tool-row`, `quick-reply`, `composer` and `input`. Set the text color wherever you set a background. [Layout and theming](https://docs.mindset.ai/docs/layout-and-theming) covers sizing, dark mode and a worked theme.

The element ships its own default colors, so it looks right on a page with no theme. Add `class="dark"` to the tag for the dark set.

Override any channel from your own stylesheet. A rule on the element beats its built-in defaults.

```css
mindset-agent {
  --ch-accent: 124 58 237;
  --ch-page: 255 255 255;
  --ch-panel: rgba(255, 255, 255, 1);
}
```

Most channels take three space-separated numbers for red, green and blue, from 0 to 255. Hex and `rgb()` don't work on those.

These channels take a full color value instead, such as `rgba(255, 255, 255, 1)`, because they carry their own transparency. Three bare numbers there give an invalid color:

| Channel                                                                                                                      | Example value             |
| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `--ch-panel`                                                                                                                 | `rgba(255, 255, 255, 1)`  |
| `--ch-overlay`                                                                                                               | `rgba(15, 23, 42, 0.4)`   |
| `--ch-edge-subtle`                                                                                                           | `rgba(226, 232, 240, 1)`  |
| `--ch-chip`                                                                                                                  | `rgba(30, 41, 59, 1)`     |
| `--ch-status-info-bg`, `--ch-status-success-bg`, `--ch-status-warning-bg`, `--ch-status-danger-bg`, `--ch-status-neutral-bg` | `rgba(219, 234, 254, 1)`  |
| `--ch-canvas-dot`                                                                                                            | `rgba(15, 23, 42, 0.05)`  |
| `--ch-phase-specify-bg`, `--ch-phase-build-bg`, `--ch-phase-verify-bg` and the four `--ch-phase-*-container` channels        | `rgba(93, 192, 166, 0.1)` |

The channels an embedder usually changes are `--ch-page`, `--ch-panel`, `--ch-card`, `--ch-surface`, `--ch-input`, `--ch-text-heading`, `--ch-text-primary`, `--ch-text-secondary`, `--ch-edge`, `--ch-accent`, `--ch-on-accent` and `--ch-btn-primary`.

The current set has 78 channels. It can grow as the interface changes, so don't treat this list as fixed. Many of them (`node-*`, `phase-*`, `step-*`, `canvas*`, `script-*`) style surfaces you rarely see in an embed.

```
--ch-page                       --ch-panel                      --ch-card
--ch-surface                    --ch-input                      --ch-card-hover
--ch-selected                   --ch-placeholder                --ch-surface-sunken
--ch-surface-raised             --ch-overlay                    --ch-text-heading
--ch-text-primary               --ch-text-secondary             --ch-text-muted
--ch-logo                       --ch-edge                       --ch-edge-subtle
--ch-edge-strong                --ch-btn-primary                --ch-btn-primary-hover
--ch-btn-secondary              --ch-btn-secondary-hover        --ch-accent
--ch-accent-hover               --ch-on-accent                  --ch-agent
--ch-agent-hover                --ch-node-agent                 --ch-node-connection
--ch-node-tool                  --ch-node-knowledge             --ch-node-widget
--ch-node-function              --ch-chip                       --ch-chip-text
--ch-chip-border                --ch-status-warning-bg          --ch-status-warning-text
--ch-status-warning-border      --ch-status-success-bg          --ch-status-success-text
--ch-status-success-border      --ch-code                       --ch-status-info-bg
--ch-status-info-text           --ch-status-info-border         --ch-status-danger-bg
--ch-status-danger-text         --ch-status-danger-border       --ch-step-neutral
--ch-step-model                 --ch-step-call                  --ch-step-widget
--ch-step-gate                  --ch-on-step-gate               --ch-script-end
--ch-script-hold                --ch-canvas                     --ch-raised
--ch-canvas-dot                 --ch-on-step                    --ch-status-neutral-bg
--ch-status-neutral-text        --ch-status-neutral-border      --ch-phase-specify-bg
--ch-phase-specify-text         --ch-phase-specify-border       --ch-phase-specify-container
--ch-phase-build-bg             --ch-phase-build-text           --ch-phase-build-border
--ch-phase-build-container      --ch-phase-verify-bg            --ch-phase-verify-text
--ch-phase-verify-border        --ch-phase-verify-container     --ch-phase-draft-container
```

Style isolation works both ways. The interface lives in a shadow root and its stylesheet is attached to that root, never to your document.

## What the element draws

The element draws the agent's replies, the widgets it shows (charts, callouts, tables, cards, badges, stats and widgets authored in the Studio), quick replies, capability cards, follow-up questions and citations. You write none of that.

Charts need a second script, `mindset-agent-charts.js`. The element fetches it from the same Mindset host as the main script, the first time a chart appears. If the main script tag carries a CSP `nonce`, the element copies it onto the chart script. See [Content security policy](https://docs.mindset.ai/docs/content-security-policy).

A scripted agent's greeting, cards, phases and questions appear only in the `<mindset-agent>` element. The UI-less client runs the agent without them.

## One SDK per page

Load `mindset-agent.js` once. A second copy, or an older Mindset embed that also registers `mindset-agent`, logs a tag collision error in the console. [SDK versions and updates](https://docs.mindset.ai/docs/sdk-versions-and-updates#load-the-script-once) covers both cases and how to fix them.
