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.

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 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)
searchRetrieveMemoryRecords — text-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. 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 unknown on purpose: nobody said the fact, AgentCore derived it.

    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