Skip to content

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

Closed
hsadhvani wants to merge 5 commits into
mainfrom
aic-3514-shadow-requests
Closed

hsadhvani wants to merge 5 commits into
mainfrom
aic-3514-shadow-requests

Conversation

@hsadhvani

@hsadhvani hsadhvani commented Oct 28, 2026 •

Copy link
Copy Markdown

Why

LaunchDarkly can now attach shadow requests to an AI Config's targeting rules and fallthrough, and delivers them inside the served variation as shadowConfiguration (AIC-3429). The SDK needs to run them: while serving variation A, also run variation B in the background on the same input and report B on the normal AI metrics, so customers can compare models on real traffic. Draft until the Shadow Testing UI ships.

Summary

Shadow requests run from core, the same way judges do. A new shadows.py picks the shadow requests for the matched rule or fallthrough, skips ended ones, samples sticky per context, strips tools and judges, and runs them in the background with a 30 second timeout. config().invoke() and .stream() call it directly.

  • config(skip_shadows=True) returns shadow_tasks on the response instead of running them, and run_shadow(task, handlers) runs one later, mirroring skip_judges and run_judge.
  • config(shadow_timeout_s=...) sets the timeout.
  • config() evaluates with variation_detail() when the client has it, to read the evaluation reason. Graphs, judges and evaluations still call variation().
  • Shadow events add isShadow, primaryVariationKey, shadowRequestId, ownerKind, ownerId and the real run's runId. The shadow's root span carries launchdarkly.is_shadow, the shadow request id and the primary variation key.
  • execute_and_track / execute_and_stream take optional run_id, extra_track_data and span_attributes. This shows in the root API report.

Nothing to turn on in the SDK: LaunchDarkly only sends shadow requests while enable-shadow-requests is on and someone has set one up.

Testing approaches

  • Added tests for selecting shadows by reason, end time, missing variations and missing handlers
  • Added tests for sticky sampling, its rate and parity with the TypeScript bucket
  • Added tests that a failing or timed out shadow never reaches the caller and is tracked once
  • Added tests for skip_shadows, picklable tasks and matching events between background and deferred runs
  • Added tests for shadow event fields, unchanged real events and shadow span attributes

Feature flags

N/A. LaunchDarkly only sends shadow requests while enable-shadow-requests is on.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@hsadhvani hsadhvani changed the title feat(shadow-requests): run Shadow Requests from the experimental entry point feat(client): run shadow requests alongside the served variation Oct 29, 2026
@hsadhvani hsadhvani closed this Oct 29, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4c5efa1. Configure here.

timeout_s=self._shadow_timeout_s,
)
if not self._skip_shadows:
start_shadows(shadow_tasks, resolved_handler_list)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shadow setup errors fail live calls

Medium Severity

Building and starting shadows now runs inline on invoke() and stream() with no outer guard. The old invocation-hook runner swallowed hook failures so the real call still ran. build_shadow_tasks only catches _entries_for, so a later parse, dict(context), or start_shadows error now fails the customer's live request even though shadow work is supposed to be best-effort.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4c5efa1. Configure here.

jeffdupont added a commit that referenced this pull request Oct 30, 2026
Steps 1 and 2 of
[AIC-3672](https://launchdarkly.atlassian.net/browse/AIC-3672). The JS
half is AIC-3673.

## What this does

- **`release-as: 1.0.0`** on all 14 packages in
`release-please-config.json`. Without it, release-please keeps bumping
0.x minors, so the next release PR would ship `launchdarkly-ai-server`
0.6.0 instead of 1.0.0.
- **Internal pins.** Every package that depends on
`launchdarkly-ai-server` now requires `>=1.0.0,<2`. release-please
doesn't update these for Python. #218 (merged) set them to `>=0.6,<2` so
either release order resolves; this tightens them to the 1.0 line.
- **`uv.lock`** needs no change: uv records workspace dependencies
without a version specifier. `uv lock --check` passes.

`changelog-sections` is left alone. #235 removes the evaluations
Experimental rows when it promotes Evaluations from Code to core.

## Merge this last

Merge this after the PRs below. Anything merged before the release PR
(#231) merges ships in 1.0.0, and a `!` change merged after 1.0 forces
2.0.

- [x] #218 fix(AIC-3598)!: packaging and public types (merged 29 Oct)
- [x] #235 feat(evaluations)!: promote Evaluations from Code to core
(merged 29 Oct). After it merges, nothing in
`launchdarkly_ai_server.experimental` is public.
- [x] #237 fix(claude-agents)!: honour max_tokens and disable thinking
by default (merged 29 Oct)
- [ ] #136 feat(AIC-3439): wildcard handlers accept only named providers
(approved; conflicts with main, needs a merge of main)
- [x] #234 fix(AIC-3568)!: give the barrel core's `__all__` (merged 29
Oct)
- [ ] #246 fix(AIC-3592): make native LangGraph hand-offs complete (spec
launchdarkly/ai-sdks-monorepo#118)
- [ ] #248 fix(AIC-3573): never send a NativeTool to the model as a
function tool (spec launchdarkly/ai-sdks-monorepo#116)
- [ ] #240 feat(client): add result variant and value to the
feature_flag span event (approved, mergeable)
- [ ] #251 fix(AIC-3716): build OpenAI native graph node tools with
FunctionTool (review required; touches the same loop as #248)

#241 (Shadow Requests) is closed; 1.0 ships with nothing experimental.

## After merging

1. Confirm the regenerated #231 lists 1.0.0 for all 14 packages, then
merge it.
2. Install each package from PyPI into an empty venv and import its
public entry points (AIC-3672 step 5).
3. Open a PR removing `release-as` straight away, or every later release
PR proposes 1.0.0 again.

## Checks

- `release-please-config.json` parses as JSON, with `release-as: 1.0.0`
on all 14 packages. The first push wrote the lines with literal
backslashes; fixed in d720896.

- `make test`: 3,711 passed, 18 skipped (before #175 and #236 merged; CI
is the current check) (with the parent checkout's `.env` LD URIs
blanked; see AIC-3593)
- `uv lock --check` passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)


[AIC-3672]:
https://launchdarkly.atlassian.net/browse/AIC-3672?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ




<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Prepares the **1.0.0 GA** cut for all 14 Python packages by teaching
release-please to release **1.0.0** instead of another **0.x** bump.
> 
> Adds **`release-as: "1.0.0"`** on every entry in
`release-please-config.json` (core `launchdarkly-ai-server`, barrel, and
all handler packages).
> 
> Tightens **internal PyPI pins** from `launchdarkly-ai-server>=0.6,<2`
to **`>=1.0.0,<2`** in each handler `pyproject.toml` and in
`packages/ai` (including **`otel`** / **`ld`** extras). Published wheels
will require the 1.0 server line once those versions ship.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ae9f2cc. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: andrewklatzke <aklatzke@launchdarkly.com>
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