Skip to content

feat: add evaluate_flags() API for single-call flag evaluation#104

Closed
dmarticus wants to merge 1 commit intomainfrom
posthog-code/evaluate-flags-api
Closed

feat: add evaluate_flags() API for single-call flag evaluation#104
dmarticus wants to merge 1 commit intomainfrom
posthog-code/evaluate-flags-api

Conversation

@dmarticus
Copy link
Copy Markdown
Contributor

Summary

Phase 1 of the Server SDK Feature Flag Evaluations RFC for posthog-rs, mirroring posthog-python#539 and posthog-js#3476.

A single call to evaluate_flags(distinct_id, options) hits /flags?v=2 once and returns a FeatureFlagEvaluations snapshot. The snapshot:

  • Resolves is_enabled(key) / get_flag(key) locally and emits a deduplicated $feature_flag_called event with the full metadata the experiments pipeline needs ($feature_flag_id, $feature_flag_version, $feature_flag_reason, $feature_flag_request_id).
  • Treats get_flag_payload(key) as event-free by design.
  • Offers only_accessed() and only([keys]) filter helpers with misuse warnings (silenceable via the new feature_flags_log_warnings client option).
  • Short-circuits when distinct_id is empty so accesses on the resulting snapshot never leak events with empty IDs.

Plus Event::with_flags(&snapshot) lets a captured event inherit $feature/<key> and $active_feature_flags from the snapshot — no second /flags round-trip.

Pre-flight Q&A

  1. Existing single-flag dedup helper? None — posthog-rs's get_feature_flag / is_feature_enabled don't fire $feature_flag_called today. The new dedup cache (Mutex<HashMap<String, HashSet<String>>>, 50k entries, full reset on overflow to match JS) is built fresh and used only by the snapshot. Retrofitting the single-flag methods onto the same helper is a Phase 2 follow-up so existing users don't see a silent emission change.
  2. Rich /flags response handler? FeatureFlagsResponse::V2 already carries FlagDetail { reason, metadata }. This PR additionally adds request_id (#[serde(rename = "requestId", default)]) so the snapshot can populate $feature_flag_request_id.
  3. Local evaluation poller? Yes — FlagPoller / AsyncFlagPoller. The new evaluate_flags runs the local pass first and tags hits with locally_evaluated=true / reason: "Evaluated locally". The poller doesn't expose a "definitions loaded at" timestamp yet; mirroring Python, the snapshot threads the field through but it stays None until a follow-up plumbs it from the poller.
  4. Capture option-passing convention? Events are built with the Event builder. flags is exposed as event.with_flags(&snapshot) rather than altering capture's signature — keeps capture(event) unchanged and reads naturally.
  5. Module path? src/feature_flag_evaluations.rs, mirroring Python's posthog/feature_flag_evaluations.py.

Phase 2 follow-ups (not in this PR)

  • Retrofit get_feature_flag / is_feature_enabled / get_feature_flag_payload onto the new dedup helper so they fire \$feature_flag_called (intentionally deferred — silent behavior change for current users).
  • Plumb flag_definitions_loaded_at from the local poller into the snapshot.
  • Consider deprecation warnings on the single-flag methods once they share the dedup helper.

Test plan

  • cargo test --all-features (async + integration) — 92 tests pass
  • cargo test --no-default-features (blocking variant) — 88 tests pass
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo fmt --all
  • Example builds: cargo build --example evaluate_flags --all-features
  • Manual smoke against US ingestion using examples/evaluate_flags.rs

Created with PostHog Code

Adds a snapshot-based feature flag API mirroring posthog-python (#539) and
posthog-node (#3476). One call to evaluate_flags(distinct_id, options) reaches
/flags?v=2 once and returns a FeatureFlagEvaluations cache that:

- Resolves is_enabled / get_flag locally with full metadata propagation
  ($feature_flag_id, $feature_flag_version, $feature_flag_reason,
  $feature_flag_request_id) on the deduplicated $feature_flag_called event
- Treats get_flag_payload as event-free
- Offers only_accessed() / only([keys]) filter helpers with warnings on
  misuse, gated by a new feature_flags_log_warnings client option
- Short-circuits empty-distinct_id snapshots so accesses never emit events

Also adds Event::with_flags(&snapshot) so a captured event inherits
\$feature/<key> and \$active_feature_flags from the snapshot without an extra
/flags request.

Both blocking and async clients implement the host trait that owns the
per-distinct_id dedup cache (cap 50_000, full reset on overflow to match the
JS SDK).

The existing get_feature_flag / is_feature_enabled methods stay silent — a
Phase 2 follow-up will retrofit them onto the same dedup helper.

Generated-By: PostHog Code
Task-Id: 2b101877-6890-43d1-8dbd-306433cd9d25
@dmarticus dmarticus closed this Apr 27, 2026
@dmarticus dmarticus deleted the posthog-code/evaluate-flags-api branch April 27, 2026 20:05
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