/** * Autonomous memory — core types. * * Ported from upstream's memory-core pattern, adapted for Postgres + pgvector * and shaped so a future graph-backend layer (Graphiti, Neo4j agent-memory, etc.) * can be added alongside the vector store without changing the tool contracts. * * Architectural boundary: the LLM tools ({@link MemoryBackend}) never talk to * Postgres directly — they go through an interface that a vector store, * a graph store, or a composite of both can satisfy. */ /** * Scope key for memory isolation. * * Two-tier model: * - Agent-tier rows (`memory/agent/*`) are stored with `user_id = NULL` * and are shared across every user of the agent. * - User-tier rows (`memory/user/*`) are stored with the caller's * `userId` and are private to that caller; other users of the same * agent never see them. * * Every read query applies `WHERE agent_id = $1 AND (user_id IS NULL OR * user_id = $caller)`, so the caller sees shared rows plus their own. * `userId` is therefore both the write target (for user-tier paths) AND * the read filter. An absent `userId` is legal — it just means this * scope is isolated/autonomous and the user tier is inert. * * Captured at tool construction time in a closure and NEVER exposed as * tool parameters. The LLM cannot override either field. */ export interface MemoryScope { agentId: string; /** * The caller's identity, used for user-tier writes AND the layered * read filter. Undefined/null for isolated/autonomous agents — in * that case only agent-tier rows are visible and writable. */ userId?: string | null; } /** * One retrieved memory record. * * `score` is a unit-interval hybrid score (0..1): 0.7 * cosine + 0.3 * text * rank in the default pgvector backend. A graph backend is free to produce * its own score as long as it stays within that range. */ export interface MemoryEntry { id: string; path: string; content: string; score: number; createdAt: Date; /** * Which backend surfaced this entry. Lets a composite store tag results so * downstream consumers (tests, telemetry, UI) can distinguish vector hits * from graph hits without inspecting the ID format. */ source?: MemoryBackendKind; /** * Which tier the row belongs to — derived from the path at read time. * See `./paths.ts` for the whitelist. UI groups rows by this field * so users can see which memories are shared (agent tier) vs private * (user tier). */ tier?: 'agent' | 'user'; /** Citation marker (Phase 2) — e.g. `memory/agent/playbook.md#L1-L8`. */ citation?: string; /** 1-indexed start/end line range used to build the citation. */ startLine?: number; endLine?: number; } export type MemoryBackendKind = 'vector' | 'graph' | 'composite'; export interface MemorySearchOptions { maxResults?: number; minScore?: number; /** * Phase 2 toggles — when the backend supports them. Each is independently * opt-in; all false = upstream Phase 1 behavior. */ mmr?: { enabled?: boolean; lambda?: number }; temporalDecay?: { enabled?: boolean; halfLifeDays?: number }; /** Citation mode: 'on' decorates; 'off' strips; 'auto' = on for direct chat. */ citations?: 'on' | 'off' | 'auto'; } export interface MemoryGetOptions { path: string; from?: number; lines?: number; } export interface MemoryReadResult { path: string; text: string; } export interface MemoryAppendInput { /** Must begin with "memory/". Enforced by the append tool wrapper. */ path: string; content: string; } export interface MemoryHealth { ok: boolean; backend: MemoryBackendKind; error?: string; } /** * The contract every memory backend implements. * * A Phase 1 pgvector store satisfies this directly. A Phase 4 graph store * (Graphiti / Neo4j agent-memory) will implement the same interface and plug * into a {@link CompositeMemoryBackend} without touching the tools. */ export interface MemoryBackend { readonly kind: MemoryBackendKind; search( scope: MemoryScope, query: string, opts?: MemorySearchOptions ): Promise; get( scope: MemoryScope, opts: MemoryGetOptions ): Promise; append(scope: MemoryScope, input: MemoryAppendInput): Promise; health(): Promise; } /** * Historical alias kept for one release so external consumers can still import * the name used in the plan doc. New code should prefer {@link MemoryBackend}. * * @deprecated use {@link MemoryBackend} */ export type MemoryStore = MemoryBackend; /** * Optional configuration bundle plumbed through `createAgentNode`. * Missing config = memory fully disabled, no tools attached. */ export interface MemoryConfig { backend: MemoryBackend; scope: MemoryScope; flush?: { softThresholdTokens?: number; reserveFloorTokens?: number; windowTokens?: number; enabled?: boolean; }; search?: { maxResults?: number; maxInjectedChars?: number; /** Phase 2 — enable MMR reranking (upstream-aligned defaults when true). */ mmr?: { enabled?: boolean; lambda?: number }; /** Phase 2 — enable temporal decay on dated memory files. */ temporalDecay?: { enabled?: boolean; halfLifeDays?: number }; /** Phase 2 — citation mode. Defaults to 'auto'. */ citations?: 'on' | 'off' | 'auto'; }; /** * Phase 2 — optional recall tracker. When set, memory_search fires a * best-effort recall record after each successful call. Failures are * swallowed so recall tracking never blocks the tool result. */ recallTracker?: { record(params: { agentId: string; query: string; hits: Array<{ id: string; path: string; score: number }>; }): Promise; }; /** * Phase state accessor. Returns the current phase so the append-tool wrapper * can gate writes to the reflection phase. Supplied by the graph runtime. */ getPhase?: () => MemoryPhase; } export type MemoryPhase = 'normal' | 'memory_flushing';