When Memory Contradicts the Tool Call, Which One Does the Agent Believe?

An agent had the live billing data and a three-week-old memory in the same context window, and answered from the memory. The reason isn't retrieval — it's how prompts are assembled, and there are three specific fixes.

When Memory Contradicts the Tool Call, Which One Does the Agent Believe?

A support agent is asked whether a customer can add a fifth seat. It retrieves a memory written three weeks ago: this account is on the Starter plan, three-seat cap. In the same turn it also calls the billing API, which returns {"plan": "growth", "seat_limit": 25}. The answer it gives the customer: you're capped at three, you'd need to upgrade first.

Both facts were sitting in the same context window. The agent picked the stale one. This is not a retrieval failure — retrieval worked perfectly, and that's the problem. It's a conflict-resolution failure, and it shows up in every agent that has both a memory store and a live data source pointed at the same entity.

Stale memory has three unfair advantages

The model isn't being careless. The way most systems assemble a prompt actively stacks the deck in favor of the older fact.

It arrives earlier, and framed as truth. Retrieved memories are usually injected near the top of the prompt, often folded into the system message under a header like "what you know about this user." Everything in a system message reads as premise. A tool result arrives hundreds of tokens later, structurally marked as something the agent went and fetched — data, not doctrine.

It's written as a conclusion, not a payload. "This customer is on Starter and has hit their seat cap" is a sentence the model can reason from directly. {"plan":"growth","seat_limit":25} requires an extra inferential hop — read the field, map it to plan tiers, compare to the question. Under load, the pre-chewed conclusion wins.

Nothing in it says when. A memory that reads "the account is on Starter" makes a present-tense claim. If it said "as of 2026-08-06, the account was on Starter," a model that also sees a live API response has an obvious tiebreak. Most memory stores keep a timestamp in metadata and never put it in the text the model actually reads.

🔍 The trace signature

Don't go looking for "wrong answers." Go looking for a correct tool call whose result was ignored in silence.

The signature is specific:

  • the tool was called, and called correctly
  • the response came back 200 with the right fields
  • the final answer contradicts a field in that response
  • and there is no reasoning text anywhere acknowledging that two sources disagreed

That last line is the tell. When a model notices a conflict and picks a side, it almost always narrates the choice — "our records show Starter, though billing reports Growth." Silence means it never registered a conflict at all. It read one fact.

A cheap detector, if your traces are structured:

for each turn:
  facts = extract_asserted_facts(injected_memories)
  for each tool_result in turn:
    for each field in tool_result:
      if contradicts(field, facts) and not mentioned_in(final_answer, field):
        flag(turn, "silent memory override")

You won't catch everything with string matching, but you don't need to. A handful of high-churn fields — plan tier, status, owner, balance, current version — cover most of the damage.

Fix 1 — Put provenance and volatility in the text, not the metadata

Whatever your store looks like underneath, the memory that reaches the prompt should carry its own age and its own confidence class:

[memory | learned 2026-08-06 from customer chat | volatility: high]
Customer said they were on the Starter plan (3 seats).

Four things changed. It's dated. It's attributed to a source with known reliability (the customer said it — the customer may be wrong). It's marked as the kind of fact that changes. And it's phrased as a past observation rather than a present claim.

Give every memory a volatility class when you write it:

  • static — the customer's company name, their timezone preference, how they like to be addressed
  • slow — team size, integration stack, stated goals
  • volatile — plan tier, seat count, ticket status, balance, open incidents
  • never-store — anything with a canonical live source that's cheap to query

Fix 2 — Declare a system of record per field

Conflict resolution is a policy decision, not something to leave to inference. Write it down once, in the system prompt, as a short list:

Billing API is authoritative for plan, seats, and invoice state. The CRM is authoritative for account owner and contract dates. Memory is authoritative for preferences and prior conversation only. When they disagree on any other field, the live API wins and you say so.

This costs maybe sixty tokens and converts an ambiguous judgment into a rule the model can follow. It also makes the failure debuggable: if the agent still picks the memory, you have a prompt-adherence bug rather than a mystery.

Fix 3 — Turn volatile memories into triggers, not answers

The strongest fix is to stop injecting volatile facts as facts at all.

The customer is on the Starter plan with a 3-seat cap.

The customer previously discussed seat limits. If seat limits come up, check the billing API before answering.

The second version preserves everything the memory was actually good for — it knows what this customer cares about, and it routes the agent to the right tool — while surrendering the claim it has no business making. Memory is excellent at what matters to this person. It is unreliable at what is true right now. Splitting those two jobs removes the conflict instead of adjudicating it.

The rule worth keeping

Memory should remember questions, not answers. It should remember that this customer asks about seats, that this repo's tests are slow, that this analyst always wants the numbers weekly — and then send the agent to a live source for the value. A remembered value is a cache with no invalidation strategy, and the model has no way to know it went stale.

If you only do one thing: date every memory in the text the model reads. Half of these failures stop being possible the moment the agent can see that one of its two facts is three weeks old.

Keep reading

Similar posts

Matched on shared tags and category — the more bars, the stronger the overlap with what you just read.