Background
Integration modules that apply Thompson Sampling client-side (for example, DXPR Builder's rl_dxpr_variant structural variant runtime) need a low-overhead decision endpoint. Routing decisions through a Drupal controller on every page view adds a full middleware stack (routing → access → CSRF → render → cache wrapper) just to read from rl_arm_data and call getThompsonScores().
The existing rl.php bare file already handles turn/reward beacons this way and bypasses the Drupal routing stack, booting the kernel once for a single service call. The same pattern makes sense for decisions.
Proposal
Add a third action, decide, to rl.php. Module-agnostic: the caller passes the experiment IDs it wants decisions for plus the arm count per experiment, and gets a JSON map of winning arm IDs back.
Request
action=decide
experiment_ids=rl_foo-abc,rl_foo-def,... (comma-separated)
arm_counts=2,3,... (parallel list of integers per experiment)
Response
{
"decisions": {
"rl_foo-abc": { "armId": "v1" },
"rl_foo-def": { "armId": "v0" }
}
}
Unknown, unregistered, or zero/one-arm experiments are omitted from the response so the caller can fall back to arm 0 (the default variant).
Implementation notes
- Branches off the existing validation block so the turn/reward hot path is unchanged.
- Uses the existing
rl.experiment_registry and rl.experiment_manager services the kernel already exposes.
- Returns
{"decisions":{}} on malformed input or when rl.experiment_manager is unavailable.
- Sends
Cache-Control: no-store, private, max-age=0 so downstream caches never serve stale Thompson decisions.
Consumer
The immediate consumer is DXPR Builder's rl_dxpr_variant runtime (js/rl-variant-runtime.js), which posts this payload on every page load to resolve structural variant containers to a single winning arm before reveal. Previously it used a Drupal-routed endpoint; moving to rl.php cuts per-request middleware cost on pages that often carry multiple containers.
Background
Integration modules that apply Thompson Sampling client-side (for example, DXPR Builder's
rl_dxpr_variantstructural variant runtime) need a low-overhead decision endpoint. Routing decisions through a Drupal controller on every page view adds a full middleware stack (routing → access → CSRF → render → cache wrapper) just to read fromrl_arm_dataand callgetThompsonScores().The existing
rl.phpbare file already handles turn/reward beacons this way and bypasses the Drupal routing stack, booting the kernel once for a single service call. The same pattern makes sense for decisions.Proposal
Add a third action,
decide, torl.php. Module-agnostic: the caller passes the experiment IDs it wants decisions for plus the arm count per experiment, and gets a JSON map of winning arm IDs back.Request
action=decideexperiment_ids=rl_foo-abc,rl_foo-def,...(comma-separated)arm_counts=2,3,...(parallel list of integers per experiment)Response
{ "decisions": { "rl_foo-abc": { "armId": "v1" }, "rl_foo-def": { "armId": "v0" } } }Unknown, unregistered, or zero/one-arm experiments are omitted from the response so the caller can fall back to arm 0 (the default variant).
Implementation notes
rl.experiment_registryandrl.experiment_managerservices the kernel already exposes.{"decisions":{}}on malformed input or whenrl.experiment_manageris unavailable.Cache-Control: no-store, private, max-age=0so downstream caches never serve stale Thompson decisions.Consumer
The immediate consumer is DXPR Builder's
rl_dxpr_variantruntime (js/rl-variant-runtime.js), which posts this payload on every page load to resolve structural variant containers to a single winning arm before reveal. Previously it used a Drupal-routed endpoint; moving torl.phpcuts per-request middleware cost on pages that often carry multiple containers.