AgentCore Memory
AWS Bedrock AgentCore Memory adapter — session/event-based managed memory. Lazy-required @aws-sdk/client-bedrock-agentcore peer-dep.
AWS Bedrock AgentCore is a managed memory service — sessions, events, retrieval, summarization all server-side. agentfootprint's
AgentCoreStoremaps theMemoryStoreinterface onto AgentCore's session/event model so you can use it as a drop-in forInMemoryStorein production.
Install
npm install @aws-sdk/client-bedrock-agentcoreLazy peer-dep declared in peerDependenciesMeta with optional: true. AWS credentials resolved via the standard SDK chain (env, profile, IAM role).
Door
import { AgentCoreStore } from 'agentfootprint/memory';agentfootprint/memory is the canonical — and only — door for every store
adapter, shared with RedisStore, sqliteVectorStore, pgVectorStore and
s3VectorsStore. Keeping vendor-backed stores off the main barrel keeps it small
and lets bundlers tree-shake when AgentCore is not used.
The older memory-providers alias subpath was removed in 9.0.0, along with fifteen others. No symbol moved or was lost with them, so upgrading is a find-and-replace on the import line.
Use
const store = new AgentCoreStore({ memoryId: 'arn:aws:bedrock-agentcore:us-west-2:000000000000:memory/demo', _client: fakeClient,});const memory = defineMemory({ id: 'agentcore-window', description: 'Last 10 turns persisted in AgentCore Memory.', type: MEMORY_TYPES.EPISODIC, strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 }, store,});The store implements every MemoryStore method. Pair with defineMemory({ store, ... }) like any other adapter.
Mapping to AgentCore primitives
AgentCore Memory is an append-only event log, not a key-value store: the server assigns each event's eventId on write (you cannot choose it), and there is no "delete the whole session" call. That single fact shapes every row below — including the two that cost O(events in session).
MemoryStore call | AgentCore mapping | Cost |
|---|---|---|
MemoryIdentity.{tenant, principal} | actorId | — |
MemoryIdentity.conversationId | sessionId | — |
MemoryEntry | one event whose payload is a single blob document holding the entry, written as JSON text | — |
put | CreateEvent (append; actorId + eventTimestamp required) | O(1) |
putMany | CreateEvent per entry, sequentialized — AgentCore has no batch write | O(n) |
list | ListEvents (paginated, includePayloads; cursor via nextToken) — the window / episodic read path | O(page) |
get(id) | ListEvents, then find by the entry id inside the blob — AgentCore ids are server-assigned, so there is nothing to GetEvent by. Highest version wins (an update appends another event) | O(events in session) |
delete(id) | ListEvents, then DeleteEvent for every event carrying that entry id | O(events in session) |
forget | ListEvents + DeleteEvent per event — there is no DeleteSession on AgentCore | O(events in session) |
search | RetrieveMemoryRecords — text-in, server-side ranking. Pass the question in options.text | O(k) |
putIfVersion | Emulated: read-then-write in a JS critical section (no native CAS) | O(events in session) |
seen / recordSignature | In-process shadow Map (NOT durable — see caveats) | O(1) |
feedback / getFeedback | In-process shadow Map (NOT durable) | O(1) |
stream | Not implemented — AgentCore Memory has no streaming data-plane operation, and the MemoryStore port has no streaming method to implement | — |
If you need O(1) keyed access at scale, use RedisStore. The list-then-find rows are fine for typical window sizes.
Caveats (call out before adopting)
These are documented in the JSDoc; promoted here for visibility:
-
putIfVersionis emulated — AgentCore appends unconditionally; there is no compare-and-set. The adapter does read-then-write inside a JS critical section. Adequate for single-writer-per-session deploys; weaker for multi-writer. -
Built-in summarization may double-compress — AgentCore has its own server-side summarization. Mixing
defineMemory({ strategy: SUMMARIZE })on top will double-compress. Pick one summarizer. -
seen/feedbackare in-process — they don't survive process restart. For durable recognition, useRedisStoreinstead (or layer Redis on top of AgentCore). -
search()takes TEXT, and searches a different population — since 7.15 the store implementssearch()on top ofRetrieveMemoryRecords. Two things follow from AgentCore embedding and ranking on its own side.It needs the question as text. The port's
queryargument is a vector, which AgentCore cannot use, so the query it actually needs travels inoptions.text:await store.search(identity, vector, { text: theUserQuestion });Omit
options.textand the call throws, naming what is missing, rather than ranking nothing and handing back[]— an empty array reads as "no matches" when it really means "wrong query form". Backends that rank locally ignoretext, so passing both is always safe.What comes back is not what you
put().list()returns the events this store wrote;search()returns the records AgentCore's own extraction strategies derived from those events — summaries, semantic facts, preferences. Their ids belong to AgentCore, sostore.get(result.id)will not find them. They arrive as entries so ranking code needs no special case, and each carriesmetadata.source: 'agentcore-memory-record'saying plainly where it came from.searchStrategyIdand the namespace reach AgentCore's side;kandminScoreare applied to what comes back. Atiersfilter excludes everything, because AgentCore records carry no tier and silently ignoring a filter somebody asked for is worse than returning nothing. -
AWS rate limits apply — production deployments should wrap with
withRetryand budget calls per session. AWS SDK has built-in retry; tune via the SDK'smaxAttemptsconfig. -
Entries written before 7.22.1 are unreadable, and now say so — the adapter used to hand each
MemoryEntryto the service as an object, and the service stores its owntoString()rendering of an object it is given and returns that string, which is not JSON and is lossy. Those entries decoded to nothing and were silently skipped:list()came back short and the agent answered as if it had never been told. Since 7.22.1 entries go in as JSON text and a blob that is present but undecodable raisesUnreadableMemoryEntryError— naming the event and session — rather than being dropped. Old entries cannot be recovered; delete them or point the store at a fresh memory resource. Details: AgentCore adapters.
Test injection
AgentCoreStore({ _client: ... }) accepts a mock-injected client for tests — no live AWS account needed for CI:
const mockClient = {
createEvent: async () => {},
listEvents: async () => ({ events: [] }),
deleteEvent: async () => {},
// optional — search() feature-detects it rather than assuming
retrieveRecords: async () => ({ records: [] }),
};
const store = new AgentCoreStore({ memoryId: 'test', _client: mockClient });That is the whole AgentCoreLikeClient shape: three required methods and one
optional. The real client built from @aws-sdk/client-bedrock-agentcore is a
thin shim over send(new CreateEventCommand(...)) and friends — a bare
@aws-sdk/client-* client is command-based, so there are no per-operation
methods on it to call. The commands it dispatches are pinned by test; see
AWS & Bedrock AgentCore.
When to use AgentCore vs Redis vs InMemory
- Production AWS-native deployment — AgentCore is the natural choice if you're already on Bedrock.
- Production multi-tenant with low latency + durable feedback — Redis (RedisStore).
- Dev / test / single-process — InMemoryStore.
- Hybrid — combine: AgentCore for causal snapshots; Redis for hot recent + feedback.
Next steps
- Memory store adapters — full adapter matrix
- Memory guide — types × strategies that pair with this store
- AWS Bedrock provider — paired LLM provider
AgentCore adapters — runtime, sessions, policy, gateway
agentCoreRuntimeHost, agentCoreSessions, agentCorePolicy and gatewayTransport — the AgentCore adapter set on ports that already existed, plus a plain statement of which parts are really verified and which are contract-mapped.
Bedrock — the LLM provider
Next Page
