Skip to content

File Memory Store

FileMemoryStore is a MemoryStore that persists memories as markdown files through the SDK’s unified Storage interface. Each topic gets its own .md file, and repeated writes to the same topic append rather than overwrite, so related facts accumulate in one place.

Because the store only talks to the Storage interface, any backend works: local files, S3, in-memory, or a custom backend you write for your own database. See Storage Backend.

Point it at a store name and the default local-file backend handles the rest:

from strands import Agent
from strands.memory import MemoryManager
from strands.vended_memory_stores.file_memory_store import (
FileMemoryStore,
)
store = FileMemoryStore(name="agent-memory")
agent = Agent(
memory_manager=MemoryManager(stores=[store]),
)

The agent can recall from the store right away, but nothing writes to it until you turn on a way to add memories. Pass

add_tool_config=TrueaddToolConfig: true

to the MemoryManager to give the agent the add_memory tool, or set extraction on the store to turn on automatic extraction. You don’t need to set writable: it defaults to true, so the store accepts writes as soon as either is on.

The store writes one markdown file per topic under a memory/<name>/ namespace in storage. When you add content, the first line becomes the filename (slugified, truncated to 50 characters, lowercased). A heading like # Travel preferences produces travel-preferences.md.

If a file with that slug already exists, the store appends the new facts (lines after the heading) to it rather than overwriting it. This keeps related memories grouped by topic without the agent needing to know what already exists.

When the slug has no usable characters (all punctuation, for example), the store falls back to a timestamped name like entry-1717459200000.md.

Most stores need only a name. To control where the files live, pass storage, the Storage backend for file operations. It defaults to LocalFileStorage at ./.strands/ (see Storage Backend).

The store also accepts the shared MemoryStore fields (name, description, max_search_resultsmaxSearchResults, writable, extraction). extraction is off until you set it (see Extraction).

The default LocalFileStorage writes to ./.strands/ relative to the working directory, so a store named agent-memory lands at ./.strands/memory/agent-memory/.

To persist elsewhere, swap the backend: S3Storage for a shared bucket, InMemoryStorage for ephemeral runs, or a custom backend:

from strands.storage import S3Storage
from strands.vended_memory_stores.file_memory_store import (
FileMemoryStore,
)
store = FileMemoryStore(
name="agent-memory",
storage=S3Storage(bucket="my-memory-bucket"),
)

The store scopes everything it reads and writes to the memory/<name>/ path prefix on the backend you pass. The prefix, not the name, is what keeps stores apart: the store lists and searches every file under it, subfolders included.

  • Give each store a distinct name without /. A store named team reads and searches the files of a store named team/alice, because they sit under its prefix.
  • Stores with the same name on the same backend read and write the same files, and their writes aren’t coordinated (see Scale and Limitations).
  • Don’t use an empty name. It scopes the store to memory/ itself, so it reads and searches every named store’s files on that backend.

The exception is a view you have already scoped with a built-in backend’s namespace(...). The store uses that view as-is and does not add memory/<name>/, so its files land directly under your prefix. Every store you hand that same view shares its files regardless of name, so give each store its own namespaced view.

To write a memory directly, call add. It stores the content and returns the file’s key, relative to the store’s memory/<name>/ namespace. The heading line drives the filename; subsequent lines are the facts:

from strands.vended_memory_stores.file_memory_store import (
FileMemoryStore,
)
store = FileMemoryStore(name="agent-memory")
key = await store.add(
"# Travel preferences\n- Prefers aisle seats"
)
print(key) # travel-preferences.md
results = await store.search("aisle seats")
for entry in results:
print(entry.content, entry.metadata.get("score"))

The store serializes concurrent writes through one instance so they don’t clobber each other.

The default search ranks files by keyword token overlap: it counts how many distinct words from the query appear in each file’s name and content, and returns the top scorers. Common stop words such as “the” and “is” don’t count, and neither does the .md extension. A query with no usable words returns nothing.

The store’s search delegates to the storage backend’s pluggable search strategy, so to change the ranking, give the backend a different strategy:

The backend indexes each write through its strategy. Bm25SearchStrategy ranks with BM25 using the built-in sqlite3 FTS5 module. It only indexes content written after you attach it, so attach it before the store’s first write:

from strands.storage import LocalFileStorage
from strands.storage.search import Bm25SearchStrategy
from strands.vended_memory_stores.file_memory_store import (
FileMemoryStore,
)
strategy = Bm25SearchStrategy()
store = FileMemoryStore(
name="agent-memory",
storage=LocalFileStorage(search_strategy=strategy),
)
results = await store.search("aisle seats")
await strategy.close()

See BM25 search for setup details, or write a custom strategy by implementing the SearchStrategy interface for your own ranking. Keep a reference to the strategy so you can call close() on it: the store does not close it for you.

Enable automatic extraction to capture memories from the conversation without the agent calling a tool:

from strands.vended_memory_stores.file_memory_store import (
FileMemoryStore,
)
store = FileMemoryStore(
name="agent-memory",
extraction=True,
)

With extraction=Trueextraction: true, the store creates a key-aware extractor that reads the existing topic filenames from storage and injects them into the extraction prompt. The model then reuses those headings when new facts belong to an existing topic, keeping related memories grouped instead of fragmenting across files.

To change only the cadence or filter, pass an ExtractionConfig with trigger or filter and no extractor; the store keeps the key-aware extractor.

To steer what the extractor captures, pass an ExtractionConfig with a custom ModelExtractor:

from strands.models import BedrockModel
from strands.memory.extraction.model_extractor import (
ModelExtractor,
)
from strands.memory.extraction.types import ExtractionConfig
from strands.vended_memory_stores.file_memory_store import (
FileMemoryStore,
)
store = FileMemoryStore(
name="agent-memory",
extraction=ExtractionConfig(
extractor=ModelExtractor(
model=BedrockModel(
model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0",
),
system_prompt=(
"Extract durable user preferences. Start each"
" entry with a markdown heading naming its topic."
),
),
),
)

Your system_prompt replaces the extractor’s default guidance; the extractor still appends the JSON output format it parses. A custom extractor replaces the key-aware one, though, so the store no longer adds its heading layout or the existing topics to the prompt. The store names each file after the first line of an entry, so to keep facts grouped by topic, have your prompt start each entry with a markdown heading naming its topic.

The store implements add but not add_messagesaddMessages, so extraction runs client-side: a ModelExtractor distills facts from the conversation and writes each through add.

The store suits working memory for a single agent or a small team: hundreds to low thousands of files. The default keyword search lists and reads every file under the store’s prefix on each search, so search cost grows with the number and size of files. Each add reads the target file and rewrites it, so write throughput depends on the storage backend.

Write locking is per store instance. Separate instances writing the same files, whether through the same name or the same namespaced view, in one process or several, are not coordinated, and the last write wins. To share memories between agents in one process, hand them the same store instance.

  • Memory - the MemoryManager concept this store plugs into, including the tools, extraction, and injection it enables.
  • Test memory store - the zero-setup vended MemoryStore backed by a local JSON file, for prototyping and tests.
  • Bedrock Knowledge Base store - the managed MemoryStore with semantic search, for production workloads.