Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions site/src/config/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ sidebar:
items:
- label: "Overview"
slug: docs/user-guide/concepts/memory/overview
- docs/user-guide/concepts/memory/test-memory-store
- docs/user-guide/concepts/memory/bedrock-knowledge-base
- docs/user-guide/concepts/agents/retry-strategies
- label: Sandbox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ sidebar:

By default a Strands agent starts every conversation from zero: it cannot recall a user's preferences, past decisions, or anything it learned in an earlier session. The `MemoryManager` gives an agent long-term memory that persists across sessions.

It works through **memory stores**, the backends that hold the memories. A store can be a vector database, a managed service like [Amazon Bedrock Knowledge Bases](./bedrock-knowledge-base), or [your own implementation](#custom-stores). The manager handles three jobs across the stores you give it:
It works through **memory stores**, the backends that hold the memories. A store can be the built-in zero-setup [test store](./test-memory-store), a managed service like [Amazon Bedrock Knowledge Bases](./bedrock-knowledge-base), or [your own implementation](#custom-stores). The manager handles three jobs across the stores you give it:

1. **Recall** - the agent searches stored knowledge on demand through a tool.
2. **Injection** - the manager folds relevant knowledge into the prompt automatically, before the model runs.
Expand All @@ -25,14 +25,18 @@ Recall and injection are enabled by default when you attach a store. Extraction

## Getting Started

Attach a memory manager to an agent through the <Syntax py="memory_manager" ts="memoryManager" /> parameter. The examples below use a `store`, a `MemoryStore` you provide; see [Bedrock Knowledge Base](./bedrock-knowledge-base) for a managed backend or [Custom Stores](#custom-stores) to create your own.
Attach a memory manager to an agent through the <Syntax py="memory_manager" ts="memoryManager" /> parameter. The [test store](./test-memory-store) below needs no cloud account and persists to disk by default, so this agent remembers across restarts with no setup. For a managed backend see [Bedrock Knowledge Base](./bedrock-knowledge-base), or [Custom Stores](#custom-stores) to create your own.

<Tabs>
<Tab label="Python">

```python
from strands import Agent
from strands.memory import MemoryManager
from strands.vended_memory_stores.test_memory_store import TestMemoryStore

# Persists to ~/.strands/memory/notes.json by default.
store = TestMemoryStore(name="notes")

agent = Agent(memory_manager=MemoryManager(stores=[store]))
```
Expand Down Expand Up @@ -500,6 +504,7 @@ Three SDK features manage different kinds of state; memory is the one that cross

## Related

- [Test memory store](./test-memory-store) - the zero-setup vended `MemoryStore` backed by a local JSON file, for prototyping and testing.
- [Bedrock Knowledge Base store](./bedrock-knowledge-base) - the vended `MemoryStore` backed by Amazon Bedrock Knowledge Bases.
- [Context Injector](../plugins/context-injector) - the generic injection plugin that memory injection builds on.
- [Session management](../agents/session-management) - persist the conversation itself across restarts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
BedrockKnowledgeBaseStore,
type BedrockKnowledgeBaseConfig,
} from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
import { TestMemoryStore } from '@strands-agents/sdk/vended-memory-stores/test-memory-store'

// Stand-in for the reader's own managed backend, used by the server-side store example.
declare const myBackend: {
Expand All @@ -32,12 +33,8 @@ declare const myBackend: {

function gettingStarted() {
// --8<-- [start:getting_started]
const store = new BedrockKnowledgeBaseStore({
name: 'preferences',
description: 'User preferences and stable facts about the user.',
writable: true,
config: { knowledgeBaseId: 'KB123', dataSourceType: 'CUSTOM', dataSourceId: 'DS456' },
})
// Persists to ~/.strands/memory/notes.json by default.
const store = new TestMemoryStore({ name: 'notes' })

const agent = new Agent({
model: new BedrockModel(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// --8<-- [start:getting_started_imports]
import { Agent, BedrockModel } from '@strands-agents/sdk'
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
import { TestMemoryStore } from '@strands-agents/sdk/vended-memory-stores/test-memory-store'
// --8<-- [end:getting_started_imports]

// --8<-- [start:turn_on_writes_imports]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
---
title: Test Memory Store
description: "A zero-infrastructure memory store backed by a local JSON file: no cloud account, lexical recall, and memories that survive across sessions."
tags: [memory]
sourceLinks:
- path: strands-py/src/strands/vended_memory_stores/test_memory_store/store.py
- path: strands-ts/src/vended-memory-stores/test-memory-store/store.ts
sidebar:
label: "Test Store"
badge:
text: New
variant: tip
---

`TestMemoryStore` is a [`MemoryStore`](./overview#stores) backed by a JSON file on the
local machine. It needs no cloud account and no provisioned resources, so you can give an
agent memory in one line and try recall, injection, and extraction while prototyping,
working offline, or writing tests, before committing to a managed backend.

It persists to disk by default, so an agent remembers across restarts with no setup. Point
it at a store name and go:

<Tabs>
<Tab label="Python">

```python
from strands import Agent
from strands.memory import MemoryManager
from strands.vended_memory_stores.test_memory_store import TestMemoryStore

# Persists to ~/.strands/memory/notes.json by default. Survives restarts.
store = TestMemoryStore(name="notes")

agent = Agent(memory_manager=MemoryManager(stores=[store]))
```
</Tab>
<Tab label="TypeScript">

```typescript
--8<-- "user-guide/concepts/memory/test-memory-store_imports.ts:basic_imports"

--8<-- "user-guide/concepts/memory/test-memory-store.ts:basic"
```
</Tab>
</Tabs>

The store is writable by default, unlike a read-only managed store. So the [`add_memory`
tool](./overview#memory-tools) and [automatic extraction](./overview#automatic-extraction)
work as soon as you enable them, with no data source to set up first.

It suits prototyping and tests, not a production corpus: each write rewrites the whole
file and recall is keyword matching rather than semantic. For a production backend, use
the [Bedrock Knowledge Base store](./bedrock-knowledge-base).

## Persistence and location

The store writes to `~/.strands/memory/<store-name>.json`, deriving the filename from
`name`. Give it an explicit `path` to control where the file lands, or turn persistence
off for a store that lives only in memory:

<Tabs>
<Tab label="Python">

```python
from strands.vended_memory_stores.test_memory_store import TestMemoryStore

# Ephemeral: nothing is written to disk, and a fresh instance forgets everything.
scratch = TestMemoryStore(name="notes", persist=False)

# Explicit file location instead of the default under ~/.strands/memory/.
project = TestMemoryStore(name="notes", path="./notes.json")
```
</Tab>
<Tab label="TypeScript">

```typescript
--8<-- "user-guide/concepts/memory/test-memory-store_imports.ts:persistence_imports"

--8<-- "user-guide/concepts/memory/test-memory-store.ts:persistence"
```
</Tab>
</Tabs>

Reach for `persist=False` in tests and throwaway runs where you want recall and ranking
without leaving a file behind. The default (persist to disk) is what demonstrates the
feature: memory that survives a restart.

The on-disk format is shared between the Python and TypeScript SDKs. Records use the same
keys and timestamp shape, so a file written by one SDK reads in the other.

## Configuration

`TestMemoryStore` takes the [shared `MemoryStore` fields](./overview#stores) (`name`,
`description`, <Syntax py="max_search_results" ts="maxSearchResults" />, `writable`,
`extraction`) plus two of its own:

| Field | Purpose |
|-------|---------|
| `persist` | Whether to write entries to disk. `True` (default) flushes to `path`; `False` keeps entries in memory only, lost when the process exits. |
| `path` | Full path to the backing JSON file. Defaults to `~/.strands/memory/<store-name>.json`. Ignored when `persist` is `False`. |

`writable` defaults to `True` here. Construction rejects an empty `name`, an empty `path`,
or a <Syntax py="max_search_results" ts="maxSearchResults" /> below `1`.

## Recall

`search` ranks entries by lexical overlap: it counts how many distinct words from the
query appear in each entry's content, and returns the highest scorers first, breaking ties
toward the most recent entry. Each result carries the count under a reserved
`_relevanceScore` metadata key. A query with no usable words returns nothing.

:::note
Recall is keyword matching, not semantic search. A query word matches only the same word,
not a synonym, so "seat" does not find an entry about "chair." For embedding-based
semantic search over a managed vector store, use the [Bedrock Knowledge Base
store](./bedrock-knowledge-base).
:::

## Writing memories

`add` stores a single piece of content and returns its id. Identical content is
deduplicated: a repeat write returns the existing record's id instead of storing a second
copy, so the at-least-once retries that extraction may perform never accumulate
duplicates.

<Tabs>
<Tab label="Python">

```python
from strands.vended_memory_stores.test_memory_store import TestMemoryStore

store = TestMemoryStore(name="notes")

# add returns the id of the stored (or already-present, on dedup) record.
result = await store.add("User prefers aisle seats", {"category": "travel"})
print(result.id)

results = await store.search("what seat does the user prefer?")
for entry in results:
print(entry.content, entry.metadata.get("_relevanceScore"))
```
</Tab>
<Tab label="TypeScript">

```typescript
--8<-- "user-guide/concepts/memory/test-memory-store_imports.ts:search_and_add_imports"

--8<-- "user-guide/concepts/memory/test-memory-store.ts:search_and_add"
```
</Tab>
</Tabs>

Writing to a store constructed with <Syntax py="writable=False" ts="writable: false" />,
or adding empty content, raises. When the backing directory is unreachable or not
writable, the write raises with the target path.

## Extraction

Enable [automatic extraction](./overview#automatic-extraction) to capture memories from
the conversation without the agent calling a tool:

<Tabs>
<Tab label="Python">

```python
from strands.vended_memory_stores.test_memory_store import TestMemoryStore

store = TestMemoryStore(
name="notes",
extraction=True, # distill facts from the conversation, every 5 turns
)
```
</Tab>
<Tab label="TypeScript">

```typescript
--8<-- "user-guide/concepts/memory/test-memory-store_imports.ts:extraction_imports"

--8<-- "user-guide/concepts/memory/test-memory-store.ts:extraction"
```
</Tab>
</Tabs>

The store implements <Syntax py="add" ts="add" /> but not <Syntax py="add_messages"
ts="addMessages" />, so extraction runs client-side: a `ModelExtractor` distills facts
from the conversation and writes each through `add`. To change the cadence or swap the
extractor, see [Automatic Extraction](./overview#automatic-extraction) on the Memory page.

## Scale and limitations

The store keeps every entry in memory and rewrites the whole file on each `add`, using an
atomic write so a crash mid-write never leaves a partial file. That design suits
prototyping and personal memory, hundreds to low thousands of entries, not a production
corpus. For large or high-throughput workloads, use a managed store like the [Bedrock
Knowledge Base store](./bedrock-knowledge-base).

Writes within a single process are serialized, so concurrent `add` calls can't clobber one
another. Writers across separate processes are not coordinated, so avoid pointing two
processes at the same file. A corrupt or wrong-shaped backing file raises with the path so
the problem is visible; a missing file starts empty.

## Related

- [Memory](./overview) - the `MemoryManager` concept this store plugs into, including the
tools, extraction, and injection it enables.
- [Bedrock Knowledge Base store](./bedrock-knowledge-base) - the managed `MemoryStore`
with semantic search, for production workloads.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Agent, BedrockModel } from '@strands-agents/sdk'
import { TestMemoryStore } from '@strands-agents/sdk/vended-memory-stores/test-memory-store'

// =====================
// Basic: persists to disk by default
// =====================

function basic() {
// --8<-- [start:basic]
// Persists to ~/.strands/memory/notes.json by default. Survives restarts.
const store = new TestMemoryStore({ name: 'notes' })

const agent = new Agent({
model: new BedrockModel(),
memoryManager: { stores: [store] },
})
// --8<-- [end:basic]

void agent
}
void basic

// =====================
// Persistence: ephemeral and explicit path
// =====================

function persistence() {
// --8<-- [start:persistence]
// Ephemeral: nothing is written to disk, and a fresh instance forgets everything.
const scratch = new TestMemoryStore({ name: 'notes', persist: false })

// Explicit file location instead of the default under ~/.strands/memory/.
const project = new TestMemoryStore({ name: 'notes', path: './notes.json' })
// --8<-- [end:persistence]

void scratch
void project
}
void persistence

// =====================
// Search and add
// =====================

async function searchAndAdd() {
// --8<-- [start:search_and_add]
const store = new TestMemoryStore({ name: 'notes' })

// add returns the id of the stored (or already-present, on dedup) record.
const { id } = await store.add('User prefers aisle seats', { category: 'travel' })

const results = await store.search('what seat does the user prefer?')
for (const entry of results) {
console.log(entry.content, entry.metadata?._relevanceScore)
}
// --8<-- [end:search_and_add]

void id
}
void searchAndAdd

// =====================
// Extraction
// =====================

function extraction() {
// --8<-- [start:extraction]
const store = new TestMemoryStore({
name: 'notes',
extraction: true, // distill facts from the conversation, every 5 turns
})
// --8<-- [end:extraction]

void store
}
void extraction
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @ts-nocheck

// --8<-- [start:basic_imports]
import { Agent, BedrockModel } from '@strands-agents/sdk'
import { TestMemoryStore } from '@strands-agents/sdk/vended-memory-stores/test-memory-store'
// --8<-- [end:basic_imports]

// --8<-- [start:persistence_imports]
import { TestMemoryStore } from '@strands-agents/sdk/vended-memory-stores/test-memory-store'
// --8<-- [end:persistence_imports]

// --8<-- [start:search_and_add_imports]
import { TestMemoryStore } from '@strands-agents/sdk/vended-memory-stores/test-memory-store'
// --8<-- [end:search_and_add_imports]

// --8<-- [start:extraction_imports]
import { TestMemoryStore } from '@strands-agents/sdk/vended-memory-stores/test-memory-store'
// --8<-- [end:extraction_imports]
Loading