/** * AgentCoreStore — AWS Bedrock **AgentCore Memory** adapter * (peer-dep `@aws-sdk/client-bedrock-agentcore`). * * import { AgentCoreStore } from 'agentfootprint/memory'; * * const store = new AgentCoreStore({ * memoryId: 'arn:aws:bedrock-agentcore:us-west-2:...:memory/my-mem', * region: 'us-west-2', * }); * * Pattern: Adapter (GoF) — maps the `MemoryStore` interface onto AgentCore Memory's * data-plane **event** model (`CreateEvent` / `GetEvent` / `ListEvents` / * `DeleteEvent`, `@aws-sdk/client-bedrock-agentcore`): * MemoryIdentity.{tenant,principal} ↔ AgentCore `actorId` * MemoryIdentity.conversationId ↔ AgentCore `sessionId` * MemoryEntry ↔ one event whose `payload` is a single * `blob` document holding the entry * * **AgentCore Memory is an append-only event log, not a key-value store.** The server * assigns each event's `eventId` on `CreateEvent` (you cannot choose it), and there is no * "delete the whole session" call. This shapes the adapter: * * • `put` → `CreateEvent` (append; `actorId` + `eventTimestamp` are required). O(1) * • `list` → `ListEvents` (paginated, `includePayloads`). ← window / episodic memory. O(page) * • `get(id)` / `delete(id)` → list-then-find by the entry id stored in the blob, since * AgentCore's ids are server-assigned. **O(events in session)** — fine for typical * window sizes; if you need O(1) keyed access at scale, use RedisStore. * • `forget` → `ListEvents` + `DeleteEvent` per event (no `DeleteSession` on AgentCore). * • `search` → `RetrieveMemoryRecords`, **text-in**. AgentCore embeds and ranks on its * own side, so it takes a natural-language query; the port's `search()` takes a * vector. Pass `options.text` and this store serves the query; omit it and it refuses * by name rather than ranking an empty set. See `search()` below for the whole story. * • `putIfVersion` / `seen` / `feedback` → in-process emulation (AgentCore has no native * CAS / dedup / feedback primitive; these don't survive process restart). * • no `stream()` — AgentCore Memory has no streaming data-plane operation, and the * `MemoryStore` port has no streaming method to implement. Inventing one for a single * backend is how a port stops being a port. * * Role: Outer ring. Lazy-requires the AWS SDK; zero runtime cost when another adapter is * in use. Emits: N/A (storage adapters don't emit). */ import type { ListOptions, ListResult, MemoryStore, PutIfVersionResult, ScoredEntry, SearchOptions } from '../../memory/store/types.js'; import type { MemoryEntry } from '../../memory/entry/index.js'; import type { MemoryIdentity } from '../../memory/identity/index.js'; /** One event as the adapter cares about it: AgentCore's id + the decoded entry. */ export interface AgentCoreEvent { /** AgentCore server-assigned event id (needed to delete it). */ readonly eventId: string; /** * The MemoryEntry decoded from the event's blob payload. * * `null` means the event carried **no blob at all** — nothing in it ever * claimed to be one of this store's entries (AgentCore writes events of its * own into the same log), so it is skipped as an absence. * * A blob that IS present and cannot be decoded never arrives here: it raises * {@link UnreadableMemoryEntryError} at the decode step instead. An unreadable * stored memory and an absent one are different facts, and only one of them is * safe to answer with silence. */ readonly entry: MemoryEntry | null; } /** * Thrown when an event carries a blob that is **present but unreadable** where a * `MemoryEntry` should be. * * The same law the session store inherits from `hosting/envelope`, applied to * this port's own shape: an unreadable stored memory and an absent one are * different facts, and only one of them is safe to answer with silence. A * memory that exists and cannot be decoded, quietly skipped, is an agent that * answers as if it were never told — indistinguishable from working, until * somebody notices the assistant has forgotten a customer's address. * * ── Why the law lives HERE and not in a shared reader ──────────────────────── * `MemoryStore` has no envelope and no shared reading path — nothing on this * port corresponds to `hosting`'s `readFormat`, the single choke point every * session adapter reads through — so the refusal belongs at the one place raw * bytes become an entry: this adapter's decode step. If a `MemoryStore` reading * path ever grows such a choke point, the law moves there and this becomes a * caller of it. * * ── Why this one QUOTES NOTHING, where the session refusal quotes a prefix ─── * Same discipline — "never the stored content" — and the same shared helper, but * a different answer, because the two shapes differ in where content begins. A * `CheckpointEnvelope` opens `{ format, data, savedAt }`, so a capped prefix is * metadata. A `MemoryEntry` opens `{ id, value, … }`, so its SECOND field is the * thing somebody asked the agent to remember, and even a short prefix would * print it. `storedShape` therefore reports type, length, JSON-ness and the * opening character and nothing else — still enough to recognise "an object * stringified by something that was not JSON", which is the only diagnosis this * message needs to support. */ export declare class UnreadableMemoryEntryError extends TypeError { readonly code: "ERR_UNREADABLE_MEMORY_ENTRY"; /** AgentCore's id for the event those bytes came from. */ readonly eventId: string; /** The AgentCore session (this store's conversation) it was stored under. */ readonly sessionId: string; /** * What came back, described by SHAPE — type, length, JSON-ness, opening * character. Never a quote: on this port the stored bytes are the memory. */ readonly storedShape: string; constructor(input: { eventId: string; sessionId: string; stored: unknown; }); } /** * Minimal, entry-semantic surface the store uses. The real implementation * (`createAgentCoreClient`) maps these onto `CreateEvent` / `ListEvents` / * `DeleteEvent`; tests inject a mock via `_client`. */ export interface AgentCoreLikeClient { /** Append one entry as an event (server assigns the eventId). */ createEvent(input: { memoryId: string; actorId: string; sessionId: string; entry: MemoryEntry; }): Promise; /** One page of the session's events (newest-first is AgentCore's default). */ listEvents(input: { memoryId: string; actorId: string; sessionId: string; maxResults?: number; nextToken?: string; }): Promise<{ events: readonly AgentCoreEvent[]; nextToken?: string; }>; /** Delete one event by its AgentCore eventId. */ deleteEvent(input: { memoryId: string; actorId: string; sessionId: string; eventId: string; }): Promise; /** * Server-side semantic retrieval (`RetrieveMemoryRecords`). Optional: a client * built before this existed still satisfies the interface, and `search()` * feature-detects it rather than assuming. */ retrieveRecords?(input: { memoryId: string; namespace: string; searchQuery: string; maxResults?: number; memoryStrategyId?: string; }): Promise<{ records: readonly AgentCoreMemoryRecord[]; }>; } /** One record as `RetrieveMemoryRecords` returns it. */ export interface AgentCoreMemoryRecord { /** AgentCore's own record id. */ readonly memoryRecordId: string; /** The record's text content. */ readonly content: string; /** Relevance as AgentCore scored it, when it reports one. */ readonly score?: number; /** Which strategy produced the record (semantic, summary, user-preference, …). */ readonly memoryStrategyId?: string; /** The namespace it was found in. */ readonly namespace?: string; /** When AgentCore created it (unix ms), when reported. */ readonly createdAt?: number; } export interface AgentCoreStoreOptions { /** AgentCore Memory ARN or id. Required. */ readonly memoryId: string; /** AWS region. Required when constructing the SDK client internally. */ readonly region?: string; /** Pre-built AgentCore client (shares one SDK config across the host app). */ readonly client?: AgentCoreLikeClient; /** Page size for `listEvents`. Default 100. */ readonly pageSize?: number; /** * Where `search()` looks. AgentCore organises extracted memory records into * namespaces configured on the Memory resource's strategies (commonly * something like `/strategies/{strategyId}/actors/{actorId}`). * * A function, because the namespace usually contains the actor: it is handed * the resolved AgentCore ids for the identity being searched. Default: * `/actors/{actorId}/sessions/{sessionId}` — the session's own records. */ readonly searchNamespace?: (scope: { readonly actorId: string; readonly sessionId: string; }) => string; /** * Restrict `search()` to one extraction strategy (semantic, summary, * user-preference…). Omit to search across all of them. * * This is the metadata filter that reaches AgentCore's own side; `tiers` / * `minScore` / `k` from {@link SearchOptions} are applied to what comes back. */ readonly searchStrategyId?: string; /** @internal Test injection — skips the SDK require entirely. */ readonly _client?: AgentCoreLikeClient; /** @internal Test injection — the AWS SDK module (to exercise the real shim with a mock SDK). */ readonly _sdk?: BedrockAgentCoreSdkModule; } /** * AgentCore Memory-backed `MemoryStore`. Implements every method except `search()`. * * @throws when `@aws-sdk/client-bedrock-agentcore` is not installed and no `_client`/`_sdk` * is supplied. */ export declare class AgentCoreStore implements MemoryStore { /** * **No.** `search()` below exists, but it is `RetrieveMemoryRecords`: * AgentCore embeds and ranks on its own side, over the records its * extraction strategies derived, and never over the vectors written * through `put`/`putMany`. Embeddings handed to this store are stored * inside the event blob and are never ranked by anything. * * Declared (8.19.0) because a method's presence could not say that. * `indexCorpus` used to accept this store, embed a whole corpus, pay for * it and report success — and the chunks were unreachable forever. The * corpus builders read this bit and refuse, naming this store and what to * use instead; nothing else changes, and `defineMemory` over AgentCore's * own retrieval is untouched. */ readonly supportsVectorSearch = false; /** * **Words, not a vector.** `search()` is `RetrieveMemoryRecords`, whose * query is natural-language text: it reads {@link SearchOptions.text} and * refuses by name without it. * * {@link supportsVectorSearch} already said this store cannot serve back the * embeddings written into it — the question a corpus BUILDER asks. This * answers the next one down, which is what a corpus READER needs: what query * form does `search()` take? Declaring it is what lets `defineRAG` know, * before the first turn rather than after the bill, that a retriever over * this store needs no `Embedder` at all — embedding the question anyway is * spend on a vector discarded on arrival. * * Added in the same batch as the Google column's Memory Bank store, which is * the second adapter of this shape. Two stores that behave identically and * declare it differently is how a capability check quietly stops meaning * anything. */ readonly ranksBy: "server-text"; private readonly client; private readonly memoryId; private readonly pageSize; private readonly searchNamespace; private readonly searchStrategyId; private closed; private readonly signatures; private readonly feedbackBag; constructor(options: AgentCoreStoreOptions); private actorId; private sessionId; private scope; private shadowKey; private feedbackKey; /** Walk every event in the session (paginated). */ private eachEvent; get(identity: MemoryIdentity, id: string): Promise | null>; put(identity: MemoryIdentity, entry: MemoryEntry): Promise; putMany(identity: MemoryIdentity, entries: readonly MemoryEntry[]): Promise; /** * Emulated optimistic concurrency. AgentCore appends unconditionally; we read-then-write * inside a JS critical section — adequate for single-writer-per-session deployments. */ putIfVersion(identity: MemoryIdentity, entry: MemoryEntry, expectedVersion: number): Promise; list(identity: MemoryIdentity, options?: ListOptions): Promise>; delete(identity: MemoryIdentity, id: string): Promise; seen(identity: MemoryIdentity, signature: string): Promise; recordSignature(identity: MemoryIdentity, signature: string): Promise; feedback(identity: MemoryIdentity, id: string, usefulness: number): Promise; getFeedback(identity: MemoryIdentity, id: string): Promise<{ average: number; count: number; } | null>; /** GDPR "everything for this identity, gone." No DeleteSession on AgentCore → delete every event. */ forget(identity: MemoryIdentity): Promise; /** * Server-side semantic retrieval over AgentCore's extracted memory records * (`RetrieveMemoryRecords`). * * ── Read this before you call it ───────────────────────────────────────── * **It takes TEXT, not the vector.** AgentCore embeds and ranks on its own * side, so `query` — the port's vector — is unusable here, and the query it * actually needs travels in `options.text`. Omit that and this method throws * a corrective error naming what is missing, because the alternative is * ranking nothing and handing back `[]`, which reads as "no matches" when it * really means "wrong query form". Pass both and every store can serve you: * local-ranking backends use the vector and ignore the text. * * **It searches a DIFFERENT population than `list()`.** `list` returns the * events this store wrote. This returns the records AgentCore's extraction * strategies derived FROM those events — summaries, semantic facts, user * preferences. The ids therefore belong to AgentCore, not to entries you * `put()`, and `store.get(entry.id)` will not find them. They arrive as * entries so ranking code needs no special case, with `metadata.source` * saying plainly where they came from. * * Filters: `searchStrategyId` and the namespace reach AgentCore's own side; * `k`, `minScore` and `tiers` are applied to what comes back. * * @throws when `options.text` is absent, or when the client cannot retrieve. */ search(identity: MemoryIdentity, query: readonly number[], options?: SearchOptions): Promise[]>; close(): Promise; private ensureOpen; } /** The slice of `@aws-sdk/client-bedrock-agentcore` the shim touches. */ export interface BedrockAgentCoreSdkModule { readonly BedrockAgentCoreClient?: new (config: { region?: string; }) => { send(cmd: unknown): Promise; }; readonly CreateEventCommand?: new (input: unknown) => unknown; readonly ListEventsCommand?: new (input: unknown) => unknown; readonly DeleteEventCommand?: new (input: unknown) => unknown; readonly RetrieveMemoryRecordsCommand?: new (input: unknown) => unknown; }