diff --git a/packages/core/src/distillation.ts b/packages/core/src/distillation.ts index 98fa730..90d36b1 100644 --- a/packages/core/src/distillation.ts +++ b/packages/core/src/distillation.ts @@ -714,7 +714,11 @@ async function runInner(input: { // Check if meta-distillation is needed (skip when cache is warm to avoid // prefix cache invalidation — row IDs change after meta-distill, busting // the prompt cache on the next turn). - const effectiveMetaThreshold = input.metaThresholdOverride ?? cfg.distillation.metaThreshold; + // Clamp override to min 2 — meta-distillation with < 2 gen-0 segments is pointless. + const effectiveMetaThreshold = Math.max( + 2, + input.metaThresholdOverride ?? cfg.distillation.metaThreshold, + ); if ( !input.skipMeta && gen0Count(input.projectPath, input.sessionID) >= diff --git a/packages/core/src/gradient.ts b/packages/core/src/gradient.ts index 27f103f..339b352 100644 --- a/packages/core/src/gradient.ts +++ b/packages/core/src/gradient.ts @@ -604,12 +604,31 @@ export function inspectSessionState(sessionID: string): { } /** - * Return the consecutive-bust counter for a session. - * Used by the gateway idle handler and urgent-distillation scheduler to - * lower the meta-distillation threshold under bust pressure. + * Return the consecutive-bust counter for a session (read-only). + * Returns 0 if the session has no in-memory state — callers treat this + * as "no bust pressure" which is the safe default. + * + * Uses Map.get() instead of getSessionState() to avoid creating phantom + * SessionState entries with zeroed calibration fields, which would cause + * the next transform() call to treat the session as uncalibrated. */ export function getConsecutiveBusts(sessionID: string): number { - return getSessionState(sessionID).consecutiveBusts; + return sessionStates.get(sessionID)?.consecutiveBusts ?? 0; +} + +/** Bust-pressure threshold for meta-distillation: consecutive busts ≥ this + * value trigger earlier consolidation of gen-0 segments. */ +export const BUST_PRESSURE_THRESHOLD = 3; + +/** + * Compute the effective meta-distillation threshold under bust pressure. + * When busts ≥ BUST_PRESSURE_THRESHOLD, lowers the threshold to 1/4 of the + * configured value (min 3) to consolidate the distilled prefix earlier. + */ +export function effectiveMetaThreshold(busts: number, configThreshold: number): number { + return busts >= BUST_PRESSURE_THRESHOLD + ? Math.max(3, Math.floor(configThreshold / 4)) + : configThreshold; } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e57a44f..0456fae 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -118,6 +118,8 @@ export { setLastTurnAtForTest, inspectSessionState, getConsecutiveBusts, + BUST_PRESSURE_THRESHOLD, + effectiveMetaThreshold, } from "./gradient"; export { formatKnowledge, diff --git a/packages/gateway/src/idle.ts b/packages/gateway/src/idle.ts index 5b97917..96815e0 100644 --- a/packages/gateway/src/idle.ts +++ b/packages/gateway/src/idle.ts @@ -27,6 +27,7 @@ import { saveSessionTracking, saveGradientState, getConsecutiveBusts, + effectiveMetaThreshold, } from "@loreai/core"; import type { LLMClient } from "@loreai/core"; import type { GatewayConfig } from "./config"; @@ -234,11 +235,9 @@ export function buildIdleWorkHandler( // to consolidate earlier — shrinks the distilled prefix before the // session becomes unsustainable. const busts = getConsecutiveBusts(sessionID); - const effectiveMetaThreshold = busts >= 3 - ? Math.max(3, Math.floor(cfg.distillation.metaThreshold / 4)) - : cfg.distillation.metaThreshold; + const metaThreshold = effectiveMetaThreshold(busts, cfg.distillation.metaThreshold); const g0 = distillation.gen0Count(projectPath, sessionID); - if (g0 >= effectiveMetaThreshold) { + if (g0 >= metaThreshold) { await distillation.metaDistill({ llm, projectPath, sessionID, model, callType }); } } catch (e) { diff --git a/packages/gateway/src/pipeline.ts b/packages/gateway/src/pipeline.ts index 36082ac..cc5fdf0 100644 --- a/packages/gateway/src/pipeline.ts +++ b/packages/gateway/src/pipeline.ts @@ -36,6 +36,7 @@ import { consumeCameOutOfIdle, needsUrgentDistillation, getConsecutiveBusts, + effectiveMetaThreshold as computeMetaThreshold, formatKnowledge, buildCompactPrompt, shouldImportLoreFile, @@ -2000,9 +2001,8 @@ function scheduleBackgroundWork( // prefix before the session becomes unsustainable. if (needsUrgentDistillation(sessionState.sessionID)) { const busts = getConsecutiveBusts(sessionState.sessionID); - const metaThresholdOverride = busts >= 3 - ? Math.max(3, Math.floor(cfg.distillation.metaThreshold / 4)) - : undefined; + const lowered = computeMetaThreshold(busts, cfg.distillation.metaThreshold); + const metaThresholdOverride = lowered < cfg.distillation.metaThreshold ? lowered : undefined; distillation .run({ llm, diff --git a/packages/gateway/test/helpers/idle-worker.ts b/packages/gateway/test/helpers/idle-worker.ts index 5ec34a3..e19de0d 100644 --- a/packages/gateway/test/helpers/idle-worker.ts +++ b/packages/gateway/test/helpers/idle-worker.ts @@ -68,6 +68,9 @@ mock.module("@loreai/core", () => ({ saveSessionTracking: () => {}, saveGradientState: () => {}, getConsecutiveBusts: () => 0, + BUST_PRESSURE_THRESHOLD: 3, + effectiveMetaThreshold: (busts: number, threshold: number) => + busts >= 3 ? Math.max(3, Math.floor(threshold / 4)) : threshold, loadSessionTracking: () => null, getKV: () => null, setKV: () => {},