Skip to content

ProviderFailurePolicy

NexusLabs.Needlr.AgentFramework

NexusLabs.Needlr.AgentFramework.Providers

ProviderFailurePolicy Class

Declarative failure-handling rule applied by TieredProviderSelector<TQuery,TResult> when a provider throws an exception during ExecuteAsync(TQuery, CancellationToken).

public sealed record ProviderFailurePolicy : System.IEquatable<NexusLabs.Needlr.AgentFramework.Providers.ProviderFailurePolicy>

Inheritance System.Object 🡒 ProviderFailurePolicy

Implements System.IEquatable<ProviderFailurePolicy>

Example

// Treat ApiAuthException like ProviderUnavailableException, but skip for 5 minutes
// and emit a structured log.
var policy = new ProviderFailurePolicy(
    Match: ex => ex is ApiAuthException,
    SkipDuration: TimeSpan.FromMinutes(5),
    OnHit: ctx =>
    {
        logger.LogWarning(ctx.Exception, "Provider {Provider} skipped until {Until}",
            ctx.ProviderName, ctx.SkipUntil);
        return ValueTask.CompletedTask;
    });

var options = TieredProviderSelectorOptions.Default with
{
    FailurePolicies = [.. TieredProviderSelectorOptions.Default.FailurePolicies, policy],
};

Remarks

Policies are evaluated in order against the thrown exception (first match wins). The first policy whose Match predicate returns true causes the selector to: 1. Add a per-provider attempt diagnostic to the chain and continue to the next provider. 2. Mark the provider as skipped for the duration in SkipDuration (if non-null), so subsequent calls bypass it without an attempt until the skip window elapses. 3. Invoke the OnHit callback (if non-null) with a ProviderFailureContext describing the failure.

If no policy matches the thrown exception, the selector re-throws the exception unchanged. The default policy in Default matches ProviderUnavailableException with no skip and no callback, preserving the framework's historical fall-through behaviour.

Cancellation is not subject to policy matching: the selector skips policy evaluation entirely when the active System.Threading.CancellationToken has been cancelled, so cancelled calls always propagate System.OperationCanceledException directly to the caller.

Callback exceptions propagate. If OnHit throws, the selector still releases quota for the failed attempt (the release happens in a finally block) and the callback's exception escapes ExecuteAsync(TQuery, CancellationToken). Subsequent providers are not attempted for that call.

Constructors

ProviderFailurePolicy(Predicate<Exception>, Nullable<TimeSpan>, Func<ProviderFailureContext,ValueTask>) Constructor

Declarative failure-handling rule applied by TieredProviderSelector<TQuery,TResult> when a provider throws an exception during ExecuteAsync(TQuery, CancellationToken).

public ProviderFailurePolicy(System.Predicate<System.Exception> Match, System.Nullable<System.TimeSpan> SkipDuration=null, System.Func<NexusLabs.Needlr.AgentFramework.Providers.ProviderFailureContext,System.Threading.Tasks.ValueTask>? OnHit=null);

Parameters

Match System.Predicate<System.Exception>

Predicate evaluated against each thrown exception to determine whether this policy applies. The first matching policy in FailurePolicies wins.

SkipDuration System.Nullable<System.TimeSpan>

Optional duration the failing provider should be skipped before being retried on subsequent calls. - null — no cross-call skip; the provider is retried on the next call. - A finite System.TimeSpan — the provider is skipped until now + SkipDuration. - IndefiniteSkip (System.TimeSpan.MaxValue) — the provider is skipped until process restart (resolves to System.DateTimeOffset.MaxValue; the selector clamps the addition to avoid overflow).

OnHit System.Func<ProviderFailureContext,System.Threading.Tasks.ValueTask>

Optional async callback invoked after the policy match is recorded but before fall-through to the next provider. Receives a ProviderFailureContext describing the failed provider, the exception, and the resulting skip-until timestamp (if any).

Example

// Treat ApiAuthException like ProviderUnavailableException, but skip for 5 minutes
// and emit a structured log.
var policy = new ProviderFailurePolicy(
    Match: ex => ex is ApiAuthException,
    SkipDuration: TimeSpan.FromMinutes(5),
    OnHit: ctx =>
    {
        logger.LogWarning(ctx.Exception, "Provider {Provider} skipped until {Until}",
            ctx.ProviderName, ctx.SkipUntil);
        return ValueTask.CompletedTask;
    });

var options = TieredProviderSelectorOptions.Default with
{
    FailurePolicies = [.. TieredProviderSelectorOptions.Default.FailurePolicies, policy],
};

Remarks

Policies are evaluated in order against the thrown exception (first match wins). The first policy whose Match predicate returns true causes the selector to: 1. Add a per-provider attempt diagnostic to the chain and continue to the next provider. 2. Mark the provider as skipped for the duration in SkipDuration (if non-null), so subsequent calls bypass it without an attempt until the skip window elapses. 3. Invoke the OnHit callback (if non-null) with a ProviderFailureContext describing the failure.

If no policy matches the thrown exception, the selector re-throws the exception unchanged. The default policy in Default matches ProviderUnavailableException with no skip and no callback, preserving the framework's historical fall-through behaviour.

Cancellation is not subject to policy matching: the selector skips policy evaluation entirely when the active System.Threading.CancellationToken has been cancelled, so cancelled calls always propagate System.OperationCanceledException directly to the caller.

Callback exceptions propagate. If OnHit throws, the selector still releases quota for the failed attempt (the release happens in a finally block) and the callback's exception escapes ExecuteAsync(TQuery, CancellationToken). Subsequent providers are not attempted for that call.

Properties

ProviderFailurePolicy.IndefiniteSkip Property

Sentinel value for SkipDuration meaning "skip indefinitely (until the host process restarts)." Resolves to System.DateTimeOffset.MaxValue in the selector's skip cache; the selector clamps the now + SkipDuration addition so passing this value will not overflow.

public static System.TimeSpan IndefiniteSkip { get; }

Property Value

System.TimeSpan

ProviderFailurePolicy.Match Property

Predicate evaluated against each thrown exception to determine whether this policy applies. The first matching policy in FailurePolicies wins.

public System.Predicate<System.Exception> Match { get; init; }

Property Value

System.Predicate<System.Exception>

ProviderFailurePolicy.OnHit Property

Optional async callback invoked after the policy match is recorded but before fall-through to the next provider. Receives a ProviderFailureContext describing the failed provider, the exception, and the resulting skip-until timestamp (if any).

public System.Func<NexusLabs.Needlr.AgentFramework.Providers.ProviderFailureContext,System.Threading.Tasks.ValueTask>? OnHit { get; init; }

Property Value

System.Func<ProviderFailureContext,System.Threading.Tasks.ValueTask>

ProviderFailurePolicy.SkipDuration Property

Optional duration the failing provider should be skipped before being retried on subsequent calls. - null — no cross-call skip; the provider is retried on the next call. - A finite System.TimeSpan — the provider is skipped until now + SkipDuration. - IndefiniteSkip (System.TimeSpan.MaxValue) — the provider is skipped until process restart (resolves to System.DateTimeOffset.MaxValue; the selector clamps the addition to avoid overflow).

public System.Nullable<System.TimeSpan> SkipDuration { get; init; }

Property Value

System.Nullable<System.TimeSpan>