InfrastructureAWS

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 AgentCoreStore maps the MemoryStore interface onto AgentCore's session/event model so you can use it as a drop-in for InMemoryStore in production.

Install

npm install @aws-sdk/client-bedrock-agentcore

Lazy 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 callAgentCore mappingCost
MemoryIdentity.{tenant, principal}actorId
MemoryIdentity.conversationIdsessionId
MemoryEntryone event whose payload is a single blob document holding the entry, written as JSON text
putCreateEvent (append; actorId + eventTimestamp required)O(1)
putManyCreateEvent per entry, sequentialized — AgentCore has no batch writeO(n)
listListEvents (paginated, includePayloads; cursor via nextToken) — the window / episodic read pathO(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 idO(events in session)
forgetListEvents + DeleteEvent per event — there is no DeleteSession on AgentCoreO(events in session)
searchRetrieveMemoryRecordstext-in, server-side ranking. Pass the question in options.textO(k)
putIfVersionEmulated: read-then-write in a JS critical section (no native CAS)O(events in session)
seen / recordSignatureIn-process shadow Map (NOT durable — see caveats)O(1)
feedback / getFeedbackIn-process shadow Map (NOT durable)O(1)
streamNot 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:

  1. putIfVersion is 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.

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

  3. seen / feedback are in-process — they don't survive process restart. For durable recognition, use RedisStore instead (or layer Redis on top of AgentCore).

  4. search() takes TEXT, and searches a different population — since 7.15 the store implements search() on top of RetrieveMemoryRecords. Two things follow from AgentCore embedding and ranking on its own side.

    It needs the question as text. The port's query argument is a vector, which AgentCore cannot use, so the query it actually needs travels in options.text:

    await store.search(identity, vector, { text: theUserQuestion });

    Omit options.text and 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 ignore text, 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, so store.get(result.id) will not find them. They arrive as entries so ranking code needs no special case, and each carries metadata.source: 'agentcore-memory-record' saying plainly where it came from. searchStrategyId and the namespace reach AgentCore's side; k and minScore are applied to what comes back. A tiers filter excludes everything, because AgentCore records carry no tier and silently ignoring a filter somebody asked for is worse than returning nothing.

  5. AWS rate limits apply — production deployments should wrap with withRetry and budget calls per session. AWS SDK has built-in retry; tune via the SDK's maxAttempts config.

  6. Entries written before 7.22.1 are unreadable, and now say so — the adapter used to hand each MemoryEntry to the service as an object, and the service stores its own toString() 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 raises UnreadableMemoryEntryError — 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

On this page