AI gateway: SOTA differentiation (verifiable ledger, policy plane, guardrail mesh, outcome-aware routing, predictive budgets, LLM-aware resilience)#538
Merged
rickcrawford merged 7 commits intoJun 25, 2026
Conversation
Turn the completed-call usage stream into a tamper-evident, optionally Ed25519-signed ledger, and wire the usage-sink seam into the request path. - usage_ledger: a `LedgerSink` (UsageSink) that hash-chains each event (SHA-256 over prev_hash || seq || recorded_at || event) into a JSONL write-ahead log, optionally signs each entry with an Ed25519 seed, and dedups by request_id (exactly-once on replay). `verify_ledger` re-derives the chain and reports the first broken link. - usage_sink: LlmUsageEvent gains Deserialize + request_id; new `ledger` UsageSinkConfig variant. - wiring: handle_ai_proxy stashes the configured sinks on the request context; the end-of-request logging hook builds one LlmUsageEvent and hands it to each sink, off the latency path. Default off (no sinks). - CLI: `sbproxy ai ledger verify <path> [--signing-seed-hex H] [--format json]`, exit 0 when the chain (and signatures) verify, 1 otherwise. - docs/ai-usage-ledger.md + examples/ai-usage-ledger/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QGQpqaW4VgcPBkESbuZjK
One sandboxed CEL expression over the AI decision pipeline that fuses
guardrail verdicts, budget state, the routing candidate, and principal
context into a closed, typed action set, instead of those rules living in
four separate config blocks.
- ai_policy: AiPolicyConfig { expression, on_error }; an `ai.*` CEL
namespace (ai.surface/model/provider, ai.guardrails.*, ai.budget.*,
ai.tokens.*, ai.principal.*); a closed AiPolicyAction set (allow,
block, redact, route_to:<model>, set_sink_tag:<tag>, audit:<priority>);
CompiledAiPolicy compiles + evaluates, failing open to `on_error`.
- handler: ai_policy config block + lazy-compiled accessor (logged +
disabled on a compile error, so a policy bug never downs the gateway).
- dispatch: one ai_policy hook after guardrail evaluation, before
selection, applying block (403), redact (PII redactor), route_to
(rewrite the model), set_sink_tag (stamped on the usage/ledger record),
and audit (structured event). Default off.
- usage_sink: LlmUsageEvent gains an optional `tag` carried by
set_sink_tag.
- docs/ai-policy-cel.md + examples/ai-policy-cel/.
The guardrail-label and budget-fraction dimensions are populated by the
guardrail mesh and predictive budgets; until those land the policy keys
on principal / surface / model.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QGQpqaW4VgcPBkESbuZjK
Evolve the input guardrails from a serial block-on-any chain into a
cascade whose full verdict set is fused under a configurable rule.
- guardrails::mesh: GuardrailMeshConfig { block_threshold, redact_on_flag,
latency_budget_ms, cache, cache_capacity }; GuardrailMesh collects every
verdict (cheap detectors first, stopping past the latency budget),
caches by a content + guardrail-set hash, and fuses into a MeshDecision
(block on a quorum, or redact-and-continue below it).
- GuardrailsConfig gains an optional `mesh` block; `message_text` exposes
the cache-key text view.
- dispatch: when the mesh is configured the messages-path check runs the
mesh instead of serial block-on-any, short-circuits on a quorum block,
redacts on a sub-threshold flag, and stashes the label set on the
context. Body-aware (agent_alignment) and per-surface checks are
unchanged. Default off preserves the serial behavior.
- the label set feeds the AI policy plane's ai.guardrails.* namespace
(WOR-1542), so a CEL rule can fuse flagged_count.
- docs/ai-guardrail-mesh.md + examples/ai-guardrail-mesh/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QGQpqaW4VgcPBkESbuZjK
Feed the realized outcome of each request back into routing so the gateway self-optimizes cost per verified outcome, instead of deciding from list price or live latency alone. - routing_feedback: a process-wide FeedbackStore keyed by provider name, holding an EWMA of realized cost, success rate, refusal rate, and latency. Score = (ewma_cost / success_rate) * (1 + refusal_rate); lower is better, a never-succeeds provider scores infinity. - routing: new `outcome_aware` strategy that picks the lowest-scoring eligible provider, and round-robins while any candidate is still warming up (so a fresh deployment behaves like round-robin until it has data). Wired into both select_inner and the allowlist-filtered path. - dispatch: the end-of-request hook folds each completed call's outcome into the store, armed only when the origin routes `outcome_aware`. Provider-side refusals (content_filter / refusal) are distinguished from our own pre-dispatch blocks (which never reach a provider). - docs/ai-outcome-aware-routing.md + examples/ai-outcome-aware-routing/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QGQpqaW4VgcPBkESbuZjK
Degrade gracefully as a budget scope approaches its cap instead of a hard cliff at 100%. - budget: BudgetConfig gains an optional `soft_landing` block (warn_at 0.8, downgrade_at 0.95, downgrade_to). BudgetTracker::soft_landing evaluates the tightest active window fraction (max of token and cost ratios across limits) and returns None / Warn / Downgrade, yielding to the hard check_limits path at or above the cap. - dispatch: after the hard pre-flight clears, the soft-landing check warns past warn_at and rewrites the model to a cheaper one past downgrade_at (downgrade_to, else per-limit, else cheapest across providers), tagging the usage record / ledger with `budget_soft_landing`. Default off reproduces the hard-block behavior. - the live fraction is published to the AI policy plane's ai.budget.* namespace (WOR-1542), so a CEL rule can compose budget pressure with the other signals. - docs/ai-predictive-budget.md + examples/ai-predictive-budget/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QGQpqaW4VgcPBkESbuZjK
…1524)
Classify each upstream failure into an LLM-specific cause and apply a retry
count per error class, instead of treating every 5xx the same.
- failure_cause: FailureCause { Timeout, RateLimit, ContextWindowExceeded,
ContentPolicy, Auth, ServerError, BadRequest, Unknown } classified from
the status and (for the context-window / content-policy classes) the
response body, even a 200 carrying a refusal. fallback_trigger maps each
cause to the general / context-window / content-policy fallback list (the
WOR-1524 distinct-trigger surface).
- RetryPolicy: per-error-class retry counts (the LiteLLM retry_policy
surface) with should_retry(cause, attempt); a class without a count uses
its default retryability.
- resilience: AiResilienceConfig gains an optional retry_policy.
- dispatch: the failover loop retries on the default status set OR the
per-error policy decision, so a 429 can be retried while a refused or
malformed request is not. Default off keeps the status-code retry set.
- docs/ai-llm-aware-resilience.md + examples/ai-llm-aware-resilience/.
The classifier is the seam for compress-and-retry (context_compress) and
redact-and-retry, which build on the existing context-overflow and
idempotency machinery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QGQpqaW4VgcPBkESbuZjK
The `-D warnings` doc gate rejects public docs that link to private items. Replace the intra-doc links to private GENESIS_HASH, MIN_SAMPLES, and cache_key with plain code spans. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QGQpqaW4VgcPBkESbuZjK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Six SOTA AI-gateway capabilities that move sbproxy beyond LiteLLM parity: governance you can prove, resilience that understands LLMs, and a closed loop that self-optimizes spend. Every feature ships behind a backward-compatible, default-off config block, so the gateway's current behavior is unchanged unless an operator opts in. Each lands with unit tests, a flat docs page, and a runnable example.
What's in it
Verifiable usage ledger. A
ledgerusage sink hash-chains every completed-call event (SHA-256 overprev_hash || seq || recorded_at || event) into a JSONL write-ahead log, optionally Ed25519-signs each entry, and dedups byrequest_id(exactly-once on replay).sbproxy ai ledger verify <path>re-derives the chain and signatures and reports the first broken link. This also wires the usage-sink seam into the request path for the first time, so completed calls actually reach the configured sinks.Unified CEL policy plane. One sandboxed CEL expression over an
ai.*namespace (guardrail verdicts, budget state, routing candidate, principal context) that emits a closed, typed action set: block, redact, route_to, set_sink_tag, audit. Evaluated after guardrail/budget evaluation and before provider selection. Fails open on a policy error so a mistake can't take the gateway down.Guardrail mesh. Evolves the input guardrails from a serial block-on-any chain into a cascade that collects the full verdict set and fuses it under a configurable rule (block on a quorum, redact-and-continue below it), with a cheap-first latency cascade and a content-addressed verdict cache. The label set feeds the policy plane.
Closed-loop outcome-aware routing. A new
outcome_awarestrategy that scores providers by the realized cost-per-success fed back from completed requests (an EWMA of realized cost, success rate, refusal rate, latency), demoting providers whose refusal or error rate is climbing. Round-robins while providers warm up, so it's safe to enable with no other change.Predictive budgets with soft-landing. Degrades gracefully as a scope approaches its cap (warn, then downgrade to a cheaper model) before the hard block at 100%, instead of a cliff. The downgrade is tagged on the usage record and the live fraction is published to the policy plane.
LLM-aware resilience. Classifies each upstream failure into a typed cause (timeout, rate-limit, context-window overflow, content-policy refusal, auth, server error, bad request), maps each to its fallback list, and applies per-error-class retry counts in the failover loop, so a 429 can be retried while a refused or malformed request is not.
Composition
The features build on each other: the mesh feeds the policy plane's
ai.guardrails.*, predictive budgets feedai.budget.*, the ledger captures policyset_sink_tagdecisions, and the failure classifier is the seam for compress-and-retry and redact-and-retry.Tests and docs
Per-feature unit tests (ledger tamper/forgery/backpressure, policy action parsing and CEL evaluation, mesh fusion and cache, routing-feedback scoring, budget soft-landing thresholds, failure classification and retry policy). New docs pages under
docs/and runnable examples underexamples/, all validated by the example-compile sweep. Default-off everywhere; the full local gate (fmt, build, nextest, doctest, clippy, docs, config-schema) is green.