/** * Rolling Session Narrative โ€” Per-session conversation summary store. * * Maintains a compact, rolling prose summary of what has happened in a CLEO * session. Each dialectic turn can contribute a `sessionNarrativeDelta` that * is appended and compressed into the rolling `narrative` field. * * ## Storage * * Data is persisted to the `session_narrative` table in brain.db. The table * is created on first write via `CREATE TABLE IF NOT EXISTS` so the module * works safely before the formal ADR-054 Drizzle migration is applied. * The Drizzle migration (`20260423000002_t1089-add-session-narrative-table`) * generated by `pnpm db:new -- --db brain --task T1089 --name add-session-narrative-table` * is the canonical source for the schema; the inline DDL here is a verbatim copy * kept for zero-dep startup resilience. * * ## Schema * * ``` * session_narrative ( * session_id TEXT PRIMARY KEY, * narrative TEXT NOT NULL, -- Rolling summary, max 2000 chars * turn_count INTEGER NOT NULL DEFAULT 0, * last_updated_at INTEGER NOT NULL, -- Unix epoch milliseconds * pivot_count INTEGER NOT NULL DEFAULT 0 * ) * ``` * * ## Pivot Detection * * `detectPivot(delta, existingNarrative)` uses cosine similarity over text * embeddings when the sqlite-vec extension and an embedding provider are both * available. A pivot is signalled when the cosine similarity between the two * texts falls below `PIVOT_COSINE_THRESHOLD`. * * When embeddings are unavailable the function falls back to a keyword-overlap * heuristic: a topic shift is detected when the new delta contains fewer than * `PIVOT_KEYWORD_OVERLAP_THRESHOLD` of its tokens in the existing narrative. * * ## PSYCHE Reference * * Inspired by `upstream psyche-lineage ยท deriver/deriver.py` (session state * derivation) โ€” PSYCHE maintains a rolling representation of what a session * "is about" that informs future retrieval and sigil generation. * * @task T1531 * @epic T1056 */ /** * Cosine similarity threshold below which a turn is classified as a pivot * when embedding-based detection is available (sqlite-vec + embedding provider * both loaded). * * A score of 0.0 means orthogonal vectors (completely unrelated topics); * 1.0 means identical direction (same topic). Empirically, topic shifts * tend to produce similarity < 0.3. * * **Tradeoffs vs keyword fallback**: * - Higher accuracy on paraphrase and synonym pivots the keyword heuristic * misses (e.g. "switch to Rust" vs "rewrite in a systems language"). * - Requires the embedding model to be loaded (~50โ€“80 MB), which adds latency * on first use and is unavailable in cold environments. * - The fallback keyword heuristic is zero-cost and always available but * cannot detect pivots expressed through semantically related but * lexically different phrasing. * * @task T1531 */ export declare const PIVOT_COSINE_THRESHOLD = 0.3; /** * A session narrative record as returned by `getSessionNarrative`. */ export interface SessionNarrativeRecord { /** Session identifier. */ sessionId: string; /** Rolling prose summary of the session so far. */ narrative: string; /** Number of dialectic turns that have contributed to this narrative. */ turnCount: number; /** Unix epoch milliseconds of the last update. */ lastUpdatedAt: number; /** Number of detected topic pivots in this session. */ pivotCount: number; } /** * Compute the cosine similarity between two Float32 vectors. * * Returns a value in [-1, 1] where 1 means identical direction and 0 means * orthogonal. Returns 0 when either vector has zero magnitude to avoid * division by zero. * * @param a - First embedding vector. * @param b - Second embedding vector (must be same length as `a`). * @returns Cosine similarity in [-1, 1]. * * @task T1531 */ export declare function cosineSimilarity(a: Float32Array, b: Float32Array): number; /** * Retrieve the current narrative record for a session. * * Returns `null` when the session has no narrative entry yet. * * @param sessionId - The session identifier to look up. * @returns The narrative record, or `null` if not found. * * @example * ```ts * const record = await getSessionNarrative('ses_20260422131135_5149eb'); * console.log(record?.narrative ?? '(no narrative yet)'); * ``` * * @task T1089 */ export declare function getSessionNarrative(sessionId: string): Promise; /** * Append a narrative delta to a session's rolling summary. * * The delta is prepended with a space separator if the existing narrative is * non-empty. When the combined result exceeds `MAX_NARRATIVE_CHARS` the oldest * content is trimmed from the left (keeping the most recent context). * * Side effect: increments `turn_count` by one and updates `pivot_count` when * `detectPivot(delta)` returns `true` against the existing narrative. * * @param sessionId - The session to update. * @param delta - New narrative content from the current turn. * @param _projectRoot - Project root (reserved for future use; unused now). * * @example * ```ts * await appendNarrativeDelta( * 'ses_20260422131135_5149eb', * 'User requested zero-dep implementation; agent proposed using existing buildRetrievalBundle.', * '/mnt/projects/cleocode', * ); * ``` * * @task T1089 */ export declare function appendNarrativeDelta(sessionId: string, delta: string, _projectRoot: string): Promise; /** * Detect whether a new narrative delta represents a significant topic pivot. * * ## Strategy selection * * **Embedding path** (preferred): when an embedding provider is available * (via `isEmbeddingAvailable()`), both `delta` and `existingNarrative` are * embedded into float vectors and compared with cosine similarity. A score * below `PIVOT_COSINE_THRESHOLD` (default 0.3) indicates a pivot. This path * catches paraphrase pivots and synonym-based topic changes that the keyword * heuristic misses, at the cost of requiring the embedding model to be loaded. * * **Keyword fallback** (no embedding provider / vec extension): if * `embedText()` returns `null` for either input, the function falls back to a * keyword-overlap heuristic. A pivot is declared when fewer than * `PIVOT_KEYWORD_OVERLAP_THRESHOLD` (0.5) of the delta's content tokens appear * in the existing narrative. * * Short deltas (< 5 tokens in the keyword path) never trigger a pivot to avoid * false positives from acknowledgement messages. The embedding path has no * minimum-length guard because the model handles short inputs gracefully. * * @param delta - Incoming narrative text from the new turn. * @param existingNarrative - Current rolling narrative content. * @returns Promise resolving to `true` when a topic pivot is detected. * * @example * ```ts * await detectPivot("switching to Rust implementation", "TypeScript type system discussion"); // true * await detectPivot("continuing the TypeScript work", "TypeScript type system discussion"); // false * ``` * * @task T1531 */ export declare function detectPivot(delta: string, existingNarrative: string): Promise; //# sourceMappingURL=session-narrative.d.ts.map