Repairing a Frozen Visuomotor Policy With Counterfactual Regret Labels & Flow Matching
You train a behavioral cloning policy. It works on the demos. You deploy it, and at some point a perturbation pushes it off track. The policy keeps producing actions that look reasonable. They’re just slightly off the manifold of successful executions, and there’s no restoring force.
The well-documented failure mode of pure BC (behavioral cloning) on manipulation is degenerate oscillation: the agent approaches the object, nudges it, backs off, re-approaches from the same angle, and loops. It never discovers it needs to go around.
The standard fixes have costs. Fine-tuning invalidates your safety case. Adding a planner requires a world model, and if your world model is wrong, the planner optimizes against a hallucination. Test-time adaptation reacts to the observation but ignores what the expert would’ve done.
My approach: don’t retrain the policy, don’t replace it, repair it in flight. Train a small recovery head, offline, that learns when the frozen policy is about to fail and how to push its action chunk back toward expert behavior. The training signal is counterfactual regret labels. The transport mechanism is flow matching in DCT-compressed action space.
Glossary (click to expand)
| Term | Meaning |
|---|---|
| BC | Behavioral Cloning. Learning a policy by supervised regression on expert demonstration data. |
| MPPI | Model Predictive Path Integral control. A sampling-based planner that rolls out candidate actions through a dynamics model and picks the lowest-cost one. |
| CFR | Counterfactual Regret Minimization. A label that says "would the repair have done better than the baseline from this exact state?" |
| DCT | Discrete Cosine Transform. A fixed mathematical basis that compresses an action chunk into a few low-frequency coefficients. |
| PACE | Policy Anchor-Conditioned Execution. Executing the full recovered action chunk before re-planning, instead of replanning every step. |
| DoF | Degrees of Freedom. The dimensionality of the action space (PushT has 2, LIBERO has 7). |
| SAE | Sparse Autoencoder. A neural net that learns a sparse, interpretable representation of latent activations. Used here for drift detection. |
| Action chunk | A sequence of H future actions predicted by the policy in one forward pass (H=8 in this work). |
| Anchor bank | A fixed collection of expert action chunks retrieved by similarity to the current state. |
| Gate | A learned binary classifier that decides whether to intervene on the frozen policy's output. |
| Flow matching | Training a velocity field to transport one distribution toward another. Here: BC chunk toward expert anchor chunk. |
The repair manifold
Think of the policy as producing actions on a manifold in action space: the set of chunks that lead to successful task completion. Expert demos live on this manifold. A well-trained BC policy produces actions near it. Under perturbation, the outputs slide off and there’s no restoring force.
A repair layer takes an off-manifold chunk & transports it back, conditioned on what the expert would’ve done from a similar state. Two questions, separable: when to intervene (a gate), and where to go (a decoder). Get either wrong and you either fail to repair or break working plans.
The gate: when to intervene
The gate is the whole game. A repair layer that fires too often is worse than one that fires too rarely. Every intervention is a chance to break a working plan.
My first gate label was “any candidate has positive regret AND baseline fails.” That conflates “there exists a better action” with “the baseline would fail if left alone.” The gate fires whenever a better option exists, even when BC+MPPI (model predictive path integral control) would’ve succeeded. You get regressions on working plans.
The fix: relabel with paired counterfactual rollouts from saved states. Run both the baseline and the repair from the same frozen state. The gate label is TRUE only if the baseline fails AND the repair succeeds.
The result
Three-seed validation on hard perturbations, 50 episodes per seed:
| Seed | BC+MPPI strict | CFR strict | Strict net | Legacy net |
|---|---|---|---|---|
| 42 | 0.08 | 0.08 | 0 (2 fix, 2 reg) | 0 |
| 123 | 0.02 | 0.06 | +2 (2 fix, 0 reg) | -0.02 |
| 999 | 0.02 | 0.00 | -1 (0 fix, 1 reg) | -0.02 |
| Total | 0.04 | 0.05 | +1 (4 fix, 3 reg) | -0.04 |
Mean net per seed is +0.33, std 1.53. The first 30-episode run gave “+3 fixes, 0 regressions.” Scaling to 3 seeds × 50 episodes showed the variance was larger than the effect. The signal is real but small.
The decoder: two approaches
Approach A: CFR (counterfactual regret) selection. MPPI proposes 256 chunks, a learned dynamics model rolls each forward, keep top-16. A transformer selects one. The world model is in the inference loop.
Approach B: flow-matching transport. Skip MPPI. Take the BC chunk, transport it toward an expert anchor via a learned velocity field in DCT (discrete cosine transform) space. No world model at inference.
The world model is the ceiling on A. I compared two dynamics models on multi-step rollout accuracy:
| Model | h=10 mean px | h=50 mean px | h=50 p90 px |
|---|---|---|---|
| 3-layer MLP | 12.8 | 46.2 | 80.4 |
| 6-layer residual v1 | 18.7 | 93.1 | 161.1 |
| 6-layer residual v3 | 18.5 | 69.1 | 107.0 |
The 3-layer MLP is the best I have, and it’s still 46px off at 50 steps. And the bigger residual is worse: it overfits short-horizon corrections and amplifies them over long rollouts. For world models, capacity is a liability. This pushed me toward Approach B.
Why DCT space
The flow head transports the BC chunk toward the anchor in DCT-edge coefficient space. The 8-step chunk keeps the first 2 timesteps raw (the edge: what you’re about to execute) and projects the rest onto 4 DCT-II basis coefficients. 8 raw values become 6 numbers.
Lower-dimensional: the velocity field has less to predict. Smooth by construction: you can’t produce a jerky chunk from 4 low-frequency coefficients. Exact edge: the immediate next action is kept raw, not smeared by DCT. When you have a strong prior, use it directly.
Both the gate and the flow head start conservative: negative bias init on the gate, zero-init on the velocity head. At initialization the repair does nothing. The model has to learn to deviate. A repair that never fires is safe; one that fires too often is dangerous.
LIBERO results
PushT is 2D. LIBERO is 7-DoF manipulation from images. If the method is real, it survives the transfer. The portable pieces: anchor bank, DCT-edge encoding, flow velocity net, intervention gate. The one adaptation: flow only transports the continuous 6-DoF chunk. The gripper stays fixed from BC.
100-episode evaluation on 3 LIBERO_SPATIAL tasks, PACE (exec_horizon=8, meaning the full recovered chunk is executed before replanning), old 6-epoch BC baseline:
| Task | BC dense | Flow dense | Flow minus BC |
|---|---|---|---|
| 0 | 134.59 | 142.27 | +7.68 |
| 1 | 110.58 | 113.89 | +3.31 |
| 2 | 115.26 | 122.64 | +7.38 |
Flow beats BC on all three tasks by +6.12 avg dense reward. The CaB gate alone adds almost nothing. Flow matching is the key driver.
Success rate is 0/100 for all methods. The BC baseline was only trained for 6 epochs (loss -4.49), so it never solves the task. Dense reward shows flow improves over BC, but neither crosses the success threshold yet. A 50-epoch BC retrain (loss ~-20.75) is in progress. These numbers show flow helps a weak policy move in the right direction; the next step is seeing if that converts to success rate with a trained baseline.
What didn’t work
Original gate label. “Positive regret AND baseline fails” produced over-intervention and regressions. The paired counterfactual label fixed it conceptually, but the effect size is still small.
More data with the wrong label. Scaled 50 to 200 episodes. Offline F1 dropped 0.90 to 0.79. More data compounds label noise.
Bigger dynamics models are worse. The 6-layer residual gets 93px error at h=50 vs 46px for the 3-layer MLP. Capacity is a liability for world models.
Always-on repair. A repair layer with no gate breaks working episodes. The conservative gate is load-bearing.
The frozen policy knows when it’s failing
A raw linear probe on the BC’s 512-dim latent space hits F1 0.98 for failure detection. A sparse-autoencoder (SAE) drift detector gets F1 0.36 to 0.49. The raw latent beats the SAE by a factor of two.
The frozen policy’s own representations already know when it’s failing. You just have to read it out. The SAE direction was a detour.
Status
| Component | Status | Notes |
|---|---|---|
| PushT CFR | Built, evaluated | +1 net strict over 180 episodes |
| Dynamics bottleneck | Diagnosed | 3-layer MLP is the ceiling; deeper is worse |
| Flow repair (LIBERO) | Trained, evaluated | +6.12 dense reward over BC, 0/100 success (BC undertrained) |
| Drift detection | Built, evaluated | F1 0.98 |
| BC retraining (50 ep) | In progress | Crashed before checkpoint save |
| Multi-seed | Not done | All results single-seed (42) |
The LIBERO +6.12 dense reward is with a 6-epoch BC. The PushT +1 net strict is small. I’m sharing these because they’re directional and worth chasing. The next step is a trained BC baseline and multi-seed runs. If the signal survives those, there’s a paper. If not, the diagnosis (world model is the ceiling, gate label is the lever, DCT flow is the right representation) is still the contribution.
Sharing work in progress. The next experiment might shift the picture. That’s the point.