Clarification Chat
June 28, 2026 · View on GitHub
Overview
A clarification chat is the protocol's way for a server to ask the agent a follow-up question before it decides a deferred request, rather than approving or rejecting outright (§Clarification Chat). Two places use it:
- Token exchange. When the agent exchanges a resource token, the PS may need more detail before granting the scope — it defers and asks a clarifying question.
- Mission proposal and governance. When the agent proposes a mission or requests permission, the PS may ask the agent to refine the intent before approving.
The agent answers the question, replaces its request with a narrower one, or withdraws — and the exchange continues until the server decides or the round limit is reached.
The question: ClarificationRequirement
When a server needs clarification it returns AAuth-Requirement: requirement=clarification and carries the question in the response body. The SDK
projects that into a typed ClarificationRequirement:
namespace AAuth.Headers;
public sealed record ClarificationRequirement(
string Clarification, // the Markdown question — UNTRUSTED, sanitize before display
int? TimeoutSeconds = null, // optional deadline to respond by
IReadOnlyList<string>? Options = null); // optional discrete choices for a closed question
Warning
The Clarification value is untrusted input from the server. Sanitize it
before rendering it to a user (§Clarification Required).
The answer: ClarificationResponse
The agent replies with one of three actions (§Agent Response to Clarification):
namespace AAuth.Agent;
public sealed class ClarificationResponse
{
public enum Kind { Respond, Update, Cancel }
public static ClarificationResponse Respond(string markdown); // answer the question
public static ClarificationResponse Update(string resourceToken, string? justification = null); // replace the request
public static ClarificationResponse Cancel(); // withdraw
}
Respondposts a Markdown answer and resumes the exchange.Updatereplaces the original request with a new resource token (for example a reduced scope) plus an optional justification.Cancelwithdraws the request entirely.
Driving the chat: ClarificationExchange
For manual control over a deferred pending URL, use ClarificationExchange. It
tracks the round count and enforces a maximum (§Clarification Limits).
namespace AAuth.Agent;
public sealed class ClarificationExchange
{
public const int DefaultMaxRounds = 5;
public ClarificationExchange(HttpClient signedClient, Uri pendingUrl, int maxRounds = DefaultMaxRounds);
public int MaxRounds { get; }
public int Rounds { get; }
public Task ApplyAsync(ClarificationResponse response, CancellationToken ct = default);
public Task RespondAsync(string markdown, CancellationToken ct = default);
public Task UpdateRequestAsync(string resourceToken, string? justification = null, CancellationToken ct = default);
public Task CancelAsync(CancellationToken ct = default);
}
The supplied HttpClient must be wired with the agent's AAuthSigningHandler so
every POST/DELETE to the pending URL is signed.
var exchange = new ClarificationExchange(signedClient, pendingUrl);
await exchange.ApplyAsync(ClarificationResponse.Respond(
"The export is for the user's own tax records, read-only."));
// Cancelling throws AAuthClarificationCancelledException after withdrawing.
// Exceeding MaxRounds throws AAuthClarificationLimitException(MaxRounds).
Both Respond and Update consume a round; Cancel issues a DELETE and throws
AAuthClarificationCancelledException. Once Rounds reaches MaxRounds the next
attempt throws AAuthClarificationLimitException.
Automatic handling during token exchange
You rarely need to drive the exchange by hand. The token-exchange request exposes
a callback the SDK invokes whenever the PS asks for clarification, looping until
the PS decides or MaxClarificationRounds is hit.
var request = new TokenExchangeRequest
{
MaxClarificationRounds = ClarificationExchange.DefaultMaxRounds,
OnClarificationRequired = async (requirement, ct) =>
{
// requirement.Clarification is untrusted — sanitize before display.
string question = Sanitize(requirement.Clarification);
if (requirement.Options is { Count: > 0 } options)
{
string choice = await AskUserToPick(question, options);
return ClarificationResponse.Respond(choice);
}
string answer = await AskUser(question);
return ClarificationResponse.Respond(answer);
},
};
var authToken = await exchangeClient.ExchangeAsync(personServer, resourceToken, request);
Automatic handling during governance
The same pattern applies to the governance clients. Supply
OnClarificationRequired (and optionally MaxClarificationRounds) on
GovernanceOptions when proposing a mission or requesting permission. The
governance client is bound to its Person Server, so no per-call PS URL is
needed:
var session = await governance.ProposeMissionAsync(
new MissionProposal("Plan my weekend trip to Seattle."),
new GovernanceOptions
{
MaxClarificationRounds = 3,
OnClarificationRequired = async (requirement, ct) =>
ClarificationResponse.Respond(await AskUser(Sanitize(requirement.Clarification))),
});
When the callback is null and the server asks for clarification, the request
fails rather than blocking.
Server side: emitting a clarification
A Person Server built on MapAAuthPersonServer
gets the server half of the protocol for free. For an out-of-scope mission
token request the helper calls the IMissionTokenConsent seam; returning
Clarify makes the SDK emit the requirement=clarification 202, accept the
agent's clarification_response / updated resource_token / DELETE on the
pending URL, record each round in the mission log, and re-consult the seam:
public sealed class LlmMissionConsent : IMissionTokenConsent
{
public async Task<MissionTokenConsentDecision> ReviewAsync(
MissionTokenConsentContext ctx, CancellationToken ct = default)
{
// First pass with no answers yet → ask the agent to justify.
if (ctx.ClarificationHistory.Count == 0)
return MissionTokenConsentDecision.Clarify("Why does this mission need this scope?");
// The agent answered — let the policy (here, an LLM) decide.
return await _reviewer.IsJustified(ctx, ctx.ClarificationHistory)
? MissionTokenConsentDecision.Grant()
: MissionTokenConsentDecision.Deny("not justified by the mission");
}
}
The SDK owns the wire protocol and the mission log; the seam owns how the question is formed and the answer judged (a consent screen, a scripted test, or an LLM reviewer). See Mission Governance (Server).
Further reading
- Mission Governance Clients — where governance clarification fits
- Mission Call Chain sample — a clarification round during an out-of-mission elevated-scope exchange, followed by a mission-forwarded call chain
- Deferred Consent — the broader deferred-response lifecycle
- Error Handling —
AAuthClarificationCancelledException,AAuthClarificationLimitException - Missions — the mission model