Your AI remembers the old address. How should memory work?

AI agent memory design becomes a practical problem when a customer changes their delivery address and the bot keeps using the old one. The conversation history contains both addresses. A summary may even describe the earlier address as a permanent preference.

Adding more history doesn’t automatically fix that. The application needs to know which fact is current, where it came from and whether it applies to this order. Let’s start by separating those questions.

A preference isn’t an instruction for every task

A saved preference might say the customer normally wants home delivery. A current instruction might say this order goes to the office. An authoritative order record might show that a previous order already shipped elsewhere.

Store these in different scopes. Useful scopes include durable preference, current task and verified business record. The current task can override a default preference without rewriting the historical order.

Anthropic’s context-engineering discussion treats context selection as an engineering problem. For this application, we’ll make that selection explicit before passing facts to the model. Context engineering reference.

Store facts with evidence and a version

A memory entry needs more than a key and value. Include subject, scope, source input ID, version, effective time and expiry where appropriate. Keep a status for superseded or disputed facts.

Don’t let a summariser assign a verified status to something the customer merely suggested. Keep the original evidence reference so a later service can distinguish a confirmed address from an inferred one.

For privacy and storage control, retain only what the product needs. A compact current state plus an event history kept under your retention rules is easier to govern than a permanent copy of every prompt in every cache.

Resolve the fact before generating the answer

The example picks a task-specific instruction before a durable preference and rejects an old update. It also expires a temporary instruction instead of carrying it into the next task forever.

import assert from 'node:assert/strict';
const facts = new Map();
const key = (scope, field) => JSON.stringify([scope, field]);
function put(scope, field, value, version, expires = Infinity) {
  const k = key(scope, field), old = facts.get(k);
  if (old && version <= old.version) return false;
  facts.set(k, { value, version, expires }); return true;
}
function resolve(task, field, now) {
  for (const scope of [`task:${task}`, 'preference']) {
    const fact = facts.get(key(scope, field));
    if (fact && fact.expires > now) return fact.value;
  }
  return null;
}
put('preference','address','Home',1);
put('task:order-a','address','Office',2,100);
assert.equal(resolve('order-a','address',10), 'Office');
assert.equal(put('task:order-a','address','Old office',1), false);
assert.equal(resolve('order-b','address',10), 'Home');
assert.equal(resolve('order-a','address',101), 'Home');
console.log('PASS: scoped memory, correction version and temporary expiry');

The task sees the office address while the preference still contains the home address. An older background summary can’t overwrite the corrected task entry. When the task fact expires, the resolver falls back according to the explicit policy.

This is a small resolver, not an automatic fact extractor. Your interpretation layer still needs to decide which proposed changes are supported by the customer’s words.

Make corrections updates, not competing memories

If a customer says “actually, use the office”, record a new version that supersedes the earlier task address. Don’t merely append another equally ranked embedding and hope retrieval returns the right one.

Use a conditional database update against the version you read. If it changes no rows, reload current state before writing. That prevents a slow summary worker from restoring an earlier value after a correction has already been accepted.

When the correction is ambiguous, preserve the uncertainty. “Send it there” needs a referent. Asking which address is cheaper than making an apparently confident mistake.

Keep verified records authoritative

Memory can help compose a response, but it must not decide whether a paid order’s address can still change. Fetch the order service’s current state before performing that action.

If fulfilment has started, the customer may need a change request rather than a direct overwrite. Store the requested change separately from the committed order. The agent should explain the actual outcome returned by the business service.

This distinction also applies to balances, stock and refund status. A previous conversation summary saying “refund sent” isn’t proof that the provider completed it.

Retrieve a bounded context

Build the prompt from the current task, relevant preferences and fresh verified results. Label their scopes and evidence clearly. Avoid loading unrelated memory simply because it belongs to the same phone number.

Scope retrieval by tenant and subject before semantic ranking. A relevant-looking fact from another shop is still the wrong fact. Include the policy version in caches so changes to the resolver don’t keep returning an old decision.

If you use a vector store for discovery, treat it as a way to find candidate evidence. Recheck current versions and permissions in the source of truth before using candidates for a consequential action.

Test contradictions deliberately

Give the agent an old preference, a new task instruction and an already committed order with a third address. Check both its answer and the tool action it proposes. The right fact depends on the question.

Also test an old summariser completing after a correction, expired instructions, deleted preferences and two tenants sharing a customer identifier. Record which evidence the resolver chose so failures are explainable.

Start with one troublesome field, such as delivery address. Give it scope, provenance and a version. Once your application can resolve that field consistently, expand the pattern to other memory that genuinely improves the conversation.

Run the example and adapt it

Save the code as 09-versioned-agent-memory.mjs and run it with Node.js 24 or later:

node 09-versioned-agent-memory.mjs

The code above includes assertions you can run locally. It tests the local decision logic with controlled inputs; it doesn’t connect to a live provider or establish production performance. Keep that boundary when adapting it to your application.

Technical references