Outcomes and errors¶
A turn ends with a typed outcome, refusals included; only real failures throw.
When the server refuses a prompt, stops an answer, or a tool is not allowed, the turn still ends normally: its outcome says what happened, and its notices say what to tell the user. The session goes on, and the user can send the next message. See Outcomes for why refusals are outcomes.
The result of a turn¶
session.send returns an AgentResult, and session.stream yields it in the turn_completed event:
| Field | What it is |
|---|---|
turnId |
The turn's id. |
outcome |
How the turn ended. See below. |
text |
The answer's text. For an answer the server stopped, what was shown before it stopped. |
notices |
The notices to show, in order. |
usage |
The tokens the turn used, summed over its model calls: promptTokens, completionTokens, totalTokens. Undefined when the server reported none. |
Outcomes¶
outcome |
What happened |
|---|---|
completed |
The turn ended normally. |
prompt_blocked |
An input check refused the prompt. Nothing of it reached a model. |
response_blocked |
An output check stopped the answer. Tool calls in it never run. |
tool_denied |
At least one tool call was refused: by the tool mode, by the user, or by the server. The answer may still be complete. |
rate_limited |
A rate limit stopped the turn. |
budget_exceeded |
A budget is used up. |
When a turn has both a refusal and a denied tool call, prompt_blocked or response_blocked wins over
tool_denied.
A refused prompt is never sent to the model again. Of an answer the server stopped, only the question stays in the history that later requests carry. A turn that was rate limited or over budget is left out of later requests as well, so the user can send the message again.
Notices¶
A notice tells the user about a refusal or a limit. Notices arrive as notice events during the turn and are
collected in result.notices:
| Field | What it is |
|---|---|
outcome |
What it is about: prompt_blocked, response_blocked, tool_denied, rate_limited, budget_exceeded, round_limit, or another value the server sends. |
message |
What to tell the user. For a policy, this is the administrator's message. |
stage |
Where it happened: input, output or tool. Optional. |
category |
The kind of rule, for example mode when the tool mode hid a tool. Optional. |
decisionId |
The id auditors and support find the decision by. Optional. |
requestId |
The server's id for the request. Optional. |
retryAfterMs |
For a rate limit or budget: when to try again, if the server said. Optional. |
A round_limit notice means the agent stopped calling tools after the most model calls a turn may make
(useMaxRounds). The turn's outcome is still completed or tool_denied.
const result = await session.send("Delete pages 3 to 5.");
if (result.outcome !== "completed") {
for (const notice of result.notices) showNotice(notice.message, notice.decisionId);
}
Errors¶
Errors are thrown only when a turn cannot run or cannot finish: the server cannot be reached, the sign-in is gone,
the server answered with an error that is not a refusal, or the answer broke off. A turn that fails is recorded with
the status error and left out of later requests. stream yields no turn_completed for it.
All the SDK's errors derive from FadenError:
| Error | When |
|---|---|
FadenSignInRequiredError |
The agent's sign-in is gone: never made, expired or revoked. Sign in again. |
FadenUnreachableError |
No answer at all: the address is wrong, the server is down, or its certificate is not trusted (a browser does not say which). cause is the platform's error. |
FadenApiError |
The server answered with an error. status is the HTTP status, and code, type and param are the error's fields. |
FadenAuthenticationError |
A FadenApiError for 401. The client has already retried once with a renewed token. |
FadenPermissionError |
A FadenApiError for 403: the user may not do this, for example without a grant for the agent or when the agent is retired. |
FadenRateLimitedError |
A FadenApiError for 429. retryAfterMs says when to try again, if the server said; isBudget is true for a budget. In an agent's turn this becomes the outcome rate_limited or budget_exceeded instead. |
FadenFeatureNotAvailableError |
A FadenApiError for a route this server does not offer (404 with the code ee_required). |
FadenStreamError |
An answer that had started broke off, and the server said so. code is the server's code. |
FadenError |
Also thrown on its own, for example when the server did not answer within timeoutMs, or when the agent's profile requires a module the builder does not have. |
When you stop a turn with an AbortSignal, the signal's reason is thrown. isAbortError(error) from
@fadenstack/client is true for it, so you can tell a stop from a failure.
Mistakes in your code throw the platform's errors: a TypeError for an address that is not an https:// URL (unless
you allowed http://), and an Error for a second turn while one runs, an incomplete builder, or a session that is
not on the device.
import {
FadenApiError,
FadenSignInRequiredError,
FadenUnreachableError,
isAbortError,
} from "@fadenstack/client";
async function ask(text: string): Promise<void> {
try {
showResult(await session.send(text));
} catch (error) {
if (isAbortError(error)) return; // the user stopped it
if (error instanceof FadenSignInRequiredError) return showSignIn();
if (error instanceof FadenUnreachableError) return showOffline();
if (error instanceof FadenApiError) return showError(`${error.status}: ${error.message}`);
throw error;
}
}
Errors in your tools¶
An error thrown by your tool does not end the turn. The model gets Error: and the message as the tool's result, the
tool_completed event has the outcome error, and the model decides what to do next. The same goes for a tool the
model names that does not exist, and for arguments that fail the checks. See
Tools and approvals.