Skip to content

Send agentless Feature Flags exposures directly to EVP - #12195

Open
leoromanovsky wants to merge 4 commits into
masterfrom
agent/java-direct-exposure-egress
Open

Send agentless Feature Flags exposures directly to EVP#12195
leoromanovsky wants to merge 4 commits into
masterfrom
agent/java-direct-exposure-egress

Conversation

@leoromanovsky

@leoromanovsky leoromanovsky commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Motivation

Java can evaluate Feature Flags from the managed CDN without a local Datadog receiver. The current writer drops exposures when no local EVP proxy exists. This loses experimentation data in the default agentless deployment.

flowchart TD
    E[Agentless flag evaluation] --> I[GET /info on the standard tracer Agent URL]
    I --> R{EVP proxy available?}
    R -->|Yes| L[Send through the Agent or serverless-init]
    R -->|No| D[Send directly to EVP]
    L --> P[Event Platform]
    D --> P
Loading

Feature Flags use normal Agent discovery. Direct delivery requires an API key. A definitive local rejection can also select direct delivery.

Changes and Decisions

This PR adds support for direct EVP exposure delivery in agentless Feature Flags deployments without a compatible local EVP route.

  • Keep Remote Configuration exposure delivery on the local EVP proxy.
  • In agentless mode, prefer a compatible local EVP proxy from the Datadog Agent or serverless-init.
  • Use authenticated direct EVP intake when no compatible local route exists and an API key is available.
  • Switch from local delivery to direct delivery after connection refusal or HTTP 403, 404, or 405.
  • Do not replay after timeouts, resets, rate limits, or server errors. This prevents possible duplicate exposures.
  • Prepare exposure delivery before application provider activation. This avoids losing the first exposure while local route discovery completes.
  • Keep the CDN poller lazy. It starts only after application provider activation.
  • Preserve the existing exposure payload, batching, and deduplication behavior.
  • Keep traces and OTLP metrics on the Datadog Agent or serverless-init. This change does not send them directly.

Validation

The shared exposure contract is enabled in DataDog/system-tests#7494.

  • Tested dd-trace-java 9992388df3 with the Spring Boot weblog.
  • The Datadog Agent, direct, and serverless-init:1.9.13 routes each passed.
  • Each route evaluated the same flag five times and produced one deduplicated exposure.
  • The direct route used no local receiver.
  • The unused direct or sidecar route received no exposure.

@leoromanovsky leoromanovsky added tag: ai generated Largely based on code generated by an AI or LLM comp: openfeature OpenFeature type: feature Enhancements and improvements labels Aug 12, 2026
@datadog-official

datadog-official Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 68.27%
Overall Coverage: 59.96% (+1.99%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 3716594 | Docs | Datadog PR Page | Give us feedback!

Copilot AI 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.

Pull request overview

This PR updates Feature Flagging exposure delivery to support agentless deployments by selecting an appropriate transport (local EVP proxy when available, with safe direct-intake fallback when necessary), and adds supporting test coverage across feature-flagging and communication modules.

Changes:

  • Introduce an exposure-specific backend selection layer (ExposureBackendApiFactory) and an agentless fallback transport (AgentlessExposureBackendApi) to switch from local EVP proxy to direct intake on definitive rejections.
  • Start exposure delivery earlier in agentless mode (before application provider activation) while keeping the configuration source lazy until activation.
  • Improve HTTP failure observability for EVP proxy calls by surfacing status codes via HttpResponseException, and add targeted tests.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/ExposureWriterTests.java Adds coverage for agentless direct-intake exposure delivery (API key header + direct endpoint).
products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/ExposureBackendApiFactoryTest.java New unit tests for backend selection rules (remote-config vs agentless, local vs direct, invalid direct URL).
products/feature-flagging/feature-flagging-lib/src/test/java/com/datadog/featureflag/AgentlessExposureBackendApiTest.java New unit tests validating fallback and replay behavior for definitive vs ambiguous failures.
products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureWriterImpl.java Switches exposure writer to use ExposureBackendApiFactory rather than the generic BackendApiFactory selection.
products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureBackendApiFactory.java New transport selection logic for exposures, including agentless direct-intake fallback (when API key exists).
products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/AgentlessExposureBackendApi.java New BackendApi wrapper that switches from local EVP to direct intake on definitive rejections.
products/feature-flagging/feature-flagging-lib/build.gradle.kts Adds config module dependency for compile-time constants (and test dependency for new tests).
products/feature-flagging/feature-flagging-agent/src/test/java/com/datadog/featureflag/FeatureFlaggingSystemTest.java Updates system test expectations for earlier exposure writer initialization in agentless mode.
products/feature-flagging/feature-flagging-agent/src/main/java/com/datadog/featureflag/FeatureFlaggingSystem.java Initializes exposure writer pre-activation for agentless mode; adds state inspection helpers used by tests.
communication/src/test/java/datadog/communication/EvpProxyApiTest.java New test ensuring non-2xx responses surface status codes and preserve expected request shape.
communication/src/main/java/datadog/communication/HttpResponseException.java New exception type carrying HTTP status codes for failed requests.
communication/src/main/java/datadog/communication/EvpProxyApi.java Throws HttpResponseException (instead of generic IOException) on non-success responses.
communication/src/main/java/datadog/communication/BackendApiFactory.java Refactors backend creation into separate local-EVP vs direct-intake factory methods.
Suppressed comments (1)

products/feature-flagging/feature-flagging-lib/src/main/java/com/datadog/featureflag/ExposureWriterImpl.java:143

  • ExposureBackendApiFactory#create() is explicitly nullable when no local EVP proxy or direct intake credentials are available, but the serializer thread treats this as an exceptional state and throws an uncaught IllegalArgumentException. This will surface as a background-thread crash even though the factory already logs that delivery is disabled. Prefer a clean shutdown (log + return) after errorCallback.run() and use an error message that matches the broader set of failure reasons (not only EVP proxy).
      evp = backendApiFactory.create();
      if (evp == null) {
        errorCallback.run();
        throw new IllegalArgumentException("EVP Proxy not available");
      }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@dd-octo-sts

dd-octo-sts Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.90 s 14.74 s [+0.2%; +1.9%] (maybe worse)
startup:insecure-bank:tracing:Agent 13.66 s 13.78 s [-1.8%; +0.0%] (no difference)
startup:petclinic:appsec:Agent 16.81 s 16.81 s [-0.9%; +1.0%] (no difference)
startup:petclinic:iast:Agent 16.86 s 16.94 s [-1.4%; +0.4%] (no difference)
startup:petclinic:profiling:Agent 16.85 s 16.65 s [+0.1%; +2.3%] (maybe worse)
startup:petclinic:sca:Agent 16.87 s 16.53 s [+1.3%; +2.8%] (significantly worse)
startup:petclinic:tracing:Agent 16.06 s 16.23 s [-1.8%; -0.3%] (maybe better)

Commit: 37165945 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@leoromanovsky
leoromanovsky marked this pull request as ready for review August 12, 2026 01:01
@leoromanovsky
leoromanovsky requested review from a team as code owners August 12, 2026 01:01
@leoromanovsky
leoromanovsky requested review from btthomas, mhlidd, sameerank, sarahchen6 and typotter and removed request for a team and typotter August 12, 2026 01:01

@datadog-official datadog-official Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Datadog Autotest: FAIL

Early local-EVP discovery can permanently disable exposure delivery without an API key: the writer exits before provider activation, yet activation reuses that dead writer even after the local route becomes available.

📊 Validated against 10 scenarios · Open Bits AI session

🤖 Datadog Autotest · Commit 3716594 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

Comment on lines +89 to +94
if (exposureWriter == null) {
final ExposureWriter newExposureWriter = new ExposureWriterImpl(sco, config);
initialize(configService, newExposureWriter);
} else {
initializeConfigurationSource(configService, exposureWriter);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Restart a writer that failed before activation

Affected agentless processes silently lose every feature-flag exposure for their lifetime.

Assertion details
  • Input: Agentless Feature Flags without an API key; local EVP discovery initially misses the proxy, the writer exits, and the proxy becomes available before provider activation.
  • Expected: Provider activation should replace a terminated early writer and rerun route selection, allowing a newly available local EVP proxy to receive exposures.
  • Actual: The early writer terminated after the initial capability miss. Provider activation then reused that terminated writer, performing no second capability check.
Suggested change
if (exposureWriter == null) {
final ExposureWriter newExposureWriter = new ExposureWriterImpl(sco, config);
initialize(configService, newExposureWriter);
} else {
initializeConfigurationSource(configService, exposureWriter);
}
if (exposureWriter == null
|| (exposureWriter instanceof ExposureWriterImpl
&& !((ExposureWriterImpl) exposureWriter).isSerializerThreadAlive())) {
if (exposureWriter != null) {
exposureWriter.close();
}
final ExposureWriter newExposureWriter = new ExposureWriterImpl(sco, config);
initialize(configService, newExposureWriter);
} else {
initializeConfigurationSource(configService, exposureWriter);
}

Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: openfeature OpenFeature tag: ai generated Largely based on code generated by an AI or LLM type: feature Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants