pi-mega-compact 0.21.3 → 0.21.5

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 (147) hide show
  1. package/README.md +2 -0
  2. package/dist/extensions/mega-config.js +41 -24
  3. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +7 -0
  4. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +12 -2
  5. package/dist/extensions/mega-events/context-handler/afterCompact.js +11 -0
  6. package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +8 -0
  7. package/dist/extensions/mega-events/context-handler/gateCheck.js +17 -0
  8. package/dist/extensions/mega-events/health-handler.js +12 -2
  9. package/dist/extensions/mega-events/session-handlers.js +1 -0
  10. package/dist/extensions/mega-pipeline/compact/run.js +11 -0
  11. package/dist/extensions/mega-runtime/helpers.js +46 -0
  12. package/dist/extensions/mega-runtime/internal-errors.js +14 -0
  13. package/dist/extensions/mega-runtime/pressure-getters.js +12 -10
  14. package/dist/extensions/mega-runtime/reset-runtime.js +3 -1
  15. package/dist/extensions/mega-runtime/runtime-instrumentation.js +171 -0
  16. package/dist/extensions/mega-runtime/runtime.js +27 -193
  17. package/dist/extensions/mega-runtime/state.js +1 -1
  18. package/dist/extensions/mega-runtime/widget.js +1 -1
  19. package/dist/extensions/mega-turn-store.js +5 -0
  20. package/dist/src/bridge/factory.js +15 -1
  21. package/dist/src/compact-summary.js +99 -0
  22. package/dist/src/compact.js +15 -99
  23. package/dist/src/contextHealth.js +13 -5
  24. package/dist/src/metrics/turns.js +6 -1
  25. package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
  26. package/dist/src/store/sqlite/context-health.js +6 -48
  27. package/dist/src/store/sqlite/embedding-cache.js +4 -0
  28. package/dist/src/store/sqlite/schema.js +4 -0
  29. package/dist/src/store/turns/memory-store/admin.js +119 -0
  30. package/dist/src/store/turns/memory-store/ctx.js +28 -0
  31. package/dist/src/store/turns/memory-store/read.js +110 -0
  32. package/dist/src/store/turns/memory-store/write.js +88 -0
  33. package/dist/src/store/turns/memory-store.js +44 -273
  34. package/dist/src/store/turns/migrations.js +4 -0
  35. package/dist/src/store/turns/schema.js +1 -0
  36. package/dist/src/store/turns/sqlite-store/read.js +13 -0
  37. package/dist/src/store/turns/sqlite-store/rows.js +4 -0
  38. package/dist/src/store/turns/sqlite-store/write.js +3 -3
  39. package/dist/src/store/turns/sqlite-store.js +4 -0
  40. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
  53. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
  54. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
  59. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
  60. package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
  61. package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
  67. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
  68. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
  69. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
  70. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
  71. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
  72. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
  73. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
  74. package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
  75. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
  76. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
  77. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
  78. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
  79. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
  80. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
  81. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
  82. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
  83. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
  84. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
  85. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
  86. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
  87. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
  88. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
  89. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
  91. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
  92. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
  93. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
  98. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
  99. package/extensions/dashboard-client/dist/index.html +1 -1
  100. package/extensions/dashboard-client/src/api/health.ts +1 -0
  101. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
  102. package/extensions/mega-config-types.ts +8 -0
  103. package/extensions/mega-config.ts +51 -27
  104. package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +7 -0
  105. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
  106. package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
  107. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
  108. package/extensions/mega-events/context-handler/gateCheck.ts +18 -0
  109. package/extensions/mega-events/health-handler.ts +13 -3
  110. package/extensions/mega-events/session-handlers.ts +1 -0
  111. package/extensions/mega-pipeline/compact/run.ts +11 -0
  112. package/extensions/mega-runtime/helpers.ts +51 -0
  113. package/extensions/mega-runtime/internal-errors.ts +30 -0
  114. package/extensions/mega-runtime/pressure-getters.ts +12 -12
  115. package/extensions/mega-runtime/reset-runtime.ts +3 -1
  116. package/extensions/mega-runtime/runtime-instrumentation.ts +200 -0
  117. package/extensions/mega-runtime/runtime.ts +29 -209
  118. package/extensions/mega-runtime/state.ts +1 -1
  119. package/extensions/mega-runtime/widget.ts +1 -1
  120. package/extensions/mega-turn-store.ts +9 -0
  121. package/package.json +2 -2
  122. package/src/bridge/factory.ts +15 -1
  123. package/src/bridge/types.ts +2 -0
  124. package/src/compact-summary.ts +120 -0
  125. package/src/compact.ts +19 -109
  126. package/src/contextHealth.ts +26 -6
  127. package/src/metrics/turns.ts +6 -1
  128. package/src/store/sqlite/context-health-cache-poison.ts +97 -0
  129. package/src/store/sqlite/context-health.ts +12 -84
  130. package/src/store/sqlite/embedding-cache.ts +4 -0
  131. package/src/store/sqlite/schema.ts +4 -0
  132. package/src/store/turns/memory-store/admin.ts +171 -0
  133. package/src/store/turns/memory-store/ctx.ts +62 -0
  134. package/src/store/turns/memory-store/read.ts +172 -0
  135. package/src/store/turns/memory-store/write.ts +126 -0
  136. package/src/store/turns/memory-store.ts +51 -330
  137. package/src/store/turns/migrations.ts +4 -0
  138. package/src/store/turns/schema.ts +1 -0
  139. package/src/store/turns/sqlite-store/read.ts +19 -0
  140. package/src/store/turns/sqlite-store/rows.ts +5 -0
  141. package/src/store/turns/sqlite-store/write.ts +3 -2
  142. package/src/store/turns/sqlite-store.ts +5 -0
  143. package/src/store/turns/types.ts +15 -0
  144. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
  145. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
  146. package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
  147. package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # pi-mega-compact
2
2
 
3
+ > **⚠️ LTS — patches only (2026-08-13).** This extension is maintained for bug fixes only. New feature development has moved to **[radcode](https://github.com/TheArchitectit/radcode)**, a Rust pi.dev replacement that has ported mega-compact's compaction/recall/dedup/RAPTOR stack. See [`docs/LTS.md`](docs/LTS.md) and [`docs/SUCCESSION.md`](docs/SUCCESSION.md).
4
+
3
5
  A local-first context compressor for the [pi coding agent](https://github.com/earendil-works/pi). Keeps long sessions running without overflowing the context window. Local by default — no cloud, no API calls, no telemetry. Bring your own localhost embedder (Ollama, ONNX, TEI) for better semantic matches, or opt in to a remote endpoint if you need to.
4
6
 
5
7
  ## Features
@@ -10,31 +10,41 @@ import { STATE_DIR_DEFAULT } from "../src/config.js";
10
10
  import { join } from "node:path";
11
11
  import { execSync } from "node:child_process"; // guardrails-allow PREVENT-PI-004: read-only `git rev-parse` to scope the store per-repo
12
12
  /**
13
- * Named compaction tiers. A tier sets the token threshold at which the
14
- * auto-trigger persists a checkpoint; pick by how aggressively you want the
15
- * session trimmed. Explicit MEGACOMPACT_THRESHOLD_TOKENS always wins.
13
+ * Named compaction tiers each tier sets the compaction fire point as a
14
+ * FRACTION of the model's context window (NOT a static token amount): the live
15
+ * + durable trim fire at `tier × window`, so they always fire BELOW pi's native
16
+ * auto-compaction (~80% of window) for any model size (200k or 1M). Pick by how
17
+ * aggressively you want the session trimmed. Explicit
18
+ * MEGACOMPACT_THRESHOLD_TOKENS always wins (`custom`, tierPct null — absolute,
19
+ * never percent-scaled). This is the SINGLE source of truth for tier fractions;
20
+ * `keyof typeof COMPACT_TIERS` is the `CompactTier` type and
21
+ * `raw in COMPACT_TIERS` validates a named preset name.
16
22
  */
17
23
  export const COMPACT_TIERS = {
18
- low: 50_000,
19
- medium: 100_000,
20
- high: 200_000,
21
- ultra: 1_000_000,
22
- mega: 10_000_000,
23
- };
24
- /**
25
- * Compaction thresholds as a FRACTION of the model's context window (NOT a
26
- * static token amount). The live + durable trim fire at tier% of the window,
27
- * so they always fire BELOW pi's native auto-compaction (~80% of window) for
28
- * any model size (200k or 1M). `custom` (explicit MEGACOMPACT_THRESHOLD_TOKENS)
29
- * is NOT scaled by this map — it stays an absolute token count.
30
- */
31
- export const TIER_PCT = {
32
24
  low: 0.5,
33
25
  medium: 0.6,
34
26
  high: 0.7,
35
27
  ultra: 0.7,
36
28
  mega: 0.75,
37
29
  };
30
+ /**
31
+ * Boot-fallback context window (env-overridable). Used ONLY as a display/seed
32
+ * placeholder before the first real context event supplies the provider's
33
+ * window — the live fire point (`effectiveThresholdImpl`) DEFERS (returns
34
+ * `+Infinity`) when the window is unknown, so this value is never a guessed
35
+ * gate. Default 200k keeps the display seed stable; set
36
+ * `MEGACOMPACT_DEFAULT_CONTEXT_WINDOW=<tokens>` to repoint it (e.g. for a
37
+ * 32k-model fleet the seed should reflect that).
38
+ */
39
+ export const DEFAULT_CONTEXT_WINDOW = envFlag("MEGACOMPACT_DEFAULT_CONTEXT_WINDOW", 200_000);
40
+ /**
41
+ * Display seed for the progress-bar "saved tokens goal" denominator. The live
42
+ * value grows dynamically (`run.ts`: `savedGoal = ceil(tokensSaved × 1.25)` once
43
+ * exceeded), so this is only the initial target before the first compaction.
44
+ * Derived from the low-tier fire point at the default window (no bare magic):
45
+ * `COMPACT_TIERS.low × DEFAULT_CONTEXT_WINDOW`. Display-only.
46
+ */
47
+ export const DEFAULT_SAVED_GOAL = Math.round(COMPACT_TIERS.low * DEFAULT_CONTEXT_WINDOW);
38
48
  function envFlag(name, fallback) {
39
49
  const v = process.env[name];
40
50
  if (v == null || v === "")
@@ -52,8 +62,8 @@ function envBool(name, fallback) {
52
62
  * Resolve the effective token threshold from TIER (or explicit) env vars.
53
63
  *
54
64
  * For a named tier the returned `thresholdTokens` is a BOOT FALLBACK
55
- * (`round(tierPct * 200_000)`) — sane before any context event reaches the
56
- * runtime. The true fire point is computed per-window at runtime via
65
+ * (`round(tierPct * DEFAULT_CONTEXT_WINDOW)`) — sane before any context event
66
+ * reaches the runtime. The true fire point is computed per-window at runtime via
57
67
  * `effectiveThresholdTokens(...)`. `custom` (explicit MEGACOMPACT_THRESHOLD_TOKENS)
58
68
  * keeps `tierPct: null` and an ABSOLUTE `thresholdTokens` (never percent-scaled).
59
69
  */
@@ -66,7 +76,7 @@ function resolveThreshold() {
66
76
  }
67
77
  const raw = (process.env.MEGACOMPACT_TIER ?? "low").toLowerCase();
68
78
  const tier = (raw in COMPACT_TIERS ? raw : "low");
69
- let tierPct = TIER_PCT[tier];
79
+ let tierPct = COMPACT_TIERS[tier];
70
80
  // 3WF-2 threshold invariant: under the umbrella, when no named tier is set
71
81
  // the fire point is the configurable % of the ACTUAL model window (default
72
82
  // 0.80 — "20% free remaining"). Tiered (named preset) keeps its preset pct;
@@ -80,8 +90,9 @@ function resolveThreshold() {
80
90
  // Boot fallback: sane gate before the first context event provides a window.
81
91
  // (NO hardcoded window in the firing path — effectiveThresholdImpl defers
82
92
  // when window unknown; this remains only a display placeholder + custom
83
- // companion under the umbrella.)
84
- const thresholdTokens = Math.round(tierPct * 200_000);
93
+ // companion under the umbrella.) Env-overridable via
94
+ // MEGACOMPACT_DEFAULT_CONTEXT_WINDOW (see DEFAULT_CONTEXT_WINDOW).
95
+ const thresholdTokens = Math.round(tierPct * DEFAULT_CONTEXT_WINDOW);
85
96
  return { tier, tierPct, thresholdTokens };
86
97
  }
87
98
  /** Clamp `n` into [lo, hi]; non-finite → fallback. */
@@ -115,8 +126,11 @@ export function effectiveThresholdTokens(opts) {
115
126
  * Resolve the optional manual arming-floor override (MEGACOMPACT_FAST_GATE_PCT).
116
127
  * Kept for backward-compat: when unset, the default arming floor equals the
117
128
  * tier's percent threshold (tierPct*100) so the dashboard stays consistent;
118
- * `custom` (tierPct null) falls back to the legacy 70% default.
129
+ * `custom` (tierPct null an explicit absolute opt-out of percent scaling by
130
+ * design) has no tier fraction to derive from, so it falls back to the named
131
+ * DEFAULT_FAST_GATE_PCT_CUSTOM. Env-overridable like every other default here.
119
132
  */
133
+ export const DEFAULT_FAST_GATE_PCT_CUSTOM = 70;
120
134
  function resolveFastGatePct(tierPct) {
121
135
  const raw = process.env.MEGACOMPACT_FAST_GATE_PCT;
122
136
  if (raw != null && raw !== "") {
@@ -124,7 +138,7 @@ function resolveFastGatePct(tierPct) {
124
138
  if (Number.isFinite(n))
125
139
  return n;
126
140
  }
127
- return tierPct != null ? Math.round(tierPct * 100) : 70;
141
+ return tierPct != null ? Math.round(tierPct * 100) : DEFAULT_FAST_GATE_PCT_CUSTOM;
128
142
  }
129
143
  /**
130
144
  * Pressure helpers for adaptive compression live in src/config.ts (pi-agnostic)
@@ -193,6 +207,9 @@ export function loadConfig() {
193
207
  memoryAutoReview: envBool("MEGACOMPACT_MEMORY_AUTO_REVIEW", true),
194
208
  memoryReviewInterval: envFlag("MEGACOMPACT_MEMORY_REVIEW_INTERVAL", 10),
195
209
  recallMaxTokens: envFlag("MEGACOMPACT_RECALL_MAX_TOKENS", 1500),
210
+ // Phase H: output-error catch — trip compaction on a truncated model output
211
+ // (S28 stopReason==='length'). Default ON; OFF byte-identical pre-H.
212
+ outputErrorCompact: envBool("MEGACOMPACT_OUTPUT_ERROR_COMPACT", true),
196
213
  windowDedupe: envBool("MEGACOMPACT_WINDOW_DEDUPE", true),
197
214
  recallTailInject: envBool("MEGACOMPACT_RECALL_TAIL_INJECT", true),
198
215
  // 3WF-1: TriggerGuard — guarantee a staged recall block on every context
@@ -7,6 +7,13 @@ export function lengthStop(event, runtime, config) {
7
7
  event.message.role === "assistant" &&
8
8
  event.message.stopReason === "length") {
9
9
  runtime.rt.lengthStopPending = true;
10
+ // Phase H: output-error catch — ALSO arm the one-shot force-compact flag so
11
+ // the next compaction gate trips immediately, freeing input headroom for
12
+ // the model's next response. Gated by config.outputErrorCompact (default
13
+ // ON; OFF = byte-identical pre-H — only the S28 auto-continue nudge fires).
14
+ if (config.outputErrorCompact) {
15
+ runtime.rt.forceCompactNextGate = true;
16
+ }
10
17
  runtime.dashboard.event("length_stop", { turnIndex: event.turnIndex });
11
18
  }
12
19
  }
@@ -1,4 +1,4 @@
1
- import { ensureConversationIdFor, recordTurnWrite } from "../../../mega-turn-store.js";
1
+ import { ensureConversationIdFor, recordTurnWrite, turnReaderFor } from "../../../mega-turn-store.js";
2
2
  /** S43: record one turn row with the cached metrics. Best-effort + non-fatal. */
3
3
  export function recordTurnRow(event, runtime, config) {
4
4
  // S43 (per-turn tracking): record one turn row with the cached metrics so
@@ -8,10 +8,16 @@ export function recordTurnRow(event, runtime, config) {
8
8
  // instead of silently producing a blank Turns tab.
9
9
  try {
10
10
  const convId = ensureConversationIdFor(config, runtime.rt.sessionId, runtime.currentStateDir);
11
+ // S49R: store the conversation-monotonic index (MAX+1) instead of the
12
+ // per-session event.turnIndex, which restarts at 0 on resume and would
13
+ // collide with UNIQUE(conversation_id, turn_index). Carry the session
14
+ // counter as sessionTurnIndex for the raw_transcript metrics join.
15
+ const turnIndex = turnReaderFor(runtime.currentStateDir).nextTurnIndexFor(convId);
11
16
  recordTurnWrite(config, {
12
17
  conversationId: convId,
13
18
  sessionId: runtime.rt.sessionId,
14
- turnIndex: event.turnIndex,
19
+ turnIndex,
20
+ sessionTurnIndex: event.turnIndex,
15
21
  role: event.role ?? "assistant",
16
22
  endedAt: Date.now(),
17
23
  startedAt: undefined,
@@ -34,5 +40,9 @@ export function recordTurnRow(event, runtime, config) {
34
40
  error: e instanceof Error ? e.message : String(e),
35
41
  stack: e instanceof Error ? e.stack?.split("\n").slice(0, 3).join(" | ") : undefined,
36
42
  });
43
+ // Sprint H (Finding 3 / Option A): the turn-row write is an internal
44
+ // store-write failure the API-error `errorRate` ring never sees. Feed the
45
+ // separate `storeErrorRate` 6th axis.
46
+ runtime.recordInternalError("store_write");
37
47
  }
38
48
  }
@@ -48,6 +48,8 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
48
48
  }
49
49
  catch (e) {
50
50
  runtime.logger.warn("planv2-epoch-fail", { error: String(e) });
51
+ // Sprint H (Option A): checkpoint_epoch write failure.
52
+ runtime.recordInternalError("store_write");
51
53
  }
52
54
  }
53
55
  // S27 DB-mirror downstream: stamp turns, rebuild wiki, seed topics, dedup.
@@ -136,6 +138,9 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
136
138
  runtime.logger.warn("wiki_rebuild_failed", {
137
139
  error: String(wikiErr),
138
140
  });
141
+ // Sprint H (Option A): wiki rebuild writes (topic model / overrides)
142
+ // to the isolated store — internal store write.
143
+ runtime.recordInternalError("store_write");
139
144
  }
140
145
  // D1: seed the topic model from raw_transcript when context_chunks is
141
146
  // thin (pre-compaction). Gated on WIKI_SEED_FROM_TURNS; non-fatal.
@@ -202,6 +207,9 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
202
207
  runtime.logger.warn("wiki_seed_failed", {
203
208
  error: String(seedErr),
204
209
  });
210
+ // Sprint H (Option A): wiki seed rebuild writes to the isolated
211
+ // store — internal store write.
212
+ runtime.recordInternalError("store_write");
205
213
  }
206
214
  // S27 Task 6: Fire-and-forget dedup pipeline.
207
215
  // Deduplicates raw_transcript rows for the compacted range.
@@ -215,6 +223,9 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
215
223
  }
216
224
  catch (e) {
217
225
  runtime.logger.warn("db-mirror-epoch-fail", { error: String(e) });
226
+ // Sprint H (Option A): db-mirror downstream maintenance writes
227
+ // (stamp turns epoch / wiki / seed / dedup) — internal store writes.
228
+ runtime.recordInternalError("store_write");
218
229
  }
219
230
  }
220
231
  // VC6 Heal lifecycle emits (post-compact). VC6A: closure-optimization
@@ -76,6 +76,9 @@ export function appendMirrorAndLedger(runtime, config, messages) {
76
76
  }
77
77
  catch (e) {
78
78
  runtime.logger.warn("db-mirror-append-fail", { error: String(e) });
79
+ // Sprint H (Option A): raw_transcript / thread / tool_results write
80
+ // failure — internal store write. Feed the `storeErrorRate` axis.
81
+ runtime.recordInternalError("store_write");
79
82
  }
80
83
  }
81
84
  // VC1B (S1): canonical messages -> v2 ledger occurrences. Flag-OFF opens
@@ -87,5 +90,10 @@ export function appendMirrorAndLedger(runtime, config, messages) {
87
90
  }
88
91
  catch (e) {
89
92
  runtime.logger.warn("vc1b-ledger-append-fail", { error: String(e) });
93
+ // Sprint H (Option A): the v2 vector-cortex ledger append is a cortex
94
+ // write — this is the host-side hook for the cortex seam (the cortex
95
+ // contract forbids the store from holding a runtime handle, so the ring
96
+ // push happens here in the adapter, not inside src/vector-cortex).
97
+ runtime.recordInternalError("vector_index");
90
98
  }
91
99
  }
@@ -50,6 +50,23 @@ export function evaluateGate(runtime, config, opts) {
50
50
  : DEFAULT_FIRE_POINT_PCT,
51
51
  stateDir: runtime.currentStateDir,
52
52
  });
53
+ // Phase H: output-error catch. A truncated model output (S28
54
+ // stopReason==='length' — "Response was truncated before completion") trips
55
+ // a one-shot force-compact so the next compaction runs IMMEDIATELY, freeing
56
+ // input headroom for the model's next response. This closes the
57
+ // small-context-model deadlock: the model truncates MID-OUTPUT below the
58
+ // 80% INPUT threshold (so the gate never fires) → "compact never" → every
59
+ // subsequent response also truncates. lengthStop.ts sets this flag
60
+ // alongside its auto-continue nudge; gated by config.outputErrorCompact
61
+ // (default ON; OFF = byte-identical pre-H). One-shot: cleared on consumption.
62
+ // thrashGuardBlocks is consulted separately by the handler; in the reported
63
+ // "compact never" case compactCount===0 so the guard is never armed and will
64
+ // not block this trip.
65
+ if (config.outputErrorCompact && runtime.rt?.forceCompactNextGate) {
66
+ runtime.rt.forceCompactNextGate = false;
67
+ runtime.diagCtxOutputErrorTrip++;
68
+ return { kind: "proceed", perModelThreshold };
69
+ }
53
70
  // S29 FAST GATE: `custom` (absolute MEGACOMPACT_THRESHOLD_TOKENS,
54
71
  // tierPct null) is an explicit opt-out of percent scaling — it keeps the
55
72
  // token gate. When pct is unavailable (window unknown / a model that
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * PREVENT-PI-004: zero network (local TrigramEmbedder + SQLite only).
10
10
  */
11
+ import { RING_MAX } from "../mega-runtime/runtime.js";
11
12
  import { defaultEmbedder } from "../../src/embedder.js";
12
13
  import { computeOutputQuality } from "../../src/contextHealth/outputQuality.js";
13
14
  import { computeTopicDrift, computeErrorEscalation, computePrefixInstability, computeDriftScore, } from "../../src/contextHealth/drift.js";
@@ -15,7 +16,6 @@ import { computePrefixHash, checkPrefixHash, evaluateCachePoison, } from "../../
15
16
  import { computeHealthScore, recordContextHealth, } from "../../src/contextHealth.js";
16
17
  import { recordCachePoisonEvent, } from "../../src/store/sqlite/context-health.js";
17
18
  import { getHealthMitigate } from "../../src/store/sqlite/meta.js";
18
- const RING_MAX = 5;
19
19
  /** Extract assistant text from the turn_end event message. */
20
20
  function extractAssistantText(event) {
21
21
  const msg = event.message;
@@ -69,8 +69,15 @@ export function handleTurnEndHealth(event, runtime, config) {
69
69
  const prefix = computePrefixInstability(0, 5);
70
70
  drift = computeDriftScore(topic, error, prefix);
71
71
  }
72
- // Error rate sub-score (1 = no errors)
72
+ // Error rate sub-score (1 = no errors) — API-retry errors only.
73
73
  const errorScore = computeErrorEscalation(runtime.recentErrorCategories);
74
+ // Sprint H (B2): store-error sub-score (1 = no store-write failures).
75
+ // Distinct from errorScore — internal store/service-write failures
76
+ // (turn rows, recall, dbMirror, ledger, epoch, stats, memory consolidate,
77
+ // compact persist, vector index, embedder, migration) never reach the
78
+ // API-retry errorRate ring. Mirrors computeErrorEscalation: 1 - (n/RING_MAX).
79
+ const recentInternalCount = Math.min(runtime.recentInternalErrors.length, RING_MAX);
80
+ const storeErrorScore = 1 - recentInternalCount / RING_MAX;
74
81
  // Cache health (from perf-handler)
75
82
  const cacheHealth = runtime.rt._lastCacheHealthScore ?? 1.0;
76
83
  // Cache poison sub-score
@@ -113,6 +120,7 @@ export function handleTurnEndHealth(event, runtime, config) {
113
120
  errorRate: errorScore,
114
121
  cacheHealth,
115
122
  cachePoison,
123
+ storeErrorRate: storeErrorScore,
116
124
  };
117
125
  const composite = computeHealthScore(sub);
118
126
  // Persist
@@ -126,6 +134,7 @@ export function handleTurnEndHealth(event, runtime, config) {
126
134
  cacheHealth,
127
135
  cachePoison,
128
136
  composite,
137
+ storeErrorScore,
129
138
  modelId: runtime.currentModel?.modelId,
130
139
  repetitionRatio,
131
140
  coherenceScore,
@@ -137,6 +146,7 @@ export function handleTurnEndHealth(event, runtime, config) {
137
146
  drift,
138
147
  outputQuality,
139
148
  errorRate: errorScore,
149
+ storeErrorRate: storeErrorScore,
140
150
  cacheHealth,
141
151
  cachePoison,
142
152
  turnIndex: event.turnIndex,
@@ -90,6 +90,7 @@ export function registerSessionHandlers(pi, runtime, config) {
90
90
  }
91
91
  catch (e) {
92
92
  runtime.logger.warn("db-auto-maintain-fail", { error: String(e) });
93
+ runtime.recordInternalError("store_write");
93
94
  }
94
95
  runtime.dashboard.event("session_start", {
95
96
  reason: event.reason,
@@ -130,6 +130,9 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
130
130
  logDaily(sid, "compact", result.checkpointId, saved, runtime.currentStateDir);
131
131
  }
132
132
  catch {
133
+ // Sprint H (Option A): session-activity / daily-log write is an internal
134
+ // store-write failure — feed the separate `storeErrorRate` axis.
135
+ runtime.recordInternalError("store_write");
133
136
  /* non-fatal: stats bookkeeping only */
134
137
  }
135
138
  // S21.2: best-effort consolidation of near-duplicate memories for this repo.
@@ -145,10 +148,14 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
145
148
  if (n > 0)
146
149
  runtime.pushTicker(`${C.green}∫${C.reset} consolidated ${n} memory dup${n === 1 ? "" : "s"}`);
147
150
  }, () => {
151
+ // Sprint H (Option A): memory-consolidation write failure.
152
+ runtime.recordInternalError("store_write");
148
153
  /* swallow: consolidate failures must never surface to the user */
149
154
  });
150
155
  }
151
156
  catch {
157
+ // Sprint H (Option A): memory-consolidation write failure (sync throw).
158
+ runtime.recordInternalError("store_write");
152
159
  /* non-fatal */
153
160
  }
154
161
  }
@@ -215,6 +222,8 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
215
222
  }
216
223
  }
217
224
  catch {
225
+ // Sprint H (Option A): RAPTOR tree-refresh write failure.
226
+ runtime.recordInternalError("vector_index");
218
227
  /* non-fatal: tree refresh never blocks a compaction */
219
228
  }
220
229
  }
@@ -228,6 +237,8 @@ function doCompact(view, keepFrom, opts, sid, config, pi, ctx, runtime) {
228
237
  const latest = all.find((cp) => cp.checkpointId === result.checkpointId);
229
238
  if (latest?.embedding) {
230
239
  void indexUpsertEmbedding(repoKey(runtime.currentStateDir), sid, latest.checkpointId, latest.embedding).catch(() => {
240
+ // Sprint H (Option A): async global vector-index upsert failure.
241
+ runtime.recordInternalError("vector_index");
231
242
  /* non-fatal: index refresh never blocks a compaction */
232
243
  });
233
244
  }
@@ -8,6 +8,7 @@
8
8
  import { join, dirname } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { readFileSync } from "node:fs";
11
+ import { normalizeSessionId } from "../../src/store.js";
11
12
  // ── Public string constants ────────────────────────────────────────────────
12
13
  // Exported via the barrel — consumers (mega-events.ts, mega-pipeline.ts) use
13
14
  // these keys to register widgets/markers with pi.
@@ -38,3 +39,48 @@ export function ownVersion() {
38
39
  CACHED_VERSION = v;
39
40
  return v;
40
41
  }
42
+ /**
43
+ * createSessionRuntime() — the default per-session runtime state.
44
+ *
45
+ * Extracted from runtime.ts (delegate-shell split) so the MegaRuntime class
46
+ * stays a field-declarations + 1-line-delegate shell. Returns a fresh
47
+ * SessionRuntime for each session (reset on session_start / session_tree).
48
+ * Mirrors the neuralwatt-mcr per-session closure.
49
+ */
50
+ export function createSessionRuntime() {
51
+ return {
52
+ sessionId: normalizeSessionId(undefined),
53
+ persistedThisSession: false,
54
+ lastCheckpointId: undefined,
55
+ lastCompactedFrom: 0,
56
+ lastCompactedTokens: 0,
57
+ dedupSkips: 0,
58
+ dedupAttempts: 0,
59
+ tokensSaved: 0,
60
+ lastCompactAt: null,
61
+ lastRecallAt: null,
62
+ lastInjectAt: null,
63
+ _prevCacheHitPct: null,
64
+ _lastCacheHealthScore: undefined,
65
+ lastNativeCompactAt: null,
66
+ compactCount: 0,
67
+ recallInjections: 0,
68
+ cacheHitTokens: 0,
69
+ lengthStopPending: false,
70
+ forceCompactNextGate: false,
71
+ errorRetryCount: 0,
72
+ errorRetryUntil: 0,
73
+ consecutiveErrors: 0,
74
+ lastErrorRetryAt: 0,
75
+ retryNudgePending: false,
76
+ errorRetrySessionCount: 0,
77
+ lastErrorText: undefined,
78
+ errorTextRepeatCount: 0,
79
+ poisonedAdviseSent: false,
80
+ providerOutageAdvised: false,
81
+ poisonedCompactSignatures: new Set(),
82
+ recallInjectedThisTurn: false,
83
+ poisonedCount: 0,
84
+ extensionInitiatedTurn: false,
85
+ };
86
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * RING_MAX — the cap for the rolling health ring buffers
3
+ * (`recentTurnEmbeddings`, `recentErrorCategories`, `recentInternalErrors`).
4
+ * Defined here (with the ring writer) and re-exported from runtime.ts so the
5
+ * ring + its writer live together; health-handler.ts imports it.
6
+ */
7
+ export const RING_MAX = 5;
8
+ /** Push a category into the internal-error ring. Mirrors `recentErrorCategories`. */
9
+ export function recordInternalErrorImpl(rt, category) {
10
+ rt.recentInternalErrors.push(category);
11
+ if (rt.recentInternalErrors.length > RING_MAX) {
12
+ rt.recentInternalErrors.shift();
13
+ }
14
+ }
@@ -51,16 +51,18 @@ export function pressureImpl(self) {
51
51
  * always below pi's native auto-compaction (~80% of window).
52
52
  */
53
53
  export function effectiveThresholdImpl(self) {
54
- // 3WF-2 threshold invariant: under the umbrella, a tiered config with an
55
- // UNKNOWN window (lastCtxWindow <= 0) DEFERS — auto-compaction must never
56
- // substitute a guessed window. Returning +Infinity keeps every downstream
57
- // `tokens >= threshold` comparison false (gateCheck token path,
58
- // agent_end durable trigger, live-trim re-compact), so no compaction fires
59
- // until the provider reports a real window. custom (tierPct null) and
60
- // umbrella-OFF fall through to the legacy helper (byte-identical).
61
- if (self.config.threeWayFailback &&
62
- self.config.tierPct != null &&
63
- self.lastCtxWindow <= 0) {
54
+ // 3WF-2 threshold invariant (LTS-correctness fix, Phase C): a tiered config
55
+ // (tierPct != null) with an UNKNOWN window (lastCtxWindow <= 0) DEFERS —
56
+ // auto-compaction must never substitute a guessed window, under ANY umbrella
57
+ // state. Returning +Infinity keeps every downstream `tokens >= threshold`
58
+ // comparison false (gateCheck token path, agent_end durable trigger, live-trim
59
+ // re-compact), so no compaction fires until the provider reports a real
60
+ // window. This closes the small-context-model deadlock: umbrella-OFF
61
+ // previously fell through to the legacy 200k helper (round(tierPct × 200k)),
62
+ // which is unreachable on a 32k window → the model truncates before the gate
63
+ // ever fires → "compact never". custom (tierPct null) always falls through
64
+ // to the explicit absolute.
65
+ if (self.config.tierPct != null && self.lastCtxWindow <= 0) {
64
66
  return Number.POSITIVE_INFINITY;
65
67
  }
66
68
  return effectiveThresholdTokens({
@@ -9,6 +9,7 @@
9
9
  * runtime-helpers.ts.
10
10
  */
11
11
  import { normalizeSessionId } from "../../src/store.js";
12
+ import { DEFAULT_SAVED_GOAL } from "../mega-config.js";
12
13
  // --------------------------------------------------------------- resetRuntime
13
14
  export function resetRuntimeImpl(self, sessionId) {
14
15
  // Only call normalizeSessionId when a real sessionId string is passed.
@@ -37,6 +38,7 @@ export function resetRuntimeImpl(self, sessionId) {
37
38
  recallInjections: 0,
38
39
  cacheHitTokens: 0,
39
40
  lengthStopPending: false,
41
+ forceCompactNextGate: false,
40
42
  errorRetryCount: 0,
41
43
  errorRetryUntil: 0,
42
44
  consecutiveErrors: 0,
@@ -60,7 +62,7 @@ export function resetRuntimeImpl(self, sessionId) {
60
62
  self.tierTrace = undefined;
61
63
  self.ticker.length = 0;
62
64
  self.pulsing = false;
63
- self.savedGoal = 50_000;
65
+ self.savedGoal = DEFAULT_SAVED_GOAL;
64
66
  self.lastWhy = undefined;
65
67
  // S31 audit P2: symmetry with bindRepo — a reset can coincide with a context
66
68
  // that re-binds the repo, so drop the memo too. Cheap; the next