Memory Fundamentals
Short-term events, long-term records, and two planes
- Explain why agents are stateless and how AgentCore Memory provides durable, framework-agnostic recall via two API planes
- Distinguish short-term raw events (synchronous, immutable, session-scoped) from long-term extracted memory records (asynchronous, cross-session, namespace-organized)
- Articulate the key mental model: no strategies means only short-term events are stored and nothing is ever extracted
- Map the control plane (`bedrock-agentcore-control`) and data plane (`bedrock-agentcore`) operations and the `MemoryClient` that wraps both
- Configure event retention within the 7-365 day window and reason about its volatile default
- Design for eventual consistency so a `retrieve` call immediately after `create_event` does not silently return nothing
Foundation models are stateless: every invocation starts from zero. AgentCore Memory is the managed service that fixes this, splitting recall into short-term raw events you write within a session and long-term insights the service extracts across sessions — but only if you attached strategies. This lesson establishes that mental model, the two API planes and their exact client names, retention, and the eventual-consistency gotcha that bites everyone who retrieves immediately after writing.
- 1Why agents forget: statelessness and the managed fix
- 2Two API planes: control vs data
- 3Short-term events vs long-term records
- 4Eventual consistency: the retrieve-too-soon trap
- 5Creating a memory and choosing retention
- 6Writing an event, retrieving a record
Why agents forget: statelessness and the managed fix
A foundation model has no memory. Each call to it is a clean slate — the model sees only what you put in the prompt for that invocation and nothing from the last one. The illusion of a conversation that "remembers" is something you construct by replaying prior turns back into the context window on every call. Do nothing, and your agent greets a returning user as a stranger.
Naively, you solve this by hand: stand up a database, serialize turns, write retrieval queries, and — for anything smarter than a transcript replay — run your own extraction jobs to distill durable facts ("the user is vegetarian", "their account ID is 456") out of raw chat. That is real infrastructure to build, secure, and operate.
AgentCore Memory is the managed answer to agent statelessness. It is a fully managed service that gives agents the ability to remember past interactions for context-aware, personalized conversations. Two properties make it broadly usable:
- Framework-agnostic and model-agnostic — it is plain API/SDK calls, so it works whether your agent runs on Strands, LangGraph, CrewAI, or bare boto3, and regardless of which model you call.
- Consumed via two API planes — a control plane for managing the memory resource and a data plane for reading and writing memory at runtime (covered below).
Crucially, Memory does not run inside your agent the way a library would. It is a separate AWS resource your code talks to over the network — which is exactly why it survives a session ending, a microVM being torn down, or a deploy that replaces your Runtime.
Key insight
Memory is durable precisely because it is external
Recall from the Runtime lessons that each session runs in an ephemeral microVM whose in-memory and on-disk state is sanitized on termination. Anything you stash in a Python dict or a temp file vanishes with the session. AgentCore Memory is a separate, persistent AWS resource, so it is the right home for state that must outlive a single session.
Two API planes: control vs data
Like Runtime, Memory splits cleanly into a control plane (manage the resource) and a data plane (use it at runtime). The boto3 client names are different, and getting them wrong is a common first error.
| Plane | boto3 client | What it does | Representative operations |
|---|---|---|---|
| Control | bedrock-agentcore-control | Lifecycle of the memory resource | CreateMemory, UpdateMemory, GetMemory, ListMemories, DeleteMemory |
| Data | bedrock-agentcore | Reading/writing memory at runtime | CreateEvent, ListEvents, RetrieveMemoryRecords, ListMemoryRecords, GetMemoryRecord, DeleteEvent, ListActors, ListSessions |
A clean way to remember it: you create a memory resource once (control plane, an admin/deploy-time action), then your agent writes and reads against it on every turn (data plane, the hot path).
import boto3
# Control plane — manage the memory resource (admin / deploy-time)
control = boto3.client("bedrock-agentcore-control")
# Data plane — write/read memory at runtime (the hot path)
data = boto3.client("bedrock-agentcore")The Python SDK gives you a higher-level wrapper, bedrock_agentcore.memory.MemoryClient, that wraps both planes behind one object so you are not juggling two clients. We use raw boto3 here to make the plane split explicit; later lessons in this module use the SDK.
from bedrock_agentcore.memory import MemoryClient
client = MemoryClient(region_name="us-east-1") # spans control + data planesWatch out
The client name is `bedrock-agentcore`, not `bedrock`
The data-plane client is boto3.client("bedrock-agentcore") and the control client is boto3.client("bedrock-agentcore-control"). Neither is the older bedrock or bedrock-runtime client (those are for Bedrock models). Calling create_event on the wrong client raises an attribute error; calling it on bedrock-agentcore-control raises an unknown-operation error.
Short-term events vs long-term records
Memory holds two fundamentally different kinds of data. Internalizing the difference is the single most important takeaway of this lesson.
Short-term memory is the turn-by-turn record of raw interaction events within a session. Properties:
- Raw — what was actually said, not a derived insight.
- Synchronous — written immediately; once
CreateEventreturns, the event is there. - Immutable — events are not edited in place.
- Scoped by
actorId+sessionId— a given actor's events live under a given session. - Written via
CreateEvent; read viaListEventsor the SDK helperget_last_k_turns.
Long-term memory is the set of insights the service automatically extracts across sessions — preferences, facts, per-session summaries, episodes. Properties:
- Derived — these are
memory records, not raw turns. - Asynchronous — produced by a background pipeline after you write events, not on the spot.
- Cross-session — the whole point is to carry knowledge from one session into the next.
- Produced only by configured memory strategies and organized by namespace.
- Retrieved via
RetrieveMemoryRecords(data plane) / the SDK'sretrieve_memories.
A mental model that keeps these straight:
create_event(...) short-term: raw turns you WRITE (synchronous)
|
| async extraction pipeline (only if strategies attached)
v
retrieve_memory_records() long-term: insights the SERVICE extracts (eventual)Short-term is the transcript; long-term is what the agent learned from transcripts over time.
Key insight
The key mental model: no strategies = nothing extracted
Short-term is what you write; long-term is what the service derives if you attached strategies. Create a memory with no strategies and you get only a durable event log — nothing is ever extracted, and retrieve_memory_records will return empty forever, not because of lag but because there is nothing to retrieve. Long-term memory is opt-in. (The strategy types themselves are the next lesson.)
Eventual consistency: the retrieve-too-soon trap
Because long-term extraction runs asynchronously, there is a delay between writing an event and being able to retrieve the insight derived from it. Write a turn, immediately call retrieve_memory_records, and you will often get nothing back — the extraction pipeline simply has not run yet. Engineers new to Memory routinely misread this as a bug.
As of writing, extraction typically lands in roughly 20-40 seconds (a [VOLATILE] figure — episodic strategies can take longer, and you should verify against the live docs rather than treat this as a contract). Retrieval latency itself is fast (on the order of a few hundred milliseconds, also a [VOLATILE] figure), so the lag you observe is extraction time, not query time.
The practical consequence is that you must design for eventual consistency. Two patterns:
import time
# Write a turn (short-term, synchronous — available immediately)
data.create_event(
memoryId="mem-123",
actorId="customer-456",
sessionId="session-789",
payload=[{"conversational": {"content": {"text": "I'm vegetarian"}, "role": "USER"}}],
)
# DON'T expect the extracted long-term record to exist yet:
recs = data.retrieve_memory_records(
memoryId="mem-123",
namespace="/customer-support/customer-456/preferences/",
searchCriteria={"searchQuery": "dietary preference", "topK": 5},
)
# recs may be empty here — extraction hasn't run- Don't gate the current turn on freshly extracted long-term memory. Within a session, replay short-term events (synchronous, always current) for immediate context; lean on long-term records for what was learned in earlier sessions.
- In tutorials and tests, wait. AWS sample code adds a
time.sleep(...)between writing and retrieving so the extraction pipeline can run. That is fine for a demo, but never put a fixed sleep on a user's critical path in production — poll or simply accept that the freshest facts surface a beat later.
Watch out
Empty retrieval right after a write is expected, not a bug
If retrieve_memory_records returns nothing moments after create_event, check two things before debugging: (a) did you actually attach a strategy? With no strategy, nothing is ever extracted. (b) Has enough time passed for async extraction (~20-40s as of writing, verify live)? Both produce an empty result for completely different reasons.
Creating a memory and choosing retention
You provision a memory with CreateMemory on the control plane. The parameters you will reach for first:
name,description— human-facing labels.eventExpiryDuration— how long raw short-term events are kept, in days. Minimum 7, maximum 365; the SDK defaults to 90 (a[VOLATILE]default — verify the live SDK).memoryStrategies(boto3) /strategies(SDK) — the extraction strategies that enable long-term memory (next lesson). Omit them for a short-term-only memory.memoryExecutionRoleArn— required when a strategy needs to call a Bedrock model on your behalf (overrides / self-managed).encryptionKeyArn— a customer-managed KMS key (CMK) is optional; otherwise a service-managed key is used.
A freshly created memory is not usable instantly — it transitions CREATING -> ACTIVE (or FAILED). Poll GetMemory until status is ACTIVE before writing events.
import boto3, time
control = boto3.client("bedrock-agentcore-control")
# Short-term-only memory: NO strategies attached -> nothing will be extracted
resp = control.create_memory(
name="ShoppingSupportAgentMemory",
description="Raw event log for the shopping support agent",
eventExpiryDuration=90, # days; min 7, max 365 (SDK default 90 — verify live)
)
memory_id = resp["memory"]["id"]
# Poll until the resource is ACTIVE before using the data plane
while control.get_memory(memoryId=memory_id)["memory"]["status"] != "ACTIVE":
time.sleep(5)Retention applies to raw events (short-term). Long-term memory records are governed separately — they are billed per record stored per month and persist independently of the event-expiry window, which is why a memory can "remember" a preference long after the conversation that revealed it has aged out of the event log.
Note
Retention and the 90-day default are volatile
The 7-day floor, 365-day ceiling, and the SDK's 90-day default are all subject to change — treat them as the current values, not timeless facts, and confirm against the live quotas and SDK docs. Set eventExpiryDuration explicitly in production rather than relying on the default so an SDK change cannot silently alter your retention.
Writing an event, retrieving a record
With an ACTIVE memory you are on the data plane. The two core verbs are create_event (write short-term) and retrieve_memory_records (read long-term).
An event carries one or more messages. Each conversational message has a role — one of USER, ASSISTANT, TOOL, or OTHER — and text content. This is short-term, synchronous, and immutable: once the call returns, the turn is durably logged.
data = boto3.client("bedrock-agentcore")
# WRITE: short-term event (synchronous, immutable)
data.create_event(
memoryId="mem-123",
actorId="customer-456",
sessionId="session-789",
payload=[
{"conversational": {"content": {"text": "What's a good vegetarian dinner?"}, "role": "USER"}},
{"conversational": {"content": {"text": "How about a chickpea curry?"}, "role": "ASSISTANT"}},
],
)
# READ: long-term records by namespace + semantic search (eventual)
recs = data.retrieve_memory_records(
memoryId="mem-123",
namespace="/customer-support/customer-456/preferences/",
searchCriteria={"searchQuery": "food the user likes", "topK": 5},
)Note the asymmetry that traces straight back to short-term vs long-term:
- Writes are keyed by
actorId+sessionId— short-term events belong to a session. - Reads of long-term memory are keyed by a namespace and a semantic
searchQuery, returning the top-Kmost relevant extracted records — not raw turns. (Namespaces and their template variables are covered in the data-model lesson.)
To read back the raw transcript instead of extracted insights, you use ListEvents or the SDK's get_last_k_turns — the short-term path, which is always synchronous and current.
Example
End-to-end shape in one glance
Create the resource on bedrock-agentcore-control and poll to ACTIVE; write turns with create_event on bedrock-agentcore (instant); retrieve learned insights with retrieve_memory_records (eventual, namespace + searchQuery + topK). Short-term in, long-term out — with a strategy and a short wait in between.
Try it: Provision a short-term memory and feel eventual consistency
Goal: create a Memory resource with raw boto3, prove the control/data plane split, and experience the long-term extraction lag firsthand — without yet using any strategy.
Prerequisites: AWS credentials with AgentCore Memory permissions, boto3 and bedrock-agentcore installed, a supported region (use us-east-1 or us-west-2).
- Create on the control plane. Using
boto3.client('bedrock-agentcore-control'), callcreate_memory(name='LabMemory', eventExpiryDuration=7). Note that you passed nomemoryStrategies— this is a short-term-only memory on purpose. Capture the returned memoryid. - Poll to ACTIVE. Loop on
get_memory(memoryId=...)['memory']['status']with a short sleep until it reportsACTIVE. Observe that the resource is not usable the instantcreate_memoryreturns. - Write events on the data plane. Switch to
boto3.client('bedrock-agentcore'). Callcreate_eventtwo or three times for a singleactorId/sessionId, each with aconversationalpayload and aroleofUSERorASSISTANT. Confirm each call returns immediately (short-term is synchronous). - Read the raw transcript back. Call
list_events(or, if you switch to the SDK,get_last_k_turns) for that actor+session and confirm your turns are all there. Short-term is current and synchronous. - Try to retrieve long-term — and watch it stay empty. Call
retrieve_memory_recordswith anamespaceand asearchCriteriasearchQuery. It returns nothing. Wait two minutes and try again. It STILL returns nothing. Explain in one sentence why waiting does not help here (hint: you attached no strategy). - Reflect. Write three sentences: (a) which client/operation lives on which plane, (b) the difference between short-term and long-term data, and (c) the two distinct reasons
retrieve_memory_recordscan come back empty — no strategy vs extraction lag.
Cleanup: call delete_memory(memoryId=...) on the control plane to avoid lingering charges.
Key takeaways
- 1Foundation models are stateless; AgentCore Memory is the managed, framework- and model-agnostic service that gives agents durable recall, consumed through two API planes.
- 2Short-term memory = raw turn-by-turn events you write (synchronous, immutable, scoped by actorId + sessionId) via CreateEvent/ListEvents/get_last_k_turns.
- 3Long-term memory = insights the service extracts across sessions (asynchronous, namespace-organized memory records) via RetrieveMemoryRecords/retrieve_memories.
- 4KEY MENTAL MODEL: no strategies attached means only short-term events are stored and nothing is ever extracted — long-term memory is strictly opt-in.
- 5Two planes with distinct boto3 clients: control = bedrock-agentcore-control (CreateMemory/GetMemory/...), data = bedrock-agentcore (CreateEvent/RetrieveMemoryRecords/...); the SDK MemoryClient wraps both.
- 6Extraction is asynchronous (~20-40s as of writing, verify live), so retrieving immediately after create_event may return nothing — design for eventual consistency, never treat empty results as a bug.
- 7Raw-event retention is configurable from 7 to 365 days (SDK default 90, volatile); poll GetMemory until ACTIVE before writing events.
Quiz
Lock in what you learned
Check your understanding
0 / 4 answered
1.You create an AgentCore Memory with no strategies, write several turns with create_event, wait two minutes, then call retrieve_memory_records — and consistently get nothing back. Why?
2.Which boto3 client and operation do you use to write a raw turn-by-turn interaction event at runtime?
3.Which statement correctly contrasts short-term and long-term memory in AgentCore Memory?
4.What is the configurable retention window for raw short-term events (eventExpiryDuration), as documented at the time of writing?
Go deeper
Hand-picked sources to keep learning
The canonical reference: what Memory is, the two planes, short-term vs long-term, and creating a memory store.
The Python SDK (MemoryClient and the session managers) that wraps both the control and data planes.
Verify the volatile numbers in this lesson: event-retention window (7-365 days), strategies/memory cap, TPS, and message limits.
Narrative walkthrough of short-term vs long-term and the extraction pipeline, with code.
Source for bedrock_agentcore.memory; check the current MemoryClient signatures and the SDK retention default (volatile).
Memory bills per short-term event, per long-term record stored per month, and per retrieval — all volatile; verify the live page.