Frontier Questions and Research Roadmaps › Experiment 3: Memory Ablation Under Changing Context
World Models HandbookAll chapters 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 with dynamics
where:
- : scalar position at step
- : control action at step
- : position decay factor
- : control gain
- : process noise at step
- : projection onto the interval
The dynamics coefficients are shared across episodes. What differs is the goal: in mode 0 and 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 . A noisy reading of the goal, with . For the non-oracle agents, this is the only initial observation carrying goal information; they also observe position.
- A reward signal after each transition. with . 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 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 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 . It uses the prior-mean goal, which is .
- Cue memory, frozen. Uses the cue to set its belief at 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 representing the prior probability that the context changed on any step.
- Oracle. Knows the mode.
All four share a certainty-equivalent controller: given , the expected goal is , and is clipped to . 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.
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 . Rewards can distinguish the goals when their predicted means differ; at 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.
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 . 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.
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()
}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

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 those likelihoods coincide. The selected trace below illustrates correction from a wrong initial belief, not guaranteed recovery in every episode.
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)
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:
- State explicitly what information the memory is supposed to carry. Here it is "which goal is active."
- 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.
- Design the reset boundary. Our episodes reset at . 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.
- 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.
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]
)
An illustrative controlled example with a hand-specified two-mode environment, not a benchmark result or evidence about a physical system.