Once you've built the memory-tool handler, the interesting decision is the one the docs leave open in a single clause: /memories is "a prefix that your handler maps onto real storage, such as a per-user directory or keys in a database." The model thinks in file paths; you decide what a path means. That mapping — disk, object storage, or database — is not a detail. It sets whether a user's memory survives a redeploy, whether two instances see the same files, and whether you can put regulated data behind it.

Here's the fast answer, then the reasoning.

Prototyping on one machine: local disk. A real multi-user agent: object storage with a per-user key prefix. Memory that must be transactional, queryable, or compliance-governed: a database. Whatever you pick, one isolated store per user — never a shared one.

Local disk: right for a demo, wrong for a fleet#

The SDK's BetaLocalFilesystemMemoryTool writes real files under a base path, and for a single-user agent on one box it's perfect: lowest possible latency, nothing to provision, and the model's flat /memories namespace maps one-to-one onto a directory. This is the filesystem-memory model at its most literal — legible, greppable files you can open and read.

It breaks on one word: durable. An ephemeral or autoscaled container's disk is not permanent — a redeploy wipes it, and the moment you run a second replica, instance B can't see the files instance A wrote. Local disk ties a user's memory to a single machine's lifetime, which is exactly the assumption a production agent violates. Use it to build; don't ship a fleet on it.

Object storage: the default for a multi-user agent#

For most production agents, memory belongs in S3 or GCS, with each user's files living under a key prefix like agent-memory/<user_id>/. This is the backend that matches how the tool actually works. The memory namespace is flat and file-shaped; an object store is a flat, file-shaped key space, so /memories/notes.txt becomes the key <user_id>/notes.txt with almost no impedance. And because the store is external, any stateless instance can serve any user — the property local disk can't give you.

The tax is latency: every view, create, and str_replace is a network round-trip of a few milliseconds rather than a memory-mapped read. In practice you hide it with a small write-through cache keyed on the user, so a burst of view calls in one turn doesn't become a burst of GETs. It's the same cost-of-round-trips discipline every agent backend eventually learns. Durability, isolation, and cost all land on the right side; this is the boring, correct default.

A database: when memory has to do more than persist#

Reach past object storage when memory files aren't just persisted but governed. Store each file as a row (or a JSONB blob) keyed by user and path, and you buy three things a bucket doesn't hand you cleanly: transactions (a rename that's atomic with a str_replace), queryability (find every memory file mentioning a project without listing a prefix), and row-level access control tied to the rest of your app's data. Under compliance constraints — per-user encryption at rest, audited access, retention policy enforced by the store — a database (or KMS-encrypted blobs over any store) is the honest answer, because the files live in your infrastructure and inherit your posture. Anthropic never holds them.

The cost is a mapping layer: an ORM or query sits between the model's /memories/... path and your schema, and you own keeping the two coherent. That's real work, and it's only worth it when the governance is a requirement rather than a nicety.

The one rule that outranks the choice#

Whichever backend you pick, the isolation boundary is not negotiable, and it lives in exactly one place: the function that resolves a /memories/... path to real storage. One directory, one key prefix, one tenant column per user — enforced there, so every one of the six commands inherits it and no command can reach another user's data. This is the same instinct as the path-traversal guard: the model's inputs are attacker-influenceable, so the boundary can't be advisory. Get the isolation right in that single function and the backend becomes a swappable implementation detail — which is the point. Start on disk, move to object storage when you have more than one user, reach for a database when memory has to answer to a compliance team, and pair all of it with context editing so the window stays small while the store holds what has to survive.