# AI Agent Memory: Sessions, Long-Term State, and Retrieval

> Design AI agent memory without a data swamp: session state, durable facts, retrieval, provenance, conflicts, privacy, deletion, and production evaluation.
> Author: Roman Belov · Published: 2026-08-21 · Source: https://futurecraft.pro/blog/ai-agent-memory/

“Give the agent memory” sounds like one feature. In production it is at least four
different data systems:

```text
current run state
conversation or workflow checkpoint
long-term facts and preferences
retrieval over documents and prior experiences
```

Mix them together and the agent starts treating an old chat summary as current account
state, a private preference as organization policy, or a failed attempt as a proven
procedure.

Memory is not a bigger prompt and not a vector database with every conversation dumped
into it. It is a governed path from **event → candidate → stored record → retrieval →
context → correction or deletion**.

This article focuses on that path. The broader
[context engineering guide](/blog/context-engineering-guide/) covers how memory competes
with instructions, tools, and retrieved documents inside the context window. For
multiple cooperating agents, pair it with the
[multi-agent architecture guide](/blog/multi-agent-architecture/).

## Separate the four state problems

| Layer | Scope | Example | Storage shape |
| --- | --- | --- | --- |
| Working state | One run or step | Current plan, intermediate IDs, retry count | Typed in-memory state |
| Thread state | One conversation or workflow | Messages, approvals, checkpoint, pending tool call | Ordered log and checkpoints |
| Long-term memory | User, project, agent, or organization | Preference, verified fact, reusable experience | Structured records with lifecycle |
| Knowledge retrieval | Corpus and access domain | Product docs, tickets, repository files | Source documents plus index |

The boundaries matter more than the names. OpenAI's Agents SDK, for example, uses a
`Session` to store conversation history for one session. Its current
[state-management guide](https://openai.github.io/openai-agents-python/running_agents/)
distinguishes client-managed sessions from server-managed conversations and warns
against mixing both persistence strategies in one run because context can be
duplicated.

LangGraph makes a similar split: checkpoints persist thread state, while a Store holds
data that can be recalled across threads. The
[memory overview](https://docs.langchain.com/oss/python/concepts/memory) calls these
short-term and long-term memory. The framework may differ; the scopes should not.

## Model memory as records, not prose

A single `memory_text` field is quick to ship and hard to operate. Use a record that can
answer who it belongs to, where it came from, whether it is still valid, and how it may
be used:

```json
{
  "memory_id": "mem_01...",
  "subject_id": "usr_internal_42",
  "namespace": ["user", "travel_preferences"],
  "kind": "preference",
  "fact": {
    "field": "seat_preference",
    "value": "aisle"
  },
  "source": {
    "type": "explicit_user_statement",
    "event_id": "evt_01..."
  },
  "confidence": "confirmed",
  "sensitivity": "personal",
  "valid_from": "2026-08-21T10:00:00Z",
  "valid_until": null,
  "created_at": "2026-08-21T10:00:02Z",
  "supersedes": null
}
```

Keep the embedding as an index derived from this source record. It is not the source of
truth. That distinction makes corrections, access changes, and deletion possible
without trying to reverse-engineer a vector.

### Choose memory types deliberately

Three long-term types are useful:

- **Semantic:** facts such as a user's language or a project's deployment region.
- **Episodic:** an earlier attempt, its actions, and verified outcome.
- **Procedural:** instructions for how the agent should perform a task.

Procedural memory deserves the strongest gate. An agent that can rewrite its own shared
instructions can turn one poisoned conversation into persistent behavior. Keep
organization policy and high-impact procedures versioned, reviewed, and read-only to
the runtime. Let the agent propose a change; do not let it silently publish one.

## Design the write path before retrieval

Most memory failures begin when too much is written. “The user mentioned it” is not a
retention policy.

Use three write paths:

### 1. Explicit and immediate

The user says “remember that I prefer aisle seats.” Validate that the data class is
allowed, show what will be stored when the consequence is not obvious, then write a
confirmed record.

### 2. Deterministic outcome

Application code records a completed booking, approved decision, or successful tool
result. Store the verified state or a reference to its system of record. Do not ask the
model to rephrase an identifier and then trust the paraphrase.

### 3. Inferred candidate

The model infers a preference or extracts a lesson from several events. Write a
candidate with provenance, not an accepted fact. A background job can deduplicate,
check policy, compare current records, and request user review where needed.

Background consolidation keeps memory work off the response path and can examine more
than one event. It also introduces lag and races. Assign each candidate an event ID,
make writes idempotent, and ensure an older job cannot overwrite a newer confirmed
record.

The OpenAI Agents SDK's current sandbox memory feature uses a similar separation:
conversation extracts are consolidated into smaller memory files, while its ordinary
`Session` remains conversation history. Its
[agent memory documentation](https://openai.github.io/openai-agents-python/sandbox/memory/)
also treats saved memory as guidance and tells the agent to trust the current
environment when they conflict.

## Resolve conflicts instead of appending forever

Memory changes. A user moves, a project migrates, a preference was temporary, or an
earlier extraction was wrong.

Define a precedence policy in code. One workable order is:

```text
current system of record
> explicit current user statement
> approved organization or project record
> recent verified outcome
> model-inferred candidate
> old summary
```

Do not merge incompatible values into prose. Preserve versions or a supersession link,
select the active record deterministically, and surface the conflict when policy cannot
resolve it. “I found two different billing contacts; which is current?” is better than
choosing the closest embedding.

Use validity intervals and TTLs for facts that naturally expire. Recency alone is not
truth: an attacker should not be able to override an approved policy merely by writing
a newer note.

## Retrieve by scope before similarity

The retrieval pipeline should narrow authority first:

```text
verified principal
→ allowed namespace and tenant
→ memory kind and validity
→ task filters
→ lexical or semantic retrieval
→ reranking
→ context budget
```

Never retrieve a broad global index and filter the results after they have reached the
model. Enforce identity and tenant boundaries in the storage query. A namespace should
be derived from verified runtime context, not from `user_id` supplied in a prompt.

Use structured lookup for exact facts:

- current timezone;
- approved language;
- active workflow ID;
- policy version;
- object ownership.

Use semantic search for fuzzy experiences and notes: similar incidents, previous
approaches, or relevant preferences expressed in different words. Combine it with
metadata filters and lexical search when identifiers or exact phrases matter.

Return a small typed packet with provenance:

```json
{
  "fact": "Prefers aisle seats",
  "status": "confirmed",
  "source_date": "2026-08-21",
  "memory_id": "mem_01..."
}
```

Give memory a fixed token budget. If ten records fit, retrieval should not return fifty
and hope the model ignores forty. Log candidates, selected records, and rejection
reasons so missed or noisy retrieval can be debugged.

## Summaries are lossy indexes

Conversation compaction is useful, but a summary should not silently become the only
record of approvals, tool results, or user commitments.

Keep an append-only event log or source messages according to your retention policy.
Store the summary with:

- the source range it covers;
- prompt or summarizer version;
- creation timestamp;
- explicit unresolved items;
- references to important source events.

Regenerate summaries after a correction or deletion. Otherwise the source fact may be
gone while its paraphrase survives in every future prompt.

## Resume workflows from checkpoints, not chat guesses

A durable agent needs exact state for side effects:

```text
workflow_id
current_step
completed_steps
pending_approval
tool_call_id and idempotency key
artifacts produced
last verified outcome
```

This is thread state, not long-term memory. Persist it transactionally around tool
effects and authorize every resume. A model-generated summary saying “payment probably
completed” cannot decide whether to charge again.

LangGraph's
[persistence model](https://docs.langchain.com/oss/python/langgraph/persistence)
writes checkpoints per thread and uses a separate Store for cross-thread memory. That
separation is a useful architecture even when you do not use LangGraph.

## Privacy and deletion are part of the schema

Before storing a class of memory, decide:

- purpose and allowed readers;
- whether the model may write, read, or only propose it;
- sensitivity and encryption requirements;
- default TTL and maximum retention;
- export, correction, and deletion paths;
- whether it may cross user, project, or organization boundaries.

Do not store passwords, access tokens, recovery codes, payment data, raw government IDs,
or secrets as conversational memory. Use the appropriate system of record and return
only the minimum needed fact to the agent.

A delete operation must fan out to the source record, vector and lexical indexes,
summaries, caches, checkpoints where applicable, and asynchronous replicas. Use a
deletion job with status and retry. Keep a content-free tombstone long enough to stop a
delayed indexing event from recreating the record. Verify completion with a read path,
not only a successful queue submission.

## Evaluate memory as a retrieval-and-write system

Build fixed scenarios from real failure shapes:

| Metric | What the scenario checks |
| --- | --- |
| Write precision | Did the system store only facts worth retaining? |
| Write recall | Did it retain the explicit permitted fact it should remember? |
| Useful retrieval | Did the task receive the relevant active record? |
| Noise | Did irrelevant memories consume context or change the answer? |
| Contradiction handling | Did newer or higher-authority data win correctly? |
| Isolation | Can one principal ever retrieve another principal's record? |
| Staleness | Are expired or superseded records excluded? |
| Deletion | Does removed content disappear from every read surface? |

Add adversarial cases: indirect instructions inside a remembered note, a prompt trying
to change namespace, a poisoned shared procedure, and a deletion racing a background
consolidation job. The memory store is untrusted input to the model and sensitive data
to the application at the same time.

## Production checklist

- [ ] Working state, thread checkpoints, long-term memory, and knowledge retrieval are separate.
- [ ] Every durable memory has owner, scope, provenance, timestamps, and sensitivity.
- [ ] Explicit facts and inferred candidates use different write paths.
- [ ] Conflicts and supersession are resolved by deterministic policy.
- [ ] Identity and tenant filters run before semantic search.
- [ ] Retrieval has a fixed context budget and exposes provenance.
- [ ] Workflow resume uses checkpoints and idempotency, not summaries.
- [ ] Shared procedures are reviewed and versioned.
- [ ] Correction and deletion propagate through indexes, summaries, and caches.
- [ ] Fixed tests cover quality, isolation, staleness, and deletion.

Good memory is selective and boring. The agent recalls the right fact, can show where
it came from, forgets it on command, and does not turn yesterday's chat into today's
authority.
