Anthropic's memory tool went generally available on the Messages API this cycle, and the framing most write-ups get wrong is worth fixing first: the memory tool is not a database. It ships no vector index, no embeddings, no automatic extraction. What it ships is a protocol — Claude asks to read and write files, and your application does the actual reading and writing against storage you own. That inversion is the whole design, and once you hold it, wiring memory in takes about ten lines.

Here's the full loop, the SDK shortcut, and the one guard you cannot skip.

The entire configuration is one tools entry#

No beta header, no input schema. You add the memory tool to a request the same way you'd add any Anthropic-provided tool:

import anthropic

client = anthropic.Anthropic()

message = client.messages.create(
    model="claude-opus-5",
    max_tokens=2048,
    messages=[{"role": "user", "content": "Help me answer this support ticket."}],
    tools=[{"type": "memory_20250818", "name": "memory"}],
)

The name must be memory; the type is the dated identifier memory_20250818. It works on every Claude 4-and-later model. When the tool is present, the API auto-injects a system instruction telling Claude to view /memories before doing anything else and to assume its context may be reset at any moment — so the model will typically open with a view of /memories on its own.

Claude asks; your code answers#

Send that request and Claude's reply will end in a tool_use block requesting a file operation:

{ "type": "tool_use", "id": "toolu_01C4...",
  "name": "memory",
  "input": { "command": "view", "path": "/memories" } }

Your job is to execute the operation against your store and return the result in a tool_result, then send the conversation back so Claude can continue — the ordinary tool-use loop. There are six commands to handle: view, create, str_replace, insert, delete, rename. A minimal dispatcher over a dict-backed store looks like this:

STORE = {}  # swap for per-user files, a DB, or cloud storage

def execute_memory(input):
    cmd, path = input["command"], input.get("path", "")
    guard(path)                                   # see the next section — do NOT skip this
    if cmd == "view":
        if path in STORE:
            return with_line_numbers(STORE[path])
        if path == "/memories":
            return listing(STORE)
        return "The path %s does not exist. Please provide a valid path." % path
    if cmd == "create":
        STORE[path] = input["file_text"]
        return "File created successfully at: %s" % path
    if cmd == "str_replace":
        STORE[path] = STORE[path].replace(input["old_str"], input.get("new_str", ""), 1)
        return "The memory file has been edited."
    if cmd == "delete":
        STORE.pop(path, None)
        return "Successfully deleted %s" % path
    # ...insert and rename follow the same shape

Claude reads whatever text you return, so the exact success and error strings are recommendations, not a contract — but matching the documented ones ("The path {path} does not exist.", "File created successfully at: {path}", and so on) keeps the model's behavior predictable. Return errors with is_error: true on the tool_result.

The one line you cannot skip#

Because your handler runs every operation Claude requests, an unvalidated path is a directory-traversal hole. A memory file named /memories/../../secrets.env will cheerfully walk out of your memory directory and read or overwrite whatever your process can reach, unless you stop it. Validate every path on every command, before you touch storage, and reject anything that resolves outside the memory root:

from pathlib import Path

ROOT = Path("/memories")

def guard(path):
    resolved = (ROOT / Path(path).relative_to("/memories")).resolve()
    if not str(resolved).startswith(str(ROOT.resolve())):
        raise ValueError("path escapes /memories")

Resolve to canonical form, confirm it still lives under /memories, and reject ../, ..\\, and URL-encoded variants like %2e%2e%2f. The demo handlers in Anthropic's docs omit this to stay short; a production handler must not. While you're there, cap file sizes, strip anything sensitive before you write it, and expire files no one has touched in a while.

The memory tool moves the trust boundary into your code. Claude proposes file operations; the security of executing them is entirely yours.

The ten-line version#

If your storage is just files on disk, skip the handler entirely. The Python and TypeScript SDKs ship BetaLocalFilesystemMemoryTool and a tool_runner that drives the whole request/response loop:

from anthropic.tools import BetaLocalFilesystemMemoryTool

memory = BetaLocalFilesystemMemoryTool(base_path="./memory")
runner = client.beta.messages.tool_runner(
    model="claude-opus-5", max_tokens=1024,
    messages=[{"role": "user", "content": "Remember Acme Corp prefers email follow-ups."}],
    tools=[memory],
)
print(runner.until_done().content)

The helpers live in the beta namespace even though the tool is GA. When local files stop being enough, subclass the abstract memory tool and back it with your own database or object store — the model-facing interface is identical, so nothing above the storage layer changes.

Where memory fits#

Memory earns its keep only next to the features that throw context away. Context editing clears old tool results once you cross a token threshold; compaction summarizes the whole conversation server-side as it nears the window. Both drop detail to save tokens. Memory is how the important detail survives: Claude writes the durable fact to a /memories file before the turn that produced it gets cleared or summarized, and reads it back later.

That also answers the question of whether you even need this. If your conversations comfortably fit the window, you probably don't — and if you're evaluating a memory layer on benchmark scores, read the number carefully first. The memory tool isn't an accuracy upgrade. It's a way to run agents past the context window without carrying the whole history — and to keep the persistence, and the security of it, on your side of the wire.