Agentic AI AcademyAgentic AI Academy
AgentCore Memory/Lesson 5 of 5

Memory in Frameworks & at the Limits

Strands integration, the batching trap, and quotas

Intermediate 12 minBuilder
What you'll be able to do
  • 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
At a glance

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.

  1. 1How Memory plugs into a framework
  2. 2Strands integration: AgentCoreMemoryConfig + AgentCoreMemorySessionManager
  3. 3The batching trap: lose your context with batch_size > 1
  4. 4LangGraph: call the data plane from your nodes
  5. 5Operating at the limits: the quotas that constrain design
  6. 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 by actorId + 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.

text
turn arrives ─▶ retrieve_memories (long-term context)
             ─▶ model call
             ─▶ create_event (persist the new turn)
             ─▶ async extraction runs later → memory records

Keep 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 the create_event / retrieve_memories calls on the framework's behalf.
python
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:

python
# 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 buffered

The 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.

python
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).

LimitValue (as of writing)Adjustable?
Memory resources / Region / account150Yes
Strategies / memory6No (hard cap)
Strategies / account900Yes
EventExpirationDuration (retention)min 7 / max 365 days (SDK default 90)No
Messages / CreateEvent100No
Message size100 KB
Event size10 MB
appendToPrompt (override instructions)30 KB
CreateEvent TPS10 (per actor/session: 5 conversational, 10 otherwise)
RetrieveMemoryRecords / ListMemoryRecords TPS30
Create/Update/Delete Memory TPS3
Get/List Memories TPS5
DeleteEvent TPS20
Long-term extraction tokens/min (built-in)150,000
Episodic extraction / session50,000 tokensNo

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, plus TOOL results), 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 CreateEvent you write. (This is exactly what batch_size trades 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_memories call.

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).

python
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.

  1. Wire Strands with batch_size=1. Build AgentCoreMemoryConfig(memory_id, session_id, actor_id, batch_size=1), wrap it in AgentCoreMemorySessionManager(config, region_name="us-east-1"), and pass it as Agent(session_manager=...). Run a few turns that state preferences ("I'm vegetarian", "I prefer morning meetings"). After ~30–60s (extraction is async), call retrieve_memories for the relevant namespace and confirm the records appear.

  2. Reproduce the batching trap. Recreate the agent with batch_size=10, run only 3 turns, and let the script exit WITHOUT a with-block or close(). Re-run retrieve_memories (and list_events) — observe that those turns are missing. Then wrap the manager in a with block (or add session_manager.close() in a finally), repeat, and confirm the turns now persist. Write one sentence on why the first run lost data and produced no error.

  3. Mirror it in LangGraph. Build a tiny graph with a retrieve_node that calls retrieve_memories before the model and a persist_node that calls create_event after it. Confirm a turn round-trips. Note that here YOU own the flush — create_event must sit where it always runs. Cross-check your method signatures against 01-tutorials/04-AgentCore-memory/ in the samples repo.

  4. Hit a limit (safely). Try to attach a 7th strategy to your memory, or send a CreateEvent with 101 messages — observe the ValidationException. Confirm from the live quotas page whether each limit is adjustable.

  5. Watch the spend. Pull the CloudWatch TokenCount metric from the Bedrock-AgentCore namespace with get_metric_statistics over 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

  1. 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.
  2. 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.
  3. 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/`.
  4. 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.
  5. 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.
  6. 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.
  7. 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