ReferenceSDKErrors and resilience
Errors and resilience
Every way an embedded agent can fail, from the session call to a turn, and what your code should do about each one.
Where failures show up#
| Where it fails | Drop-in element | UI-less client |
|---|---|---|
| Your server's session create call | An HTTP error your backend receives | The same |
getSession returns something the SDK can't use | mindset:error with invalid-auth | A session_error event |
getSession throws | Nothing at once. The next turn fails with turn-failed | Nothing at once. The next turn fails, and its promise rejects |
| Your code calls the element wrongly | mindset:error with a configuration code | Not applicable |
| A turn fails | mindset:error with turn-failed, plus a run_error runtime event when the run itself failed | A run_error event when the run itself failed, and the promise from send() rejects |
| A turn stops at a limit | A complete runtime event carrying bound | The same, on on() |
On the element, runtime events arrive as mindset:runtime-event. On the UI-less client, everything arrives on chat.on(). The events reference has every field.
What to do about each one#
Branch on the code or reason, never on the message text. Treat a code you don't recognize as a general failure, because new ones can be added.
| Signal | What happened | What to do |
|---|---|---|
Create: 400 validation_error | Your request body is wrong, or the agent has no published version to embed | Fix the request or publish the agent. Retrying the same call won't help |
Create: 403 agent_not_available_here | Embedded in your own product isn't ticked for the agent | An org admin ticks it. See Build an agent |
Create: 404 not_found | Wrong org slug, environment slug or agent; a key without admin scope; a personal agent; or an unknown user without createUserIfNeeded | Check each against the agent's Embed tab. Retrying won't help |
Create: 409 user_not_active | The user is invited but not yet active, or was removed | Don't retry. Tell the user they don't have access |
Create: 409 no_version_in_force | No configuration version is active | Activate one in the Studio |
| Create: a 5xx, or no answer | Mindset or the network failed | Return an error from your endpoint. The SDK calls getSession again the next time it needs a session |
getSession throws | Your endpoint failed or was unreachable | Nothing extra. The SDK asks again later. Make sure your endpoint returns a non-2xx status and your function throws, rather than returning an error body |
invalid-auth, or session_error with invalid-session-credential | getSession returned something the SDK can't read | Return your endpoint's parsed JSON body (r.json()) unchanged. Not the Response, and not a field from inside it |
session_error with expired-session-credential | The session was readable but already used up | Create a new session on every getSession call. Don't cache one |
missing-agent | configure() ran with no agent | Set the agent attribute |
not-configured | send() or a set method ran before configure(), or after the element left the page | Call configure() first |
stale-configuration | The agent or conversation-id attribute changed after configure() | Call configure() again |
invalid-attribute | The situational-awareness or passthrough-params attribute isn't a JSON object, or is over its size limit | Fix the attribute. The agent keeps what it had |
invalid-configuration | The element can't do what it was configured to do | Follow the message, which names the setting |
turn-failed | A turn failed: network, credential or the run | If a run_error runtime event came with it, follow its row below. A network or credential failure (an expired session, a getSession that threw) has no run_error. Let the user try again |
run_error, cancelled | Someone stopped the turn (aborted: true) | Show "stopped". It isn't an error |
run_error, model_error | A model call failed | Offer a retry, after checking effects |
run_error, step_limit | The run used its whole step budget | Offer to continue or retry, after checking effects. If it keeps happening, the agent needs work in the Studio |
run_error, tool_schema | The model provider refused one of the agent's tool definitions | Don't retry. It fails the same way until the tool is fixed in the Studio |
run_error, unavailable | The agent can't run as asked, for example it has no version in force | Don't retry. Tell the user the assistant isn't available |
run_error, stale_configuration | The agent's active version changed, and the SDK's own retry didn't get past it | Retry once more. The SDK has already fetched the new configuration |
run_error, failed | Anything else | Offer a retry, after checking effects |
complete with bound | The turn stopped at a limit: step_limit, deadline, spend_limit or repetition. It is complete, and can be continued | Show the reply, and let the user continue. bound.limit holds the limit's value |
Check effects before you retry#
A turn can fail after the agent has already done something. run_error.effects lists the tool calls that changed something and finished before the failure. Each has toolName, toolCallId and access, and may have a reference (a receipt the system returned) and output.
This UI-less handler puts the rows above into code:
chat.on((event) => {
if (event.type !== "run_error") return;
if (event.aborted) {
showNotice("Stopped.");
return;
}
const done = event.effects ?? [];
if (done.length > 0) {
const list = done.map((e) => e.reference ?? e.toolName).join(", ");
showNotice(`Something went wrong, but these already happened: ${list}. Check before trying again.`);
return;
}
switch (event.reason) {
case "tool_schema":
case "unavailable":
showNotice("The assistant isn't available right now.");
break;
case "model_error":
case "step_limit":
case "stale_configuration":
case "failed":
default:
showRetryButton(() => chat.retry().catch(() => {}));
break;
}
});showNotice and showRetryButton stand for your own interface code. retry() runs the last turn again and replaces its reply. Its promise rejects if that turn fails too, and the run_error event reports it, so the empty catch only stops an unhandled rejection.
On the element, listen for mindset:runtime-event and read e.detail the same way:
const agent = document.querySelector("mindset-agent");
agent.addEventListener("mindset:runtime-event", (e) => {
const event = e.detail;
if (event.type === "run_error" && !event.aborted && event.effects?.length) {
console.warn("A turn failed after changing something:", event.effects);
}
});
agent.addEventListener("mindset:error", (e) => {
const { code, message } = e.detail;
if (code === "invalid-auth") console.error("Fix getSession:", message);
else if (code === "stale-configuration") reconfigure(); // your own function that calls configure() again
else console.error("Mindset error:", code, message);
});What the SDK already handles#
- Renewal. The SDK renews its access token while the page is open, and calls
getSessionagain when it can't. - A changed agent version. When the agent's active version changes mid-conversation, the SDK fetches the new configuration and runs the turn again, once, before you see anything. That works while the turn hasn't produced output yet.
- A conversation ID it can't use. It starts a fresh conversation and tells you with
conversation_id(mindset:conversationon the element), withreplacedBecause: "discarded". Store whatever ID arrived last. - History that won't load.
history_settledstill arrives, with an empty list, within 15 seconds. It can't tell you whether the history was empty or failed to load.
Spend limits#
Each person using an embedded agent has an hourly spend ceiling on their turns, roughly $25 an hour per person on each Mindset server. Past it, their turns are refused until spend in the window drops, and a refused turn fails like any other. See and control cost covers what the ceiling covers and what it doesn't.
Related#
- Events reference for every event and field.
- The mindset-agent element for the element's error codes.
- Session API for every create error.