Worked example: a wrong answer from stale data
This walks through one bug end to end using the Anthropic example agent in examples/anthropic-agent.mjs: a trip helper that checks the weather in two cities with a get_weather tool and recommends one.
The model's wording differs from run to run; the outputs below show the shape of each step.
The report
A user complains that the agent recommended Lisbon on a weekend with a storm warning. The agent itself looks fine. The question is what it does when the weather data is right, and whether the fix holds.
1. Record the run
npx blackbox record --out runs/trip.json -- node examples/anthropic-agent.mjsLisbon: 24°C and sunny beats Oslo's rain.
◼ blackbox · record wrote runs/trip.json (9 steps, success)2. Confirm it reproduces offline
npx blackbox replay runs/trip.json -- node examples/anthropic-agent.mjsLisbon: 24°C and sunny beats Oslo's rain.
◼ blackbox · replay ✓ PASS replayed 9 steps from runs/trip.json with no model calls sent (success)The same answer, from the same code, with no API call. From here on, the recorded run is the fixed point you experiment against.
3. Find the fact to change
npx blackbox inspect runs/trip.json 1 model_output 4312fcd4 Model → tool_calls: get_weather, get_weather
2 tool_call f0e0c05d Tool called: get_weather
3 tool_result d948816f Tool result: get_weather → ok
4 tool_call 521e3670 Tool called: get_weather
5 tool_result a150bc86 Tool result: get_weather → okStep 3 is the weather the tool returned for Lisbon: {"tempC":24,"sky":"sunny"}, which was stale.
4. Fork with the correct data
npx blackbox fork runs/trip.json --at 3 --set '{"tempC":9,"sky":"storm"}' --out runs/storm.json --live \
-- node examples/anthropic-agent.mjsNeither is great; Lisbon has a storm and Oslo has rain.
◼ blackbox · fork wrote runs/storm.json (9 steps, success); steps 0–2 are copied from runs/trip.json, step 3 is your new resultBlackbox replayed steps 0–2 and checked that the agent made the same requests, gave it the corrected result at step 3, and let the live model continue.
5. See exactly what changed
npx blackbox diff runs/trip.json runs/storm.jsonFirst divergence at index 3
changed value (result):
parent: {"tempC":24,"sky":"sunny"}
child: {"tempC":9,"sky":"storm"}
Outcome: same final status (success), but the final answer changed
parent answer: Lisbon: 24°C and sunny beats Oslo's rain.
child answer: Neither is great; Lisbon has a storm and Oslo has rain.With correct data the agent does not recommend Lisbon. The bug is in the data source, not the agent's reasoning.
6. Keep it fixed
Commit runs/storm.json and pin its behavior:
npx blackbox assert runs/storm.json --expect-status success \
--expect-final-answer "Neither is great; Lisbon has a storm and Oslo has rain."and check in CI that the agent's requests have not drifted:
npx blackbox replay runs/storm.json -- node examples/anthropic-agent.mjsDesign notes
- Why a hash chain. Each step's hash covers the previous step's hash, so a step's content cannot be edited without
verifynoticing unless every later hash is recomputed (the cassette's top-level fields and step ids are not covered). It also makes the fork claim checkable: steps 0–2 ofstorm.jsonhave the same hashes as steps 0–2 oftrip.json. - Why strict matching. Replay compares each request with the recorded one before answering it (what is compared). A replay that passes therefore shows your agent still sends the recorded requests and tool calls, which a replay that answers blindly in order cannot.
- Why fork at tool results. Tool results are the facts from outside the model that an agent reasons over, and they are the facts most often wrong in production. Changing one and letting the model continue answers "what would it have done if…".
- Why provider-neutral steps. The same cassette format covers Anthropic, OpenAI and imported Claude Code sessions, and none of them store provider ids or usage data.