/** * Tier-2 attention consolidation — the dream-cycle's review of the working-memory * buffer (E3 · Epic T11289 · Saga T11283). * * The biological loop closed by this module: * * `jot` → Tier-2 `brain_attention` (scope-keyed, decays) * → **dream-cycle consolidates** (this module) * → promote salient → Tier-3 / BRAIN (durable observation) * | discard noise / expired * | keep mid-salience open for the next cycle. * * ## One scorer, one verdict (reconciles the split AC) * * Every reviewed attention entry receives EXACTLY ONE verdict per dream cycle — * `promote` | `keep` | `discard` — from the SAME composite 6-signal scorer used * for observation promotion ({@link computePromotionScore} in * `promotion-score.ts`). There is NO parallel attention scorer: the epic's * AC2/AC3/AC4 were a single criterion mis-split into three fragments, and * {@link decideAttentionVerdict} reconciles them into one coherent decision * (child T11385). * * ## Promotion is via the conduit, never a parallel path * * A `promote` verdict routes through the sticky-convert conduit shape * ({@link promoteAttentionToMemory} in `sticky/convert.ts` → `observeBrain`), * carrying the entry's `scope_kind`/`scope_id`/`agent_id` as provenance so a * promoted entry from agent A can never surface under agent B's scope. The * source row is then marked `status='consolidated'` (idempotent — re-running the * dream cycle never double-promotes). * * ## Decay is via the homeostatic sweep, never reinvented * * `discard`/expiry reuses the accessor's in-SQL TTL + decay-floor sweep * ({@link BrainDataAccessor.expireAttention}, the same `expireAttention` policy * the CLI + focus path share) so Tier-2 never grows unbounded. Thresholds are * honoured in SQL — never load-all-then-JS-filter. * * @task T11382 — ingest brain_attention into the dream cycle * @task T11383 — promote salient entries via the conduit * @task T11384 — decay/discard low-salience entries via the homeostatic sweep * @task T11385 — single per-entry promote|keep|discard verdict from the scorer * @epic T11289 EP-DREAM-CONSOLIDATE-TIER2 * @saga T11283 SG-COGNITIVE-SUBSTRATE */ import type { BrainAttentionRow } from '../store/schema/memory-schema.js'; import { type PromotionSignals } from './promotion-score.js'; /** * Score at or above which an attention entry is PROMOTED to durable memory. * * The promotion SCORER is shared verbatim with observation promotion * ({@link computePromotionScore} — one scorer, no parallel implementation). The * promotion BAR, however, is calibrated to the attention signal space rather * than reusing the observation bar ({@link PROMOTION_THRESHOLD} = 0.6). * * Three of the six signals are structurally unavailable to a raw working-memory * jot: `citation_count` (a jot is not retrieved through the citation log), * `user_verified` (jots are not owner-verified), and `outcome_correlated` (no * task-outcome link). Those carry combined weight 0.5, so the maximum attainable * composite for ANY jot is the remaining `quality + stability + recency` ≈ 0.5 — * a jot can never reach the 0.6 observation bar. Calibrating to 0.35 cleanly * separates a salient, detailed/tagged jot (~0.40) from a mid-salience note * (~0.29) and noise (~0.23) within that 0..0.5 attention band. (See the verdict * distribution test for the empirical separation.) */ export declare const ATTENTION_PROMOTE_THRESHOLD = 0.35; /** * Decay floor below which an OPEN, unpromoted attention entry is DISCARDED. * * Matches {@link DEFAULT_DECAY_THRESHOLD} (0.1) used by the live-items query and * the CLI sweep, so an item hidden from reads is also the item swept here. */ export declare const ATTENTION_DISCARD_THRESHOLD = 0.1; /** The single per-entry verdict produced by one dream cycle (reconciled AC). */ export type AttentionVerdict = 'promote' | 'keep' | 'discard'; /** * The audited outcome of reviewing one attention entry in a dream cycle. * * @task T11385 */ export interface AttentionReview { /** The attention item id (`att__`). */ id: string; /** The single verdict this entry received this cycle. */ verdict: AttentionVerdict; /** Composite 6-signal score that drove the verdict, in `[0, 1]`. */ score: number; /** Scope the entry is keyed to — carried into promotion provenance. */ scopeKind: BrainAttentionRow['scopeKind']; /** Scope id the entry is keyed to. */ scopeId: string; /** * For a `promote` verdict, the id of the durable `brain_observations` row the * entry was promoted into via the conduit. `null` for `keep`/`discard`. */ promotedToId: string | null; } /** * Aggregated result of one Tier-2 consolidation pass over the attention buffer. * * Surfaced inside {@link RunConsolidationResult.attentionConsolidation} so the * `brain_consolidation_events` row records the Tier-2 outcome alongside every * other consolidation step. * * @task T11382 */ export interface AttentionConsolidationResult { /** Total live attention entries reviewed this cycle. */ reviewed: number; /** Entries promoted to durable memory via the conduit (verdict `promote`). */ promoted: number; /** Entries left `open` for the next cycle (verdict `keep`). */ kept: number; /** Entries swept to `discarded` (verdict `discard` + the TTL/decay sweep). */ discarded: number; /** Per-entry audited verdicts (for the test assertion + observability). */ reviews: AttentionReview[]; } /** * Derive the composite-scorer {@link PromotionSignals} for an attention entry. * * Attention rows are lightweight working-memory jots — they do not carry the * citation/quality/stability columns a `brain_observations` row accrues. We map * the available signal onto the EXISTING 6-signal vector rather than inventing a * parallel scorer: * * - `citationCount` — always 0 (a jot is not retrieved through the citation log). * - `qualityScore` — a content-richness proxy: longer + tagged jots read as * higher quality, normalised to `[0, 1]` (null would default to 0.5, which is * too generous for a one-word jot, so we compute an explicit proxy). * - `stabilityScore` — the entry's own `decay_score` when present (an entry that * has survived decay is more stable); null → scorer default (0.5). * - `createdAt` — drives the recency factor (fresh jots score higher). * - `userVerified` — 0 (jots are not owner-verified). * - `outcomeCorrelated` — 0 (no task-outcome correlation for raw jots). * * @param row - The attention row to score. * @returns The 6-signal vector consumed by {@link computePromotionScore}. * @task T11382 * @task T11385 */ export declare function attentionToPromotionSignals(row: BrainAttentionRow): PromotionSignals; /** * Decide the SINGLE verdict for one attention entry from its composite score and * liveness — the reconciliation of the parent epic's split AC2/AC3/AC4. * * Exactly one of `promote` | `keep` | `discard` is returned: * * 1. `discard` — the entry is already past its TTL (`expires_at <= now`) OR its * `decay_score` is below {@link ATTENTION_DISCARD_THRESHOLD}. (The accessor * sweep flips these to `discarded`; deciding `discard` here keeps the verdict * audit consistent with what the sweep will do.) * 2. `promote` — a live entry whose composite score ≥ * {@link ATTENTION_PROMOTE_THRESHOLD}. * 3. `keep` — everything else: a live, mid-salience entry that stays `open` for * the next cycle. * * @param row - The attention entry under review. * @param score - Its composite 6-signal score (from {@link computePromotionScore}). * @param now - Reference time (unix ms) for the TTL check; defaults to `Date.now()`. * @param promoteThreshold - Promote bar (defaults to {@link ATTENTION_PROMOTE_THRESHOLD}). * @param discardThreshold - Discard decay floor (defaults to {@link ATTENTION_DISCARD_THRESHOLD}). * @returns The single verdict for this entry. * @task T11385 */ export declare function decideAttentionVerdict(row: BrainAttentionRow, score: number, now?: number, promoteThreshold?: number, discardThreshold?: number): AttentionVerdict; /** * Options for {@link consolidateAttention}. * * @task T11382 */ export interface ConsolidateAttentionOptions { /** Reference time (unix ms) for recency + TTL. Defaults to `Date.now()`. */ now?: number; /** Max entries reviewed per cycle (bounds the pass). Defaults to 200. */ limit?: number; /** Promote threshold override (defaults to {@link ATTENTION_PROMOTE_THRESHOLD}). */ promoteThreshold?: number; /** Discard decay floor override (defaults to {@link ATTENTION_DISCARD_THRESHOLD}). */ discardThreshold?: number; } /** * Review the live Tier-2 attention buffer and apply one promote|keep|discard * verdict per entry — the dream-cycle's consolidation pass over working memory. * * Steps: * * 1. Read every LIVE (`open`, non-expired, above-decay-floor) attention entry * across ALL scopes via the accessor's leakage-safe `findAttention` (no scope * restriction → the whole buffer; JSONB `tags` read via `json(col)`). This is * the dream-cycle reviewing the buffer, NOT a per-agent read. * 2. Score each entry with the EXISTING composite 6-signal scorer and decide one * verdict ({@link decideAttentionVerdict}). * 3. `promote` → route through the conduit ({@link promoteAttentionToMemory}), * carrying scope provenance, then mark the source `consolidated` (idempotent). * 4. `discard`/expiry → the homeostatic sweep * ({@link BrainDataAccessor.expireAttention}) flips TTL/decay-floor entries to * `discarded` in SQL. * 5. `keep` → left `open` for the next cycle. * * Every verdict is audited via pino ({@link getLogger}) so a teardown-race never * routes a deferred log through the vitest console interceptor. * * Leakage preservation: scope provenance is attached at promotion time, so agent * A's promoted entry never surfaces under agent B's scope — the structural * guarantee the buffer's scope-key already provides for reads is carried forward * into durable memory. * * @param projectRoot - Absolute project root (brain.db resolution). * @param options - Reference time, per-cycle limit, threshold overrides. * @returns The aggregated consolidation result + per-entry audit. * @task T11382 * @task T11383 * @task T11384 * @task T11385 */ export declare function consolidateAttention(projectRoot: string, options?: ConsolidateAttentionOptions): Promise; //# sourceMappingURL=attention-consolidate.d.ts.map