MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1
Degradation and cancellation
Provide an explicit degraded outcome without absorbing cancellation or inventing measured confidence.
Objectives
- Expected errors and degraded mode
- Response origin and uncalibrated confidence
- User cancellation and no implicit cloud fallback
The dependency can fail
Triage must remain understandable when the model is unavailable, slow or returns an unusable contract. EP011 proposed a resilient facade: business code calls a service that manages the model call. The current program retains this separation, replaces the filming menu with arguments and explicitly reports Origine, CauseBascule and Confiance. Default mode is a deliberate local heuristic without a model attempt.
One bounded attempt, then a defined outcome
ResilientAgentTriageService uses Polly to bound one model attempt to 60 seconds; --timeout deliberately waits then expires after two seconds. --unavailable raises a simulated transport outage, --invalid simulates invalid output and --live calls the configured model. There is no retry or automatic provider change. A model result must have category Access, Billing, Incident or General and a nonblank decision.
public async Task<TriageResult> ClasserAsync(string request, ScenarioDemo scenario, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (scenario == ScenarioDemo.FallbackDirect)
return fallback.Classer(request, "Explicit heuristic mode.");
var pipeline = new ResiliencePipelineBuilder<TriageResult>().AddTimeout(
scenario == ScenarioDemo.TimeoutSimule ? TimeSpan.FromSeconds(2) : timeout).Build();
try
{
var result = await pipeline.ExecuteAsync(async token =>
{
if (scenario == ScenarioDemo.TimeoutSimule) await Task.Delay(Timeout.InfiniteTimeSpan, token);
if (scenario == ScenarioDemo.OllamaIndisponibleSimule) throw new HttpRequestException("Simulated provider outage.");
if (scenario == ScenarioDemo.SortieInvalideSimulee) throw new InvalidOperationException("Simulated invalid output.");
var value = await model(request, token);
if (value.Categorie is not ("Access" or "Billing" or "Incident" or "General")
|| string.IsNullOrWhiteSpace(value.Decision))
throw new InvalidOperationException("Invalid triage output.");
return value with { Origine = "Model", CauseBascule = null, Confiance = null,
Explication = "Model classification; no calibrated confidence is available." };
}, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
return result;
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }
catch (Exception ex) when (ex is TimeoutRejectedException or OperationCanceledException
or HttpRequestException or InvalidOperationException or System.Text.Json.JsonException)
{
cancellationToken.ThrowIfCancellationRequested();
return fallback.Classer(request, ex.GetType().Name);
Source code · Demo350 · Folder
The filter catches TimeoutRejectedException, OperationCanceledException, HttpRequestException, InvalidOperationException and JsonException. It first rechecks the caller token. Other exception categories propagate. This list is the current policy; it cannot distinguish every programming failure that might surface as InvalidOperationException. No repeated-failure circuit breaker is configured, despite its presence in the earlier list of possibilities.
Do less and expose provenance
- Input + token → explicit heuristic mode or model attempt
- Valid model output → Origine=Model, Confiance=null
- Handled failure → keywords → Origine=Heuristic, visible cause, Confiance=null
- Caller cancellation → propagated exception, no replacement heuristic result
Fallback checks invoice/payment before password/login/account, then urgent/production; otherwise General. A request containing both “production” and “login” therefore becomes Access by rule ordering. The decision asks for human review without actually creating a request in an external system. This is limited keyword triage. Earlier capped-score wording is corrected: Confiance is null even when the model claims 0.99. Neither a deterministic heuristic nor a model-generated number is a calibrated probability.
public TriageResult Classer(string request, string reason)
{
var text = request.ToLowerInvariant();
var category = text.Contains("invoice") || text.Contains("payment") ? "Billing"
: text.Contains("password") || text.Contains("login") || text.Contains("account") ? "Access"
: text.Contains("urgent") || text.Contains("production") ? "Incident" : "General";
return new(category, "Create a support request for human review.", null,
"Degraded mode: keyword heuristic only; confidence has not been measured.", "Heuristic", reason);
Cancel without falling back
The service checks cancellation before explicit fallback, after a model result and in the failure filter. If network failure and cancellation occur together, cancellation must win. The console then returns 130 and states that no heuristic result was produced. Tests inject a model that cancels then fails, and an already-cancelled call. These are actual service calls with controlled transport; they do not establish that every provider immediately stops computing.
Make failure observable
dotnet run --project demos/Demo350_GracefulDegradation/Demo350.App.csproj -c Release --no-builddotnet run --project demos/Demo350_GracefulDegradation/Demo350.App.csproj -c Release --no-build --unavailabledotnet run --project demos/Demo350_GracefulDegradation/Demo350.App.csproj -c Release --no-build --invaliddotnet run --project demos/Demo350_GracefulDegradation/Demo350.App.csproj -c Release --no-build --timeoutCompare the four JSON outputs. All must show Heuristic and null Confiance, but causes differ: explicit mode, HttpRequestException, InvalidOperationException, TimeoutRejectedException. Exit code 0 here means a degraded triage result was returned; it does not have the same business meaning as Completed in Demo330. To cancel, run --timeout then Ctrl+C before two seconds, or run the cancellation/failure race tests below.
dotnet test tests/Maf.Control.Tests/Maf.Control.Tests.csproj -m:1 -c Release --no-build --no-restoreEvaluate the reduced contract
Availability of typed output does not guarantee correctness. Keywords produce false positives and miss other languages or phrasing. Live mode creates and disposes the model client per request; fallback remains local. Cancellation and timeouts are cooperative. The program adds no paid backup provider, retry, monetary control or durable business journal.
