Memory — Reference
Source: packages/gecx-chat/src/memory/
Schema: schemas/memory.schema.json
MemoryConfig
Passed to createChatClient({ memory }).
| Field | Type | Default | Notes |
|---|---|---|---|
adapter | MemoryAdapter | — | Required. Backing store. |
write.tool | boolean | true | Register memory.save/update/delete. |
write.approvalPolicy | ToolApprovalPolicy | 'auto' | Applied to memory.save. |
write.extractor | MemoryExtractor | undefined | Runs after each assistant turn. |
write.requireUserApproval | boolean | false | Gate extractor outputs behind user confirmation. |
write.maxEntryChars | number | 500 | Per-entry text cap. |
read | MemoryReadConfig | { mode: 'inject' } | See below. |
retention.defaultTtlMs | number | undefined | Per-entry TTL on insert. Combines with RetentionStorage. |
quota.maxEntriesPerScope | number | undefined | Per-scope quota. |
quota.maxTotalBytes | number | undefined | Best-effort byte ceiling. |
initialEnabled | boolean | true | Initial user-level enabled flag. Persisted. |
MemoryReadConfig (discriminated union)
type MemoryReadConfig =
| { mode: 'inject'; maxEntries?: number; maxTokens?: number;
injection?: 'client' | 'server'; formatter?: MemoryFormatter; semantic?: boolean }
| { mode: 'recall' }
| { mode: 'hybrid'; inject: { maxEntries?: number; maxTokens?: number; semantic?: boolean; formatter?: MemoryFormatter } }
| { mode: 'off' };
Defaults: maxEntries=50, maxTokens=1024.
MemoryEntry
interface MemoryEntry {
id: string;
scope: { identityId: string; conversationId?: string };
text: string; // <= 500 chars
key?: string; // upsert-by-archival key
source: 'tool' | 'extraction' | 'user' | 'import';
createdAt: number; // epoch ms
updatedAt: number;
expiresAt?: number;
archivedAt?: number;
tags?: string[];
embedding?: number[];
metadata?: Record<string, unknown>;
}
MemoryAdapter
interface MemoryAdapter {
readonly name?: string;
readonly capabilities: { search?: boolean; sync?: boolean; embeddings?: boolean };
list(scope: MemoryScope, opts?: ListOpts): Promise<MemoryEntry[]>;
save(entry: NewMemoryEntry): Promise<MemoryEntry>;
update(id: string, patch: Partial<MemoryEntry>): Promise<MemoryEntry>;
delete(id: string): Promise<void>;
clear(scope: MemoryScope): Promise<void>;
search?(scope: MemoryScope, query: string, k?: number): Promise<MemoryEntry[]>;
subscribe?(scope: MemoryScope, listener: (event: MemoryEvent) => void): () => void;
dispose?(): Promise<void>;
}
Built-in adapters
createLocalMemoryAdapter({ storage })createServerMemoryAdapter({ endpoint, fetch?, headers?, signal?, supportsSearch? })createHybridMemoryAdapter({ remote, cache, onRemoteFailure?, resolveConflict? })
REST contract for createServerMemoryAdapter
| Method | Path | Body / Query | Returns |
|---|---|---|---|
| GET | {endpoint} | ?identityId=...&conversationId=...&limit=...&cursor=...&tag=...&includeArchived=1 | { entries: MemoryEntry[] } |
| POST | {endpoint} | NewMemoryEntry | { entry: MemoryEntry } |
| PATCH | {endpoint}/{id} | Partial<MemoryEntry> | { entry: MemoryEntry } |
| DELETE | {endpoint}/{id} | — | 204 |
| DELETE | {endpoint} | ?identityId=...&conversationId=... | 204 |
| GET | {endpoint}/search | ?identityId=...&q=...&k=... | { entries: MemoryEntry[] } (if supportsSearch) |
The reference implementation in apps/applied-ai-retail/app/api/memory/route.ts
satisfies this contract.
useMemory(opts?)
import { useMemory } from 'gecx-chat/react';
const {
entries, // MemoryEntry[]
status, // 'idle' | 'loading' | 'syncing' | 'error' | 'unavailable'
error, // ChatSdkError | null
enabled, // boolean
pendingApprovals, // MemoryCandidate[]
save, update, remove, clear,
approve, reject,
setEnabled,
} = useMemory({ scope: 'both' });
useMemory() returns a stable no-op result when the client was constructed
without memory — safe to call unconditionally.
<MemoryList>
Unstyled, fully accessible. Slot props:
<MemoryList
scope="identity"
renderEntry={(entry, controls) => <li>{entry.text}</li>}
renderEmpty={() => <p>Nothing saved yet</p>}
renderError={(error) => <Alert>{error.userMessage}</Alert>}
renderPendingApproval={(candidate, controls) => /* ... */}
heading={null}
/>
Built-in tools
| Name | Always registered? | Input | Notes |
|---|---|---|---|
memory.save | Yes | { text, key?, scope?, tags? } | Default approvalPolicy: 'auto'. |
memory.update | Yes | { id, text?, tags? } | Same approval policy as save. |
memory.delete | Yes | { id } | Default approvalPolicy: 'user_confirm'. |
memory.recall | Only when read.mode is 'recall' or 'hybrid' | { query, k? } | Returns MemoryRecallResultPart. |
Events
MemoryEvent is emitted on the store's event channel:
client.memory?.onEvent((event) => {
switch (event.kind) {
case 'saved': /* MemoryEntry */
case 'updated': /* prev + entry */
case 'deleted': /* id */
case 'cleared': /* scope */
case 'archived': /* superseded by id */
case 'extraction.proposed': /* candidates[] */
case 'extraction.resolved': /* approved | rejected */
case 'sync.conflict.resolved': /* hybrid winner+loser */
case 'enabled_changed': /* user toggled */
case 'error': /* ChatSdkError */
}
});
Analytics events
Emitted on the ProductAnalyticsEvent channel:
memory_saved · memory_recalled · memory_deleted · memory_cleared ·
memory_extraction_proposed · memory_extraction_resolved ·
memory_consent_changed
See analytics.md for sink wiring.
Error codes
Source:
docs/reference/memory.md