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 Agentfrom strands.memory import MemoryManagerfrom strands.vended_memory_stores.file_memory_store import ( FileMemoryStore,)
store = FileMemoryStore(name="agent-memory")
agent = Agent( memory_manager=MemoryManager(stores=[store]),)import { Agent, BedrockModel, MemoryManager } from '@strands-agents/sdk'import { FileMemoryStore } from '@strands-agents/sdk/vended-memory-stores/file-memory-store'
const store = new FileMemoryStore({ name: 'agent-memory' })
const agent = new Agent({ model: new BedrockModel(), memoryManager: new 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: trueto 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.
How It Works
Section titled “How It Works”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.
Configuration
Section titled “Configuration”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_resultsmaxSearchResultswritable, extraction). extraction is off until you
set it (see Extraction).
Storage Backend
Section titled “Storage Backend”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 S3Storagefrom strands.vended_memory_stores.file_memory_store import ( FileMemoryStore,)
store = FileMemoryStore( name="agent-memory", storage=S3Storage(bucket="my-memory-bucket"),)import { FileMemoryStore } from '@strands-agents/sdk/vended-memory-stores/file-memory-store'import { S3Storage } from '@strands-agents/sdk/storage'
const store = new FileMemoryStore({ name: 'agent-memory', storage: new S3Storage('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 namedteamreads and searches the files of a store namedteam/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 tomemory/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.
Writing Memories
Section titled “Writing Memories”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"))import { FileMemoryStore } from '@strands-agents/sdk/vended-memory-stores/file-memory-store'
const store = new FileMemoryStore({ name: 'agent-memory' })
const key = await store.add('# Travel preferences\n- Prefers aisle seats')console.log(key) // travel-preferences.md
const results = await store.search('aisle seats')for (const entry of results) { console.log(entry.content, entry.metadata?.score)}The store serializes concurrent writes through one instance so they don’t clobber each other.
Search
Section titled “Search”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 LocalFileStoragefrom strands.storage.search import Bm25SearchStrategyfrom 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()QmdSearchStrategy ranks with BM25 using
QMD (requires
@tobilu/qmd) and re-indexes the storage directory on
each search, so existing files are searchable:
import { FileMemoryStore } from '@strands-agents/sdk/vended-memory-stores/file-memory-store'import { LocalFileStorage } from '@strands-agents/sdk/storage'import { QmdSearchStrategy } from '@strands-agents/sdk/storage/search/qmd'
const strategy = new QmdSearchStrategy()const store = new FileMemoryStore({ name: 'agent-memory', storage: new LocalFileStorage('./.strands/', { searchStrategy: strategy, }),})
// ... use the store ...
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.
Extraction
Section titled “Extraction”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,)import { FileMemoryStore } from '@strands-agents/sdk/vended-memory-stores/file-memory-store'
const store = new FileMemoryStore({ name: 'agent-memory', extraction: true,})With extraction=Trueextraction: true
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 BedrockModelfrom strands.memory.extraction.model_extractor import ( ModelExtractor,)from strands.memory.extraction.types import ExtractionConfigfrom 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.
Pass these fields in the extraction config to customize the built-in extractor without replacing it:
model: the model that distills facts. Defaults to the agent’sauxModelif set, otherwise the agent’s own model; set a cheaper one to cut cost.systemPrompt: what counts as a durable fact. It replaces the default guidance; the store still appends its heading layout and the existing topics, and the extractor appends the JSON output format.
import { BedrockModel } from '@strands-agents/sdk'import { FileMemoryStore } from '@strands-agents/sdk/vended-memory-stores/file-memory-store'
const store = new FileMemoryStore({ name: 'agent-memory', extraction: { model: new BedrockModel({ modelId: 'us.anthropic.claude-haiku-4-5-20251001-v1:0', }), systemPrompt: 'Extract durable user preferences as discrete facts.', },})The store implements add but not
add_messagesaddMessagesModelExtractor distills
facts from the conversation and writes each through add.
Scale and Limitations
Section titled “Scale and Limitations”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.
Related
Section titled “Related”- Memory - the
MemoryManagerconcept this store plugs into, including the tools, extraction, and injection it enables. - Test memory store - the
zero-setup vended
MemoryStorebacked by a local JSON file, for prototyping and tests. - Bedrock Knowledge Base store -
the managed
MemoryStorewith semantic search, for production workloads.