pi-mega-compact 0.21.2 → 0.21.4

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 (141) hide show
  1. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +1 -0
  2. package/dist/extensions/mega-compact-child.js +126 -0
  3. package/dist/extensions/mega-config.js +5 -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/health-handler.js +12 -2
  8. package/dist/extensions/mega-events/session-handlers.js +1 -0
  9. package/dist/extensions/mega-pipeline/compact/run.js +11 -0
  10. package/dist/extensions/mega-runtime/helpers.js +45 -0
  11. package/dist/extensions/mega-runtime/internal-errors.js +14 -0
  12. package/dist/extensions/mega-runtime/runtime-instrumentation.js +166 -0
  13. package/dist/extensions/mega-runtime/runtime.js +27 -193
  14. package/dist/extensions/mega-runtime/state.js +1 -1
  15. package/dist/extensions/mega-runtime/widget.js +1 -1
  16. package/dist/extensions/mega-turn-store.js +5 -0
  17. package/dist/src/bridge/factory.js +195 -0
  18. package/dist/src/bridge/types.js +10 -0
  19. package/dist/src/bridge.js +1 -0
  20. package/dist/src/contextHealth.js +13 -5
  21. package/dist/src/metrics/turns.js +6 -1
  22. package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
  23. package/dist/src/store/sqlite/context-health.js +6 -48
  24. package/dist/src/store/sqlite/embedding-cache.js +4 -0
  25. package/dist/src/store/sqlite/schema.js +4 -0
  26. package/dist/src/store/turns/memory-store/admin.js +119 -0
  27. package/dist/src/store/turns/memory-store/ctx.js +28 -0
  28. package/dist/src/store/turns/memory-store/read.js +110 -0
  29. package/dist/src/store/turns/memory-store/write.js +88 -0
  30. package/dist/src/store/turns/memory-store.js +44 -273
  31. package/dist/src/store/turns/migrations.js +4 -0
  32. package/dist/src/store/turns/schema.js +1 -0
  33. package/dist/src/store/turns/sqlite-store/read.js +13 -0
  34. package/dist/src/store/turns/sqlite-store/rows.js +4 -0
  35. package/dist/src/store/turns/sqlite-store/write.js +3 -3
  36. package/dist/src/store/turns/sqlite-store.js +4 -0
  37. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
  50. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
  51. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
  58. package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
  72. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
  88. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
  89. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
  91. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
  92. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
  93. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
  94. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
  95. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
  96. package/extensions/dashboard-client/dist/index.html +1 -1
  97. package/extensions/dashboard-client/src/api/health.ts +1 -0
  98. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
  99. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +6 -0
  100. package/extensions/mega-compact-child.ts +129 -0
  101. package/extensions/mega-config-types.ts +5 -0
  102. package/extensions/mega-config.ts +5 -0
  103. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
  104. package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
  105. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
  106. package/extensions/mega-events/health-handler.ts +13 -3
  107. package/extensions/mega-events/session-handlers.ts +1 -0
  108. package/extensions/mega-pipeline/compact/run.ts +11 -0
  109. package/extensions/mega-runtime/helpers.ts +46 -0
  110. package/extensions/mega-runtime/internal-errors.ts +30 -0
  111. package/extensions/mega-runtime/runtime-instrumentation.ts +195 -0
  112. package/extensions/mega-runtime/runtime.ts +29 -209
  113. package/extensions/mega-runtime/state.ts +1 -1
  114. package/extensions/mega-runtime/widget.ts +1 -1
  115. package/extensions/mega-turn-store.ts +9 -0
  116. package/package.json +2 -2
  117. package/src/bridge/factory.ts +245 -0
  118. package/src/bridge/types.ts +140 -0
  119. package/src/bridge.ts +24 -0
  120. package/src/contextHealth.ts +26 -6
  121. package/src/metrics/turns.ts +6 -1
  122. package/src/store/sqlite/context-health-cache-poison.ts +97 -0
  123. package/src/store/sqlite/context-health.ts +12 -84
  124. package/src/store/sqlite/embedding-cache.ts +4 -0
  125. package/src/store/sqlite/schema.ts +4 -0
  126. package/src/store/turns/memory-store/admin.ts +171 -0
  127. package/src/store/turns/memory-store/ctx.ts +62 -0
  128. package/src/store/turns/memory-store/read.ts +172 -0
  129. package/src/store/turns/memory-store/write.ts +126 -0
  130. package/src/store/turns/memory-store.ts +51 -330
  131. package/src/store/turns/migrations.ts +4 -0
  132. package/src/store/turns/schema.ts +1 -0
  133. package/src/store/turns/sqlite-store/read.ts +19 -0
  134. package/src/store/turns/sqlite-store/rows.ts +5 -0
  135. package/src/store/turns/sqlite-store/write.ts +3 -2
  136. package/src/store/turns/sqlite-store.ts +5 -0
  137. package/src/store/turns/types.ts +15 -0
  138. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
  139. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
  140. package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
  141. package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
@@ -0,0 +1,140 @@
1
+ /**
2
+ * bridge/types.ts — contract types for the bidirectional mega-compact bridge.
3
+ *
4
+ * A pi-agnostic, unit-testable adapter surface that wraps the engine's
5
+ * compaction / recall / memory / fork / vector APIs behind one factory so an
6
+ * external host (ithacus) can drive them without importing pi-runtime types.
7
+ * Every type here mirrors a real engine signature (see factory.ts for the
8
+ * wiring).
9
+ */
10
+
11
+ import type { EngineMessage } from "../types.js";
12
+
13
+ /** Same shape as the engine's internal message. Identity re-export. */
14
+ export type BridgeMessage = EngineMessage;
15
+
16
+ /** Bridge construction options. */
17
+ export interface BridgeOptions {
18
+ stateDir: string;
19
+ }
20
+
21
+ /** Input to compact a message slice into a checkpoint. */
22
+ export interface BridgeCompactInput {
23
+ sessionId: string;
24
+ messages: BridgeMessage[];
25
+ keepFrom?: number;
26
+ summary?: string;
27
+ keyDecisions?: string[];
28
+ nextSteps?: string[];
29
+ filesModified?: string[];
30
+ compressionPressure?: number;
31
+ }
32
+
33
+ /** Useful subset of CompactResult. */
34
+ export interface BridgeCompactResult {
35
+ skipped: boolean;
36
+ deduped: boolean;
37
+ summary: string;
38
+ checkpointId?: string;
39
+ tokenEstimate: number;
40
+ originalTokenEstimate?: number;
41
+ compactedFrom?: number;
42
+ }
43
+
44
+ /** Options for checkpoint recall (per-session). */
45
+ export interface BridgeRecallOptions {
46
+ sessionId: string;
47
+ query: string;
48
+ limit?: number;
49
+ recallMaxTokens?: number;
50
+ skipInjected?: boolean;
51
+ }
52
+
53
+ /** Mapped from RecallInjectResult. */
54
+ export interface BridgeRecallResult {
55
+ block: string;
56
+ report: string[];
57
+ hitCount: number;
58
+ empty: boolean;
59
+ }
60
+
61
+ /** Options for durable memory recall (stateDir-scoped, no sessionId). */
62
+ export interface BridgeMemoryRecallOptions {
63
+ query: string;
64
+ limit?: number;
65
+ minSimilarity?: number;
66
+ crossRepo?: boolean;
67
+ crossRepoCosine?: number;
68
+ recallMaxTokens?: number;
69
+ }
70
+
71
+ export interface BridgeMemoryRecallResult {
72
+ block: string;
73
+ report: string[];
74
+ hitCount: number;
75
+ empty: boolean;
76
+ }
77
+
78
+ /** Options to fork a child conversation off a parent turn. */
79
+ export interface BridgeForkOptions {
80
+ parentConversationId: string;
81
+ turnIndex: number;
82
+ }
83
+
84
+ /** Fork result: success variant OR a graceful error variant. */
85
+ export interface BridgeForkSuccess {
86
+ childConversationId: string;
87
+ checkpointIds: string[];
88
+ forkTurnIndex: number;
89
+ }
90
+ export interface BridgeForkError {
91
+ error: "TURN_NOT_FOUND" | "NO_RECALL";
92
+ }
93
+ export type BridgeForkResult = BridgeForkSuccess | BridgeForkError;
94
+
95
+ /** Options for a top-k corpus / vector query. */
96
+ export interface BridgeCortexOptions {
97
+ query: string;
98
+ limit?: number;
99
+ repo?: string;
100
+ }
101
+
102
+ export interface BridgeCortexResult {
103
+ results: Array<{ checkpointId: string; score: number; summary?: string }>;
104
+ hitCount: number;
105
+ }
106
+
107
+ /** Input to persist a durable memory. */
108
+ export interface BridgeAddMemoryInput {
109
+ content: string;
110
+ kind?: string;
111
+ tags?: string[];
112
+ category?: string;
113
+ }
114
+
115
+ /** Input to record a turn fact. */
116
+ export interface BridgeRecordTurnInput {
117
+ conversationId: string;
118
+ sessionId: string;
119
+ turnIndex: number;
120
+ /** S49R: pi's per-session counter, carried for the raw_transcript join. */
121
+ sessionTurnIndex?: number;
122
+ role?: string;
123
+ endedAt?: number;
124
+ ctxTokens?: number;
125
+ ctxPercent?: number;
126
+ model?: string;
127
+ }
128
+
129
+ /** The bridge surface exposed to the host. */
130
+ export interface MegaBridge {
131
+ compact(input: BridgeCompactInput): BridgeCompactResult;
132
+ recallCheckpoints(opts: BridgeRecallOptions): BridgeRecallResult;
133
+ recallMemories(opts: BridgeMemoryRecallOptions): Promise<BridgeMemoryRecallResult>;
134
+ recallAndInlineAsync(opts: BridgeRecallOptions): Promise<BridgeRecallResult>;
135
+ fork(opts: BridgeForkOptions): BridgeForkResult;
136
+ cortexQuery(opts: BridgeCortexOptions): BridgeCortexResult;
137
+ addMemory(input: BridgeAddMemoryInput): number | void;
138
+ recordTurn(input: BridgeRecordTurnInput): void;
139
+ close(): void;
140
+ }
package/src/bridge.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * bridge.ts — public barrel for the mega-compact bidirectional bridge.
3
+ *
4
+ * Hosts import only this file. The factory and all contracts live under
5
+ * src/bridge/ (kept thin per the delegate-shell pattern).
6
+ */
7
+ export type {
8
+ MegaBridge,
9
+ BridgeOptions,
10
+ BridgeMessage,
11
+ BridgeCompactInput,
12
+ BridgeCompactResult,
13
+ BridgeRecallOptions,
14
+ BridgeRecallResult,
15
+ BridgeMemoryRecallOptions,
16
+ BridgeMemoryRecallResult,
17
+ BridgeForkOptions,
18
+ BridgeForkResult,
19
+ BridgeCortexOptions,
20
+ BridgeCortexResult,
21
+ BridgeAddMemoryInput,
22
+ BridgeRecordTurnInput,
23
+ } from "./bridge/types.js";
24
+ export { createMegaBridge } from "./bridge/factory.js";
@@ -23,23 +23,39 @@ export interface ContextHealthSubScores {
23
23
  drift: number;
24
24
  /** Output quality score (0–1, higher = healthier output). */
25
25
  outputQuality: number;
26
- /** Error-rate score (0–1, higher = fewer errors). */
26
+ /** Error-rate score (0–1, higher = fewer API-retry errors). */
27
27
  errorRate: number;
28
28
  /** Cache health score (0–1, higher = healthier cache). */
29
29
  cacheHealth: number;
30
30
  /** Cache poison score (0–1, higher = less poison). */
31
31
  cachePoison: number;
32
+ /**
33
+ * Store-error rate score (0–1, higher = fewer internal store-write
34
+ * failures). Sprint H (B2): a SEPARATE 6th axis from `errorRate` — `errorRate`
35
+ * reflects API-retry errors only; `storeErrorRate` reflects internal
36
+ * store/service-write failures (turn rows, recall, dbMirror, ledger, epoch,
37
+ * stats, memory consolidate, compact persist, vector index, embedder,
38
+ * migration). The two are equally loud at 0.09 each.
39
+ */
40
+ storeErrorRate: number;
32
41
  }
33
42
 
34
43
  /**
35
- * Weighted composite health score from five sub-dimensions.
44
+ * Weighted composite health score from six sub-dimensions (Sprint H — B2).
36
45
  *
37
46
  * Weights:
38
47
  * output quality 22% — primary measure of model output health
39
48
  * drift 22% — session-level coherence
40
49
  * cache poison 20% — prompt-cache integrity
41
50
  * cache health 18% — cache hit rate stability
42
- * error rate 18% — error frequency
51
+ * error rate 9% — API-retry error frequency (split from 18%)
52
+ * store error 9% — internal store-write failure frequency (NEW 6th axis)
53
+ *
54
+ * The 0.18 `errorRate` budget was split into `errorRate 0.09` +
55
+ * `storeErrorRate 0.09` so a silent store-write failure is as visible as a
56
+ * retryable API error. Weights sum to 1.0. NOTE (step-change): historical
57
+ * context_health rows keep their stored composite (point-in-time snapshots — no
58
+ * backfill), so the trend line shows a boundary step where weights change.
43
59
  *
44
60
  * Returns 0–1: 1 = fully healthy, 0 = severely degraded.
45
61
  */
@@ -49,7 +65,8 @@ export function computeHealthScore(sub: ContextHealthSubScores): number {
49
65
  sub.drift * 0.22 +
50
66
  sub.cachePoison * 0.20 +
51
67
  sub.cacheHealth * 0.18 +
52
- sub.errorRate * 0.18;
68
+ sub.errorRate * 0.09 +
69
+ sub.storeErrorRate * 0.09;
53
70
  return Math.max(0, Math.min(1, raw));
54
71
  }
55
72
 
@@ -66,6 +83,8 @@ export interface ContextHealthRow {
66
83
  cacheHealth: number;
67
84
  cachePoison: number;
68
85
  composite: number;
86
+ /** Sprint H (B2): 6th axis — internal store-write failure score (0–1). */
87
+ storeErrorScore?: number;
69
88
  modelId?: string;
70
89
  repetitionRatio?: number;
71
90
  coherenceScore?: number;
@@ -89,9 +108,9 @@ export function recordContextHealth(
89
108
  db.prepare(
90
109
  `INSERT INTO context_health
91
110
  (ts, session_id, turn_index, drift_score, output_quality,
92
- error_score, cache_health, cache_poison, composite,
111
+ error_score, cache_health, cache_poison, composite, store_error_score,
93
112
  model_id, repetition_ratio, coherence_score, prefix_hash)
94
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
113
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
95
114
  ).run(
96
115
  row.ts,
97
116
  row.sessionId,
@@ -102,6 +121,7 @@ export function recordContextHealth(
102
121
  row.cacheHealth,
103
122
  row.cachePoison,
104
123
  row.composite,
124
+ row.storeErrorScore ?? null,
105
125
  row.modelId ?? null,
106
126
  row.repetitionRatio ?? null,
107
127
  row.coherenceScore ?? null,
@@ -113,7 +113,12 @@ export function turnMetrics(
113
113
 
114
114
  return turns.map((t) => {
115
115
  const recall = store.listRecallByIndex(conversationId, t.turnIndex);
116
- const agg = rawAggBySessionTurn.get(`${t.sessionId}::${t.turnIndex}`);
116
+ // S49R: raw_transcript is keyed by pi's per-session counter. Pre-migration
117
+ // turn rows have NULL sessionTurnIndex and their turnIndex IS the session
118
+ // counter, so fall back to turnIndex (COALESCE). Post-migration rows join
119
+ // on sessionTurnIndex so resume doesn't zero the raw message count.
120
+ const sessionKey = t.sessionTurnIndex ?? t.turnIndex;
121
+ const agg = rawAggBySessionTurn.get(`${t.sessionId}::${sessionKey}`);
117
122
  const rawCount = agg?.raw_count ?? 0;
118
123
  const uniqueCount = agg?.unique_count ?? 0;
119
124
  return {
@@ -0,0 +1,97 @@
1
+ /**
2
+ * context-health-cache-poison.ts — `cache_poison_events` table accessors.
3
+ *
4
+ * Extracted from context-health.ts (Sprint H split — context-health.ts crossed
5
+ * the soft limit when storeErrorScore was added). Records cache-poison advisory
6
+ * events when cache corruption/inconsistency is detected at a given cache layer.
7
+ *
8
+ * PREVENT-PI-004: local SQLite only, zero network.
9
+ * PREVENT-002: all SQL uses ? placeholders, no string-concatenated values.
10
+ * Pi-agnostic: no pi runtime types.
11
+ */
12
+ import { getStateDir } from "../../store.js";
13
+ import { openStore } from "./utils.js";
14
+
15
+ /** A single cache_poison_events row (as stored + returned). */
16
+ export interface CachePoisonEvent {
17
+ id: number;
18
+ ts: number;
19
+ turnIndex: number;
20
+ sessionId: string;
21
+ layer: number | null;
22
+ detail: string | null;
23
+ severity: string | null;
24
+ }
25
+
26
+ /**
27
+ * Record one cache-poison advisory event. All values are fully parameterized
28
+ * (PREVENT-002). Non-fatal: any write error is logged to stderr and silently
29
+ * swallowed so advisory logging never blocks the agent loop.
30
+ */
31
+ export function recordCachePoisonEvent(
32
+ stateDir: string = getStateDir(),
33
+ event: {
34
+ ts: number;
35
+ turnIndex: number;
36
+ sessionId: string;
37
+ layer: number;
38
+ detail: string;
39
+ severity: "warn" | "alert";
40
+ },
41
+ ): void {
42
+ try {
43
+ const db = openStore(stateDir);
44
+ db.prepare(
45
+ `INSERT INTO cache_poison_events
46
+ (ts, turn_index, session_id, layer, detail, severity)
47
+ VALUES (?, ?, ?, ?, ?, ?)`,
48
+ ).run(event.ts, event.turnIndex, event.sessionId, event.layer, event.detail, event.severity);
49
+ } catch (err) {
50
+ // Non-fatal: advisory logging must never block the agent loop.
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ process.stderr.write(`[context-health] recordCachePoisonEvent error: ${msg}\n`);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Read cache poison events since `sinceTs`, ordered descending by ts (most
58
+ * recent first), capped at 100 rows. Returns an empty array on any error
59
+ * (non-fatal). SQL is fully parameterized (PREVENT-002).
60
+ */
61
+ export function readCachePoisonEvents(
62
+ stateDir: string = getStateDir(),
63
+ sinceTs: number,
64
+ ): CachePoisonEvent[] {
65
+ try {
66
+ const db = openStore(stateDir);
67
+ const rows = db
68
+ .prepare(
69
+ `SELECT id, ts, turn_index, session_id, layer, detail, severity
70
+ FROM cache_poison_events
71
+ WHERE ts >= ?
72
+ ORDER BY ts DESC
73
+ LIMIT 100`,
74
+ )
75
+ .all(sinceTs) as Array<{
76
+ id: number;
77
+ ts: number;
78
+ turn_index: number;
79
+ session_id: string;
80
+ layer: number | null;
81
+ detail: string | null;
82
+ severity: string | null;
83
+ }>;
84
+
85
+ return rows.map((r) => ({
86
+ id: r.id,
87
+ ts: r.ts,
88
+ turnIndex: r.turn_index,
89
+ sessionId: r.session_id,
90
+ layer: r.layer,
91
+ detail: r.detail,
92
+ severity: r.severity,
93
+ }));
94
+ } catch {
95
+ return [];
96
+ }
97
+ }
@@ -41,6 +41,8 @@ export interface LatestContextHealth {
41
41
  driftScore: number;
42
42
  outputQuality: number;
43
43
  errorScore: number;
44
+ /** Sprint H (B2): internal store-write failure score (NULL→1 on pre-upgrade DBs). */
45
+ storeErrorScore: number;
44
46
  cacheHealth: number;
45
47
  cachePoison: number;
46
48
  ts: number;
@@ -48,15 +50,13 @@ export interface LatestContextHealth {
48
50
  }
49
51
 
50
52
  /** A single cache_poison_events row (as stored + returned). */
51
- export interface CachePoisonEvent {
52
- id: number;
53
- ts: number;
54
- turnIndex: number;
55
- sessionId: string;
56
- layer: number | null;
57
- detail: string | null;
58
- severity: string | null;
59
- }
53
+ // Sprint H split: cache_poison_events accessors live in context-health-cache-poison.ts.
54
+ // Re-exported here so existing importers (barrel `export *`) are unaffected.
55
+ export {
56
+ recordCachePoisonEvent,
57
+ readCachePoisonEvents,
58
+ type CachePoisonEvent,
59
+ } from "./context-health-cache-poison.js";
60
60
 
61
61
  // ─── context_health accessors ────────────────────────────────────────────────
62
62
 
@@ -205,7 +205,7 @@ export function readLatestContextHealth(
205
205
  const db = openStore(stateDir);
206
206
  const row = db
207
207
  .prepare(
208
- `SELECT composite, drift_score, output_quality, error_score,
208
+ `SELECT composite, drift_score, output_quality, error_score, store_error_score,
209
209
  cache_health, cache_poison, ts, model_id
210
210
  FROM context_health
211
211
  ORDER BY ts DESC
@@ -217,6 +217,7 @@ export function readLatestContextHealth(
217
217
  drift_score: number;
218
218
  output_quality: number;
219
219
  error_score: number;
220
+ store_error_score: number | null;
220
221
  cache_health: number;
221
222
  cache_poison: number;
222
223
  ts: number;
@@ -230,6 +231,7 @@ export function readLatestContextHealth(
230
231
  driftScore: row.drift_score,
231
232
  outputQuality: row.output_quality,
232
233
  errorScore: row.error_score,
234
+ storeErrorScore: row.store_error_score ?? 1,
233
235
  cacheHealth: row.cache_health,
234
236
  cachePoison: row.cache_poison,
235
237
  ts: row.ts,
@@ -266,77 +268,3 @@ export function readContextHealthTrend(
266
268
  return [];
267
269
  }
268
270
  }
269
-
270
- // ─── cache_poison_events accessors ───────────────────────────────────────────
271
-
272
- /**
273
- * Record one cache poison advisory event. Fully parameterized (PREVENT-002).
274
- * Non-fatal: write errors are logged to stderr and silently swallowed.
275
- */
276
- export function recordCachePoisonEvent(
277
- stateDir: string = getStateDir(),
278
- event: {
279
- ts: number;
280
- turnIndex: number;
281
- sessionId: string;
282
- layer: number;
283
- detail: string;
284
- severity: "warn" | "alert";
285
- },
286
- ): void {
287
- try {
288
- const db = openStore(stateDir);
289
- db.prepare(
290
- `INSERT INTO cache_poison_events
291
- (ts, turn_index, session_id, layer, detail, severity)
292
- VALUES (?, ?, ?, ?, ?, ?)`,
293
- ).run(event.ts, event.turnIndex, event.sessionId, event.layer, event.detail, event.severity);
294
- } catch (err) {
295
- // Non-fatal: advisory logging must never block the agent loop.
296
- const msg = err instanceof Error ? err.message : String(err);
297
- process.stderr.write(`[context-health] recordCachePoisonEvent error: ${msg}\n`);
298
- }
299
- }
300
-
301
- /**
302
- * Read cache poison events since `sinceTs`, ordered descending by ts (most
303
- * recent first), capped at 100 rows. Returns an empty array on any error
304
- * (non-fatal). SQL is fully parameterized (PREVENT-002).
305
- */
306
- export function readCachePoisonEvents(
307
- stateDir: string = getStateDir(),
308
- sinceTs: number,
309
- ): CachePoisonEvent[] {
310
- try {
311
- const db = openStore(stateDir);
312
- const rows = db
313
- .prepare(
314
- `SELECT id, ts, turn_index, session_id, layer, detail, severity
315
- FROM cache_poison_events
316
- WHERE ts >= ?
317
- ORDER BY ts DESC
318
- LIMIT 100`,
319
- )
320
- .all(sinceTs) as Array<{
321
- id: number;
322
- ts: number;
323
- turn_index: number;
324
- session_id: string;
325
- layer: number | null;
326
- detail: string | null;
327
- severity: string | null;
328
- }>;
329
-
330
- return rows.map((r) => ({
331
- id: r.id,
332
- ts: r.ts,
333
- turnIndex: r.turn_index,
334
- sessionId: r.session_id,
335
- layer: r.layer,
336
- detail: r.detail,
337
- severity: r.severity,
338
- }));
339
- } catch {
340
- return [];
341
- }
342
- }
@@ -76,6 +76,10 @@ export function getOrComputeEmbedding(db: DatabaseSync, content: string): number
76
76
  process.stderr.write(
77
77
  `[embedding-cache] getOrComputeEmbedding error: ${msg}\n`
78
78
  );
79
+ // Sprint H (Option A): this leaf src/ cache-persist path has no runtime
80
+ // handle, so the embedder cache-write failure is recorded by callers that
81
+ // own a runtime, not here.
82
+ // guardrails-allow INTERNAL-ERR: src/ leaf cache writer, no runtime handle — callers record embedder cache failures
79
83
  return [];
80
84
  }
81
85
  }
@@ -71,6 +71,10 @@ export function initSchema(db: DatabaseSync): void {
71
71
  // S43: conversation_id + last_turn_id on session_state (legacy DBs have NULL).
72
72
  ensureColumn(db, "session_state", "conversation_id", "TEXT");
73
73
  ensureColumn(db, "session_state", "last_turn_id", "INTEGER");
74
+ // Sprint H (B2): store_error_score column on context_health — the 6th health
75
+ // axis. Additive; DBs created before this version have NULL → the health
76
+ // handler degrades to 1.0 (no data → healthy), matching the empty-ring rule.
77
+ ensureColumn(db, "context_health", "store_error_score", "REAL");
74
78
  // S35: idempotent seed of the 9 achievement rows. ON CONFLICT(id) DO
75
79
  // NOTHING so a re-open never clobbers an already-unlocked row's
76
80
  // unlocked_at. No user input reaches this SQL (PREVENT-002 safe).
@@ -0,0 +1,171 @@
1
+ /**
2
+ * admin.ts — TurnAdmin method bodies for InMemoryTurnStore (extracted from
3
+ * memory-store.ts). Free functions operating on a MemoryTurnStoreCtx; the
4
+ * shell class delegates here. Mirrors sqlite-store/admin.ts.
5
+ *
6
+ * Arrays (recall, forks) are mutated IN PLACE (push/length=0), never
7
+ * reassigned, so the ctx references captured by the shell stay valid. This
8
+ * differs from the original class methods, which reassigned `this.recall` /
9
+ * `this.forks` — the rewrite is behavior-identical but in-place so the
10
+ * extracted ctx model works.
11
+ */
12
+ import type {
13
+ PruneReport,
14
+ RetentionPolicy,
15
+ SessionId,
16
+ StoreSnapshot,
17
+ } from "../types.js";
18
+ import {
19
+ allocId,
20
+ normalizeSessionId,
21
+ resetIdCounter,
22
+ type MemoryTurnStoreCtx,
23
+ } from "./ctx.js";
24
+
25
+ export function prune(
26
+ ctx: MemoryTurnStoreCtx,
27
+ policy: RetentionPolicy,
28
+ ): PruneReport {
29
+ const cutoff = Date.now() - policy.maxTurnAgeMs;
30
+ let turnsRemoved = 0;
31
+ let recallRemoved = 0;
32
+
33
+ for (const [convId, turnIds] of ctx.convIndex.entries()) {
34
+ const total = turnIds.length;
35
+ const canDelete = Math.max(0, total - policy.keepMinPerConversation);
36
+ if (canDelete === 0) continue;
37
+
38
+ // Find old turns to delete
39
+ const oldIds = turnIds
40
+ .filter((id) => {
41
+ const row = ctx.turns.get(id);
42
+ return row && row.entry.endedAt < cutoff;
43
+ })
44
+ .slice(0, canDelete);
45
+
46
+ for (const id of oldIds) {
47
+ // Remove recall for this turn, in place (preserve ctx.recall reference)
48
+ const before = ctx.recall.length;
49
+ let w = 0;
50
+ for (let i = 0; i < ctx.recall.length; i++) {
51
+ if (ctx.recall[i].turnId !== id) ctx.recall[w++] = ctx.recall[i];
52
+ }
53
+ ctx.recall.length = w;
54
+ recallRemoved += before - ctx.recall.length;
55
+
56
+ ctx.turns.delete(id);
57
+ turnsRemoved++;
58
+ }
59
+
60
+ // Update convIndex
61
+ ctx.convIndex.set(
62
+ convId,
63
+ turnIds.filter((id) => ctx.turns.has(id)),
64
+ );
65
+ }
66
+
67
+ const branchesPreserved = ctx.forks.length;
68
+
69
+ return {
70
+ turnsRemoved,
71
+ recallRemoved,
72
+ branchesPreserved,
73
+ freedBytes: 0, // in-memory — no file size to measure
74
+ };
75
+ }
76
+
77
+ export function vacuum(_ctx: MemoryTurnStoreCtx): void {
78
+ // No-op for in-memory store
79
+ }
80
+
81
+ export function checkpoint(ctx: MemoryTurnStoreCtx): StoreSnapshot {
82
+ // Emit turns in a stable order (by internal ID / insertion order)
83
+ const sorted = [...ctx.turns.values()].sort((a, b) => a.id - b.id);
84
+ const turns = sorted.map((r) => r.entry);
85
+
86
+ // Build mapping: internal ID → 1-based position
87
+ const idToPos = new Map<number, number>();
88
+ for (let i = 0; i < sorted.length; i++) {
89
+ idToPos.set(sorted[i].id, i + 1);
90
+ }
91
+
92
+ // Replace recall's turnId with the stable 1-based position
93
+ const recall = ctx.recall.map((r) => ({
94
+ turnId: String(idToPos.get(r.turnId) ?? r.turnId),
95
+ checkpointId: r.entry.checkpointId,
96
+ score: r.entry.score,
97
+ source: r.entry.source,
98
+ raptorLevel: r.entry.raptorLevel,
99
+ }));
100
+
101
+ return {
102
+ version: 1,
103
+ exportedAt: Date.now(),
104
+ turns,
105
+ recall,
106
+ forks: [...ctx.forks],
107
+ };
108
+ }
109
+
110
+ export function restore(ctx: MemoryTurnStoreCtx, from: StoreSnapshot): void {
111
+ clear(ctx);
112
+
113
+ // Map: 1-based position → new internal ID
114
+ const posToNewId = new Map<number, number>();
115
+
116
+ for (let i = 0; i < from.turns.length; i++) {
117
+ const t = from.turns[i];
118
+ const newId = allocId();
119
+ const sid = normalizeSessionId(t.sessionId);
120
+ const normalized = { ...t, sessionId: sid };
121
+ ctx.turns.set(newId, { id: newId, entry: normalized });
122
+
123
+ // Update indices
124
+ const convIds = ctx.convIndex.get(t.conversationId) ?? [];
125
+ convIds.push(newId);
126
+ ctx.convIndex.set(t.conversationId, convIds);
127
+ ctx.sessionConv.set(sid, t.conversationId);
128
+
129
+ posToNewId.set(i + 1, newId);
130
+ }
131
+
132
+ // Re-attach recall with position-based TurnId → new internal ID mapping
133
+ for (const r of from.recall) {
134
+ const targetInternalId = posToNewId.get(Number(r.turnId));
135
+ if (targetInternalId !== undefined) {
136
+ ctx.recall.push({
137
+ turnId: targetInternalId,
138
+ entry: r,
139
+ });
140
+ }
141
+ }
142
+
143
+ // In-place replace forks (preserve ctx.forks reference)
144
+ ctx.forks.length = 0;
145
+ ctx.forks.push(...from.forks);
146
+ }
147
+
148
+ export function clear(ctx: MemoryTurnStoreCtx): void {
149
+ ctx.turns.clear();
150
+ ctx.recall.length = 0;
151
+ ctx.forks.length = 0;
152
+ ctx.convIndex.clear();
153
+ ctx.sessionConv.clear();
154
+ resetIdCounter();
155
+ }
156
+
157
+ export function stampTurnsEpoch(
158
+ ctx: MemoryTurnStoreCtx,
159
+ sessionId: SessionId,
160
+ epochId: string,
161
+ ): number {
162
+ const sid = normalizeSessionId(sessionId);
163
+ let stamped = 0;
164
+ for (const row of ctx.turns.values()) {
165
+ if (row.epochId === undefined && row.entry.sessionId === sid) {
166
+ row.epochId = epochId;
167
+ stamped++;
168
+ }
169
+ }
170
+ return stamped;
171
+ }