memory-layer
A DynamoDB-backed BaseStore for LangGraph — cross-thread memory for your agents without standing up Postgres/pgvector.
LangGraph's checkpointer persists state inside a thread — when a user opens a new conversation, the graph starts from zero. The store protocol is LangGraph's answer to that: memory that survives across threads, shared by every session for the same user (or the same tenant). LangGraph ships an official store for Postgres. If your stack is already DynamoDB — which a lot of serverless/Fargate deployments are — there wasn't an official option. memory-layer is that option.
from memory_layer import DynamoDBStore
store = DynamoDBStore(table_name="my-app-memories")
graph = builder.compile(checkpointer=checkpointer, store=store)
That's the whole integration. No new infra beyond one DynamoDB table you probably already know how to provision.
Why this exists
- You're already on DynamoDB. Adding Postgres + pgvector just for agent memory is a real infra cost — a new engine, a new backup story, a new thing to monitor — for a feature that, for most products, doesn't need vector search on day one.
- LangGraph's
storeprotocol is a clean seam. It's designed so storage is swappable — your agent code shouldn't care whether memories live in Postgres, Dynamo, or Redis. This fills the Dynamo gap in that seam. - Memory doesn't have to mean embeddings. Most products get real value from "the last N things we know about this user," fetched by recency — no vector index required.
memory-layerstarts there, and gives you a clean place to add semantic ranking later if you actually need it.
Features
DynamoDBStore— a completelanggraph.store.base.BaseStoreimplementation:get/put/search(and their async counterparts), real pagination via DynamoDB'sLastEvaluatedKey(not a "hope the first page has enough" heuristic), per-item filtering, and per-type TTL (e.g. auto-expireepisodicmemories after 90 days whilesemanticones never expire).SimpleRetrieval— fetch a user's N most recent memories and turn them into a ready-to-inject prompt block. No embeddings, no extra dependencies.MemoryWriter— an LLM-driven extraction step: hand it a conversation, it classifies and persists the facts worth remembering, viawith_structured_output(a real schema-validated response, not a hand-rolled JSON parser hoping the model didn't wrap the array in a sentence).- Scopes, not just users. Namespaces are plain tuples (
("user", user_id),("instance", tenant_id)) — model per-user memory, per-tenant shared context, or your own scope, however your product actually shapes ownership.
Install
pip install langgraph-dynamodb-store
Want LLM-driven extraction? MemoryWriter takes any LangChain BaseChatModel — bring the one you already use, no extra install needed. numpy/langchain-openai are only required for the semantic-retrieval extra (see Roadmap):
pip install "langgraph-dynamodb-store[semantic]"
DynamoDB table
One table, one GSI. Create it however you provision infra (CDK/Terraform/console) — here's the raw shape via the AWS CLI, if you just want to try it out:
aws dynamodb create-table \
--table-name my-app-memories \
--attribute-definitions \
AttributeName=owner_id,AttributeType=S \
AttributeName=memory_id,AttributeType=S \
AttributeName=created_at,AttributeType=S \
--key-schema \
AttributeName=owner_id,KeyType=HASH \
AttributeName=memory_id,KeyType=RANGE \
--global-secondary-indexes '[{
"IndexName": "owner_id-created_at-index",
"KeySchema": [
{"AttributeName": "owner_id", "KeyType": "HASH"},
{"AttributeName": "created_at", "KeyType": "RANGE"}
],
"Projection": {"ProjectionType": "ALL"}
}]' \
--billing-mode PAY_PER_REQUEST
# Optional but recommended — lets episodic memories actually expire instead of
# accumulating forever. memory-layer sets the `ttl` attribute; DynamoDB does the rest.
aws dynamodb update-time-to-live \
--table-name my-app-memories \
--time-to-live-specification "Enabled=true, AttributeName=ttl"
Set MEMORY_TABLE=my-app-memories or pass table_name explicitly — DynamoDBStore(table_name="my-app-memories").
Quickstart
from memory_layer import DynamoDBStore
store = DynamoDBStore(table_name="my-app-memories")
namespace = ("user", "user-123")
store.put(namespace, "mem-1", {"content": "Prefers responses in Spanish", "type": "semantic"})
store.put(namespace, "mem-2", {"content": "Reviewed Q3 numbers on 2026-08-01", "type": "episodic"})
memories = store.search(namespace, limit=10)
for m in memories:
print(m.value["content"])
Inside a LangGraph node
LangGraph injects store into any node whose signature asks for it:
from langgraph.store.base import BaseStore
from langchain_core.runnables import RunnableConfig
from memory_layer import DynamoDBStore
from memory_layer.retrieval import SimpleRetrieval
store = DynamoDBStore(table_name="my-app-memories")
graph = builder.compile(checkpointer=checkpointer, store=store)
def supervisor_node(state: AgentState, config: RunnableConfig, store: BaseStore) -> dict:
user_id = state["context"]["user_id"]
retrieval = SimpleRetrieval(store, limit=5)
memories = retrieval.fetch(("user", user_id))
memory_block = retrieval.to_prompt_block(memories)
# ...inject memory_block into the system prompt
return {}
Writing memories back
from memory_layer.writer import MemoryWriter
writer = MemoryWriter(llm=your_chat_model, store=store)
async def memory_writer_node(state: AgentState) -> dict:
await writer.extract_and_save(
namespace=("user", state["context"]["user_id"]),
messages=state["messages"],
session_id=state["context"]["session_id"],
)
return {}
The extraction LLM only needs with_structured_output support — every major provider's LangChain integration has it.
Memory types
| Type | What it's for | Default TTL |
|---|---|---|
semantic |
Stable preferences and facts ("prefers Spanish", "works on the Acme account") | none |
episodic |
Specific past events ("reviewed Q3 numbers on 2026-08-01") | 90 days |
procedural |
Recurring work patterns ("always starts with a channel breakdown") | none |
TTL policy per type lives in memory_layer.store._TTL_SECONDS_BY_TYPE — override it if 90 days isn't the right default for your product.
Scopes
A namespace is just a tuple — memory-layer doesn't prescribe what it means, but the common shapes are:
("user", cognito_sub) # private to one user
("instance", tenant_id) # shared across every user of one tenant
Roadmap
- Semantic retrieval — embed memories + query, rank by cosine similarity, for products that outgrow "most recent N" (roughly ~20+ memories per user is where this starts to matter). Lives behind the
semanticextra so the core library stays dependency-light. - Deduplication on write — skip persisting a fact that's a near-duplicate of one already stored.
- Bring-your-own embeddings backend — DynamoDB-native cosine similarity to start; pluggable enough to swap in pgvector/a real vector store later if volume ever justifies it.
Development
pip install -e ".[dev]"
pytest
Tests run against moto — no real AWS account or network access required.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langgraph_dynamodb_store-0.1.0.tar.gz.
File metadata
- Download URL: langgraph_dynamodb_store-0.1.0.tar.gz
- Upload date:
- Size: 13.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a7417a16f2cd5b05448311b4c80cda8f2f9707afec5b55b03af5bbc896e06e7
|
|
| MD5 |
8327db2cdd61fb8bafd48caba24a0a39
|
|
| BLAKE2b-256 |
700c62e8f4aa4b10e7df2b03740a0a162b871967f2dc0b4e23b8cca4792dd56c
|
Provenance
The following attestation bundles were made for langgraph_dynamodb_store-0.1.0.tar.gz:
Publisher:
publish.yml on mauriciosneira/memory-layer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langgraph_dynamodb_store-0.1.0.tar.gz -
Subject digest:
0a7417a16f2cd5b05448311b4c80cda8f2f9707afec5b55b03af5bbc896e06e7 - Sigstore transparency entry: 2501938520
- Sigstore integration time:
-
Permalink:
mauriciosneira/memory-layer@8927350bedf4ef330af5fb9d5b394d1c217c4471 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mauriciosneira
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8927350bedf4ef330af5fb9d5b394d1c217c4471 -
Trigger Event:
release
-
Statement type:
File details
Details for the file langgraph_dynamodb_store-0.1.0-py3-none-any.whl.
File metadata
- Download URL: langgraph_dynamodb_store-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45936c1106948e4f87e57434b6be5b3ebeba207aa5ece5e74cf63c977478620a
|
|
| MD5 |
2d65cf39002729bd56f07feb4cc83c8e
|
|
| BLAKE2b-256 |
fa76010ad478b8c005e847db8a95655ace4150f3e7fe12701a7cf97368df78a4
|
Provenance
The following attestation bundles were made for langgraph_dynamodb_store-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on mauriciosneira/memory-layer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langgraph_dynamodb_store-0.1.0-py3-none-any.whl -
Subject digest:
45936c1106948e4f87e57434b6be5b3ebeba207aa5ece5e74cf63c977478620a - Sigstore transparency entry: 2501938619
- Sigstore integration time:
-
Permalink:
mauriciosneira/memory-layer@8927350bedf4ef330af5fb9d5b394d1c217c4471 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mauriciosneira
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8927350bedf4ef330af5fb9d5b394d1c217c4471 -
Trigger Event:
release
-
Statement type: