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.
Bounded pages and bundled SDKs
MemoryStore.list() allows an adapter to return a smaller page. AgentCore
accepts at most 100 events per ListEvents request, so this adapter caps a
larger caller limit while preserving the service cursor. Always follow
ListResult.cursor to continue; an empty filtered page can still have a cursor.
pageSize must be an integer from 1 through 100 (default 100), checked before
any SDK client is constructed. The same bound applies to its session scans.
Search caps both RetrieveMemoryRecords.maxResults and SearchCriteria.topK
at 100. These are the service's documented limits:
AWS ListEvents,
RetrieveMemoryRecords
and SearchCriteria.
As on every shipped MemoryStore, caller limit and k must be finite positive
integers when provided. Zero, negatives, fractions and nonfinite values fail
locally with a RangeError before an AWS call, including when no rows would be
returned. Omission/undefined preserves the existing defaults. A larger positive
integer still receives the caps described above.
A bundler may not follow the default lazy SDK loader. Supply the module you imported instead; command construction still belongs to the adapter:
import * as sdk from '@aws-sdk/client-bedrock-agentcore';
import { AgentCoreStore } from 'agentfootprint/memory';
const store = new AgentCoreStore({ memoryId, region: 'us-west-2', sdk });BedrockAgentCoreSdkModule is exported from the memory door for typed wrappers
and offline doubles. The former internal _sdk option is removed: rename it to
sdk, without an alias. An entry-semantic client remains supported. This
runnable example uses a scripted module and makes no AWS calls:
const store = new AgentCoreStore({ memoryId: 'offline-memory', pageSize: 100, sdk });const identity = { conversationId: 'offline-thread' };let cursor: string | undefined;let count = 0;let pages = 0;do { // A legal port request; AgentCore caps each call to its own 100-event limit. const page = await store.list(identity, { limit: 1000, ...(cursor && { cursor }) }); count += page.entries.length; pages++; cursor = page.cursor;} while (cursor !== undefined);// Zero is an invalid caller bound, not a request for zero or all rows.try { await store.list(identity, { limit: 0 }); throw new Error('An invalid request size was accepted.');} catch (error) { if (!(error instanceof RangeError)) throw error;}await store.close();For extraction write options, use @aws-sdk/client-bedrock-agentcore 3.1114.0
or later: that is the first version modelling both extractionMode and
extractionConfig. The official changelog
introduces them in 3.1074.0 and 3.1114.0 respectively. The runtime schema check
still refuses them before writing if either field cannot be carried.
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. Each value is{ content }, the record's text where both memory formatters read it, so a read-only memory over this store puts the extracted facts in the prompt:defineMemory({ id: 'facts', type: MEMORY_TYPES.SEMANTIC, strategy: { kind: MEMORY_STRATEGIES.TOP_K, topK: 5 }, // no embedder: AgentCore ranks the words store, // an AgentCoreStore readOnly: true, }); // the model sees: <memory role="unknown" …>Ada prefers window seats.</memory>The role is
unknownon purpose: nobody said the fact, AgentCore derived it.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
