Skip to content

feat(client): run shadow requests alongside the served variation - #193

Closed
hsadhvani wants to merge 1 commit into
mainfrom
aic-3514-shadow-requests-core
Closed

hsadhvani wants to merge 1 commit into
mainfrom
aic-3514-shadow-requests-core

Conversation

@hsadhvani

@hsadhvani hsadhvani commented Oct 29, 2026 •

Copy link
Copy Markdown

Why

Shadow requests let a team try a candidate variation on real traffic without serving it and compare it on the normal AI metrics. This replaces the closed #187, which shipped it as experimental behind a new core invocation hook. We are not shipping new features as experimental, and a judges-style core design is easier to read.

Summary

Adds src/shadows.ts, called directly from config().invoke() and .stream() the way judges are. config() evaluates with variationDetail, picks the shadows for the matched rule or the fallthrough, samples them per context, and runs each as a background invoke of the candidate variation with its tools and judges removed. Shadow events carry the primary run's runId plus isShadow, primaryVariationKey, shadowRequestId, ownerKind and ownerId, and the root span gets the three launchdarkly.shadow attributes. skipShadows: true returns the tasks instead, to run later with the new runShadow(task, handlers). shadowTimeoutMs defaults to 30000, and shutdown() drops running shadows. Follows TESTING.md §3.30 and A.22.

Testing approaches

  • Added tests for shadow selection by evaluation reason, expiry, disabled or missing variations and missing handlers.
  • Added tests for sticky sampling, the sampling rate and buckets that match the Python SDK.
  • Added tests that a failing or timed out shadow leaves the caller's response and events unchanged, and that a timeout is tracked once.
  • Added tests for skipShadows, runShadow, structuredClone of a task and identical events in both modes.
  • Added tests for the shadow event fields, the span attributes and the shutdown hook.
  • Updated the package export, shutdown hook and usage tests for runShadow.

Feature flags

N/A


Note

Overview
Adds shadow requests so config().invoke() and .stream() can run candidate AI Config variations on the same input in the background, emit normal $ld:ai:* metrics tagged with isShadow: true, and never change the served response, latency, or primary events.

config() now evaluates flags with optional variationDetail (when present) to learn which rule or fallthrough matched, reads shadowConfiguration from the raw variation, samples entries per context (Python-aligned bucketing), and starts each shadow after the primary handler begins. Shadows run as plain invokes with tools and judges stripped, share the primary runId, and honor shadowTimeoutMs (default 30s). skipShadows: true returns shadowTasks instead (including on stream done), mirroring deferred judges via the new public runShadow(task, handlers).

Telemetry updates extend TrackData and root spans with shadow metadata (primaryVariationKey, shadowRequestId, owner fields, launchdarkly.is_shadow via __ld_span), and extractVariation strips shadowConfiguration from parsed configs while exposing raw variation + evaluation reason. shutdown() registers a Shadow requests hook that drops in-flight background shadows.

Reviewed by Cursor Bugbot for commit 5194767. Bugbot is set up for automated code reviews on this repo. Configure here.

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