Memory in Frameworks & at the Limits
Strands integration, the batching trap, and quotas
- Integrate AgentCore Memory into a Strands agent with AgentCoreMemoryConfig and AgentCoreMemorySessionManager passed as the Agent session_manager
- Explain the batching gotcha and guarantee no buffered messages are lost by using a with-block or calling close()
- Implement the LangGraph pattern by calling create_event and retrieve_memories from graph nodes, cross-checking the official samples
- Operate Memory inside its key quotas — the 6-strategy cap, retention bounds, message-per-event limit, and per-operation TPS
- Account for the per-event, per-record-per-month, and per-retrieval pricing dimensions, plus the extra Bedrock model charges that overrides and self-managed strategies incur
AgentCore Memory plugs into agent frameworks so your agent reads and writes long-lived context without hand-rolling the data plane. This lesson wires Memory into Strands via AgentCoreMemoryConfig + AgentCoreMemorySessionManager (and exposes the batching trap that silently drops buffered messages), shows the LangGraph pattern of calling create_event/retrieve_memories from graph nodes, and walks the quotas, retention, and pricing dimensions you must operate inside — all of which are volatile, so verify them against the live AWS pages.
- 1How Memory plugs into a framework
- 2Strands integration: AgentCoreMemoryConfig + AgentCoreMemorySessionManager
- 3The batching trap: lose your context with batch_size > 1
- 4LangGraph: call the data plane from your nodes
- 5Operating at the limits: the quotas that constrain design
- 6Pricing dimensions and watching token spend
How Memory plugs into a framework
By this point you've driven AgentCore Memory directly: create_event to write a turn, retrieve_memories to pull back long-term records, the MemorySessionManager/MemorySession helpers from the Python SDK. Frameworks don't replace any of that — they wire those same data-plane calls into the agent loop so you stop threading session state by hand.
The mental model is unchanged from the SDK lessons:
- Short-term = raw turn-by-turn events you write with
CreateEvent; immutable, scoped byactorId+sessionId. - Long-term = insights the service extracts asynchronously via the strategies you attached, retrieved with
RetrieveMemoryRecords. - No strategies = only short-term events; nothing gets extracted.
What a framework integration adds is the plumbing: on each turn it persists the user/assistant messages as an event and, before the model runs, retrieves relevant long-term records and injects them into context. AgentCore ships a first-party integration for Strands (the AWS-native SDK) that does exactly this through a session manager. For other frameworks like LangGraph, there is no confirmed first-party module — you call the same data-plane methods yourself from inside your nodes.
turn arrives ─▶ retrieve_memories (long-term context)
─▶ model call
─▶ create_event (persist the new turn)
─▶ async extraction runs later → memory recordsKeep the eventual-consistency rule from the SDK lesson in mind: extraction is asynchronous, so a retrieve_memories fired immediately after the matching create_event may return nothing until the strategy pipeline catches up.
Note
The integration is a convenience, not a new capability
Everything a framework session manager does, you could do with raw MemoryClient / MemorySessionManager calls. The value is that it hooks into the framework's turn lifecycle for you. That also means the gotchas below are about when the integration flushes its writes — not about a different storage model.
Strands integration: AgentCoreMemoryConfig + AgentCoreMemorySessionManager
The Strands integration lives in bedrock_agentcore.memory.integrations.strands. You build a config object that binds a memory resource to one actor/session, wrap it in a session manager, and hand that manager to the Strands Agent. From then on, Strands persists turns and retrieves context through AgentCore automatically.
The two classes that matter:
AgentCoreMemoryConfig(memory_id, session_id, actor_id, batch_size=...)— binds an existing memory resource to a single conversation: which memory, which actor, which session, and how aggressively to batch writes.AgentCoreMemorySessionManager(agentcore_memory_config, region_name)— the session manager that performs thecreate_event/retrieve_memoriescalls on the framework's behalf.
from strands import Agent
from bedrock_agentcore.memory.integrations.strands import (
AgentCoreMemoryConfig,
AgentCoreMemorySessionManager,
)
memory_config = AgentCoreMemoryConfig(
memory_id="mem-123", # an ACTIVE memory resource
actor_id="customer-456", # who this conversation is about
session_id="session-789", # this specific conversation
batch_size=1, # 1 = flush every turn (see the gotcha below)
)
session_manager = AgentCoreMemorySessionManager(
agentcore_memory_config=memory_config,
region_name="us-east-1",
)
# Hand the session manager to the Strands Agent — that's the whole wiring.
agent = Agent(session_manager=session_manager)
response = agent("I'm allergic to peanuts — remember that for next time.")Note the binding granularity: a config is per actor + session. A new conversation (new session_id) or a different end user (new actor_id) means a new AgentCoreMemoryConfig and a new session manager. This mirrors AgentCore's core concepts exactly — the actorId/sessionId pair is the scoping key for short-term events.
Underneath, the memory resource itself still has to exist and be ACTIVE, with whatever strategies you want attached at CreateMemory time. The integration consumes a memory; it does not create one.
Tip
Attach strategies when you create the memory, not in the framework
AgentCoreMemoryConfig only references a memory_id. Whether you get any long-term records depends on the strategies you attached at CreateMemory (semantic, summary, user-preference, episodic). With no strategies, the Strands integration will faithfully store short-term events and retrieve nothing from long-term, because nothing is being extracted.
The batching trap: lose your context with batch_size > 1
AgentCoreMemoryConfig takes a batch_size. This is the single most important operational detail in the Strands integration, and it bites people in production.
With batch_size > 1, the session manager buffers messages in memory and only flushes a CreateEvent once the buffer reaches the threshold. Batching cuts the number of data-plane calls (and the per-event cost), which is attractive under load. But if your process ends — the agent finishes, the request returns, the container is reclaimed — while messages are still sitting below the threshold in the buffer, those buffered messages are never written and are silently lost. No error, no retry; the turns simply vanish from memory.
The fix is to make the flush deterministic. Either use the session manager as a context manager so it flushes on exit, or call close() explicitly in a finally:
# Option A — with-block: the buffer is flushed when the block exits
with AgentCoreMemorySessionManager(
agentcore_memory_config=memory_config,
region_name="us-east-1",
) as session_manager:
agent = Agent(session_manager=session_manager)
agent("Book me the 9am, and I prefer aisle seats.")
agent("Actually make it the 10am.")
# <- buffered messages are flushed here, even with batch_size > 1
# Option B — explicit close() in finally
session_manager = AgentCoreMemorySessionManager(
agentcore_memory_config=AgentCoreMemoryConfig(
memory_id="mem-123", actor_id="customer-456",
session_id="session-789", batch_size=10,
),
region_name="us-east-1",
)
try:
agent = Agent(session_manager=session_manager)
agent("...")
finally:
session_manager.close() # flush whatever is still bufferedThe safest default while you're learning is batch_size=1, which flushes every turn — no buffer to lose. Reach for a larger batch_size only once you've wrapped the manager in a with block or a guaranteed close(), and especially in any short-lived, per-request handler where the process may exit between turns.
Watch out
Silent data loss — there is no error
Buffered-message loss does not raise. The agent answers normally, the user is happy, and the turns are simply absent from memory the next time you retrieve_memories. In a Runtime deployment where each invocation may run in a fresh microVM, a batch_size > 1 without a guaranteed flush is a recipe for intermittently missing history that is painful to debug. Always pair batch_size > 1 with a with-block or close().
Key insight
Why this exists at all
Each CreateEvent is a billable short-term event and counts against the per-actor/session CreateEvent TPS budget. Batching trades durability-on-crash for fewer calls and lower cost under sustained load. It's a deliberate knob — just one whose failure mode is invisible unless you control the flush.
LangGraph: call the data plane from your nodes
There is no confirmed first-party AgentCore Memory integration module for LangGraph at the time of writing. Don't go looking for a magic session manager — the supported pattern is to call the same data-plane methods (create_event / retrieve_memories) directly from inside your graph nodes, using the Python SDK you already know.
A typical shape: a retrieve node that pulls long-term context before the model node, and a persist node that writes the completed turn as an event.
from bedrock_agentcore.memory import MemoryClient
memory = MemoryClient(region_name="us-east-1")
MEMORY_ID = "mem-123"
def retrieve_node(state):
"""Pull relevant long-term records before the model runs."""
records = memory.retrieve_memories(
memory_id=MEMORY_ID,
namespace=f"/users/{state['actor_id']}/preferences/",
query=state["user_input"],
top_k=5,
)
state["memory_context"] = records
return state
def persist_node(state):
"""Write the completed turn as a short-term event."""
memory.create_event(
memory_id=MEMORY_ID,
actor_id=state["actor_id"],
session_id=state["session_id"],
messages=[
(state["user_input"], "USER"),
(state["assistant_output"], "ASSISTANT"),
],
)
return state
# graph.add_node("retrieve", retrieve_node)
# graph.add_node("persist", persist_node)
# graph.add_edge("retrieve", "model")
# graph.add_edge("model", "persist")Because you own the calls, you also own the flush — there's no hidden buffer to lose, but you must place the create_event somewhere it always runs after the model turn. Cross-check the official samples before you commit to an exact signature: the AgentCore samples repo, under 01-tutorials/04-AgentCore-memory/, has the up-to-date framework patterns and is the source of truth as these integrations evolve.
Watch out
Verify against the samples, not from memory
A first-party LangGraph Memory module is explicitly UNVERIFIED in our research. If a future release ships one, prefer it; until then, the node-level create_event / retrieve_memories pattern is the documented approach. Pin exact method signatures from 01-tutorials/04-AgentCore-memory/ in the samples repo rather than trusting a snippet's argument names.
Operating at the limits: the quotas that constrain design
Several quotas shape how you architect a Memory-backed agent. Every number here is [VOLATILE] — verify the live quotas page before you rely on it; the values below are as of writing. Crucially, note which limits are adjustable (you can request an increase) versus fixed (hard, design around them).
| Limit | Value (as of writing) | Adjustable? |
|---|---|---|
| Memory resources / Region / account | 150 | Yes |
| Strategies / memory | 6 | No (hard cap) |
| Strategies / account | 900 | Yes |
EventExpirationDuration (retention) | min 7 / max 365 days (SDK default 90) | No |
Messages / CreateEvent | 100 | No |
| Message size | 100 KB | — |
| Event size | 10 MB | — |
appendToPrompt (override instructions) | 30 KB | — |
CreateEvent TPS | 10 (per actor/session: 5 conversational, 10 otherwise) | — |
RetrieveMemoryRecords / ListMemoryRecords TPS | 30 | — |
| Create/Update/Delete Memory TPS | 3 | — |
| Get/List Memories TPS | 5 | — |
DeleteEvent TPS | 20 | — |
| Long-term extraction tokens/min (built-in) | 150,000 | — |
| Episodic extraction / session | 50,000 tokens | No |
The two that most often force a design change:
- 6 strategies per memory, not adjustable. You cannot buy your way past it. If you need more than six extraction behaviors, split across multiple memory resources or consolidate strategies. Plan your semantic/summary/user-preference/episodic mix up front.
- 100 messages per
CreateEvent. A single event can hold multiple messages (a USER + ASSISTANT pair, plusTOOLresults), but not unbounded history — chunk long transcripts into multiple events.
The TPS limits are per operation, so a burst of writes from many concurrent sessions can throttle on CreateEvent (10 TPS, and only 5 TPS for conversational payloads per actor/session). Catch ThrottlingException and back off. Retention is bounded 7–365 days with an SDK default of 90 — raw short-term events expire on that schedule, so anything you need beyond it must live as a long-term record.
Watch out
Treat every number as volatile
Quotas, retention bounds, and TPS ceilings change. Teach yourself the shape — fixed vs adjustable, per-operation TPS, the 7–365 day retention window — and re-read the live AgentCore quotas page before sizing a production workload or filing a limit-increase request. The 6-strategy cap is the one most likely to be a hard architectural constraint.
Pricing dimensions and watching token spend
AgentCore Memory is consumption-based, and the dimensions map onto the short-term / long-term split you already know. As always, the exact rates are [VOLATILE] — learn the dimensions and price them against the live pricing page.
The billing dimensions:
- Per short-term event — each
CreateEventyou write. (This is exactly whatbatch_sizetrades off: fewer events, lower cost — at the durability risk above.) - Per long-term record stored, per month — the extracted memory records that strategies produce, billed for as long as they're retained.
- Per retrieval — each
RetrieveMemoryRecords/retrieve_memoriescall.
On top of that, the extraction work itself can incur separate Bedrock model charges, depending on the strategy tier:
- Built-in strategies — AgentCore runs the managed extraction/consolidation pipeline; the extraction tokens count against the long-term extraction budget.
- Built-in with overrides and self-managed strategies — these call a Bedrock model you choose, and those LLM calls are billed to your account in addition to the Memory dimensions above. Overrides require a
memoryExecutionRoleArn.
The lever you actually watch in operations is token consumption for long-term extraction, which is metered. Monitor it via the CloudWatch metric TokenCount in the Bedrock-AgentCore namespace, and remember the built-in extraction budget (150,000 tokens/min as of writing) and the per-session episodic budget (50,000 tokens).
import boto3
cw = boto3.client("cloudwatch", region_name="us-east-1")
resp = cw.get_metric_statistics(
Namespace="Bedrock-AgentCore",
MetricName="TokenCount",
StartTime="2026-06-01T00:00:00Z",
EndTime="2026-06-04T00:00:00Z",
Period=3600,
Statistics=["Sum"],
)
# Alarm on this to catch a runaway extraction bill from a chatty agent.The practical takeaway: a verbose agent with several built-in strategies generates a lot of extraction tokens. Episodic in particular is heavier (it adds a Reflection step). Alarm on TokenCount before a noisy rollout, not after the bill arrives.
Tip
Cheapest tier is self-managed, costliest control is built-in
Built-in strategies have the highest storage cost but zero extra model wiring. Self-managed has the lowest storage cost because you own extraction — but you pay your own Bedrock model bill and write the pipeline. Overrides sit in between: AgentCore's pipeline plus your chosen model billed to your account. Pick the tier by how much control vs. convenience you need, then price it on the live page.
Try it: Wire Memory into Strands, trip the batching trap, and watch tokens
Goal: integrate AgentCore Memory into a Strands agent, prove the batching gotcha to yourself, then mirror the pattern in LangGraph and watch the token meter.
Prereqs: an ACTIVE memory resource with at least one strategy attached (use agentcore add memory --name LabMem --strategies SEMANTIC,USER_PREFERENCE or create it via bedrock-agentcore-control), and bedrock-agentcore + strands installed.
-
Wire Strands with batch_size=1. Build
AgentCoreMemoryConfig(memory_id, session_id, actor_id, batch_size=1), wrap it inAgentCoreMemorySessionManager(config, region_name="us-east-1"), and pass it asAgent(session_manager=...). Run a few turns that state preferences ("I'm vegetarian", "I prefer morning meetings"). After ~30–60s (extraction is async), callretrieve_memoriesfor the relevant namespace and confirm the records appear. -
Reproduce the batching trap. Recreate the agent with
batch_size=10, run only 3 turns, and let the script exit WITHOUT awith-block orclose(). Re-runretrieve_memories(andlist_events) — observe that those turns are missing. Then wrap the manager in awithblock (or addsession_manager.close()in afinally), repeat, and confirm the turns now persist. Write one sentence on why the first run lost data and produced no error. -
Mirror it in LangGraph. Build a tiny graph with a
retrieve_nodethat callsretrieve_memoriesbefore the model and apersist_nodethat callscreate_eventafter it. Confirm a turn round-trips. Note that here YOU own the flush —create_eventmust sit where it always runs. Cross-check your method signatures against01-tutorials/04-AgentCore-memory/in the samples repo. -
Hit a limit (safely). Try to attach a 7th strategy to your memory, or send a
CreateEventwith 101 messages — observe theValidationException. Confirm from the live quotas page whether each limit is adjustable. -
Watch the spend. Pull the CloudWatch
TokenCountmetric from theBedrock-AgentCorenamespace withget_metric_statisticsover your lab window. Note how much extraction your strategies consumed, and sketch a CloudWatch alarm threshold you'd set before a noisy rollout.
Deliverable: the two flush behaviors side by side (lost vs. persisted), a working LangGraph round-trip, the limit error you triggered, and your TokenCount reading with a proposed alarm.
Key takeaways
- 1Strands integration: build `AgentCoreMemoryConfig(memory_id, session_id, actor_id, batch_size)`, wrap it in `AgentCoreMemorySessionManager(config, region_name)`, and pass that as `Agent(session_manager=...)`. The config is scoped per actor + session.
- 2Batching gotcha: with `batch_size > 1`, messages buffer until the threshold and are SILENTLY LOST if the process ends first. Always use a `with`-block or call `close()`; default to `batch_size=1` until you've guaranteed the flush.
- 3LangGraph has no confirmed first-party Memory module — call `create_event` / `retrieve_memories` from graph nodes yourself, and cross-check the samples under `01-tutorials/04-AgentCore-memory/`.
- 4Key fixed limits (volatile — verify): 6 strategies per memory (NOT adjustable), 100 messages per `CreateEvent`, retention 7–365 days (SDK default 90). Memories/Region/account (150) and strategies/account (900) are adjustable.
- 5TPS is per operation: `CreateEvent` 10 TPS (5 for conversational per actor/session), `RetrieveMemoryRecords`/`ListMemoryRecords` 30 TPS, Create/Update/Delete Memory 3 TPS. Handle `ThrottlingException` with backoff.
- 6Pricing dimensions: per short-term event, per long-term record stored/month, per retrieval. Built-in-with-overrides and self-managed strategies also incur Bedrock model charges billed to your account.
- 7Monitor long-term extraction spend via the CloudWatch `TokenCount` metric in the `Bedrock-AgentCore` namespace; episodic extraction is heavier (adds Reflection) and has its own per-session token budget.
Quiz
Lock in what you learned
Check your understanding
0 / 4 answered
1.You wire AgentCore Memory into Strands with `batch_size=20`, run a five-turn conversation in a per-request handler, and the handler returns. Later, `retrieve_memories` shows none of those turns. What happened?
2.Which snippet correctly hands AgentCore Memory to a Strands agent?
3.You need eight distinct extraction behaviors on a single conversation and plan to file a quota-increase request to raise the strategies-per-memory cap. What should you do?
4.An agent uses built-in-with-overrides and self-managed strategies, and the monthly bill is higher than the Memory pricing dimensions alone would predict. What's the most likely extra cost, and how do you watch it?
Go deeper
Hand-picked sources to keep learning
Official guide to AgentCoreMemoryConfig + AgentCoreMemorySessionManager and passing it to a Strands Agent.
The Memory service docs; see also agentcore-sdk-memory.html for create_event / retrieve_memories.
Verify the volatile numbers here: 6-strategy cap, retention bounds, messages/CreateEvent, per-operation TPS.
Live rates for the Memory dimensions: per short-term event, per long-term record/month, per retrieval.
Cross-check the LangGraph node pattern and exact signatures under 01-tutorials/04-AgentCore-memory/.
Background on strategy tiers (built-in / overrides / self-managed) and their cost trade-offs.