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

Memory Fundamentals

Short-term events, long-term records, and two planes

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

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.

  1. 1Why agents forget: statelessness and the managed fix
  2. 2Two API planes: control vs data
  3. 3Short-term events vs long-term records
  4. 4Eventual consistency: the retrieve-too-soon trap
  5. 5Creating a memory and choosing retention
  6. 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.

Planeboto3 clientWhat it doesRepresentative operations
Controlbedrock-agentcore-controlLifecycle of the memory resourceCreateMemory, UpdateMemory, GetMemory, ListMemories, DeleteMemory
Databedrock-agentcoreReading/writing memory at runtimeCreateEvent, 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).

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

python
from bedrock_agentcore.memory import MemoryClient

client = MemoryClient(region_name="us-east-1")  # spans control + data planes

Watch 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 CreateEvent returns, 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 via ListEvents or the SDK helper get_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's retrieve_memories.

A mental model that keeps these straight:

text
  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:

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

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

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

  1. Create on the control plane. Using boto3.client('bedrock-agentcore-control'), call create_memory(name='LabMemory', eventExpiryDuration=7). Note that you passed no memoryStrategies — this is a short-term-only memory on purpose. Capture the returned memory id.
  2. Poll to ACTIVE. Loop on get_memory(memoryId=...)['memory']['status'] with a short sleep until it reports ACTIVE. Observe that the resource is not usable the instant create_memory returns.
  3. Write events on the data plane. Switch to boto3.client('bedrock-agentcore'). Call create_event two or three times for a single actorId/sessionId, each with a conversational payload and a role of USER or ASSISTANT. Confirm each call returns immediately (short-term is synchronous).
  4. 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.
  5. Try to retrieve long-term — and watch it stay empty. Call retrieve_memory_records with a namespace and a searchCriteria searchQuery. 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).
  6. 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_records can come back empty — no strategy vs extraction lag.

Cleanup: call delete_memory(memoryId=...) on the control plane to avoid lingering charges.

Key takeaways

  1. 1Foundation models are stateless; AgentCore Memory is the managed, framework- and model-agnostic service that gives agents durable recall, consumed through two API planes.
  2. 2Short-term memory = raw turn-by-turn events you write (synchronous, immutable, scoped by actorId + sessionId) via CreateEvent/ListEvents/get_last_k_turns.
  3. 3Long-term memory = insights the service extracts across sessions (asynchronous, namespace-organized memory records) via RetrieveMemoryRecords/retrieve_memories.
  4. 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.
  5. 5Two planes with distinct boto3 clients: control = bedrock-agentcore-control (CreateMemory/GetMemory/...), data = bedrock-agentcore (CreateEvent/RetrieveMemoryRecords/...); the SDK MemoryClient wraps both.
  6. 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.
  7. 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