/** * PostgreSQL **reference adapters** for the two durable seams a deployment must supply to make a * stateless runner durable (the "session center" + design/45 durable-checkpoint): * * - {@link PgSessionRepo} — a durable `SessionRepo` (append-only event log). Hand it to the built-in * `TtlSessionStore` (`new TtlSessionStore({ repo })`) and you get the full session center: wake on * `acquire`, idle-cache, cross-instance optimistic lock. This is the README §11 recipe, on Postgres. * - {@link PgCheckpointStore} — a durable `CheckpointStore` (design/45 F4/1C): atomic CAS `resolve`, * `expire` fence, deadline `reap`. * * **Driver-neutral**: this module depends on no Postgres client. Pass any `(text, params) => {rows}` * function ({@link PgQueryFn}) — `node-postgres` (`(t, p) => pool.query(t, p)`), `pg-mem` in tests, or * a thin wrapper over postgres.js. The SQL sticks to portable basics (text/bigint/jsonb columns, * `ON CONFLICT`, conditional `UPDATE … RETURNING`). * * **Concurrency contract** (mirrors the TiDB backend, README §11 / design/10): * - Session appends are serialized by the `(session_id, seq)` primary key: two replicas that woke the * same session race their next `INSERT`; the loser hits a unique violation and gets * `SessionError("conflict")` (catch with `isSessionConflict`) — never a silent fork. * - `resolve`/`expire` are single-row conditional UPDATEs over the same `pending` row, so the DB * serializes them: exactly one wins (the once-only foundation, design/45 §2.1). * * Reference-grade scope (open-source profile): full-tree wake (no F3 bounded-tail query), no * connection management (the caller owns the pool), no retention/GC worker (run `reap` + your own * retention policy on a schedule). */ import { Session } from "../internal/harness.js"; import type { SessionMetadata, SessionRepo } from "../internal/harness.js"; import { type Checkpoint, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type ReopenReason, type ResolveExpectation, type ResumeOutcome } from "../core/checkpoint-store.js"; import { type MemoryNoteHeader, type MemoryNoteRecord, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type StructuredNoteInput } from "../core/memory.js"; import type { ToolResultSlice, ToolResultStore } from "../core/tool-result-store.js"; /** The subset of a query result this module reads. `rowCount` is deliberately unused (drivers * disagree on it); statements that need a count use `RETURNING` and read `rows.length`. */ export interface PgQueryResult { rows: Array>; } /** * The only thing the adapters need from your Postgres client: positional-parameter query. * `node-postgres`: `const q: PgQueryFn = (text, params) => pool.query(text, params)`. */ export type PgQueryFn = (text: string, params?: unknown[]) => Promise; /** Table names used by {@link ensurePgAgentSchema} and the adapters (single source of truth). */ export declare const PG_AGENT_TABLES: { readonly sessions: "agent_sessions"; readonly sessionEntries: "agent_session_entries"; readonly checkpoints: "agent_checkpoints"; readonly memory: "agent_memory"; /** design/84 Seam B: per-scope periodic-consolidation cursor (one row per writeScope). */ readonly memoryCursor: "agent_memory_cursor"; readonly toolResults: "agent_tool_results"; }; /** Options for {@link ensurePgAgentSchema}. */ export interface PgAgentSchemaOptions { /** * Shape of the memory `embedding` column: * - omitted → `jsonb` (portable mode: vectors stored as JSON, cosine computed in process — works on * ANY Postgres, no extension); * - `{ dimensions, pgvector: true }` → `CREATE EXTENSION vector` + a `vector(d)` column, so * {@link PgMemoryStore} with `pgvectorSql: true` ranks by the native `<=>` cosine-distance operator * (index-accelerable with an HNSW/IVFFlat index you add per your scale). * Pick once at schema time and keep {@link PgMemoryStoreOptions} consistent with it. */ memoryVector?: { dimensions: number; pgvector?: boolean; }; } /** * Create the three tables (idempotent, `IF NOT EXISTS`). Call once at startup, or run the same DDL * through your own migration tool. JSON payloads are `jsonb`; values are always **passed as JSON * strings** (every driver can bind a string; Postgres casts it into the column type). */ export declare function ensurePgAgentSchema(query: PgQueryFn, opts?: PgAgentSchemaOptions): Promise; /** Postgres SQLSTATE 23505; fall back to message sniffing for emulators that don't set `code`. * Exported for `MemoryBackend` dialect implementations (the Pg reference implementation moved to * @sema-ai/server — see docs/MEMORY-BACKEND-DIALECT-NOTES.md); one sniffing rule, every consumer. */ export declare function isUniqueViolation(err: unknown): boolean; /** * Durable `SessionRepo` on Postgres. Plug into the built-in cache: * ```ts * const repo = new PgSessionRepo((t, p) => pool.query(t, p)); * const runner = new Runner({ brain, sessionStore: new TtlSessionStore({ repo }) }); * ``` * `TtlSessionStore` defaults to `evict: "forget"` for a custom repo, so idle eviction never deletes * durable history; `open` of an unknown id throws `not_found` and the store falls back to `create`. */ export declare class PgSessionRepo implements SessionRepo { private readonly query; constructor(query: PgQueryFn); private load; create(options?: { id?: string; }): Promise; open(metadata: SessionMetadata): Promise; list(): Promise; delete(metadata: SessionMetadata): Promise; /** Fork caveats (1.94.0 review obs#1/#2): an EXPLICIT `options.id` must not be shared with a * concurrent `create` of the same id (create's orphan purge can race the in-flight entry batches — * unreachable under default uuidv7 ids; explicit-id reuse is a caller contract); and forking onto an * id holding orphan residue from a previously failed fork fails LOUD with a unique violation * (`create` purges such residue, `fork` deliberately does not — loud beats silently adopting). */ fork(sourceMetadata: SessionMetadata, options?: { entryId?: string; position?: "before" | "at"; id?: string; }): Promise; } /** * Durable `CheckpointStore` on Postgres (design/45 §2.1). The `status` **column** is authoritative * (the CAS target); the `checkpoint` jsonb is the full payload, whose embedded `status` is overwritten * from the column on `get`. `resolve` persists the outcome atomically with the status flip (the v2 * resumable-resume hook the interface reserves). */ export declare class PgCheckpointStore implements CheckpointStore { private readonly query; constructor(query: PgQueryFn); put(token: CheckpointToken, cp: Checkpoint): Promise; get(token: CheckpointToken): Promise; resolve(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): Promise; reopen(token: CheckpointToken, scope: string, reason: ReopenReason): Promise; setPendingSteer(token: CheckpointToken, scope: string, steer: { text: string; trusted: boolean; }): Promise; expire(token: CheckpointToken, scope: string): Promise; reap(scope: string, cutoff: number): Promise; listByScope(scope: string): Promise; } /** * Durable `ToolResultStore` on Postgres. Required for durable suspend (design/45): the runner refuses * to suspend over the default `InMemoryToolResultStore` (its offload refs die with the process and a * cross-replica resume could never read them back). Write-once per ref (`ON CONFLICT DO NOTHING`, the * interface contract); slicing matches the in-memory store exactly (JS `slice` semantics). */ export declare class PgToolResultStore implements ToolResultStore { private readonly query; constructor(query: PgQueryFn); put(ref: string, content: string): Promise; get(ref: string, opts?: { offset?: number; limit?: number; }): Promise; } /** Produces an embedding for one note/query. `dimensions` must match the schema's `memoryVector`. */ export interface PgEmbedder { embed(text: string): Promise; dimensions: number; } /** Options for {@link PgMemoryStore} — pick the recall mode (all three honor the same cosine-distance * contract `score ∈ [0,2], 0 = identical`, so the consolidation band needs no recalibration). */ export interface PgMemoryStoreOptions { /** * When set, notes are embedded on `append`/`update` and `searchScored` ranks by cosine distance. * Without it, `searchScored` falls back to the same lexical (Jaccard) distance as the in-memory * reference store — fine for dev, weaker recall. */ embedder?: PgEmbedder; /** * `true` → rank in SQL with pgvector's `<=>` operator (schema must have been created with * `memoryVector: { pgvector: true }`). `false`/omitted → embeddings live in `jsonb` and cosine is * computed in process after fetching the scope (portable: any Postgres, no extension; right up until * a scope is huge — then switch pgvector on). Requires `embedder`. * * Migration note (1.94.0 review obs#3): the SQL path EXCLUDES rows without an embedding * (`WHERE embedding IS NOT NULL`) — unlike the in-process path, which keeps such legacy rows as * lexical-fallback candidates. **Backfill embeddings before enabling `pgvectorSql`**, or pre-embedder * notes silently drop out of consolidation/recall. Conversely, REMOVING the embedder later is a * downgrade (obs#4): a subsequent `update` stores `embedding = null` for that note. */ pgvectorSql?: boolean; } /** * Durable `MemoryStore` on Postgres — the full surface (L2 read/append, L3 `search`, consolidation * trio `searchScored`/`update`/`delete`, design/65 structured notes + manifest), with **switchable * recall** (see {@link PgMemoryStoreOptions}): lexical → in-process cosine over `jsonb` embeddings → * native pgvector `<=>`. All three return the same cosine-distance scale, so upgrading recall is a * config change, not a recalibration. * * Insertion order is the uuidv7 id order (time-sortable), matching the in-memory store's append order * for `read`/`search` output. Concurrency: same-scope writes from different sessions follow the * MemoryStore contract's eventual-consistency allowance (at worst a near-duplicate survives one extra * consolidation pass); `update` is a single-row UPDATE, never a read-modify-write of the whole scope. */ export declare class PgMemoryStore implements MemoryStore { private readonly query; private readonly embedder?; private readonly pgvectorSql; /** design/81 Slice 5 rung: native when the pgvector `<=>` SQL path is on, portable when an embedder ranks * in-process over the jsonb column, lexical when no embedder. */ get vectorMode(): MemoryVectorMode; constructor(query: PgQueryFn, opts?: PgMemoryStoreOptions); private embeddingParam; read(scope: string): Promise; append(scope: string, note: string): Promise; appendStructured(scope: string, note: StructuredNoteInput): Promise; private insert; clear(scope: string): Promise; /** design/84 Seam B: read the per-scope periodic-consolidation cursor (the opaque note-id high-water mark). */ getConsolidationCursor(scope: string): Promise; /** design/84 Seam B: upsert the per-scope periodic-consolidation cursor (last-writer-wins). */ setConsolidationCursor(scope: string, cursor: string): Promise; search(scope: string, query: string, limit?: number): Promise; searchScored(scope: string, query: string, limit?: number): Promise; update(scope: string, id: string, text: string): Promise; delete(scope: string, id: string): Promise; listStructuredNotes(scope: string): Promise; getByIds(scope: string, ids: string[]): Promise; private toHeader; } //# sourceMappingURL=pg.d.ts.map