Frontier Questions and Research Roadmaps › Experiment 3: Memory Ablation Under Changing Context

Michael BrenndoerferAugust 11, 202616 min read
World Models HandbookAll chapters
Part 12 of 12Reliable World Models
Chapter 72Frontier Questions and Research Roadmaps › Experiment 3: Memory Ablation Under Changing Context
Reading section 7 of 11 · Experiment 3: Memory Ablation Under Changing Context · 16 minSection 7 of 11 · 16 min

Book contents

Section 7 of 11 · 16 min read

Frontier Questions and Research Roadmaps › Experiment 3: Memory Ablation Under Changing ContextOne section · Sections in this chapter7 / 11 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

The third experiment compares specified cue-retention and belief-update rules, then changes the goal or corrupts the cue. It is an information-policy comparison, not a pure storage-capacity ablation: the memoryless baseline deliberately ignores the initial cue and later rewards, whereas the updating agent uses both.

To study the benefit of historical information, use a task where the current observation omits relevant information. A fully observed task can still test memory overhead, distraction or harm, but cannot establish that history is necessary. Here the current position omits the hidden goal. The initial cue and later rewards provide information about that goal, and the flip condition changes whether retained cue information remains valid.

A Partially Observed Goal Task

The agent controls a scalar position xtx_t with dynamics

xt+1=clip ⁣(0.9 xt+0.8 at+ηt,  [−5,5]),at∈[−1,1]x_{t+1} = \mathrm{clip}\!\left( 0.9\, x_t + 0.8\, a_t + \eta_t, \; [-5, 5] \right), \qquad a_t \in [-1, 1]

where:

  • xtx_t: scalar position at step tt
  • at∈[−1,1]a_t \in [-1, 1]: control action at step tt
  • 0.90.9: position decay factor
  • 0.80.8: control gain
  • ηt\eta_t: process noise at step tt
  • clip(⋅,[−5,5])\mathrm{clip}(\cdot, [-5, 5]): projection onto the interval [−5,5][-5, 5]

The dynamics coefficients are shared across episodes. What differs is the goal: x⋆=+1x^\star=+1 in mode 0 and x⋆=−1x^\star=-1 in mode 1. The non-oracle agents do not observe the mode directly. They can receive an initial cue and subsequent noisy rewards:

  • A cue at t=0t = 0. A noisy reading of the goal, cue=x⋆+ν\text{cue} = x^\star + \nu with ν∼N(0,0.72)\nu \sim \mathcal{N}(0, 0.7^2). For the non-oracle agents, this is the only initial observation carrying goal information; they also observe position.
  • A reward signal after each transition. rt=−(xt+1−x⋆)2+ξtr_t = -(x_{t+1} - x^\star)^2 + \xi_t with ξt∼N(0,1.52)\xi_t \sim \mathcal{N}(0, 1.5^2). This is slow and noisy, but it is the only channel that can reveal a change.

The cue supplies evidence before the first action, but is available only at the start and can be corrupted. A cue-only agent that freezes its belief cannot correct it without another signal or update. Rewards arrive after transitions and can support later correction when their goal-conditioned likelihoods differ. At xt+1=0x_{t+1}=0 those likelihoods coincide, so that reward alone does not distinguish the two goals. The updating agent combines cue and reward evidence under its assumed model; correction speed depends on the observed evidence and chosen actions.

Part II, Chapter 2's belief-state machinery supplies a useful representation: a distribution btb_t over the hidden mode, updated from observations under a specified model. Its adequacy depends on that model and the decision rule. A belief representation alone does not guarantee robustness to noisy cues or changing goals; this experiment tests one particular filter and controller.

Four agents:

  • Memoryless. Ignores the cue entirely and starts from a uniform prior p(m=1)=0.5p(m=1) = 0.5. It uses the prior-mean goal, which is 00.
  • Cue memory, frozen. Uses the cue to set its belief at t=0t=0 and never updates it.
  • Cue memory with Bayesian update. Uses the cue, then updates the belief from each observed reward, with a hazard rate of 0.050.05 representing the prior probability that the context changed on any step.
  • Oracle. Knows the mode.

All four share a certainty-equivalent controller: given p=P(m=1)p=P(m=1), the expected goal is x^⋆=1−2p\hat{x}^\star=1-2p, and a=(x^⋆−0.9x)/0.8a=(\hat{x}^\star-0.9x)/0.8 is clipped to [−1,1][-1,1]. The map from belief to action is fixed; access to information and belief updates differ. This minimizes the one-step expected squared error under the stated prior and zero-mean process noise when clipping is inactive, but it is not a Bayes-optimal multi-step controller. Actions can influence how informative future rewards are, and this controller does not optimize that information-gathering trade-off.

In[29]:
Code
GAIN = 0.8
DECAY3 = 0.9
T_STEPS = 20
FLIP_AT = 10
GOALS = (1.0, -1.0)
SIG_PROC = 0.05
SIG_REW = 1.5
SIG_CUE = 0.7
HAZARD = 0.05
X_LIMIT = 5.0


def env_step(x, a, proc_noise):
    return float(np.clip(DECAY3 * x + GAIN * a + proc_noise, -X_LIMIT, X_LIMIT))


def prior_from_cue(cue):
    lik = [np.exp(-0.5 * ((cue - g) / SIG_CUE) ** 2) for g in GOALS]
    return float(lik[1] / (lik[0] + lik[1]))


def belief_update(p, x_next, r_obs):
    lik = [
        np.exp(-0.5 * ((r_obs + (x_next - g) ** 2) / SIG_REW) ** 2)
        for g in GOALS
    ]
    post = p * lik[1] / (p * lik[1] + (1.0 - p) * lik[0] + 1e-300)
    return float((1.0 - HAZARD) * post + HAZARD * (1.0 - post))


def belief_action(x, p):
    x_star_hat = (1.0 - p) * GOALS[0] + p * GOALS[1]
    return float(np.clip((x_star_hat - DECAY3 * x) / GAIN, -1.0, 1.0))

The reward likelihood uses the observed next state and the hypothetical mean reward −(xt+1−g)2-(x_{t+1}-g)^2. Rewards can distinguish the goals when their predicted means differ; at xt+1=0x_{t+1}=0 those means coincide, so this reward alone is uninformative about the mode. belief_update first conditions on the current reward, then applies the symmetric flip hazard. Its returned value is the prior for the next decision, not the current-time posterior. The plotted trace stores that next-decision prior.

The assumed hazard is the probability of a symmetric mode flip between steps. For hazards between zero and one half, applying it moves the reward-conditioned belief toward equal probabilities. This can prevent extreme commitment under anticipated change, but does not ensure faster recovery or lower cost. The frozen agent retains its soft cue belief, the memoryless agent uses an equal-probability belief, and the filter updates from reward before applying the hazard. The sweep measures the trade-off for the selected episodes and decision rule.

In[30]:
Code
def make_episode(rng, corrupt_cue=False, flip_goal=False):
    mode = int(rng.integers(0, 2))
    corrupted = bool(corrupt_cue and rng.random() < 0.5)
    cue_sign = -GOALS[mode] if corrupted else GOALS[mode]
    return {
        "mode": mode,
        "corrupted": corrupted,
        "flip_goal": flip_goal,
        "x0": float(rng.uniform(-1.0, 1.0)),
        "cue": cue_sign + float(rng.normal(scale=SIG_CUE)),
        "proc": rng.normal(scale=SIG_PROC, size=T_STEPS),
        "rew": rng.normal(scale=SIG_REW, size=T_STEPS),
    }


AGENTS = {
    "Memoryless": {"use_cue": False, "update": False, "oracle": False},
    "Cue memory, frozen": {"use_cue": True, "update": False, "oracle": False},
    "Cue memory, Bayesian": {"use_cue": True, "update": True, "oracle": False},
    "Oracle": {"use_cue": True, "update": True, "oracle": True},
}


def rollout(episode, agent):
    x = episode["x0"]
    if agent["oracle"]:
        p = float(episode["mode"] == 1)
    elif agent["use_cue"]:
        p = prior_from_cue(episode["cue"])
    else:
        p = 0.5
    total = 0.0
    trace = []
    for t in range(T_STEPS):
        mode_t = episode["mode"]
        if episode["flip_goal"] and t >= FLIP_AT:
            mode_t = 1 - mode_t
        x_star = GOALS[mode_t]
        if agent["oracle"]:
            p = float(mode_t == 1)
        a = belief_action(x, p)
        x_next = env_step(x, a, episode["proc"][t])
        r_obs = -((x_next - x_star) ** 2) + episode["rew"][t]
        total += (x_next - x_star) ** 2
        if agent["update"] and not agent["oracle"]:
            p = belief_update(p, x_next, r_obs)
        x = x_next
        trace.append(p)
    return total, np.array(trace)

Conditions and Common Random Numbers

Three conditions exercise the memory in different ways:

  • Stable goal, clean cue. The base case. Memory should help.
  • Goal flips at t=10t = 10. The cue is now stale. A memory that does not update is actively harmful.
  • Corrupted cue. Each episode independently has probability 0.5 of reversing the cue-generating mean. The Gaussian cue noise remains unchanged. Under the agents' assumed clean-cue likelihood, a reversed mean can favor the wrong goal, but realized cue direction and confidence vary.

The stable condition compares the specified memories when the goal remains fixed. The flip condition introduces stale goal information; the corrupted-cue condition introduces misleading initial information in a mixture of episodes. These test two failure modes, not an exhaustive memory taxonomy. Stable-condition performance alone does not establish performance under either stress condition.

Episodes are generated with a fixed seed and shared across agents, so exogenous cue and noise draws and the initial state match across agents. Actions, subsequent states, and observed rewards can differ; sharing additive reward noise does not make reward observations identical. This common-random-numbers design aligns exogenous randomness. It can reduce paired-difference variance when the agent costs have positive covariance, but does not remove environment variance or guarantee variance reduction for every pair. Unpaired draws can add comparison noise; whether they do so depends on that covariance. Use the paired episode-cost differences when estimating uncertainty in this matched comparison.

In[31]:
Code
CONDITIONS = {
    "Stable goal": {"corrupt_cue": False, "flip_goal": False},
    "Goal flips at t=10": {"corrupt_cue": False, "flip_goal": True},
    "Corrupted cue": {"corrupt_cue": True, "flip_goal": False},
}

N_EPISODES = 300


def evaluate_condition(agent, cond, n_episodes=N_EPISODES, seed=5):
    rng = np.random.default_rng(seed)
    episodes = [make_episode(rng, **cond) for _ in range(n_episodes)]
    return np.array([rollout(ep, agent)[0] for ep in episodes])


results = {
    agent_name: {
        cond_name: evaluate_condition(agent, cond)
        for cond_name, cond in CONDITIONS.items()
    }
    for agent_name, agent in AGENTS.items()
}

mem_mean = {
    (a, c): float(v.mean()) for a, r in results.items() for c, v in r.items()
}
mem_sem = {
    (a, c): float(v.std(ddof=1) / np.sqrt(len(v)))
    for a, r in results.items()
    for c, v in r.items()
}
Out[32]:
Console
agent                            Stable goal    Goal flips at t=10         Corrupted cue
Memoryless                   20.086 +/- 0.027       20.038 +/- 0.024       20.033 +/- 0.024
Cue memory, frozen            5.220 +/- 0.812       35.617 +/- 0.363       36.051 +/- 2.020
Cue memory, Bayesian          5.611 +/- 0.362       11.724 +/- 0.287        9.780 +/- 0.456
Oracle                        0.323 +/- 0.022        1.616 +/- 0.024        0.329 +/- 0.022
Out[33]:
Visualization
Grouped log-scale bar chart with standard error bars showing mean episode cost for four agents across stable, flipped, and corrupted-cue conditions.
Mean episode cost and standard errors for four specified agents under three seeded conditions. Both cue-based agents improve on the memoryless baseline in the stable condition; frozen cue memory has higher cost in the flip and corrupted-cue conditions. The small stable-condition mean difference between frozen and updating memory is not a significance result.

What the Numbers Say

In the stable condition, both cue-based agents have substantially lower mean cost than the memoryless agent. The frozen agent has a slightly lower sample mean than the filter at hazard 0.05, but the reported standard errors do not establish a reliable difference between these two means. The hazard shrinks beliefs toward equal probability; its contribution is better examined with the matched hazard sweep below than attributed from this comparison alone.

The frozen agent has a lower stable-condition sample mean than the filter at hazard 0.05; that is neither a significance result nor an optimality claim. An updating filter with hazard zero can learn from noisy rewards and has lower stable cost in the sweep below. That sweep separates belief updates from the positive-hazard shrinkage toward equal probability: a small positive hazard improves the tested goal-flip mean cost but raises stable-condition mean cost. The chosen update rule and change condition both matter.

In the seeded flip condition, the Bayesian agent has lower mean cost than the frozen agent. The table measures episode cost, not a distribution of detection or recovery times. The frozen agent never updates its cue belief; its mean cost exceeds the memoryless baseline here. This is not a universal ranking of updating and non-updating memories.

The memoryless controller uses an equal-probability goal belief and therefore targets the midpoint. That hedge avoids strong commitment to the wrong endpoint, but also fails to track either endpoint accurately. The frozen controller retains a concentrated, not certain, cue belief. After a change, that concentration can make its actions more costly than the midpoint hedge. A stored estimate can become harmful when its context changes and the controller continues to trust it; the size and direction of the effect depend on the change process and decision rule.

In the corrupted-cue condition, the updating agent has lower mean cost than the frozen agent. Some initial cues favor the wrong goal, with varying confidence. Rewards can then supply evidence for correction when their two goal-conditioned likelihoods differ; at xt+1=0x_{t+1}=0 those likelihoods coincide. The selected trace below illustrates correction from a wrong initial belief, not guaranteed recovery in every episode.

In[34]:
Code
trace_rng = np.random.default_rng(99)
trace_episode = None
for _ in range(200):
    candidate = make_episode(trace_rng, corrupt_cue=True, flip_goal=False)
    if candidate["corrupted"]:
        trace_episode = candidate
        break

_, trace_bayes = rollout(trace_episode, AGENTS["Cue memory, Bayesian"])
_, trace_frozen = rollout(trace_episode, AGENTS["Cue memory, frozen"])
_, trace_oracle = rollout(trace_episode, AGENTS["Oracle"])
true_mode = float(trace_oracle[0] > 0.5)
steps = np.arange(1, T_STEPS + 1)
Out[35]:
Visualization
Line chart of belief over time for a frozen cue memory and a Bayesian filter with a hazard rate, with a dashed horizontal line marking the true mode.
Next-decision prior for the negative goal in one selected corrupted-cue episode. The horizontal reference marks the true mode. This trace illustrates belief correction by the hazard filter; it does not estimate a population detection or recovery time.

Scope and Limitations of the Toy

This task is a sufficient-statistic toy. The belief is over a two-element set, the update is a one-dimensional filter, and the assumed hazard rate is a single scalar. It is a heuristic prior, not a correctly matched generator: the flip condition changes deterministically at step ten rather than sampling independent flips with probability 0.05. Open-world deployments may involve unknown, high-dimensional or drifting context and an unknown change process, rather than this known binary state space. Other deployments can have small known modes or stationary context. Part XII, Chapter 5 discusses monitoring and correction under changing operating conditions.

The agent knows the two modes and the Gaussian reward-noise model. Its assumed hazard does not match the deterministic flip schedule. In the corrupted-cue condition, the cue likelihood is also misspecified: half the cues come from the opposite goal's Gaussian, but prior_from_cue still assumes the clean-cue model. Recovery here therefore concerns a specified controller and misspecified filter, not a generally correct Bayesian solution. A real deployment can require learning or validating its state space, observation model, change process and update rule separately.

What transfers from the toy is the structural lesson:

  1. State explicitly what information the memory is supposed to carry. Here it is "which goal is active."
  2. Pair memory with an uncertainty-aware update. Likelihood updates revise an uncertain goal belief as new reward evidence arrives, including when the hazard is zero. A positive hazard separately models possible goal changes by moving the next-decision prior toward equal probability; it is not required for evidence-based updating and does not guarantee better performance.
  3. Design the reset boundary. Our episodes reset at t=0t = 0. Persisting the previous episode's belief into a new episode can start the controller with misleading evidence when the mode changes. Its effect depends on the initialization and update rule; cross-episode memory need not fail if those rules account for the boundary.
  4. Evaluate under change. Report the stable condition and the changed condition. Evaluation only on stable contexts cannot establish recovery after change; it also does not guarantee good stable performance.

The memory experiment above fixed the hazard rate. Sweeping it exposes a non-monotone trade-off. Moving from zero to a small positive hazard sharply lowers cost in the deterministic-flip condition, but increasing it further eventually raises that cost again. Stable-condition cost rises over the tested grid; near 0.5, repeated hedging discards useful accumulated information.

In[36]:
Code
import numpy as np


def belief_update_hazard(p, x_next, r_obs, hazard):
    lik = [
        np.exp(-0.5 * ((r_obs + (x_next - g) ** 2) / SIG_REW) ** 2)
        for g in GOALS
    ]
    post = p * lik[1] / (p * lik[1] + (1.0 - p) * lik[0] + 1e-300)
    return float((1.0 - hazard) * post + hazard * (1.0 - post))


def rollout_hazard(episode, hazard):
    x = episode["x0"]
    p = prior_from_cue(episode["cue"])
    total = 0.0
    for t in range(T_STEPS):
        mode_t = episode["mode"]
        if episode["flip_goal"] and t >= FLIP_AT:
            mode_t = 1 - mode_t
        x_star = GOALS[mode_t]
        a = belief_action(x, p)
        x_next = env_step(x, a, episode["proc"][t])
        r_obs = -((x_next - x_star) ** 2) + episode["rew"][t]
        total += (x_next - x_star) ** 2
        p = belief_update_hazard(p, x_next, r_obs, hazard)
        x = x_next
    return total


hazard_grid = np.array([0.0, 0.01, 0.05, 0.10, 0.20, 0.50])
stable_rng = np.random.default_rng(5)
stable_eps = [
    make_episode(stable_rng, corrupt_cue=False, flip_goal=False)
    for _ in range(200)
]
flip_rng = np.random.default_rng(6)
flip_eps = [
    make_episode(flip_rng, corrupt_cue=False, flip_goal=True)
    for _ in range(200)
]

stable_costs = np.array(
    [np.mean([rollout_hazard(ep, h) for ep in stable_eps]) for h in hazard_grid]
)
flip_costs = np.array(
    [np.mean([rollout_hazard(ep, h) for ep in flip_eps]) for h in hazard_grid]
)
Out[37]:
Visualization
Line chart of episode cost versus hazard rate for a stable goal and for a goal flip at step ten, showing the trade-off between stable performance and recovery.
Mean episode cost as a function of the hazard rate in the Bayesian memory update, under a stable goal and under a goal flip at step ten. Stable-condition cost rises over this grid; the deterministic-flip condition benefits from a small positive hazard but worsens again at larger hazards. The minimizing tested hazards differ between conditions.

An illustrative controlled example with a hand-specified two-mode environment, not a benchmark result or evidence about a physical system.