Skip to content

RemoteEval for Sync and Async Clients#118

Open
madhuchavva wants to merge 6 commits into
mainfrom
feat/remote-eval
Open

RemoteEval for Sync and Async Clients#118
madhuchavva wants to merge 6 commits into
mainfrom
feat/remote-eval

Conversation

@madhuchavva
Copy link
Copy Markdown
Contributor

@madhuchavva madhuchavva commented May 27, 2026

Add remote evaluation support

Summary

Adds remoteEval / remote_eval to both the sync GrowthBook class and the async GrowthBookClient, matching the JS SDK's behavior. The SDK POSTs the user's targeting context to {api_host}/api/eval/{client_key} on a self-hosted GrowthBook proxy/edge and runs its existing local eval_feature over the user-filtered response.

What's included

  • Options — 3 new fields with defaults: remote_eval, cache_key_attributes, remote_eval_cache_size. Zero impact on existing callers.
  • FeatureRepositoryPOST /api/eval/{client_key} plumbing; payload-aware cache key (matches JS: {clientKey}||{json({ca, fv, url})}; forcedFeatures deliberately excluded).
  • Sync GrowthBook — 5 validation guards (missing client_key, decryption_key, sticky_bucket_service, stale_while_revalidate, *.growthbook.io host); new set_url / set_forced_variations setters that trigger a blocking refetch when remote eval is on; SSE branching; skip global callback registration in remote-eval mode to prevent singleton cross-pollution between instances.
  • Async GrowthBookClient — per-UserContext POST with an LRU-bounded cache (default 1000), inflight coalescing so concurrent identical evals issue one POST, preload_remote_eval() to warm the cache at request boundaries, SSE → cache-flush invalidation.
  • rule.tracks — fires trackingCallback for each deferred experiment-tracking event the proxy attaches to force rules (matches JS in packages/sdk-js/src/core.ts).
  • Closes Feature/remote eval #63 (replaces stale PR).

Test plan

  • 34 new tests in tests/test_remote_eval.py — POST URL/body shape, all 5 validation guards, setter-triggered refetches, cache hit/miss on attribute change, cache_key_attributes narrowing, forcedFeatures excluded from cache key, singleton no-cross-pollution, SSE flush, missing/empty savedGroups, async LRU eviction, inflight coalescing, rule.tracks firing (including malformed-entry resilience)

Out of scope (deliberately)

  • UserContext.forced_features on the async client — always sends []; add when a customer asks
  • Encrypted remote-eval responses — JS SDK doesn't support this either
  • Local AbstractStickyBucketService with remote eval — proxy handles sticky bucketing server-side; rejected at construction
  • Running the benchmark harness against main — separate follow-up once the bench commit lands on main

@madhuchavva madhuchavva marked this pull request as ready for review May 27, 2026 01:43
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