Outcomes and errors¶
This page covers how a turn ends: the AgentResult, its outcome and notices, and the exceptions the SDK throws when something really fails.
Refusals are outcomes¶
When the server refuses a prompt, stops an answer, denies a tool or applies a limit, the turn still ends normally. The refusal is part of the result, with a notice to show the user, and the session goes on. Only real failures, such as an unreachable server or a lost sign-in, throw.
Outcomes describes the model behind this on the server.
The result of a turn¶
SendAsync returns an AgentResult, and StreamAsync ends with a TurnCompleted event that carries it.
| Property | Meaning |
|---|---|
Outcome |
How the turn ended: a ChatOutcome value. |
Text |
The answer's text. For a stopped answer, the text streamed before it was stopped. |
Notices |
The GovernanceNotice values raised during the turn, in order. |
Notice |
The first notice, or null when there was none. |
Usage |
Token counts for the turn (UsageDetails from Microsoft.Extensions.AI), when the server reported them. |
TurnId |
The turn's id. |
using Faden.Governance;
var result = await session.SendAsync("Delete page 2.");
switch (result.Outcome)
{
case ChatOutcome.Completed:
ShowAnswer(result.Text);
break;
case ChatOutcome.PromptBlocked:
ShowNotice(result.Notice?.Message);
LetTheUserRephrase();
break;
default:
ShowAnswer(result.Text);
foreach (var notice in result.Notices)
{
ShowNotice(notice.Message);
}
break;
}
Outcome values¶
ChatOutcome is in the Faden.Governance namespace.
| Value | Meaning |
|---|---|
Completed |
The turn ended normally. |
PromptBlocked |
An input check refused the prompt. Nothing of it reached a model, and it is never sent again. The user can rephrase and send a new message. |
ResponseBlocked |
An output check stopped the answer. Tool calls in it are never run. Only the question stays in the session's history. |
ToolDenied |
At least one tool call was refused: by the tool mode, by the user, because an approval expired, or by the server. The answer may still be complete. |
RateLimited |
The server's rate limit applied. The notice's RetryAfter says when to try again, if the server said. |
BudgetExceeded |
A budget is used up. The notice's RetryAfter says when to try again, if the server said. Budgets are Planned. |
ApprovalRequired |
A call waits for the user's decision. An agent turn does not end with it, because the agent waits for the decision inside the turn. |
A refusal of the prompt or the answer decides the outcome before a denied tool does.
Reaching the limit of model calls per turn (UseMaxRounds) is not an outcome of its own: the turn ends with a round_limit notice and the outcome it had.
Governance notices¶
A GovernanceNotice is what the user is told about a refusal or a limit, and what an auditor finds it by. The stream raises each one as a NoticeRaised event when it happens; the result lists them all.
| Property | Meaning |
|---|---|
Outcome |
prompt_blocked, response_blocked, tool_denied, rate_limited, budget_exceeded or round_limit. |
Message |
The text to show. For a policy, it is the administrator's message. |
Stage |
Where it happened: input, output or tool. |
Category |
The policy's category, when there is one. |
DecisionId |
The id of the server's decision. Show it where users report problems; administrators find the decision by it. |
RequestId |
The id of the request, when there is one. |
RetryAfter |
For a rate limit or a budget: when to try again, if the server said. |
Show Message as it is. Do not replace it with your own text: the administrator wrote it for the user.
Exceptions¶
Failures throw. All SDK exceptions derive from FadenException, in the Faden namespace.
| Exception | When |
|---|---|
FadenException |
The server could not be reached, or did not answer within the client's Timeout. Also thrown by StartAsync when the agent requires a client module the builder does not have. |
FadenApiException |
The server answered with an error. Status is the HTTP status; Code, Type and Param are the error's fields, for example the code agent_not_granted. |
FadenAuthenticationException |
401: the token was refused, after the SDK retried once with a fresh token. |
FadenPermissionException |
403: the user may not do this, for example because there is no grant for the agent or the agent is retired. |
FadenRateLimitedException |
429 from a call outside an agent turn, such as the plain chat client or a resource. RetryAfter says when to try again; IsBudget is true for a budget. Inside a turn, a 429 becomes the RateLimited or BudgetExceeded outcome instead. |
FadenFeatureNotAvailableException |
404 with the code ee_required: this server does not offer the route. |
FadenStreamException |
An answer that had started broke off, and the server said so. Code is the server's error code. |
FadenSignInRequiredException |
The agent's sign-in is gone: never made, expired, or revoked. Sign in again. |
FadenApiException is the base of the four HTTP-specific exceptions after it.
The SDK also throws standard .NET exceptions:
| Exception | When |
|---|---|
OperationCanceledException |
You cancelled the turn or the request. |
InvalidOperationException |
A turn is already running in the session, the builder is incomplete, or agent.Profile is read before the agent started. |
KeyNotFoundException |
OpenSessionAsync was given an id that is not stored on this device. |
ArgumentException |
For example, an http:// server URL without AllowInsecureHttp, or AddTools on an object without tools. |
A turn that fails with an exception is recorded as failed in the session and is never sent to the model again. The session goes on with the next message.
try
{
var result = await session.SendAsync(text, cancellationToken);
Show(result);
}
catch (FadenSignInRequiredException)
{
ShowSignIn();
}
catch (FadenPermissionException e)
{
ShowError($"You may not use this agent: {e.Message}");
}
catch (FadenException e)
{
ShowError(e.Message);
}
Catch the more specific exceptions first: they all derive from FadenException.