Skip to content

feat: Spec getExperimentFlag, trackEvent and trackExposureEvent - #17

Open
gagantrivedi wants to merge 2 commits into
mainfrom
openhands/issue-13
Open

gagantrivedi wants to merge 2 commits into
mainfrom
openhands/issue-13

Conversation

@gagantrivedi

@gagantrivedi gagantrivedi commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Specs the three experimentation methods from the issue, as a new @events capability.

What the spec now covers

Feature file When step SDK method
features/get_experiment_flag.feature the application gets the experiment flag "{feature}" for identity "{identifier}" with traits: getExperimentFlag
features/track_event.feature the application tracks event "{event}" for identity "{identifier}" with value {value} and metadata: trackEvent
features/track_exposure_event.feature the application tracks an exposure of feature "{feature}" for identity "{identifier}" with value {value} and metadata: trackExposureEvent

getExperimentFlag gets two scenarios: it resolves the feature for the identity and
persists its traits, and it records no exposure for an identity outside a running
experiment — the behaviour that separates it from getIdentityFlags and the one the
reference implementations go out of their way to get right.

The events path

compose.yml now brings up the experimentation services alongside Flagsmith, mirroring
Flagsmith's own docker-compose.yml, so @events scenarios run against the real path
rather than a stand-in for it:

SDK ─► events (flagsmith/events-ingestion-api) ─► kafka ─► warehouse-delivery ─► clickhouse ─► runner
  • Flagsmith is pinned to 2.287.0, up from 2.276.0. That is the first release shipping
    experimentation/warehouses/clickhouse.py, the experimentation_environment_keys and
    experimentation_delivery_connections db views that ingestion and delivery read, and
    experimentation/clickhouse/events.sql. EXPERIMENTATION_ENABLED is on, and
    WAREHOUSE_CREDENTIALS_SECRET is shared with warehouse-delivery.
  • Given the SDK sends events to Flagsmith connects a ClickHouse warehouse to the
    scenario's environment (POST /environments/<key>/warehouse-connections/) and asks for a
    client with events enabled. Both are needed: events are off by default in every SDK, and
    the events API only accepts an environment that has a warehouse connection.
  • The runner reads delivered events out of ClickHouse (runner/sdk_spec/warehouse.py),
    keyed by the environment's API key — the same key Flagsmith reads them by in
    WarehouseConnectionViewSet.events.

Because the events client is a separate step, Create the SDK client with events enabled,
an SDK that has not implemented it reports it missing and the scenarios behave like any
other new step: xfail in the compatibility matrix, red in SDK repositories until they add
it. Every harness's marker regex was broadened from exactly Create the SDK client to any
marker starting with it; existing steps files are unaffected, and harness/rust and
harness/swift already infer "this step creates a client" from the absence of a client
parameter, so both creation steps work there unchanged.

What a reviewer should check

  • The ClickHouse column mapping and the waits. An event crosses an SDK's send timer,
    Kafka, and a delivery batch, so Flagsmith has received the following events: polls for
    60s and Flagsmith has received no events waits 30s. warehouse-delivery runs with
    BATCH_MAX_WAIT_MS: 1000 to keep that bounded. The negative can only produce false
    passes, never false failures.
  • value is asserted as text because all three reference SDKs stringify it, and
    metadata is matched on the entries the table names, because SDKs add their own and the
    ingestion server lifts sdk_version into its own column.
  • clickhouse-init fetches events.sql from the raw URL pinned to v2.287.0, the way
    Flagsmith's compose does from main. Vendoring the schema here instead is an easy swap.
  • EVENTS_SDK_URL defaults to http://events:8080, the compose service, rather than
    being derived from another URL. That keeps run-spec.yml unchanged, so SDK repositories
    pick events up by bumping their pinned ref. Running a harness on the host needs
    EVENTS_SDK_URL=http://localhost:8080, which the README says.

Additive for SDKs, so this is a minor version: new scenarios, a new tag, new When steps
and a new client-creating step, with no existing step reworded. The pinned Flagsmith
version moves, as a spec release's pin is expected to.

Testing

Docker does not run in the environment this was written in, so the stack could not be
brought up locally. The runner was exercised end to end against stubs shaped like the real
services — ClickHouse's HTTP interface returning JSONEachRow with traits/metadata as
JSON text, and an ingestion endpoint that rejects an environment with no warehouse
connection. All six scenarios pass; mutating the fake SDK to send a wrong value, or to send
an exposure outside an experiment, fails the right scenario with a readable message;
removing Create the SDK client with events enabled leaves the four @events scenarios
xfail and the @core ones passing. The stubs are not part of this change.

CI on this PR confirms the stack comes up — all eight containers healthy, kafka-init and
clickhouse-init completed — and that the existing scenarios still pass against Flagsmith
2.287.0. It cannot exercise the @events assertions, because every SDK still xfails the
events client. The first real run of them will be whichever SDK implements that step first.

Closes #13

This pull request was opened by an AI agent (OpenHands).

Adds the `@events` capability: three feature files covering the SDK methods
that power experimentation, and the pieces the runner needs to check them.

SDKs send tracking and exposure events to Flagsmith's events API, which the
open-source Flagsmith image does not serve, so `events-api/` stands in for it
and records what an SDK sends. Events are off by default in every SDK and its
URL is not derived from the Flagsmith API URL, so scenarios that need them say
`Given the SDK sends events to Flagsmith` and the runner creates the client
with `Create the SDK client with events enabled` instead. SDKs that have not
implemented that step report it missing, as for any other new step.

Every harness now recognises a step marker starting with `Create the SDK
client`, rather than only that exact text. Existing steps files are unaffected.

Closes #13
Comment thread events-api/events_api.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We're bringing up live Flagsmith in the spec testing workflow. I expect these endpoints to not be mocked.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed, and fixed in 3342bac — events-api/ is deleted.

I had looked for an events endpoint on the Flagsmith image and concluded there wasn't one. I'd missed that the ingestion service is a separate published image, and that your docker-compose.yml wires up the whole path. compose.yml now mirrors it: kafka + kafka-init, clickhouse + clickhouse-init, flagsmith/events-ingestion-api:0.5.1 as events, and flagsmith/warehouse-delivery:0.4.0. The SDK posts to the real ingestion server and the runner reads the delivered rows out of ClickHouse.

Three things that came with it, which are worth a look:

  • Pinned Flagsmith 2.276.0 → 2.287.0. 2.276.0 has neither experimentation/warehouses/clickhouse.py nor the experimentation_environment_keys / experimentation_delivery_connections db views the ingestion server and delivery read. 2.287.0 is the first release carrying all of them plus experimentation/clickhouse/events.sql. EXPERIMENTATION_ENABLED and WAREHOUSE_CREDENTIALS_SECRET are set on the flagsmith service, the latter shared with warehouse-delivery.
  • Given the SDK sends events to Flagsmith now connects a warehouse to the scenario's environment via POST /environments/<key>/warehouse-connections/. experimentation_environment_keys only lists keys whose environment has a connection, so without it the ingestion server rejects the batch.
  • clickhouse-init fetches events.sql from the raw URL pinned to v2.287.0, the way your compose does from main. Say the word if you'd rather vendor the schema into this repo.

One honest caveat on verification: Docker does not run in the environment I work in, so I could not bring this stack up. I exercised the runner end to end against stubs shaped like the real thing — ClickHouse's HTTP interface returning JSONEachRow with traits/metadata as JSON text, an ingestion endpoint that rejects an environment with no warehouse connection — and the six scenarios pass, with deliberate mutations failing the right ones. What CI proves on this PR is that the stack comes up and the existing scenarios still pass; it cannot exercise the assertions, because every SDK still xfails Create the SDK client with events enabled. The first real run of these scenarios will be whichever SDK implements that step first, so please treat the timings (60s arrival, 30s settle) and the ClickHouse column mapping as the parts most worth a second pair of eyes.

— OpenHands agent

Comment thread features/get_experiment_flag.feature Outdated
Comment on lines +3 to +6
An SDK resolves a feature for an identity and records the exposure, in one call, so that
an application can show a variant and have it measured. Only an identity enrolled in a
running experiment is exposed: every other evaluation is left out of the data, so that
an experiment's results count the identities it actually reached.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
An SDK resolves a feature for an identity and records the exposure, in one call, so that
an application can show a variant and have it measured. Only an identity enrolled in a
running experiment is exposed: every other evaluation is left out of the data, so that
an experiment's results count the identities it actually reached.
An SDK resolves a feature for an identity and records the exposure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied in 3342bac.

I took the same cut to track_exposure_event.feature, which you didn't comment on but reads the same way, so the three @events files are consistent: An SDK records which variant of a feature an identity was shown, as a "$flag_exposure" event. Happy to revert that one if you meant the edit narrowly.

— OpenHands agent

Comment thread features/track_event.feature Outdated
Comment on lines +3 to +5
An SDK sends an application's tracking events to Flagsmith, so that an experiment can
measure what the identities it exposed went on to do. Values reach Flagsmith as text,
whatever type the application tracked them as.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
An SDK sends an application's tracking events to Flagsmith, so that an experiment can
measure what the identities it exposed went on to do. Values reach Flagsmith as text,
whatever type the application tracked them as.
An SDK sends an application's tracking events to Flagsmith.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied in 3342bac.

— OpenHands agent

Comment thread runner/sdk_spec/events_api.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as https://github.com/Flagsmith/sdk-spec/pull/17/changes#r4340296883 — ideally, we'd like the harness to use the live API we offer via docker compose.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 3342bac. runner/sdk_spec/events_api.py is gone, replaced by runner/sdk_spec/warehouse.py, which queries the ClickHouse that compose.yml connects to each @events environment:

SELECT event, feature_name, identifier, value, traits, metadata
FROM events
WHERE environment_key = {environment_key:String}
ORDER BY timestamp, event

So the runner now reads the far end of the real delivery path — ingestion → Kafka → warehouse delivery → warehouse — rather than a recording of what the SDK posted.

Two details that changed with it:

  • Events are read by the environment's API key, not the server-side key the SDK authenticates with. That matches how Flagsmith reads them itself: WarehouseConnectionViewSet.events passes self.kwargs["environment_api_key"] as environment_key.
  • traits and metadata arrive as JSON text and are decoded before comparison. metadata is still matched on just the entries the table names, which now also covers the ingestion server lifting sdk_version into its own column.

The waits grew to match the longer path: 60s for an event to arrive, 30s before concluding one was never sent. warehouse-delivery runs with BATCH_MAX_WAIT_MS: 1000 to keep that bounded.

— OpenHands agent

Replaces the events API stand-in with the services Flagsmith ships, mirroring
its own docker-compose.yml: the SDK posts to `flagsmith/events-ingestion-api`,
which queues to Kafka, from where `flagsmith/warehouse-delivery` inserts into
the environment's warehouse. The spec provisions a ClickHouse as that
warehouse and the runner reads the delivered events out of it.

`Given the SDK sends events to Flagsmith` now also connects the warehouse to
the scenario's environment, which is what makes the events API accept it.
Events are keyed by the environment's API key, as Flagsmith keys them.

Pins Flagsmith 2.287.0, the first release shipping the ClickHouse warehouse
and the db views the ingestion server reads, and turns experimentation on.

Also trims the three feature descriptions to a sentence, as reviewed.
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.

Support getExperimentFlag, trackEvent and trackExposure methods

3 participants