/** * writeMessages — write-side stage that persists new turn messages as * `MemoryEntry`s. * * Reads from scope: `identity`, `turnNumber`, `newMessages` * Writes to store: one entry per message, id = `msg-{turnNumber}-{index}` * * Every written entry carries `source.turn` + `source.identity` so * retrieval stages can later show "recalled from turn 5" with correct * cross-session provenance. A content-hash signature is also registered * via `store.recordSignature` so subsequent `seen()` calls recognize * near-duplicate content without loading the full entries. * * Why a deterministic `id` format? * `msg-{turn}-{index}` lets write-then-re-write be idempotent: a stage * that re-runs in the same turn (retries, resumable turns) overwrites * the same id instead of growing history. For non-turn-scoped writes, * pass a custom `idFrom` that produces whatever shape your app needs. * * PII / redaction: * Message content is stored VERBATIM. If your messages contain PII * (names, addresses, secrets), redact BEFORE writing — either by * mutating `scope.newMessages` upstream or by wrapping the call site * with a redaction helper. The storage layer does NOT scrub for you. * Pair with footprintjs's `RedactionPolicy` for end-to-end coverage. * * Extended thinking blocks (Anthropic) / reasoning tokens (OpenAI): * Persisting reasoning blocks is expensive — they can be 10-100× the * size of the final message. Strip them from `scope.newMessages` before * calling this stage UNLESS you plan to recall them (e.g. for debugging * replay). A common pattern: write reasoning to `tier: 'cold'` with a * short `ttlMs` so they age out quickly. */ import type { TypedScope } from 'footprintjs'; import type { MemoryStore } from '../store/index.js'; import type { LLMMessage as Message } from '../../adapters/types.js'; import type { MemoryState } from './types.js'; export interface WriteMessagesConfig { /** The store to persist to. */ readonly store: MemoryStore; /** * Optional id producer — receives (turn, index, message) and returns * the `MemoryEntry.id`. Defaults to `msg-{turn}-{index}` which makes * re-runs of the same turn idempotent. Override for app-level ids * (e.g. use a message's server-side id). */ readonly idFrom?: (turn: number, index: number, message: Message) => string; /** * Optional signature producer for the recognition set. When present, * each message produces a signature that is registered via * `store.recordSignature`; `seen()` later returns `true` for the same * content. Default: skip (many apps don't need recognition). */ readonly signatureFrom?: (message: Message) => string; /** * Optional TTL in milliseconds from `Date.now()`. When set, written * entries expire this long after they were stored. Useful for * compliance retention windows ("delete chat history after 30 days"). */ readonly ttlMs?: number; /** * Optional tier for the entries. Typical pattern: * - `'hot'` for the last few turns * - `'warm'` for older turns * - `'cold'` for archived * Stages in Layer 3+ can filter on tier. Omitting leaves entries * untiered (read stages still see them; tier-filtered reads skip them). */ readonly tier?: 'hot' | 'warm' | 'cold'; } export declare function writeMessages(config: WriteMessagesConfig): (scope: TypedScope) => Promise;