Skip to content

AI gateway: SOTA differentiation (verifiable ledger, policy plane, guardrail mesh, outcome-aware routing, predictive budgets, LLM-aware resilience)#538

Merged
rickcrawford merged 7 commits into
mainfrom
rickcrawford/wor-1539-sota-ai-gateway-differentiation
Jun 25, 2026
Merged

AI gateway: SOTA differentiation (verifiable ledger, policy plane, guardrail mesh, outcome-aware routing, predictive budgets, LLM-aware resilience)#538
rickcrawford merged 7 commits into
mainfrom
rickcrawford/wor-1539-sota-ai-gateway-differentiation

Conversation

@rickcrawford

Copy link
Copy Markdown
Contributor

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 ledger usage sink hash-chains every completed-call event (SHA-256 over prev_hash || seq || recorded_at || event) into a JSONL write-ahead log, optionally Ed25519-signs each entry, and dedups by request_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_aware strategy 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 feed ai.budget.*, the ledger captures policy set_sink_tag decisions, 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 under examples/, all validated by the example-compile sweep. Default-off everywhere; the full local gate (fmt, build, nextest, doctest, clippy, docs, config-schema) is green.

rickcrawford and others added 7 commits June 24, 2026 21:27
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
@rickcrawford rickcrawford merged commit 495863e into main Jun 25, 2026
5 checks passed
@rickcrawford rickcrawford deleted the rickcrawford/wor-1539-sota-ai-gateway-differentiation branch June 25, 2026 05:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant