strands-memorysync
MemorySync for Strands Agents (AWS's agent SDK): automatic memory injection before every model call, duplicate-proof turn-by-turn extraction, and a crash-proof lifecycle — with zero framework dependencies.
pip install strands-memorysync
Quick start
from strands import Agent
from strands.memory import MemoryManager
from strands_memorysync import MemorySyncStore
store = MemorySyncStore(
api_key="ms_...", # or MEMORYSYNC_API_KEY
user_id="customer-42", # required — who these memories belong to
session_id="support", # tags the facts from this conversation
)
agent = Agent(memory_manager=MemoryManager(stores=[store]))
agent("which seat should I book?")
The MemoryManager consults the store before every model call
(automatic injection — not tool-gated), and the extraction sink sends the
user's messages to MemorySync, which extracts the durable facts they
contain. The agent's replies are not sent: assistant replies are not
stored as memories.
Why this one
Mem0 (mem0_memory tool in strands-tools) |
Zep (zep-strands 0.1.0) |
MemorySync | |
|---|---|---|---|
| Automatic injection | ✗ tool-gated — the LLM must decide to recall | ✓ | ✓ |
initialize() safety |
— | ✗ shipped an event-loop crash (RuntimeError: bound to a different event loop; fix unreleased) |
✓ deliberately inert — a test asserts zero network calls |
| First-turn recall | — | ✗ 5-turn extraction lag: early recalls return nothing | ✓ turn-by-turn extraction works from the first exchange (idempotent writes make it safe) |
| Failure blast radius | sync client blocks the loop | ✗ store errors surface as unhandled AggregateMemoryError |
✓ search and extraction fail open; only explicit add() raises |
| Model-facing danger | ✗ exposes a delete action — one prompt injection wipes memories |
— | ✓ search + save tools only; identity never model-facing |
| Data at rest | ✗ default FAISS path in /tmp — wiped on restart |
cloud | ✓ MemorySync cloud |
| Framework pin | — | ✗ strands-agents>=1.45 |
✓ zero dependencies (Protocol duck-typing) |
| Retry safety | ✗ duplicates | — | ✓ deterministic idempotency seeds — a retried turn is recognised and not extracted twice |
| Python | — | ✗ ≥3.11 | ✓ ≥3.10 (matches the framework) |
Semantics worth knowing
-
User messages are sent to fact extraction; only durable facts are stored. Each user message in a batch goes to MemorySync as plain text with
role: "user"under thestrands::<session>session scope. The server extracts the durable facts it contains and stores only those, tagged withsession_id: "strands::<session>"; filler such as "ok" or "thanks" yields none, and the message text itself is not stored. Deterministic seeds let the server recognise manager retries and batch overlaps as the same turn and not extract it twice. -
Assistant replies are not sent (they are not stored as memories); tool and system messages never are. A batch with nothing the user said makes no request at all.
-
Turn-by-turn extraction. With the default
extraction=True, Strands extracts every 5 invocations (the synchronousagent(...)entry point also flushes after each call;invoke_asyncdoes not). For turn-by-turn extraction on every path, pass an invocation trigger:from strands.memory import InvocationTrigger store = MemorySyncStore(user_id="customer-42", extraction={"trigger": InvocationTrigger()})
MemorySync's writes are cheap and idempotent, so this is safe, and memories are recallable from the very first exchange.
-
AddMessagesContext.sequence_numberstravel as each user turn'ssequencemetadata, which its facts carry. -
Free-tier quota exhaustion is silent by design (adds accepted-without- storing, reads empty); evaluation keys surface strict
429s. -
store.get_tools()returnsmemorysync_search+memorysync_save(@tool-decorated) — enabled via the MemoryManager's tool config.
Configuration
| Parameter | Default | Meaning |
|---|---|---|
user_id |
— (required) | End user the memories belong to |
session_id |
"default" |
Session scope: the session_id (strands::<session>) carried by the facts from this conversation |
top_k |
5 |
Memories considered per model call |
recall_timeout |
1.2 |
Hard recall budget, seconds |
min_prompt_chars |
8 |
Skip recall for trivial queries |
writable |
True |
Allow the extraction sink to send user messages for fact extraction |
extraction |
True |
Strands extraction wiring; {"trigger": InvocationTrigger()} recommended (see above) |
expose_tools |
True |
Return tools from get_tools() |
Development
pip install -e . strands-agents pytest pytest-asyncio "httpx>=0.25,<1"
python -m pytest tests -q # 26 tests incl. a REAL Agent turn (stub Model)
License
MIT
Metadata
Release files for strands-memorysync 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| strands_memorysync-1.1.0.tar.gz | 18.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| strands_memorysync-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.1 kB
Release files / strands_memorysync-1.1.0.tar.gz
| Download URL | strands_memorysync-1.1.0.tar.gz |
|---|---|
| Size | 18.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e065ad240630bd870bd56bc5cf1e4cfa9ae3ccd7e7ca803c2202c74e6b1e89ab
|
|
BLAKE2b-256 checksum How to use checksums |
8d35059128c26e9afae212d563df753d7443bff49c2bad47f7f3d3338284f32e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|
Release files / strands_memorysync-1.1.0-py3-none-any.whl
| Download URL | strands_memorysync-1.1.0-py3-none-any.whl |
|---|---|
| Size | 13.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7acbd6771f2f9c2786af116a2966d3d989e9ed5739c5c22df6eff07a0e5f31b3
|
|
BLAKE2b-256 checksum How to use checksums |
bf0b41f9a031a2339372edf86a812bacfc2d1cb926ebed13b46fb64bd546bcf2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|