Confounded incentives, closed loop¶
A synthetic observational marketplace: a demand confounder drives both the historical incentive
— the past policy raised incentives in busy periods — and the completions it is meant to buy. This
is confounded logging, not a randomised switchback, so the offline effect estimate is biased. The
plant is x' = a*x + b_true*u + noise, controlled over a receding horizon toward a target, and
the business cost is asymmetric: missing riders (churn) costs more than wasted budget.
Two controllers act on it in closed loop:
- certainty-equivalence trusts the biased estimate, under-actuates and undershoots the target;
- confounding-robust takes an assumed sensitivity level
Γ, turns it into a radius on the effect, and shifts the incentive to hedge the costlier error.
The radius calibration — the effect's half-width set to (Γ-1)/(Γ+1) times the estimate — is a
benchmark assumption, not a general consequence of the sensitivity model. The script sweeps the
true confounding strength, which neither controller knows.
uv run python scripts/run_dynamic_confounding_demo.py
What it printed when this site was built:
== confounding-robust control in CLOSED LOOP on a confounded dynamic plant ==
(x' = a*x + b_true*u + noise; churn 4x budget-waste; assumed MSM Gamma=2.5)
true confounding CE cost robust cost robust wins
0.0 3.011 3.672 premium
0.4 7.440 2.367 yes
0.8 13.213 2.428 yes
1.2 18.840 4.840 yes
worst-case cost CE 18.840 -> robust 4.840
savings at realistic confounding : 82%
premium when unconfounded : 4% of CE downside
The static minimax controller now runs in closed loop: pessimism bounds the
accumulated downside, wins beyond a confounding threshold, pays a bounded premium.
Pessimism is not free: near zero confounding the robust controller pays a premium, because there its
conservatism costs more than it saves.
The sensitivity level Γ explains the radius;
tutorial 5 runs the static version step by step.
Code: confounding_robust_tracking_benchmark in chc.regret, surfaced by
chc.sensitivity.