/** * Sentient Dream Cycle — Real LLM-backed BRAIN observation synthesis (T1680). * * Implements autonomous cognitive dreaming: periodically collecting recent BRAIN * observations, clustering them by topic similarity, and calling an LLM to * extract durable memories (decisions, patterns, learnings, constraints) from * each cluster. Extracted memories are routed through the existing * `verifyAndStore` gate in extraction-gate.ts — NO new pipeline. * * ## Cycle (default every 4 hours): * * 1. **Collect** — query last 24 h of brain_observations across types * (`hygiene:*`, `session:*`, `decision:*`, `pattern:*`). * 2. **Cluster** — Jaccard similarity on n-gram overlap (title+narrative). * If `brain.embeddings` available, vector similarity is preferred. * 3. **Synthesise** — for each cluster of ≥ DREAM_CLUSTER_MIN_SIZE * observations, call the daemon LLM with a structured-output schema * (`{decisions, patterns, learnings, constraints}`) mirroring the * llm-extraction.ts pattern exactly. * 4. **Verify-and-store** — route each extracted memory through the * existing `verifyAndStore` gate (extraction-gate.ts). * 5. **Digest** — store a summary as a BRAIN observation tagged * `sentient:dream-cycle-`. * * Wire-in: `safeRunTick` fires `maybeTriggerDreamCycle` every * `DREAM_CYCLE_INTERVAL_MS` (default 4 h). The trigger is fire-and-forget * so it never blocks the tick outcome. * * LLM provider: reads `llm.daemon.provider` and `llm.daemon.model` from the * global `~/.cleo/config.json` via `getRawConfigValue`. Credentials resolved * via `resolveLLMForRole('consolidation')` (T9255 — Phase 2 role-based routing). * * ## Test injection * * Every side-effecting dependency is injectable via `DreamCycleOptions`: * - `client` — Anthropic client stub (no real network in tests) * - `collectObservations` — override the DB query * - `observeMemory` — override the BRAIN write * - `verifyAndStoreFn` — override the extraction gate * - `isKilled` — kill-switch check * - `dreamCycleIntervalMs = 0` — forces trigger every tick * - `dreamCycle = null` — disables entirely * * @task T1680 * @epic T1676 * @see packages/core/src/memory/llm-extraction.ts — pattern mirrored exactly * @see packages/core/src/sentient/hygiene-scan.ts — safe wrapper pattern * @see packages/core/src/sentient/stage-drift-tick.ts — interval-gate pattern */ import type Anthropic from '@anthropic-ai/sdk'; import type { MemoryCandidate } from '../memory/extraction-gate.js'; /** * Default interval between dream cycle runs (4 hours in milliseconds). * Configurable via `DreamCycleTickOptions.dreamCycleIntervalMs`. */ export declare const DREAM_CYCLE_INTERVAL_MS: number; /** * How far back (ms) to look for BRAIN observations to cluster. * Default: last 24 hours. */ export declare const DREAM_LOOKBACK_MS: number; /** * Minimum number of observations in a cluster before the LLM is called. * Clusters smaller than this are skipped — too little signal. */ export declare const DREAM_CLUSTER_MIN_SIZE = 5; /** * Maximum number of clusters to synthesise per dream cycle run. * Prevents runaway LLM cost on large observation sets. */ export declare const DREAM_MAX_CLUSTERS = 10; /** * Jaccard similarity threshold above which two observations are placed in * the same cluster. Value in [0, 1]. Higher = tighter clusters. */ export declare const DREAM_JACCARD_THRESHOLD = 0.15; /** * Default daemon LLM model when no `llm.roles.consolidation`, `llm.default`, * nor `llm.daemon` entry is configured. Re-exported for tests + downstream * code that historically relied on this constant — sourced from * `IMPLICIT_FALLBACK_MODEL` (T9255) so there is exactly one literal. */ export declare const DREAM_DEFAULT_MODEL = "claude-haiku-4-5-20251001"; /** * Default daemon LLM provider when `llm.daemon.provider` is not configured. */ export declare const DREAM_DEFAULT_PROVIDER: "anthropic"; /** * Minimum importance score for extracted memories to be stored. * Below this threshold, memories are dropped. */ export declare const DREAM_MIN_IMPORTANCE = 0.6; /** * A single BRAIN observation as collected from brain_observations. */ export interface CollectedObservation { /** Unique observation ID (O-* or similar). */ id: string; /** Short title of the observation. */ title: string; /** Full narrative/text content of the observation. */ narrative: string; /** ISO 8601 creation timestamp. */ createdAt: string; /** * Observation type tag (e.g. `hygiene:orphan`, `session:end`, `decision:*`). * May be an empty string when the column is null. */ observationType: string; } /** * A cluster of thematically-related observations produced by the Jaccard step. */ export interface ObservationCluster { /** Sequential cluster index within this run. */ index: number; /** Member observations. */ observations: CollectedObservation[]; /** * Representative topic label derived from the most-shared n-grams. * Used in the LLM prompt for context. */ topicLabel: string; } /** * A single memory item extracted by the LLM during the dream synthesis step. * Schema mirrors `ExtractedMemory` from llm-extraction.ts. */ export interface DreamExtractedMemory { /** `decision | pattern | learning | constraint` */ type: 'decision' | 'pattern' | 'learning' | 'constraint'; /** Declarative knowledge content (≤ 500 chars). */ content: string; /** Importance 0.0–1.0. Only values ≥ DREAM_MIN_IMPORTANCE are persisted. */ importance: number; /** Referenced code symbols, file paths, or concepts. */ entities: string[]; /** Why this memory is worth keeping (≤ 200 chars). */ justification: string; } /** * Summary digest stored as a BRAIN observation at end of each dream run. */ export interface DreamCycleDigest { /** Unique run ID (UUID). */ runId: string; /** ISO 8601 timestamp the cycle started. */ startedAt: string; /** Number of observations collected in the lookback window. */ observationsCollected: number; /** Number of clusters formed. */ clustersFormed: number; /** Number of clusters that reached the minimum size and were synthesised. */ clustersSynthesised: number; /** Total memories extracted across all clusters. */ memoriesExtracted: number; /** Number of memories successfully stored or merged. */ memoriesStored: number; /** Number of memories rejected (below threshold or gate-rejected). */ memoriesRejected: number; /** Non-fatal warnings encountered during the run. */ warnings: string[]; } /** * Full outcome returned by `runDreamCycle`. */ export interface DreamCycleOutcome { /** How the cycle ended. */ kind: 'killed' | 'no-api-key' | 'no-observations' | 'no-clusters' | 'completed' | 'error'; /** Human-readable detail. */ detail: string; /** Digest of what was processed (present on `completed` outcomes). */ digest?: DreamCycleDigest; } /** * Options for `runDreamCycle`. All side-effecting deps are injectable. */ export interface DreamCycleOptions { /** Absolute path to the project root (contains `.cleo/`). */ projectRoot: string; /** Absolute path to sentient-state.json (for kill-switch check). */ statePath: string; /** * Override for the Anthropic client. Injected by tests to avoid real API * calls. When omitted (undefined), a client is constructed from the resolved * API key via `buildDaemonClient`. Pass `null` explicitly to signal "no * API key available" without triggering real credential resolution. */ client?: Pick | null; /** * Override for the brain observation collector. * When omitted, queries `brain_observations` directly. * Pass a function returning [] to simulate no-observations. */ collectObservations?: (projectRoot: string, lookbackMs: number) => Promise; /** * Override for the verify-and-store gate. * When omitted, calls `verifyAndStore` from extraction-gate.ts. */ verifyAndStoreFn?: (projectRoot: string, candidate: MemoryCandidate) => Promise<{ action: 'stored' | 'merged' | 'pending' | 'rejected'; }>; /** * Override for the BRAIN observation writer (dream-cycle digest). * When omitted, calls `memoryObserve` from `@cleocode/core/internal`. */ observeMemory?: (params: { text: string; title: string; type?: string; }, projectRoot: string) => Promise; /** * Kill-switch check. When omitted, reads `statePath` via `readSentientState`. */ isKilled?: () => Promise; /** * Lookback window in ms. Defaults to {@link DREAM_LOOKBACK_MS} (24 h). */ lookbackMs?: number; /** * Minimum cluster size to synthesise. Defaults to {@link DREAM_CLUSTER_MIN_SIZE}. */ clusterMinSize?: number; /** * Jaccard similarity threshold. Defaults to {@link DREAM_JACCARD_THRESHOLD}. */ jaccardThreshold?: number; /** * Maximum clusters to synthesise per run. Defaults to {@link DREAM_MAX_CLUSTERS}. */ maxClusters?: number; /** * Minimum importance for extracted memories. Defaults to {@link DREAM_MIN_IMPORTANCE}. */ minImportance?: number; } /** * Options for the tick-level cadence trigger. * Passed to `maybeTriggerDreamCycle` from `safeRunTick`. */ export interface DreamCycleTickOptions { /** Absolute project root. */ projectRoot: string; /** Absolute path to sentient-state.json. */ statePath: string; /** * Override for the dream cycle function — lets tests assert calls without * touching the real brain.db stack or LLM. * * Pass `null` to disable the dream cycle entirely (test escape hatch). */ dreamCycle?: ((options: DreamCycleOptions) => Promise) | null; /** * Interval between dream cycle runs (ms). * Defaults to {@link DREAM_CYCLE_INTERVAL_MS} (4 h). * Pass `0` to trigger every tick (useful for integration tests). */ dreamCycleIntervalMs?: number; /** Injected options forwarded to `runDreamCycle`. */ dreamCycleOptions?: Omit; } /** * Run one full dream cycle. * * Steps: * 1. Kill-switch check. * 2. Resolve LLM client (daemon provider config + credentials). * 3. Collect observations from the last 24 h. * 4. Cluster by Jaccard similarity. * 5. Synthesise each eligible cluster. * 6. Verify and store extracted memories via extraction-gate. * 7. Emit dream-cycle digest as BRAIN observation. * * Never throws — all errors are caught and reported in the outcome. * * @param options - Dream cycle options (see {@link DreamCycleOptions}). * @returns {@link DreamCycleOutcome} describing how the cycle ended. * * @task T1680 */ export declare function runDreamCycle(options: DreamCycleOptions): Promise; /** * Safe wrapper for {@link runDreamCycle} — swallows unexpected exceptions. * * Used from `safeRunTick` as a fire-and-forget best-effort call. * Errors never propagate to the tick caller. * * @param options - Dream cycle options. * @returns Cycle outcome or an error outcome on unexpected throw. * * @task T1680 */ export declare function safeRunDreamCycle(options: DreamCycleOptions): Promise; /** * Evaluate the dream cycle cadence and fire {@link safeRunDreamCycle} when * enough time has elapsed since the last run. * * Mirrors the pattern in `maybeTriggerStageDriftScan` from stage-drift-tick.ts. * Respects the injectable `options.dreamCycle` override (null = disabled). * Errors are swallowed — dream cycle must never crash the tick. * * @param options - Tick-level options (see {@link DreamCycleTickOptions}). * * @internal * @task T1680 */ export declare function maybeTriggerDreamCycle(options: DreamCycleTickOptions): Promise; /** * Reset the dream cycle interval timestamp. * * Intended for test teardown only — allows tests to reset the cadence so * the next tick fires immediately. * * @internal * @task T1680 */ export declare function _resetDreamCycleAt(): void; /** * Return the current dream cycle interval timestamp (ms). * * Read-only accessor for test assertions. * * @internal * @task T1680 */ export declare function _getLastDreamCycleAt(): number; //# sourceMappingURL=dream-cycle.d.ts.map