Chapter 13: The 6 Memory Tools and How to Use Them
By the end of this chapter, you will understand what each of the six memory tools does, know how to use the full memory API for reading, writing, updating, and versioning memories, and be able to build safe concurrent memory updates using preconditions.
The Big Idea
Memory stores give Claude a persistent knowledge layer. The six memory tools are the interface through which Claude interacts with that layer — and through which you, as the developer, can read, write, and audit memory content directly via the API.
These tools are automatic: when you attach a memory store to a session, Claude gains access to them without any additional configuration. You don't have to tell Claude to use them. But understanding what each tool does lets you design better memory architectures and debug memory behavior when something unexpected happens.
The six tools, as defined in the memory documentation:
| Tool | Description |
|---|---|
memory_list |
List memories in a store, optionally filtered by path prefix |
memory_search |
Full-text search across memory contents |
memory_read |
Read a memory's contents |
memory_write |
Create or overwrite a memory at a path |
memory_edit |
Modify an existing memory |
memory_delete |
Remove a memory |
Two tools for finding memories (list and search). One for reading. Two for writing (write creates/overwrites; edit modifies). One for deleting.
The Analogy
Think of memory tools as the operations in a well-organized filing system.
memory_list is looking through the file index: "show me all files in the /preferences/ drawer."
memory_search is the search box: "find every document that mentions 'Python formatting' anywhere in its content."
memory_read is pulling a specific file from the cabinet and reading it.
memory_write is creating a new file or replacing one that already exists at that location.
memory_edit is opening an existing file and making a targeted change — like correcting one sentence without rewriting the whole document.
memory_delete is removing a file from the cabinet.
The API you use to manage memory from your code mirrors these same operations — the same actions Claude takes automatically are available to you programmatically for seeding, auditing, and correcting memory content.
How It Actually Works
memory_list — Browse by Path
Lists memories in a store, with an optional filter by path prefix. Returns metadata (path, size, sha256 hash) but not content.
page = client.beta.memory_stores.memories.list(
store.id,
path_prefix="/",
)
for memory in page.data:
print(
f"{memory.path} ({memory.size_bytes} bytes, sha={memory.content_sha256[:8]})"
)
(Memory)
The path prefix filter uses trailing slash convention: /notes/ matches /notes/a.md but NOT /notes_backup/old.md. This lets you browse a specific "directory" in the memory store without accidentally matching sibling paths.
Use memory_list to:
- Audit what memories exist in a store
- Check which topics have been documented
- Get file sizes before deciding whether to read them
memory_search — Find by Content
Full-text search across memory contents. Returns memories where the content matches the query. This is what Claude uses to find relevant memories before starting a task — it searches for terms related to the current work.
From the developer side, you can call the API equivalent to audit what Claude would find for a given query:
# Via the API, similar to how Claude uses memory_search internally
results = client.beta.memory_stores.memories.list(
store.id,
path_prefix="/",
)
# The actual search tool is available to Claude during sessions
The memory_search tool is the reason small, focused memories outperform large monolithic ones: search returns documents, not excerpts. A precise, focused memory document on one topic is more reliably retrieved than a sprawling document that covers many topics loosely.
memory_read — Get the Full Content
Reads a specific memory's full content by ID:
mem = client.beta.memory_stores.memories.retrieve(
memory_id,
memory_store_id=store.id,
)
print(mem.content)
(Memory)
memory_read is how Claude loads the full text of a memory it identified via memory_list or memory_search. Use this in your code to read and verify memory content — to confirm what the agent learned, check for errors, or export memory content.
memory_write — Create or Overwrite
Creates a new memory at a path, or overwrites an existing one. This is the primary write operation:
mem = client.beta.memory_stores.memories.write(
memory_store_id=store.id,
path="/preferences/formatting.md",
content="Always use tabs, not spaces.",
)
(Memory)
If a memory already exists at the path, it's overwritten. If not, it's created. The path acts as the unique identifier within the store.
Safe writes with the not_exists precondition:
To only write if nothing exists at that path yet (preventing accidental overwrites):
client.beta.memory_stores.memories.write(
memory_store_id=store.id,
path="/preferences/formatting.md",
content="Always use 2-space indentation.",
precondition={"type": "not_exists"},
)
Returns 409 memory_precondition_failed if a memory already exists at the path. (Memory)
This is the safe-initialization pattern: seed memory only once, don't clobber what already exists.
memory_edit — Targeted Updates
Modifies an existing memory without overwriting it. Use this for updates rather than rewrites:
client.beta.memory_stores.memories.update(
mem.id,
memory_store_id=store.id,
path="/archive/2026_q1_formatting.md", # rename it
)
For content changes with concurrency safety, use the content_sha256 precondition — this ensures you're editing a specific known version of the content:
client.beta.memory_stores.memories.update(
memory_id=mem.id,
memory_store_id=store.id,
content="CORRECTED: Always use 2-space indentation.",
precondition={"type": "content_sha256", "content_sha256": mem.content_sha256},
)
Returns 409 memory_precondition_failed on hash mismatch. (Memory)
The content_sha256 precondition is the optimistic concurrency mechanism: if someone (or another session) changed the memory between when you read it and when you tried to update it, the SHA256 won't match and the update fails safely. This prevents race conditions in multi-agent or multi-session scenarios.
memory_delete — Remove a Memory
Removes a memory from the store:
client.beta.memory_stores.memories.delete(
mem.id,
memory_store_id=store.id,
)
Optionally, pass expected_content_sha256 for a conditional delete — only delete if the content is what you expect:
client.beta.memory_stores.memories.delete(
mem.id,
memory_store_id=store.id,
expected_content_sha256=mem.content_sha256,
)
(Memory)
The hash-conditional delete prevents accidentally deleting a memory that was updated since you last read it.
Memory Versioning — The Audit Trail
Every mutation creates an immutable version record (memver_...):
for v in client.beta.memory_stores.memory_versions.list(
store.id,
memory_id=mem.id,
):
print(f"{v.id}: {v.operation}")
Operation types: "created", "modified", "deleted". (Memory)
Redacting sensitive content from version history:
If a memory version contains content that should be expunged — PII, an API key that was accidentally written, outdated credentials — you can redact it:
client.beta.memory_stores.memory_versions.redact(
version_id,
memory_store_id=store.id,
)
"Redact hard clears content, content_sha256, content_size_bytes, and path; all other fields, including the actor and timestamps, are preserved." (Memory)
Redaction is non-destructive to the audit trail: you can see that a version existed and who created it, but the sensitive content is gone.
Memory Events in the Event Stream
When a session has memory stores attached, the agent's memory operations appear as agent.tool_use events in the event stream. (Memory) You'll see events like:
agent.tool_use { name: "memory_search", input: { query: "formatting preferences" } }
agent.tool_use { name: "memory_read", input: { memory_id: "mem_01..." } }
agent.tool_use { name: "memory_write", input: { path: "/session/summary.md", content: "..." } }
This visibility into memory operations lets you:
- Confirm the agent is consulting memory before starting work
- Verify it's writing the right things at the end of a session
- Debug unexpected behavior by tracing what was read vs. what was written
Try It Yourself
Create a memory store and write several memories:
store = client.beta.memory_stores.create( name="Project Context", description="Context for my coding project.", ) # Write formatting preferences client.beta.memory_stores.memories.write( memory_store_id=store.id, path="/preferences/code-style.md", content="Use 2-space indentation. Prefer functional patterns. Write tests for all functions.", ) # Write project conventions client.beta.memory_stores.memories.write( memory_store_id=store.id, path="/project/conventions.md", content="All API endpoints follow REST conventions. Use snake_case for variables, PascalCase for classes.", )List all memories in the store:
page = client.beta.memory_stores.memories.list(store.id, path_prefix="/") for memory in page.data: print(f"{memory.path} ({memory.size_bytes} bytes)")Read a specific memory:
mem = client.beta.memory_stores.memories.retrieve( page.data[0].id, memory_store_id=store.id, ) print(mem.content)Update a memory with a safe precondition:
# First, get the current SHA current_sha = mem.content_sha256 # Safe update — only proceeds if content hasn't changed client.beta.memory_stores.memories.update( memory_id=mem.id, memory_store_id=store.id, content="Use 4-space indentation. Prefer functional patterns. Write tests for all functions.", precondition={"type": "content_sha256", "content_sha256": current_sha}, )View the version history:
for v in client.beta.memory_stores.memory_versions.list(store.id, memory_id=mem.id): print(f"{v.id}: {v.operation} at {v.created_at}")You should see both the
createdandmodifiedversions.Attach the store to a session and observe memory tool usage in the event stream (as covered in Chapter 10). Watch for
agent.tool_useevents with memory tool names.
Common Pitfalls
Writing large memories instead of many small ones. The 100 KB limit per memory is the hard constraint, but the practical constraint is even tighter: large memories are less precisely retrieved by
memory_search. A 90 KB monolithic memory on "all project context" is harder to navigate than 20 focused 4 KB memories on specific topics.Not using preconditions for concurrent updates. If multiple sessions run simultaneously and both might write to the same memory path, you can get lost-update problems without preconditions. Use
content_sha256preconditions for any memory that might be written from multiple places.Forgetting to use
read_onlyfor shared reference memory. If you have a knowledge base that multiple agents should read but only administrators should update, attach it withaccess: "read_only". Without this, any agent session can overwrite your carefully curated knowledge base.Never reviewing what the agent wrote. Claude writes memories autonomously. After a few sessions, check what was actually written. Look for incorrect conclusions, outdated information, or sensitive content that shouldn't be persisted. Regular memory audits are part of responsible memory management.
Ignoring version history for debugging. When an agent's behavior changes unexpectedly, check the memory version history. A recent
modifiedentry might explain the behavioral change — something was written in a previous session that's now shaping current behavior.
Toolkit
Memory Store CRUD Cheat Sheet — Quick-reference code snippets for all six operations (list, search, read, write, edit, delete) with the precondition variants for write and edit. One snippet per operation.
Memory Audit Template — A structured checklist for reviewing memory store contents: list all memories, spot large or stale files, check version history for unexpected modifications, identify content that should be redacted.
Chapter Recap
- The six memory tools divide into: discovery (
memory_list,memory_search), reading (memory_read), writing (memory_write,memory_edit), and deletion (memory_delete). Claude uses them automatically when a memory store is attached — no configuration needed. - Use preconditions for safe concurrent writes:
not_existsto initialize safely,content_sha256to update without racing. Both return409 memory_precondition_failedon conflict. - Every mutation creates an immutable version record. Use
memory_versions.listto audit changes, andmemory_versions.redactto expunge sensitive content from history while preserving the audit trail.