pi-mega-compact 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/config/vector-cortex.js +20 -0
  2. package/dist/dedup/digest.js +30 -0
  3. package/dist/dedup/l1-lsh-v2.js +60 -0
  4. package/dist/dedup/l1-lsh.js +52 -0
  5. package/dist/dedup/l1-minhash-v2.js +193 -0
  6. package/dist/dedup/l1-minhash.js +91 -0
  7. package/dist/dedup/l1-verify.js +54 -0
  8. package/dist/dedup/mmr.js +45 -0
  9. package/dist/dedup/normalize.js +39 -0
  10. package/dist/dedup/raptor/buildHistory.js +164 -0
  11. package/dist/dedup/raptor/guardrails.js +83 -0
  12. package/dist/dedup/raptor/incremental.js +304 -0
  13. package/dist/dedup/raptor/index.js +190 -0
  14. package/dist/dedup/raptor/kmeans.js +152 -0
  15. package/dist/dedup/raptor/multilevel.js +184 -0
  16. package/dist/dedup/raptor/retrieval.js +93 -0
  17. package/dist/dedup/raptor/summarizer.js +86 -0
  18. package/dist/dedup/raptor/tree.js +202 -0
  19. package/dist/dedup/topk.js +60 -0
  20. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
  21. package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +63 -0
  22. package/dist/extensions/mega-events/context-handler/gateCheck.js +59 -0
  23. package/dist/extensions/mega-events/context-handler/liveTrim.js +178 -0
  24. package/dist/extensions/mega-events/context-handler/pipelineRun.js +37 -0
  25. package/dist/extensions/mega-events/context-handler.js +39 -305
  26. package/dist/src/config/vector-cortex.js +20 -0
  27. package/dist/src/config.js +1 -1
  28. package/dist/src/dedup/l1-lsh-v2.js +60 -0
  29. package/dist/src/dedup/l1-minhash-v2.js +193 -0
  30. package/dist/src/dedup/normalize.js +1 -1
  31. package/dist/src/vector-cortex/conformance/emit.js +46 -0
  32. package/dist/src/vector-cortex/conformance/manifest.js +235 -0
  33. package/dist/src/vector-cortex/conformance/runner.js +111 -0
  34. package/dist/src/vector-cortex/conformance/triadB-reader.js +95 -0
  35. package/dist/src/vector-cortex/encoder/asset.js +142 -0
  36. package/dist/src/vector-cortex/encoder/emit.js +42 -0
  37. package/dist/src/vector-cortex/encoder/runtime.js +228 -0
  38. package/dist/src/vector-cortex/encoder/types.js +85 -0
  39. package/dist/src/vector-cortex/migrations/minhash-v2.js +209 -0
  40. package/dist/vector-cortex/conformance/emit.js +46 -0
  41. package/dist/vector-cortex/conformance/manifest.js +235 -0
  42. package/dist/vector-cortex/conformance/runner.js +111 -0
  43. package/dist/vector-cortex/conformance/triadB-reader.js +95 -0
  44. package/dist/vector-cortex/encoder/asset.js +142 -0
  45. package/dist/vector-cortex/encoder/emit.js +42 -0
  46. package/dist/vector-cortex/encoder/runtime.js +228 -0
  47. package/dist/vector-cortex/encoder/types.js +85 -0
  48. package/dist/vector-cortex/migrations/minhash-v2.js +209 -0
  49. package/extensions/dashboard-client/dist/assets/{AreaChart-CAnWomhw.js → AreaChart-BQXFF7UI.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{AreaChart-CAnWomhw.js.map → AreaChart-BQXFF7UI.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/assets/{BarChart-lZ2-s71k.js → BarChart-BVtoz9Eg.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{BarChart-lZ2-s71k.js.map → BarChart-BVtoz9Eg.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{CacheTab-BL2Thh66.js → CacheTab-trI2TIGy.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{CacheTab-BL2Thh66.js.map → CacheTab-trI2TIGy.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{EventsTab-DEh2Z9fp.js → EventsTab-CNUbk9__.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{EventsTab-DEh2Z9fp.js.map → EventsTab-CNUbk9__.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/{HealthTab-DTwKclxk.js → HealthTab-K-bd_CM-.js} +2 -2
  58. package/extensions/dashboard-client/dist/assets/{HealthTab-DTwKclxk.js.map → HealthTab-K-bd_CM-.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DDfBEdbX.js → MaintenanceTab-CoEioKzx.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DDfBEdbX.js.map → MaintenanceTab-CoEioKzx.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-Dgfmg-0Q.js → MemoryMapTab-CJo24ulv.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-Dgfmg-0Q.js.map → MemoryMapTab-CJo24ulv.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{MetricsTab-DOcMm0Tx.js → MetricsTab-DCNlQWWP.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{MetricsTab-DOcMm0Tx.js.map → MetricsTab-DCNlQWWP.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{OverviewTab-DHT5aoHg.js → OverviewTab-CQQlyIQ6.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{OverviewTab-DHT5aoHg.js.map → OverviewTab-CQQlyIQ6.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{ReposTab-CGAmtR_c.js → ReposTab-Cqm82_yn.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{ReposTab-CGAmtR_c.js.map → ReposTab-Cqm82_yn.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{SessionsTab-BIgx6iKT.js → SessionsTab-A8xTAdET.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{SessionsTab-BIgx6iKT.js.map → SessionsTab-A8xTAdET.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/{SetupTab-ADghKuEI.js → SetupTab-CqaSgaZl.js} +2 -2
  72. package/extensions/dashboard-client/dist/assets/{SetupTab-ADghKuEI.js.map → SetupTab-CqaSgaZl.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-C33UiiOM.js → TimeSavedCard-C_f3q8GR.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-C33UiiOM.js.map → TimeSavedCard-C_f3q8GR.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{TurnsTab-DdbL9wPN.js → TurnsTab-BEsafACo.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{TurnsTab-DdbL9wPN.js.map → TurnsTab-BEsafACo.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js +2 -0
  78. package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js.map +1 -0
  79. package/extensions/dashboard-client/dist/assets/{WikiTab-Ch00OFKs.js → WikiTab-Bf01M2_Z.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{WikiTab-Ch00OFKs.js.map → WikiTab-Bf01M2_Z.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{button-ByeEdo2g.js → button-BEJPTk_E.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{button-ByeEdo2g.js.map → button-BEJPTk_E.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{card-DesoLXvc.js → card-DK1V382s.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{card-DesoLXvc.js.map → card-DK1V382s.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CCAB7Iz4.js → generateCategoricalChart-CRtaPphL.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CCAB7Iz4.js.map → generateCategoricalChart-CRtaPphL.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/assets/{index-B6cgyAPr.js → index-CB6BslE4.js} +3 -3
  88. package/extensions/dashboard-client/dist/assets/{index-B6cgyAPr.js.map → index-CB6BslE4.js.map} +1 -1
  89. package/extensions/dashboard-client/dist/assets/{switch-DIZC-oe-.js → switch-C4FV6B9k.js} +2 -2
  90. package/extensions/dashboard-client/dist/assets/{switch-DIZC-oe-.js.map → switch-C4FV6B9k.js.map} +1 -1
  91. package/extensions/dashboard-client/dist/assets/{toggle-CIoWaH2e.js → toggle-GBmeENMd.js} +2 -2
  92. package/extensions/dashboard-client/dist/assets/{toggle-CIoWaH2e.js.map → toggle-GBmeENMd.js.map} +1 -1
  93. package/extensions/dashboard-client/dist/assets/{useSSE-Bv4943l1.js → useSSE-BOKZGSn1.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{useSSE-Bv4943l1.js.map → useSSE-BOKZGSn1.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/index.html +1 -1
  96. package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +1 -1
  97. package/extensions/dashboard-client/tsconfig.json +1 -1
  98. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
  99. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +93 -0
  100. package/extensions/mega-events/context-handler/gateCheck.ts +101 -0
  101. package/extensions/mega-events/context-handler/liveTrim.ts +241 -0
  102. package/extensions/mega-events/context-handler/pipelineRun.ts +79 -0
  103. package/extensions/mega-events/context-handler.ts +45 -347
  104. package/package.json +1 -1
  105. package/src/config/vector-cortex.ts +22 -0
  106. package/src/config.ts +2 -0
  107. package/src/dedup/l1-lsh-v2.ts +78 -0
  108. package/src/dedup/l1-minhash-v2.ts +221 -0
  109. package/src/dedup/normalize.ts +1 -1
  110. package/src/store/sqlite/game-scores.ts +2 -1
  111. package/src/vector-cortex/conformance/emit.ts +56 -0
  112. package/src/vector-cortex/conformance/manifest.ts +298 -0
  113. package/src/vector-cortex/conformance/runner.ts +165 -0
  114. package/src/vector-cortex/conformance/triadB-reader.ts +110 -0
  115. package/src/vector-cortex/encoder/asset.ts +155 -0
  116. package/src/vector-cortex/encoder/emit.ts +51 -0
  117. package/src/vector-cortex/encoder/runtime.ts +283 -0
  118. package/src/vector-cortex/encoder/types.ts +165 -0
  119. package/src/vector-cortex/migrations/minhash-v2.ts +263 -0
  120. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js +0 -2
  121. package/extensions/dashboard-client/dist/assets/VectorCortexTab-Cl1pbEdR.js.map +0 -1
@@ -0,0 +1,37 @@
1
+ import { runCompact } from "../../mega-pipeline.js";
2
+ import { pressureFromPct, pressureRatio } from "../../mega-config.js";
3
+ /**
4
+ * Invoke the compaction pipeline with adaptive pressure. Returns a tailed view
5
+ * ("return") when compaction skipped, or "proceed" with the non-skipped result
6
+ * and the computed pressure (consumed by live-trim's critical-over hatch).
7
+ */
8
+ export function invokePipeline(pi, runtime, config, ctx, opts) {
9
+ // Adaptive compression (Fix E): scale compression strength + keepFrom depth
10
+ // with how close we are to the model context limit. Null-safe: when the
11
+ // token-fallback path ran (pct unavailable) use the token-basis pressure
12
+ // (the same basis the runtime `pressure` getter uses for custom/no-window).
13
+ const pressure = opts.pct != null
14
+ ? pressureFromPct(opts.pct)
15
+ : pressureRatio(opts.currentTokens, runtime.effectiveThreshold);
16
+ const ran = runCompact(pi, runtime, config, ctx, opts.messages, {
17
+ compressionPressure: pressure,
18
+ });
19
+ // D.3: skip paths fall back to replay instead of returning empty.
20
+ // If runCompact skipped and we have a valid trimCache, replay it
21
+ // (free stability win) — otherwise defer to the next event.
22
+ if (ran.skipped) {
23
+ runtime.diagCtxRunSkipped++;
24
+ if (runtime.trimCache &&
25
+ runtime.trimCache.checkpointId === runtime.rt.lastCheckpointId &&
26
+ runtime.trimCache.cut <= opts.messages.length) {
27
+ const recent = opts.messages.slice(runtime.trimCache.cut); // guardrails-allow PREVENT-PI-002: cached `cut` was sanitized by computeLiveTrimCut (src/boundary.ts); replayed verbatim, transcript only grows within an epoch.
28
+ runtime.diagLiveTrimFires++;
29
+ runtime.diagLiveTrimReplays++;
30
+ runtime.snapshot(ctx);
31
+ const skipView = [{ ...runtime.trimCache.summaryAgentMsg }, ...recent];
32
+ return { kind: "return", view: opts.tailResult(skipView) ?? { messages: skipView } };
33
+ }
34
+ return { kind: "return", view: opts.tailResult() ?? undefined };
35
+ }
36
+ return { kind: "proceed", ran, pressure };
37
+ }
@@ -1,15 +1,11 @@
1
- import { openStore, resolveModelThreshold, DEFAULT_SAFETY_MARGIN_PCT, DEFAULT_FIRE_POINT_PCT, } from "../../src/store/sqlite.js";
2
- import { autoCompactCheck } from "../../src/compact.js";
3
- import { estimateSessionTokens, estimateBlockTokens, estimateMessageTokens, } from "../../src/tokens.js";
4
- import { runCompact, piCompactWouldNoop } from "../mega-pipeline.js";
5
- import { pressureFromPct, pressureRatio, } from "../mega-config.js";
6
- import { appendMirrorMessages } from "./mirror-append.js";
7
- import { appendMessagesToLedger } from "../mega-runtime/vector-cortex-ledger.js";
8
- import { epochIdFor } from "../../src/mirror/epoch.js";
9
- import { computeLiveTrimCut, liveTrimSummaryMessage } from "../mega-trim.js";
10
- import { messageContentText } from "./context-handler/messageText.js";
1
+ import { estimateSessionTokens } from "../../src/tokens.js";
2
+ import { piCompactWouldNoop } from "../mega-pipeline.js";
11
3
  import { buildTailResult } from "./context-handler/tailResult.js";
12
4
  import { persistEpochAndMaintain } from "./context-handler/afterCompact.js";
5
+ import { appendMirrorAndLedger } from "./context-handler/dbMirrorAppend.js";
6
+ import { evaluateGate } from "./context-handler/gateCheck.js";
7
+ import { invokePipeline } from "./context-handler/pipelineRun.js";
8
+ import { buildLiveTrimView } from "./context-handler/liveTrim.js";
13
9
  /** Register the context event handler (live-trim auto-trigger). */
14
10
  export function registerContextHandler(pi, runtime, config) {
15
11
  // ---- Auto-trigger: live trim (compact and continue) + native durable ----
@@ -62,107 +58,14 @@ export function registerContextHandler(pi, runtime, config) {
62
58
  return;
63
59
  }
64
60
  const view = viewForFallback ?? runtime.engineView(messages);
65
- // S27 DB-mirror: append incoming messages to raw_transcript.
66
- // Runs BEFORE fast-gate so every message is captured, even if we
67
- // don't compact this turn. Append is idempotent (content_hash PK).
68
- // F3: high-water mark (mirror-append.ts) skips already-processed
69
- // messages on subsequent events. On fork/rewind (shorter list or
70
- // boundary hash mismatch) the mark is dropped, falling back to a
71
- // full reprocess.
72
- if (config.dbMirror) {
73
- try {
74
- const db = openStore(runtime.currentStateDir);
75
- appendMirrorMessages(db, messages, runtime.rt.sessionId, epochIdFor(runtime.rt.sessionId), runtime.currentTurn);
76
- // P2.2: populate conversation_thread + tool_results tables for
77
- // prompt-cache analytics and durable separation. The live-array
78
- // separation (buildSeparatedPrompt / buildCacheOptimizedPrompt in
79
- // tailResult above) is sufficient for the prompt-construction path;
80
- // these DB writes persist the split for post-hoc analysis, dashboard
81
- // queries, and future readers. Non-fatal — failure here never breaks
82
- // the agent loop (PREVENT-PI-004: zero network, local SQLite only).
83
- {
84
- const sid = runtime.rt.sessionId;
85
- const turn = runtime.currentTurn;
86
- const now = Date.now();
87
- const threadStmt = db.prepare("INSERT OR IGNORE INTO conversation_thread (conversation_id, role, content, turn_index, timestamp) VALUES (?, ?, ?, ?, ?)");
88
- const toolStmt = db.prepare("INSERT OR IGNORE INTO tool_results (conversation_id, role, content, turn_index, timestamp) VALUES (?, ?, ?, ?, ?)");
89
- for (const m of messages) {
90
- const role = m.role;
91
- const content = messageContentText(m);
92
- if (role === "user" || role === "assistant") {
93
- threadStmt.run(sid, role, content, turn, now);
94
- }
95
- else if (role === "toolResult" || role === "bashExecution") {
96
- toolStmt.run(sid, role, content, turn, now);
97
- }
98
- }
99
- }
100
- }
101
- catch (e) {
102
- runtime.logger.warn("db-mirror-append-fail", { error: String(e) });
103
- }
104
- }
105
- // VC1B (S1): canonical messages -> v2 ledger occurrences. Flag-OFF opens
106
- // no DB (byte-identical predecessor); non-fatal. onFailure surfaces
107
- // per-append rejections (e.g. EVT_SEQ_REGRESSION on rewind/fork) as
108
- // structured warnings rather than swallowing them silently.
109
- try {
110
- appendMessagesToLedger(runtime.currentStateDir, runtime.rt.sessionId, messages, runtime.logger);
111
- }
112
- catch (e) {
113
- runtime.logger.warn("vc1b-ledger-append-fail", { error: String(e) });
114
- }
115
- // S52 / v0.16.1: per-model threshold override. The user can tune the
116
- // fire point + safety margin PER MODEL (different providers' models range
117
- // 8K-1M+ context, so one global tier % is wrong). Falls back to env/default
118
- // when no override row exists. Computed once here + reused in the tail cap
119
- // below; the lookup is a single SQLite PK hit (cheap; cached after the
120
- // first read in a session).
121
- const _modelIdForThreshold = runtime.currentModel?.modelId ?? null;
122
- const _perModelThreshold = resolveModelThreshold(_modelIdForThreshold, {
123
- safetyMarginFallback: DEFAULT_SAFETY_MARGIN_PCT,
124
- firePointFallback: config.tierPct != null
125
- ? Math.round(config.tierPct * 100)
126
- : DEFAULT_FIRE_POINT_PCT,
127
- stateDir: runtime.currentStateDir,
128
- });
129
- // S29 FAST GATE: drive the auto-trigger off the context % (the number the
130
- // menu bar shows), NOT the token count — the model under-reports tokens,
131
- // so a token-only gate misses the overshoot that causes max-output-tokens
132
- // truncation. The fire point is the per-model override when present,
133
- // otherwise the tier's percent threshold (tierPct) unless overridden by
134
- // MEGACOMPACT_AUTO_PCT_TRIGGER. `custom` (absolute
135
- // MEGACOMPACT_THRESHOLD_TOKENS, tierPct null) is an explicit opt-out of
136
- // percent scaling — it keeps the token gate. When pct is unavailable
137
- // (window unknown / a model that doesn't report percent) a tiered config
138
- // falls back to the token gate (S27 boot-fallback guarantee) instead of
139
- // skipping compaction — a percent-only gate would regress that.
140
- let gatePassed = false;
141
- if (config.tierPct != null && pct != null) {
142
- // Per-model override is a % (10-90); tierPct is a fraction (0.1-1.0).
143
- // Prefer the override; fall back to autoPctTrigger + tierPct.
144
- const tierPctFraction = config.autoPctTrigger ?? config.tierPct;
145
- const perModelFraction = _perModelThreshold.firePointPct / 100;
146
- const firePct = _modelIdForThreshold != null ? perModelFraction : tierPctFraction;
147
- gatePassed = pct / 100 >= firePct;
148
- }
149
- else {
150
- // custom tier OR tiered-but-pct-unavailable → token gate (S27 fallback).
151
- if (currentTokens < runtime.effectiveThreshold) {
152
- runtime.diagCtxFastGate++;
153
- return tailResult() ?? undefined;
154
- }
155
- const check = autoCompactCheck(currentTokens, runtime.effectiveThreshold); // SERVER-STYLE CONFIRM (local)
156
- if (!check.shouldCompact) {
157
- runtime.diagCtxNoCompact++;
158
- return tailResult() ?? undefined;
159
- }
160
- gatePassed = true;
161
- }
162
- if (!gatePassed) {
163
- runtime.diagCtxFastGate++;
164
- return tailResult() ?? undefined;
165
- }
61
+ // S27 DB-mirror + VC1B ledger append. Runs BEFORE the fast-gate so every
62
+ // message is captured, even if we don't compact this turn. Non-fatal.
63
+ appendMirrorAndLedger(runtime, config, messages);
64
+ // S29 FAST GATE: drive the auto-trigger off the context percent (see
65
+ // gateCheck.ts). Returns a tailed view when the gate does not pass.
66
+ const gate = evaluateGate(runtime, config, { pct, currentTokens, tailResult });
67
+ if (gate.kind === "return")
68
+ return gate.view;
166
69
  // D.2: Replay MUST be exempt from debounce — replay is free (no compute,
167
70
  // no re-write) and prevents unnecessary KV-cache invalidation. Check
168
71
  // replay FIRST, before debounce, so two context events <2s apart both
@@ -206,36 +109,20 @@ export function registerContextHandler(pi, runtime, config) {
206
109
  return tailResult() ?? undefined;
207
110
  }
208
111
  runtime.debounceUntil = now + 2000;
209
- // Adaptive compression (Fix E): scale compression strength + keepFrom depth
210
- // with how close we are to the model context limit. Null-safe: when the
211
- // token-fallback path ran (pct unavailable) use the token-basis pressure
212
- // (the same basis the runtime `pressure` getter uses for custom/no-window).
213
- const pressure = pct != null
214
- ? pressureFromPct(pct)
215
- : pressureRatio(currentTokens, runtime.effectiveThreshold);
216
- const ran = runCompact(pi, runtime, config, ctx, messages, {
217
- compressionPressure: pressure,
112
+ // Adaptive-compression pipeline invocation (see pipelineRun.ts). Returns a
113
+ // tailed view ("return") when compaction skipped; otherwise "proceed" with
114
+ // the result + pressure consumed by the live-trim stage below.
115
+ const pipeline = invokePipeline(pi, runtime, config, ctx, {
116
+ messages,
117
+ pct,
118
+ currentTokens,
119
+ tailResult,
218
120
  });
219
- // D.3: skip paths fall back to replay instead of returning empty.
220
- // If runCompact skipped and we have a valid trimCache, replay it
221
- // (free stability win) — otherwise defer to the next event.
222
- if (ran.skipped) {
223
- runtime.diagCtxRunSkipped++;
224
- if (runtime.trimCache &&
225
- runtime.trimCache.checkpointId === runtime.rt.lastCheckpointId &&
226
- runtime.trimCache.cut <= messages.length) {
227
- const recent = messages.slice(runtime.trimCache.cut); // guardrails-allow PREVENT-PI-002: cached `cut` was sanitized by computeLiveTrimCut (src/boundary.ts); replayed verbatim, transcript only grows within an epoch.
228
- runtime.diagLiveTrimFires++;
229
- runtime.diagLiveTrimReplays++;
230
- runtime.snapshot(ctx);
231
- const skipView = [{ ...runtime.trimCache.summaryAgentMsg }, ...recent];
232
- return tailResult(skipView) ?? { messages: skipView };
233
- }
234
- return tailResult() ?? undefined;
235
- }
121
+ if (pipeline.kind === "return")
122
+ return pipeline.view;
236
123
  // S27 DB-mirror: write checkpoint_epoch + stamp turn epochs + auto-wiki +
237
124
  // topic seed + fire-and-forget dedup. Best-effort + non-fatal.
238
- await persistEpochAndMaintain(runtime, config, ran);
125
+ await persistEpochAndMaintain(runtime, config, pipeline.ran);
239
126
  // LEGACY path (rollback): v0.4.28 ctx.compact() + the no-op gate. The
240
127
  // manual compact path aborts the in-flight turn — only used behind the flag.
241
128
  // Read live from env (in addition to the load-time config) so the flag can be
@@ -289,172 +176,19 @@ export function registerContextHandler(pi, runtime, config) {
289
176
  return;
290
177
  }
291
178
  // S16 LIVE trim: collapse the compacted region to a summary + recent anchor.
292
- // Non-destructive: pi keeps the real transcript; only this LLM call sees the
293
- // trimmed window. We compute the cut on the engine view (pure, tested) then
294
- // slice the ORIGINAL pi AgentMessage[] from that index (lossless alignment,
295
- // mirroring dropCompactedRange) and prepend a user-role summary message.
296
- // A build failure or unsafe cut returns nothing (no trim this call — the
297
- // next context event retries). The anchor floor is read live from env (the
298
- // config value is the cached default) so it can be tuned per-test / per-run
299
- // without reloading the module.
300
- try {
301
- const anchorEnv = process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
302
- const anchorUserMessages = anchorEnv != null &&
303
- anchorEnv !== "" &&
304
- Number.isFinite(Number(anchorEnv))
305
- ? Number(anchorEnv)
306
- : config.anchorUserMessages;
307
- const cut = computeLiveTrimCut(view, {
308
- compactedFrom: ran.result.compactedFrom,
309
- summary: ran.result.summary,
310
- anchorUserMessages,
311
- // CRITICAL-OVER ESCAPE HATCH: when context is at/over ~90% of the
312
- // window, relief takes priority over the anchor floor. Without this,
313
- // computeLiveTrimCut bails to null (can't satisfy the floor) and the
314
- // model is fed a raw overflow that errors every turn — the
315
- // "Already compacted" + overflow death-spiral (2026-08-01 incident).
316
- // A thin anchor is recoverable; an overflowed session is not.
317
- //
318
- // CRITICAL: pct is null for OpenAI-compatible providers that don't
319
- // report usage.percent (e.g. neuralwatt). Without the token-pressure
320
- // fallback the hatch never armed → cut=null → raw overflow → 400
321
- // "conversation too long even after compaction" (2026-08-03 incident
322
- // on glm-5.2-short, 200K window). Now also fires on pressure >= 0.9
323
- // (token-basis) so the hatch arms regardless of whether the provider
324
- // reports pct.
325
- criticalOver: (pct ?? 0) >= 90 || pressure >= 0.9,
326
- });
327
- if (cut === null) {
328
- runtime.diagCtxCutNull++;
329
- runtime.logger.info("live-trim-skip", {
330
- sessionId: runtime.rt.sessionId,
331
- compactedFrom: ran.result.compactedFrom,
332
- viewLen: view.length,
333
- anchorUserMessages,
334
- criticalOver: (pct ?? 0) >= 90,
335
- });
336
- return tailResult() ?? undefined; // unsafe / below anchor floor — no trim this call
337
- }
338
- const summaryMsg = liveTrimSummaryMessage({
339
- compactedFrom: ran.result.compactedFrom,
340
- summary: ran.result.summary,
341
- anchorUserMessages: config.anchorUserMessages,
342
- });
343
- // Synthesize a user-role AgentMessage carrying the compacted summary.
344
- const summaryAgentMsg = {
345
- role: "user",
346
- content: summaryMsg.text,
347
- // v0.8.6: stable timestamp across the epoch (NOT Date.now()) so the
348
- // summary message bytes — and thus the KV-cache prefix — don't drift
349
- // on every replay within the same compaction epoch.
350
- timestamp: runtime.rt.lastCompactAt ?? Date.now(),
351
- };
352
- const recentRaw = messages.slice(cut); // guardrails-allow PREVENT-PI-002: `cut` is the pre-sanitized `compactedFrom` produced by src/boundary.ts computeDropRange, so the preserved run begins on a toolPair-safe index.
353
- // FIX 2 (2026-08-03 incident): TOKEN-BUDGET CAP on the live-trim view.
354
- // Compaction fires at tier% of the window (140K for a 200K window),
355
- // but a SINGLE turn can inject a huge tool output (file read, bash) that
356
- // jumps context from 139K → 199K+ before the next gate fires. When that
357
- // happens [summary + preserved tail] can STILL exceed the model window,
358
- // and the provider rejects with 400 "conversation too long even after
359
- // compaction". The anchor floor (PREVENT-PI-001) keeps ≥N user messages
360
- // but has NO token cap, so a 2-message tail of two 80K bash outputs sails
361
- // right past the window.
362
- //
363
- // Cap: when the model context window is known, reserve room for the
364
- // summary + the model's max output tokens + a 10% safety margin, then
365
- // drop oldest preserved messages from the front of `recentRaw` until the
366
- // tail fits. Never drops below the FINAL message (always keep the latest
367
- // turn so the agent can respond). This is a last-resort HARD cap — it
368
- // only fires when the preserved tail alone is oversized, which is rare.
369
- const ctxWindow = runtime.lastCtxWindow;
370
- // Reuse the per-model threshold resolved at the gate (single lookup).
371
- const modelThreshold = _perModelThreshold;
372
- // Reserve room for output tokens. Use the model's reported max output
373
- // when known; fall back to 10% of the window (scales with any model —
374
- // 20K for a 200K window, 100K for a 1M window) so we never let the
375
- // preserved tail eat the model's output budget when maxTokens is unknown.
376
- const maxOutput = runtime.currentModel?.maxTokens && runtime.currentModel.maxTokens > 0
377
- ? runtime.currentModel.maxTokens
378
- : Math.ceil(ctxWindow * 0.1);
379
- let recent = recentRaw;
380
- if (ctxWindow > 0 && recentRaw.length > 1) {
381
- const summaryTokens = estimateBlockTokens(summaryMsg.text);
382
- // Reserve: summary + max output + per-model safety margin (0-20%).
383
- const safetyMargin = Math.ceil(ctxWindow * (modelThreshold.safetyMarginPct / 100));
384
- const budget = ctxWindow - maxOutput - safetyMargin - summaryTokens;
385
- if (budget > 0) {
386
- // Walk recent from the front, dropping oldest first until the
387
- // remaining tail fits. Use the AgentMessage→engine-text estimate via
388
- // messageContentText (already imported) + estimateMessageTokens.
389
- let tailTokens = 0;
390
- for (let i = recentRaw.length - 1; i >= 0; i--) {
391
- const m = recentRaw[i];
392
- tailTokens += estimateMessageTokens({
393
- text: messageContentText(m),
394
- });
395
- if (tailTokens > budget) {
396
- // Keep from i+1 onward; but never fewer than the final message.
397
- const startIdx = Math.min(i + 1, recentRaw.length - 1);
398
- if (startIdx > 0) {
399
- recent = recentRaw.slice(startIdx);
400
- runtime.logger.warn("live-trim-tail-cap", {
401
- sessionId: runtime.rt.sessionId,
402
- dropped: startIdx,
403
- tailTokens,
404
- safetyMarginPct: modelThreshold.safetyMarginPct,
405
- budget,
406
- ctxWindow,
407
- });
408
- }
409
- break;
410
- }
411
- }
412
- }
413
- }
414
- // v0.8.6: cache the trim view so subsequent gated calls in this epoch
415
- // replay it verbatim (stabilizing the KV-cache prefix) instead of
416
- // regenerating a fresh summary + sentinel every fire.
417
- runtime.trimCache = {
418
- // v0.8.7: key the replay cache on the STABLE epoch signal
419
- // (rt.lastCheckpointId) instead of ran.result.checkpointId, which is
420
- // dedup-volatile: on a re-compact that dedups onto a DIFFERENT existing
421
- // checkpoint, result.checkpointId is the matched id (engine.ts:188) while
422
- // lastCheckpointId is only updated on a genuinely new checkpoint
423
- // (compact.ts:100-104). Keying on result.checkpointId would make
424
- // trimCache.checkpointId != rt.lastCheckpointId forever after that
425
- // dedup fire, disabling replay for the rest of the epoch (the
426
- // alternating cache-miss that 0.8.6 meant to fix). Prefer the stable
427
- // signal; fall back to result.checkpointId then the epoch timestamp
428
- // only for the no-checkpoint edge case.
429
- checkpointId: runtime.rt.lastCheckpointId ??
430
- ran.result.checkpointId ??
431
- `epoch-${runtime.rt.lastCompactAt ?? Date.now()}`,
432
- cut,
433
- summaryAgentMsg,
434
- ctxPct: pct ?? null,
435
- ctxTokens: currentTokens,
436
- };
437
- runtime.snapshot(ctx);
438
- // DIAG (team-run relief): confirm the live trim actually fires + how big
439
- // the window still is. The return is non-durable (per-LLM-call only), so
440
- // this is the signal that the model is being fed a compacted view while
441
- // the on-disk transcript + context meter keep growing.
442
- runtime.diagLiveTrimFires++;
443
- runtime.logger.info("live-trim", {
444
- sessionId: runtime.rt.sessionId,
445
- inputMsgs: messages.length,
446
- outputMsgs: recent.length + 1,
447
- compactedFrom: cut,
448
- ctxPct: pct,
449
- ctxTokens: usage?.tokens ?? null,
450
- });
451
- return (tailResult([summaryAgentMsg, ...recent]) ?? {
452
- messages: [summaryAgentMsg, ...recent],
453
- });
454
- }
455
- catch {
456
- runtime.diagCtxThrown++;
457
- return tailResult() ?? undefined; // non-fatal: no trim this call; the next context event retries
458
- }
179
+ // See liveTrim.ts. Non-destructive: pi keeps the real transcript; only this
180
+ // LLM call sees the trimmed window. Returns undefined on unsafe cut/throw
181
+ // the next context event retries.
182
+ return buildLiveTrimView(runtime, config, ctx, {
183
+ messages,
184
+ view,
185
+ pct,
186
+ currentTokens,
187
+ usageTokens: usage?.tokens,
188
+ pressure: pipeline.pressure,
189
+ ran: pipeline.ran,
190
+ perModelThreshold: gate.perModelThreshold,
191
+ tailResult,
192
+ });
459
193
  });
460
194
  }
@@ -53,6 +53,26 @@ export const VC1B_ENABLED = () => sprintFlag("MEGACOMPACT_VC1B");
53
53
  * consumer is the resilience emit seam + the safety adapter's triad selection.
54
54
  */
55
55
  export const VC0C_ENABLED = () => sprintFlag("MEGACOMPACT_VC0C");
56
+ /**
57
+ * VC1C — cross-language conformance v2 (FixtureManifestV2 / DowngradeReport /
58
+ * MinHashV2 + M4 minhash-v2 migration).
59
+ * Default ON. `MEGACOMPACT_VC1C=0` disables and is byte-identical to the
60
+ * predecessor (mode C: a v2 conformance runner that accepts authority fixtures
61
+ * and the manifest validator idle; the sync dedup scan stays on the v1 path;
62
+ * zero `vector_cortex_*` VC1C emissions). The real consumers are the conformance
63
+ * emit seam, the minhash-v2 backfill seam and the downgrade-export seam.
64
+ */
65
+ export const VC1C_ENABLED = () => sprintFlag("MEGACOMPACT_VC1C");
66
+ /**
67
+ * VC2A — offline model runtime and asset decision (ModelManifestV1 /
68
+ * EncoderRuntime).
69
+ * Default ON. `MEGACOMPACT_VC2A=0` disables and is byte-identical to the
70
+ * predecessor (mode C: no asset manifest is read/verified, the encoder runtime
71
+ * idles in mode C, zero `vector_cortex_encoder_*` emissions; the trigram/lexical
72
+ * paths are unchanged). The real consumers are the encoder emit seam and the
73
+ * encoder runtime's A/B/C selection.
74
+ */
75
+ export const VC2A_ENABLED = () => sprintFlag("MEGACOMPACT_VC2A");
56
76
  // ---------------------------------------------------------------------------
57
77
  // Breaker state machine constants (TRIAD_RESILIENCE.md §breaker).
58
78
  // Rolled numbers for one 60s window; VC0C consumes these at its breaker seam.
@@ -114,4 +114,4 @@ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
114
114
  // default ON, `=0`/`_DISABLED` off. Re-exported from src/config/vector-cortex.ts
115
115
  // so root consumers share one source of truth.
116
116
  // ---------------------------------------------------------------------------
117
- export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
117
+ export { VC0A_ENABLED, VC0B_ENABLED, VC1A_ENABLED, VC0C_ENABLED, VC1B_ENABLED, VC1C_ENABLED, VC2A_ENABLED, BREAKER_WINDOW_MS, BREAKER_MIN_ATTEMPTS, BREAKER_PERF_FAILURES, BREAKER_PERF_FAILURE_RATE, BREAKER_CORRECTNESS_FAILURES, BREAKER_COOLDOWN_MS, BREAKER_PROBE_COUNT, BREAKER_RETRY_BASE_MS, BREAKER_RETRY_CAP_MS, BREAKER_RETRY_JITTER, BREAKER_HYSTERESIS_FAILURE_RATE, BREAKER_HYSTERESIS_BUDGET_P95_MS, BREAKER_MIN_HEALTHY_RESIDENCE_MS, } from "./config/vector-cortex.js";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * l1-lsh-v2.ts — Locality-Sensitive Hashing banding over MinHashV2 signatures
3
+ * (VC1C, M4 minhash-v2).
4
+ *
5
+ * Splits the frozen 2048-byte v2 signature (256 x u64 LE slots) into 64 bands
6
+ * of FOUR u64 values each. Each band yields a deterministic bucket key derived
7
+ * from its 4 u64 little-endian bytes (32 bytes) hashed with a 64-bit FNV-1a,
8
+ * scoped by session id and the frozen v2 version tag so buckets never collide
9
+ * across sessions or the v1 path. Deterministic: same (sessionId,
10
+ * signatureBytes) -> same 64 bucket keys, every run, every language.
11
+ *
12
+ * Mixed v1/v2 comparison and bucket mixing is REJECTED upstream with
13
+ * `MINHASH_VERSION_MISMATCH` (see minhash-v2 migration); this module only ever
14
+ * bands a v2 signature.
15
+ *
16
+ * Pure compute, no deps, no storage, no network (PREVENT-PI-004 / PREVENT-011).
17
+ */
18
+ import { MINHASH_VERSION, NUM_HASHES_V2, U64_BYTES, } from "./l1-minhash-v2.js";
19
+ /** Number of bands (frozen). */
20
+ export const BANDS_V2 = 64;
21
+ /** u64 slots per band (frozen): 64 * 4 = 256 total slots. */
22
+ export const VALUES_PER_BAND_V2 = 4;
23
+ /** Band byte length: 4 u64 LE = 32 bytes. */
24
+ export const BAND_BYTES_V2 = VALUES_PER_BAND_V2 * U64_BYTES;
25
+ /** Total signature byte length this scheme expects (256 * 8 = 2048). */
26
+ export const SIGNATURE_BYTES_EXPECTED_V2 = NUM_HASHES_V2 * U64_BYTES;
27
+ const MASK64 = 0xffffffffffffffffn;
28
+ /** 64-bit FNV-1a over a byte buffer. */
29
+ function fnv1a64Bytes(buf) {
30
+ let h = 0xcbf29ce484222325n;
31
+ for (let i = 0; i < buf.length; i++) {
32
+ h ^= BigInt(buf[i] ?? 0);
33
+ h = (h * 0x100000001b3n) & MASK64;
34
+ }
35
+ return h;
36
+ }
37
+ /**
38
+ * Compute the 64 LSH bucket keys for a v2 signature within a session. `bytes`
39
+ * is the 2048-byte little-endian signature (see encodeSignatureV2). Each bucket
40
+ * key is the hex of an 8-byte little-endian FNV-1a-64 over the band's 32 bytes
41
+ * prefixed by `<sessionId>|v2|band-` — so the "bucket bytes" are deterministic
42
+ * and reproducible across languages.
43
+ */
44
+ export function lshBandsV2(bytes, sessionId, version = MINHASH_VERSION) {
45
+ if (bytes.length !== SIGNATURE_BYTES_EXPECTED_V2) {
46
+ throw new Error(`lshBandsV2: expected ${SIGNATURE_BYTES_EXPECTED_V2} signature bytes, got ${bytes.length}`);
47
+ }
48
+ const keys = [];
49
+ const prefix = Buffer.from(`${sessionId}|v${version}|`, "utf8");
50
+ for (let band = 0; band < BANDS_V2; band++) {
51
+ const start = band * BAND_BYTES_V2;
52
+ const bandBytes = bytes.slice(start, start + BAND_BYTES_V2);
53
+ const combined = Buffer.concat([prefix, Buffer.from(bandBytes)]);
54
+ const h = fnv1a64Bytes(combined);
55
+ const out = Buffer.allocUnsafe(U64_BYTES);
56
+ out.writeBigUInt64LE(h, 0);
57
+ keys.push(`b${band}:${out.toString("hex")}`);
58
+ }
59
+ return keys;
60
+ }