Memory — Reference

Source: packages/gecx-chat/src/memory/ Schema: schemas/memory.schema.json

MemoryConfig

Passed to createChatClient({ memory }).

FieldTypeDefaultNotes
adapterMemoryAdapterRequired. Backing store.
write.toolbooleantrueRegister memory.save/update/delete.
write.approvalPolicyToolApprovalPolicy'auto'Applied to memory.save.
write.extractorMemoryExtractorundefinedRuns after each assistant turn.
write.requireUserApprovalbooleanfalseGate extractor outputs behind user confirmation.
write.maxEntryCharsnumber500Per-entry text cap.
readMemoryReadConfig{ mode: 'inject' }See below.
retention.defaultTtlMsnumberundefinedPer-entry TTL on insert. Combines with RetentionStorage.
quota.maxEntriesPerScopenumberundefinedPer-scope quota.
quota.maxTotalBytesnumberundefinedBest-effort byte ceiling.
initialEnabledbooleantrueInitial 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

MethodPathBody / QueryReturns
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

NameAlways registered?InputNotes
memory.saveYes{ text, key?, scope?, tags? }Default approvalPolicy: 'auto'.
memory.updateYes{ id, text?, tags? }Same approval policy as save.
memory.deleteYes{ id }Default approvalPolicy: 'user_confirm'.
memory.recallOnly 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

See error-codes.md#memory.

Source: docs/reference/memory.md