Docs / SDK / Reference / Events reference
View as MarkdownEvents reference
Every event an embedded agent sends while a turn runs and after it finishes, with each payload and how to handle it.
Use this page to handle everything an embedded agent tells your code: text as it streams, tools starting and finishing, the end of a turn, and which conversation is running.
Two kinds of event
Runtime events come from the agent runtime as a turn runs: text arriving, tools running, the turn ending. Most of this page is about them.
SDK events are about the conversation rather than the turn: which conversation this is, what history was restored, and whether the session works. The SDK owns these, because the runtime knows nothing about stored conversations.
How you receive them
| Runtime events | SDK events | |
|---|---|---|
| Drop-in element | mindset:runtime-event, with the event as detail | conversation_id as mindset:conversation. A session_error becomes mindset:error with code invalid-auth. history_settled is not passed on |
| UI-less client | On chat.on() | On chat.on(). Use isSdkEvent(event) to tell the two kinds apart |
// Drop-in element
document.querySelector("mindset-agent").addEventListener("mindset:runtime-event", (e) => {
const event = e.detail;
if (event.type === "text_delta") console.log(event.content);
});// UI-less client
chat.on((event) => {
if (event.type === "text_delta") console.log(event.content);
});The element's own configuration and transport failures arrive separately, as mindset:error. See error codes.
The rule that keeps your integration working
Switch on type and ignore anything you don't recognize. The vocabulary is additive only. New event types and new fields can appear. Existing ones keep their names and shapes. Code that throws on an unfamiliar type will break one day.
The shape of a turn
run_started is the first event of a turn. run_finished or run_error ends it. complete carries the final answer.
What happens in between varies by turn and by model. Don't rely on any order beyond those brackets.
Runtime events
The fields sit on the event object itself, next to type.
type | Fields | What it means |
|---|---|---|
run_started | runId? | The turn has started |
run_finished | runId? | The turn finished cleanly |
run_error | message, runId?, aborted?, reason?, effects?, stepLimit? | The turn ended in an error. See run_error |
text_delta | content | A piece of the reply. Join content across these for the full text |
thinking_delta | content | A piece of the model's summarized reasoning while it thinks |
stream_flush | none | Streaming paused for now. Flush any buffered text. The turn isn't over until complete |
tool_announce | toolName, toolCallId | The model has named a tool it's about to call, while its arguments are still streaming |
tool_start | toolName, toolCallId, args? | A tool is about to run |
tool_end | toolName, toolCallId, durationMs, output?, isError?, refused? | A tool finished. isError marks a failed call, with the error in output. refused marks a call that never ran because the agent wasn't allowed it |
widget-payload | doc, kind?, summary?, toolCallId? | Something to render, produced during the turn. See widget-payload |
citation_applied | enrichedText | The reply with citation markers such as [1] inserted |
quick_replies | question, options | Two to four short replies the user can tap, under a short question |
follow_up_questions | questions | Two or three open questions to continue with |
references | records | The turn used retrieval. records are the sources |
conversation_title | title | A short generated title for the conversation |
complete | response, messages, conversationId?, bound? | The final answer. response is the agent's text. See complete |
thinking_delta
Display only. It never joins the answer and isn't stored with the conversation. Models that don't think never send it, so don't wait for it.
tool_announce and tool_start
tool_announce is a timing hint and may not arrive at all. tool_start always fires when the tool actually runs, with the same toolCallId. Build on tool_start, and pair it with tool_end by toolCallId.
widget-payload
Branch on kind. Don't pass doc straight to a renderer.
kind | What doc is |
|---|---|
chart, callout, table, card, badge, stat, widget | A widget tree to draw |
quick_replies, capability_cards | A different shape, carrying replies or cards. Draw these as tappable options that send the chosen one |
summary is a plain-text description of the widget. On the UI-less client, where no Mindset renderer is served, showing summary is a reasonable fallback. To send a widget interaction back to the agent, use widgetAction().
Display only, and many turns don't produce one.
run_error
reason, when present, says which kind of failure it was:
reason | Meaning |
|---|---|
cancelled | Someone stopped the turn. Always comes with aborted: true |
step_limit | The run used its step budget mid-work. stepLimit is the budget |
model_error | A model call failed. Trying again may work |
tool_schema | A tool definition was refused by the model provider. It fails the same way until the tool is fixed in AMS |
unavailable | The agent can't run as asked, for example it has no version in force |
stale_configuration | The agent's active version changed since this conversation loaded it |
failed | Anything else |
effects, when present, lists tool calls that changed something and finished before the run failed. Check it before you offer a retry, so a retry doesn't repeat a booking or a payment.
Stopping a turn
When someone stops a turn, you get run_error with aborted: true and reason: "cancelled". The model call was cut and the turn ended as requested. Show it as "stopped", not as an error.
complete
messages is the full message list at the end of the turn. conversationId is the conversation the turn belonged to.
bound, when present, means the turn stopped at a limit before the job was done. bound.reason is step_limit, deadline, spend_limit or repetition, and bound.limit is the limit's value. The turn is still complete and can be continued.
No bound means no limit was hit. It doesn't prove the agent finished the job.
SDK events
These arrive on the UI-less client's on() alongside runtime events.
conversation_id
{ type: "conversation_id", conversationId, replacedConversationId?, replacedBecause? }
Which conversation this is. It fires when the ID first becomes known: at once if you supplied a conversationId, otherwise after the first completed turn. It fires again only when the conversation changes:
replacedBecause: "discarded": the ID you supplied couldn't be used, so a fresh conversation started. This arrives before any turn.replacedBecause: "switched":switchConversation()moved to another conversation. The old ID still works.
Store the latest conversationId against the user and hand it back next visit. It is a correlation key, not a credential. The element re-emits this event to your page as mindset:conversation.
history_settled
{ type: "history_settled", messages }
The restored transcript, so a custom interface can draw a returning user's history. It fires once per conversation, every time: with restored messages, with an empty list, or with an empty list after a failed or stalled read (the read gives up after 15 seconds). You can keep your input closed until it arrives.
Each message has role and content, and optionally widget as { doc, summary, kind }. When a turn showed a widget, its content is that widget's summary, so drawing role and content alone still gives an honest transcript. The list is frozen.
It can't tell "no history" from "history couldn't be loaded". Both arrive as an empty list.
The element doesn't pass this event to your page, because it contains the user's private conversation and DOM events are readable by every script on the page. The element draws the history itself.
session_error
{ type: "session_error", code, message, context }
The value your getSession returned can't be used. It arrives straight away, when getSession first answers, not on the first turn.
code | Meaning | Fix |
|---|---|---|
invalid-session-credential | The SDK couldn't read what came back | Return your endpoint's parsed JSON body unchanged |
expired-session-credential | It was readable but already used up | Create a new session instead of reusing a cached one |
message says what to return. context names the shape that came back, such as { received: "Response" }, never the value. More codes can be added.
If getSession throws instead, there's no session_error. The SDK asks again later.
On the element, this arrives as mindset:error with code invalid-auth.
Reserved event types
The runtime defines interrupt, guard_intercept, guard_passed, exit_confirmation and plan_update, but doesn't send them today. Don't build for them yet. Your default branch already ignores them.