Observation masking keeps a rolling window of recent tool results and replaces older ones with a placeholder. The transcript still shows what the agent tried; the expensive bytes leave the billed context.
JetBrains Research on SWE-bench Verified (Qwen3-Coder 480B): a 10-turn masking window was 52% cheaper than unmanaged history with solve rate +2.6%. LLM summarization also cut cost over 50% but ran 13% longer - summaries can smooth over stop signals. Masking matched or beat summarization in 4 of 5 settings they tested.
MASK = "[tool output masked - older turn]"
def mask_observations(messages, keep_last=10, drop_failed=False):
"""Replace tool results outside the rolling window with a placeholder."""
tool_turns = [i for i, m in enumerate(messages) if is_tool_result(m)]
live = set(tool_turns[-keep_last:])
out = []
for i, m in enumerate(messages):
if not is_tool_result(m) or i in live:
out.append(m)
elif drop_failed and is_error(m):
continue
else:
out.append({**m, "content": MASK})
return out
Two details that decide whether it saves money
- Mask before the call, not in stored history - keep the full log on disk; rewriting stored history can invalidate a cache-stable prefix.
- Mask in batches at a threshold - every mask changes the prefix from that point forward; re-masking one more turn on every call burns the cache.
Evidence and siblings: Prune the log, not the window. Discipline frame: Context engineering.