/** * E2 prediction first-class object (v0.31 / docs/plans/2026-05-26-e2-prediction-object.md). * * Canonical store for ex-ante claims that can be closed against ex-post * outcomes. The `predictions` table holds every field (including * `claim_text`); a memory row mirrors the claim for recall/inspect surfaces * but is NOT the source of truth — ON DELETE SET NULL on memory_id means * memory deletion gracefully orphans the prediction without losing data. * * Tenant scoping: every helper requires tenantId. The schema's BEFORE INSERT * + BEFORE UPDATE triggers (`trg_predictions_tenant_match_*`) enforce that * `predictions.tenant_id` matches the referenced memory's tenant_id when * `memory_id IS NOT NULL`. Cross-tenant references are unrepresentable at * the schema level. * * Dual-write atomicity: `savePrediction` writes the memory + predictions * row inside `writeEntry`'s SAVEPOINT 'write_entry' (store.ts:1196). The * afterWrite hook (store.ts:1199-1201) runs inside the same SAVEPOINT, so * a failure in either step rolls back both. Pattern matches supersede * (api.ts:1486) and the Slack/GitHub connectors. * * J3 (reference-class / planning-fallacy detector) reads from * `loadPredictionsByClass` to compute per-class base rates from * (estimate_value, actual_value) at query time. J3 is a follow-up episode; * this module ships the data layer. */ export type ClosureState = 'open' | 'closed' | 'closed-unknown'; export declare const VALID_CLOSURE_STATES: ReadonlySet; export interface Prediction { id: number; /** Nullable: ON DELETE SET NULL allows memory deletion (forget / * consolidate / archive) without breaking the prediction row. */ memoryId: string | null; tenantId: string; classTag: string; claimText: string; estimateValue: number | null; estimateUnit: string | null; targetDate: string | null; actualValue: number | null; closureState: ClosureState; closedAt: string | null; closureNote: string | null; createdAt: string; } export interface SavePredictionOpts { classTag: string; claimText: string; estimateValue?: number; estimateUnit?: string; targetDate?: string; } export interface ClosePredictionOpts { closureState: ClosureState; actualValue?: number; closureNote?: string; } export interface ListPredictionsOpts { closureState?: ClosureState; limit?: number; } /** * Create a new prediction. Writes a memory mirror + a predictions table * row atomically inside `writeEntry`'s SAVEPOINT 'write_entry'. On any * failure (audit write, predictions INSERT, trigger ABORT), the SAVEPOINT * rolls back — neither the memory row nor the predictions row lands. * * The memory is tagged `['prediction', classTag]` with `source='prediction'` * and `kind='distilled'`. It surfaces in `hippo recall` so the agent can * see open predictions naturally; the predictions table is the canonical * structured store used by J3. */ export declare function savePrediction(hippoRoot: string, tenantId: string, opts: SavePredictionOpts, actor?: string): Prediction; /** * Close an existing open prediction. Updates the predictions row only; * the memory mirror is NOT mutated in v1 (predictions table is canonical). * J3 computes accuracy (clean vs regressed) from (estimateValue, * actualValue) at query time. */ export declare function closePrediction(hippoRoot: string, tenantId: string, id: number, opts: ClosePredictionOpts, actor?: string): Prediction; export declare function loadPredictionById(hippoRoot: string, tenantId: string, id: number): Prediction | null; export declare function loadPredictionsByClass(hippoRoot: string, tenantId: string, classTag: string, opts?: ListPredictionsOpts): Prediction[]; export interface PredictionBaserate { classTag: string; /** Count of closed predictions with a numeric actual_value (excludes * open + closed-unknown). The denominator for MAE. */ nClosed: number; /** Count of closed rows where estimate_value > 0 (i.e. ratio is defined). * Subset of nClosed used for meanRatio + p50Ratio. */ nRatioEligible: number; meanEstimate: number | null; meanActual: number | null; /** mean(actual / estimate) over the nRatioEligible subset. Null when * nRatioEligible = 0 (e.g. all closed predictions had estimate=0). */ meanRatio: number | null; /** Median ratio over the nRatioEligible subset. */ p50Ratio: number | null; /** Mean absolute error = mean(|actual - estimate|) over the nClosed set. */ mae: number | null; /** Human-readable summary string for direct surface in CLI / MCP / HTTP. * Empty when nClosed = 0. */ summary: string; } /** * Compute base-rate stats for closed predictions in a class. Used by J3 * reference-class / planning-fallacy detector. Direct application of * Lovallo-Kahneman (2003) inside-vs-outside view. * * Filter: closure_state='closed' AND estimate_value IS NOT NULL AND * actual_value IS NOT NULL. Excludes closed-unknown (no actual to * compare against) and open (not yet resolved). * * Audit-emit is BUILT IN here (single source of truth, no caller-site * drift risk). Plan-eng-critic round 1 HIGH recommendation: emit inside * helper, not at 3 call sites. */ export declare function computePredictionBaserate(hippoRoot: string, tenantId: string, classTag: string, actor?: string, /** v0.32 / J3.2 — when false, skip the predict_baserate audit emit. The * J3.2 orchestrator (computePlanningFallacyHint, below) calls this with * emitAudit=false and emits its own `recall_autodebias_hint` audit row * instead, so the predict_baserate channel stays scoped to deliberate * CLI / HTTP / MCP predict-baserate calls and does NOT pollute on every * recall containing a forward-claim phrase. Default true preserves the * v1.13.0 J3 audit semantics for the 3 direct callers (cmdPredict * baserate, /v1/predictions/stats route, hippo_predict_baserate MCP * handler) — none of them pass this argument. */ emitAudit?: boolean): PredictionBaserate; export declare function loadOpenPredictions(hippoRoot: string, tenantId: string, opts?: { classTag?: string; limit?: number; }): Prediction[]; /** * J3.2 surface delivered on `RecallResult.planningFallacyHint` when an * agent's recall query carries a forward-prediction phrase AND the closest * matching prediction class has closed historical data. * * The agent sees its track record at the moment of forecasting, anchoring * on the outside view (Lovallo-Kahneman 2003) rather than the inside-view * inside the planning fallacy. * * Plan: docs/plans/2026-05-26-j32-auto-injection.md. */ export interface PlanningFallacyHint { classTag: string; /** Verbatim PredictionBaserate.summary, e.g. * "Last 5 estimates in class migration-effort averaged 2.10x actual (MAE 1.40)." */ baserateSummary: string; /** Discriminator vs hypothetical future manual-override hints. */ source: 'j3.2-auto'; /** The regex match snippet that triggered detection. Lets the agent * see WHY the hint appeared and self-correct if detection misfires * (e.g. "I wasn't predicting; ignore"). */ detectedPhrase: string; nClosed: number; /** Null only when every closed-row had estimate_value=0 (ratio undefined). */ meanRatio: number | null; } /** * v1.13.4 / J3.2 follow-up — "watching" variant emitted when the * forward-claim regex matched but no PlanningFallacyHint baserate was * returned. Dogfood diary (docs/dogfood/2026-05-27-track-j-warnings.md) * Trial 2a confirmed the pre-v1.13.4 silent paths were the most common * real-world J3.2 failure mode: a natural-language query carries a * forward-claim phrase but its non-stopword tokens don't overlap with * any prediction class tag, so hippo silently emitted nothing despite * the regex match. The watching variant surfaces the detection event * + a one-line suggestion so the agent can either re-tag the prediction * or pass the suggestion through to the user. */ export interface PlanningFallacyWatching { /** The forward-claim phrase the detector matched (verbatim regex match snippet). */ detectedPhrase: string; /** Why hippo couldn't produce a baserate hint despite the match. * - 'no_class_match': no class scored >=1 on token overlap. * - 'tiebreak': >=2 classes tied at the same best score (silent on ambiguity). */ reason: 'no_class_match' | 'tiebreak'; /** One-line agent-facing suggestion for how the user can give hippo * enough signal to produce a baserate next time. */ suggestion: string; } /** * v1.13.4 / J3.2 follow-up — richer return type for * `computePlanningFallacyOutput`. Carries EITHER `hint` (baserate * available) OR `watching` (regex fired, no baserate), or NEITHER (mode=off, * no queryText, no regex match, or nClosed=0 silent path). Never both. * * Existing `computePlanningFallacyHint` (preserved as a backward-compat * wrapper) returns only the hint variant; new code should call * `computePlanningFallacyOutput` directly to surface the watching variant. */ export interface PlanningFallacyOutput { hint?: PlanningFallacyHint; watching?: PlanningFallacyWatching; } export type AutodebiasMode = 'off' | 'regex'; export interface ComputePlanningFallacyHintOpts { /** Override env. When undefined, reads process.env.HIPPO_AUTODEBIAS at * call time (per-call to allow test-time env toggling without module * reload). 'off' short-circuits to null BEFORE the regex gate so the * AUTODEBIAS=off path pays zero work. */ mode?: AutodebiasMode; /** Actor for any audit emissions. Defaults to 'recall' (caller didn't * specify). MUST thread through to the inner computePredictionBaserate * call (passed as its `actor` arg) so MCP/HTTP-originated auto-hints * carry the right attribution instead of the 'cli' default. */ actor?: string; } /** * J3.2 orchestrator (v1.13.4: richer return type — see computePlanningFallacyHint * below for the backward-compat wrapper that returns only the hint variant). * * Composes the forward-claim detector + class resolver + baserate compute, * with telemetry-grade audit emission at every decision point (success, * no-class-match, tiebreak). * * Returns `{}` (neither hint nor watching) on: * - mode === 'off' (env-disabled; pays only the env read, skips regex) * - empty queryText * - no forward-claim regex match * - resolved class has nClosed=0 (no historical data yet; silent) * * Returns `{ watching: ... }` on (v1.13.4 NEW — was silent null pre-1.13.4): * - resolver returns no class (no overlap ≥ 1; emits no_class_match audit) * - resolver returns tiebreak (≥2 classes tied at best; emits tiebreak audit) * * Returns `{ hint: ... }` on success: calls computePredictionBaserate(..., * emitAudit=false) so the predict_baserate audit channel stays scoped to * deliberate predict-baserate calls (the orchestrator's own recall_autodebias_hint * audit carries n_closed + mean_ratio in metadata so no telemetry is lost), * then emits recall_autodebias_hint audit + returns the hint. * * Latency budget (plan §Latency): ~50us regex-only on miss; ~750-850us * on full match+resolve+baserate path. Well under 50ms target. */ export declare function computePlanningFallacyOutput(hippoRoot: string, tenantId: string, queryText: string, opts?: ComputePlanningFallacyHintOpts): PlanningFallacyOutput; /** * v1.13.4 backward-compat wrapper: thin shim around * computePlanningFallacyOutput that returns only the hint variant. * Existing callers (api.recall, cmdRecall, MCP handler) that don't yet * consume the watching variant continue to work unchanged. * * New callers that want to surface the silent no-class-match / tiebreak * paths to users should call computePlanningFallacyOutput directly. */ export declare function computePlanningFallacyHint(hippoRoot: string, tenantId: string, queryText: string, opts?: ComputePlanningFallacyHintOpts): PlanningFallacyHint | null; //# sourceMappingURL=predictions.d.ts.map