pi-mega-compact 0.4.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 (69) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +375 -0
  3. package/extensions/DASHBOARD.md +160 -0
  4. package/extensions/dashboard-server.test.ts +124 -0
  5. package/extensions/dashboard-server.ts +459 -0
  6. package/extensions/error-patterns.ts +175 -0
  7. package/extensions/mega-compact.test.ts +351 -0
  8. package/extensions/mega-compact.ts +846 -0
  9. package/extensions/openclaw-mega-compact.ts +370 -0
  10. package/package.json +61 -0
  11. package/src/adapt.ts +120 -0
  12. package/src/boundary.test.ts +61 -0
  13. package/src/boundary.ts +94 -0
  14. package/src/canary.ts +126 -0
  15. package/src/compact.test.ts +99 -0
  16. package/src/compact.ts +262 -0
  17. package/src/config/dedup.ts +120 -0
  18. package/src/config.ts +15 -0
  19. package/src/dedup/dedup.test.ts +46 -0
  20. package/src/dedup/digest.ts +40 -0
  21. package/src/dedup/l1-lsh.ts +67 -0
  22. package/src/dedup/l1-minhash.ts +90 -0
  23. package/src/dedup/l1-verify.ts +55 -0
  24. package/src/dedup/l1.test.ts +57 -0
  25. package/src/dedup/mmr.ts +54 -0
  26. package/src/dedup/normalize.ts +41 -0
  27. package/src/dedup/raptor/guardrails.ts +112 -0
  28. package/src/dedup/raptor/index.ts +118 -0
  29. package/src/dedup/raptor/kmeans.ts +156 -0
  30. package/src/dedup/raptor/raptor.test.ts +238 -0
  31. package/src/dedup/raptor/retrieval.ts +102 -0
  32. package/src/dedup/raptor/summarizer.ts +91 -0
  33. package/src/dedup/raptor/tree.ts +254 -0
  34. package/src/dedup/sprint12.test.ts +242 -0
  35. package/src/dedup/topk.ts +61 -0
  36. package/src/dedup-engine.test.ts +609 -0
  37. package/src/e2e.test.ts +843 -0
  38. package/src/embedder.ts +111 -0
  39. package/src/engine.test.ts +123 -0
  40. package/src/engine.ts +192 -0
  41. package/src/extractive.test.ts +156 -0
  42. package/src/extractive.ts +265 -0
  43. package/src/httpEmbedder.ts +154 -0
  44. package/src/log.test.ts +47 -0
  45. package/src/log.ts +60 -0
  46. package/src/monitoring.ts +171 -0
  47. package/src/ratio.bench.test.ts +1316 -0
  48. package/src/recall.integration.test.ts +96 -0
  49. package/src/recall.test.ts +59 -0
  50. package/src/recall.ts +100 -0
  51. package/src/sprint14.test.ts +245 -0
  52. package/src/store/backfill.ts +263 -0
  53. package/src/store/bloom.ts +122 -0
  54. package/src/store/compression.test.ts +83 -0
  55. package/src/store/compression.ts +203 -0
  56. package/src/store/integrity.ts +65 -0
  57. package/src/store/migrate.test.ts +158 -0
  58. package/src/store/migrate.ts +108 -0
  59. package/src/store/sprint10.test.ts +182 -0
  60. package/src/store/sqlite.ts +519 -0
  61. package/src/store.test.ts +169 -0
  62. package/src/store.ts +192 -0
  63. package/src/supersede.test.ts +42 -0
  64. package/src/supersede.ts +67 -0
  65. package/src/tokens.ts +35 -0
  66. package/src/types.test.ts +10 -0
  67. package/src/types.ts +49 -0
  68. package/src/vectorStore.test.ts +480 -0
  69. package/src/vectorStore.ts +544 -0
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026 TheArchitectit
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,375 @@
1
+ # pi-mega-compact
2
+
3
+ A **layered, local, vector-backed context compressor** for the
4
+ [pi coding agent](https://github.com/earendil-works/pi). It compacts long
5
+ sessions into a **local SQLite store** and offers **deduped inline recall** — all
6
+ running **locally inside the extension**, with **no remote MCP server** and
7
+ **zero network calls at runtime** (PREVENT-PI-004).
8
+
9
+ > **v0.2.0** — storage backend is now **`better-sqlite3`** (a single,
10
+ > in-process, FS-backed SQLite database) replacing the old per-session gzipped
11
+ > JSON checkpoint files. The legacy `.checkpoints.json.gz` snapshots are
12
+ > retained as disaster-recovery fallbacks and auto-imported on first run.
13
+
14
+ ---
15
+
16
+ ## What this is (the 30-second version)
17
+
18
+ pi's context window is finite. When a session gets long, pi-mega-compact:
19
+
20
+ 1. **Watches** context usage and, past a threshold, **compacts** the older part of
21
+ the conversation into a short structured summary + key facts ("a checkpoint").
22
+ 2. **Stores** each checkpoint in a **local vector database** (SQLite) with an
23
+ embedding, so similar regions can be found later — and so **duplicate
24
+ work is never stored twice**.
25
+ 3. **Recalls** the right checkpoints automatically when you resume a session or
26
+ invoke a recall command, re-injecting only what's relevant (deduped against
27
+ what's already in view).
28
+
29
+ Everything lives on **your disk**. No telemetry, no API, no MCP server, no cloud.
30
+ The only optional network surface is a **user-triggered localhost dashboard** you
31
+ open yourself.
32
+
33
+ ### Why "mega"?
34
+
35
+ The compaction pipeline is a **Trident** — three deterministic stages that run
36
+ over your conversation before anything is persisted. The checkpoint it produces
37
+ is small (a summary + key decisions + next steps + files touched), so the same
38
+ session that would otherwise overflow its window keeps going on a fraction of the
39
+ tokens.
40
+
41
+ ---
42
+
43
+ ## How it works
44
+
45
+ ```
46
+ Layer 5 Recall / Inline ONE local vector store → 3 entry points, 1 dedup engine
47
+ Layer 4 Persist / Checkpoint compactSession() → embed + store in SQLite (chkpt_xxx)
48
+ Layer 3 Cluster (vectorize) local vector index → semantic dedup + recall
49
+ Layer 2 Collapse (summarize) summarizeMessages() heuristic + agent summary on /mega-compact
50
+ Layer 1 Supersede (prune) drop obsolete file-reads / superseded turns (zero cost)
51
+ ─────────────────────────────────────────────────────────────────────────
52
+ Trigger context/turn_end → % gate → auto_compact_check → fire
53
+ Marker insert compact-marker; dedupe so repeated triggers cost ~0 tokens
54
+ Cancel session_before_compact → { cancel:true } once persisted (no double-compact)
55
+ ```
56
+
57
+ **One store, three ways to read it back — one dedup engine:**
58
+
59
+ | Entry point | Trigger | Behavior |
60
+ |---|---|---|
61
+ | **Auto-inline** (Layer 5) | `session_start` / `session_tree` | Resume → `recallAndInline(source:"resume")` prepends the most relevant checkpoints, deduped against current context. |
62
+ | **On-demand recall** | `/mega-recall [query]` | Semantic search the store, dedupe, and inline the top-K. |
63
+ | **Dedup sentinel** | every compact | A lightweight `mega-compact-marker` entry lets auto-inline and recall skip re-injecting / re-vectorizing already-present regions. |
64
+
65
+ **The dedup cascade** (shared across all entry points) collapses redundant work
66
+ so storage and recall stay lean:
67
+
68
+ - **L0 — exact:** SHA-256 content hash + region hash + summary hash. Identical (or
69
+ whitespace/casing-normalized) regions collapse to one row.
70
+ - **L1 — near-dup:** MinHash signatures + LSH bucketing + trigram verification
71
+ catch one-word rewordings that L0 misses.
72
+ - **L2 — semantic:** cosine over the embedding collapses paraphrases; MMR
73
+ diversifies retrieval so a cluster of near-hits yields distinct results.
74
+ - **RAPTOR — pre-compression tree** (shadow mode by default): a hierarchical
75
+ summary tree over checkpoints, built + logged but not served until promoted.
76
+
77
+ Every tier is gated by its own feature flag (see [Configuration](#configuration)).
78
+ A tier can be put in `MARK_ONLY` (record the decision, don't collapse) as a safe
79
+ partial-rollout or auto-degrade state.
80
+
81
+ ### Embedding (two modes, both local)
82
+
83
+ The default embedder is **`TrigramEmbedder`** — a deterministic hashed trigram
84
+ bag (512-dim, L2-normalized), **zero dependencies, instant, fully offline**. It
85
+ is heuristic-strength, which is the right bar for "inline the right checkpoint,"
86
+ not production RAG.
87
+
88
+ **Optional: bring-your-own (BYO) localhost embedder.** Set
89
+ `MEGACOMPACT_EMBEDDING_URL` to a **localhost/127.0.0.1** endpoint you run
90
+ yourself (local ONNX/TEI/llamafile/Ollama-embeddings). The extension talks to it
91
+ from `src/httpEmbedder.ts` (loopback-only — a remote host is rejected at config
92
+ time, preserving PREVENT-PI-004). Compacted content never leaves the machine and
93
+ no model ships with the extension. See `src/httpEmbedder.ts` for the
94
+ OpenAI-style contract and the `MEGACOMPACT_EMBEDDING_KEY` / `MEGACOMPACT_EMBEDDING_HEADERS`
95
+ / `MEGACOMPACT_EMBEDDING_DIM` options.
96
+
97
+ > **Note on MiniLM:** a `MEGACOMPACT_MINILM` flag exists in `src/config/dedup.ts`
98
+ > but defaults to **off**, and the MiniLM (all-MiniLM-L6-v2) ONNX embedder was
99
+ > prototyped then deliberately **not shipped** (async-vs-sync conflict with the
100
+ > synchronous VectorStore, second native dep, no free semantic win without a
101
+ > network call). The `Embedder` interface remains the seam — inject a local
102
+ > embedder (e.g. via your own `MEGACOMPACT_EMBEDDING_URL`) instead.
103
+
104
+ ---
105
+
106
+ ## Installation
107
+
108
+ > **Full step-by-step guide** (pi + OpenClaw + every command + troubleshooting):
109
+ > [`docs/INSTALL_AND_USAGE.md`](docs/INSTALL_AND_USAGE.md).
110
+
111
+ ### Requirements
112
+
113
+ - **Node >= 18**
114
+ - `npm install` builds the **`better-sqlite3`** native module (one-time, local
115
+ compile). No network call and no API key are needed at runtime.
116
+ - A pi coding agent install that loads extensions from `~/.pi/agent/extensions/`.
117
+
118
+ ### From a git checkout
119
+
120
+ ```bash
121
+ git clone https://github.com/TheArchitectit/pi-mega-compact.git \
122
+ ~/.pi/agent/extensions/pi-mega-compact
123
+ cd ~/.pi/agent/extensions/pi-mega-compact
124
+ npm install
125
+ npm run build
126
+ ```
127
+
128
+ ### Register with pi
129
+
130
+ Either copy/link the extension into pi's extensions dir (the clone above already
131
+ does), **or** add it to your pi config's `pi.extensions` list:
132
+
133
+ ```jsonc
134
+ {
135
+ "pi": {
136
+ "extensions": ["~/.pi/agent/extensions/pi-mega-compact/extensions/mega-compact.ts"]
137
+ }
138
+ }
139
+ ```
140
+
141
+ Or use the bundled helper (needs `jq`):
142
+
143
+ ```bash
144
+ ./install.sh # copy into ~/.pi/agent/extensions/pi-mega-compact
145
+ ./install.sh -s # symlink instead of copy (dev mode)
146
+ ```
147
+
148
+ ### Verify
149
+
150
+ ```bash
151
+ cd ~/.pi/agent/extensions/pi-mega-compact
152
+ npm test # all unit/integration tests pass (192 as of v0.2.0)
153
+ npm run lint # tsc --noEmit + guardrails scan clean
154
+ ```
155
+
156
+ ### Uninstall
157
+
158
+ ```bash
159
+ rm -rf ~/.pi/agent/extensions/pi-mega-compact
160
+ ```
161
+
162
+ Then remove the path from pi's `pi.extensions` array.
163
+
164
+ ---
165
+
166
+ ## How to use it
167
+
168
+ Once installed and registered, pi-mega-compact runs **automatically** — you don't
169
+ have to drive it. Past the context threshold it compacts in the background and
170
+ drops a checkpoint; on resume it re-inlines the relevant ones silently.
171
+
172
+ The commands (slash commands inside pi):
173
+
174
+ | Command | Description |
175
+ |---|---|
176
+ | `/mega-compact [summary...]` | Manually compact the current session. A summary arg is used verbatim; otherwise the COLLAPSE heuristics build one. Persists a `chkpt_xxx`. |
177
+ | `/mega-compact off` | Disable auto-compaction for this session. |
178
+ | `/mega-status` | Show config + current context usage + store stats (checkpoint count, dedup rate, tokens saved). |
179
+ | `/mega-recall [query]` | Semantic-search the local store, dedupe against the current window, and inline the top-K relevant checkpoints. No query → uses your latest message. |
180
+ | `/mega-tier [name]` | Set the compaction tier (`low` / `medium` / `high` / `ultra` / `mega`). Shows current tier with no arg. |
181
+ | `/mega-dashboard` | Start the **localhost-only** live dashboard and open it in a browser (token gauge, store stats, live event stream). |
182
+ | `/mega-dashboard-status` | Report dashboard server status. |
183
+ | `/mega-dashboard-stop` | Stop the dashboard server. |
184
+
185
+ ### Live stats widget
186
+
187
+ Above the pi editor the extension shows a compact widget:
188
+
189
+ ```
190
+ ⚡ medium │ 142k/200k tokens (71%) │ 3 chkpts │ 🤖 2 agents │ turn 5
191
+ ◐ armed │ dedup: 92% │ saved: 45k tok
192
+ ```
193
+
194
+ - **Tier** — active compaction tier (low/medium/high/ultra/mega)
195
+ - **Token usage** — current / max context window and %
196
+ - **Checkpoints** — persisted checkpoints for the session
197
+ - **Trigger state** — ○ idle, ◐ armed, ● ready
198
+ - **Dedup hit rate** — % of checkpoints collapsed as duplicates
199
+ - **Active agents / turn** — sub-agent count and conversation turn (when > 0)
200
+
201
+ ---
202
+
203
+ ## Configuration (env-backed)
204
+
205
+ All defaults are in `src/config/dedup.ts` (single source of truth). Set env vars
206
+ before starting pi.
207
+
208
+ | Variable | Default | Meaning |
209
+ |---|---|---|
210
+ | `MEGACOMPACT_FAST_GATE_PCT` | `70` | Context-usage % that arms the auto-trigger. |
211
+ | `MEGACOMPACT_TIER` | `low` | Named trigger preset — sets the token threshold. `low`(50k) `medium`(100k) `high`(200k) `ultra`(1M) `mega`(10M). |
212
+ | `MEGACOMPACT_THRESHOLD_TOKENS` | _(tier default)_ | Explicit token budget confirming compaction. Overrides `MEGACOMPACT_TIER` when set. |
213
+ | `MEGACOMPACT_ANCHOR_USER_MESSAGES` | `3` | Never drop the most recent N user messages (anchor floor). |
214
+ | `MEGACOMPACT_PRESERVE_RECENT` | `4` | Preserve the most recent N messages verbatim. |
215
+ | `MEGACOMPACT_AUTO` | `true` | Enable the auto-trigger. |
216
+ | `MEGACOMPACT_AUTO_INLINE` | `true` | Auto-inline on resume / branch. |
217
+ | `MEGACOMPACT_AUTO_INLINE_K` | `3` | Top-K checkpoints to auto-inline. |
218
+ | `MEGACOMPACT_DEDUP_SIM` | `0.90` | Cosine threshold to collapse near-dupes. |
219
+ | `MEGACOMPACT_STATE_DIR` | _(none — per-repo default)_ | Override the store location. By default state is per-repo at `<repo>/.pi/mega-compact/`; this env var forces a single explicit dir (used as the fallback for non-git cwds). |
220
+
221
+ #### Dedup pipeline flags (v0.2.0 — single source: `src/config/dedup.ts`)
222
+
223
+ These gate the L0/L1/L2/RAPTOR dedup tiers. Defaults reproduce the all-active
224
+ Sprint 13 behavior. `MARK_ONLY_*` tiers run + record their decision but never
225
+ collapse (safe partial-rollout / auto-degrade state).
226
+
227
+ | Variable | Default | Meaning |
228
+ |---|---|---|
229
+ | `MEGACOMPACT_L0_ENABLED` | `true` | L0 exact content-hash dedup. |
230
+ | `MEGACOMPACT_L1_ENABLED` | `true` | L1 MinHash/LSH near-dup verification. |
231
+ | `MEGACOMPACT_L2_ENABLED` | `true` | L2 semantic cosine dedup + MMR retrieval diversity. |
232
+ | `MEGACOMPACT_RAPTOR_ENABLED` | `false` | RAPTOR pre-compression tree (**shadow mode by default** — builds + logs, does not serve retrieval). |
233
+ | `MEGACOMPACT_MARK_ONLY_L0` | `false` | L0: record, don't collapse. |
234
+ | `MEGACOMPACT_MARK_ONLY_L1` | `false` | L1: record, don't collapse. |
235
+ | `MEGACOMPACT_MARK_ONLY_L2` | `false` | L2: record, don't collapse. |
236
+ | `MEGACOMPACT_MINILM` | `false` | MiniLM embedder flag — **off; not shipped** (see Embedding). BYO via `MEGACOMPACT_EMBEDDING_URL`. |
237
+ | `MEGACOMPACT_EMBEDDING_URL` | _(unset)_ | BYO localhost embedder endpoint (loopback-only; enables `HttpEmbedder`). |
238
+ | `MEGACOMPACT_L2_THRESHOLD` | `0.85` | L2 cosine firing point (trigram-honest; set higher for semantic backends). |
239
+ | `MEGACOMPACT_L1_JACCARD` | `0.8` | L1 MinHash/LSH near-dup Jaccard threshold. |
240
+ | `MEGACOMPACT_MMR_LAMBDA` | `0.5` | MMR retrieval-diversity weight (λ·relevance − (1−λ)·maxSim). |
241
+ | `MEGACOMPACT_SEMDEDUP_COSINE` | `0.95` | Offline SemDeDup pair threshold → `dedup_status='removed'`. |
242
+ | `MEGACOMPACT_FP_RATE_L0` | `0.01` | L0 false-positive alert threshold (auto → MARK_ONLY). |
243
+ | `MEGACOMPACT_FP_RATE_L1L2` | `0.05` | L1/L2 false-positive alert threshold (auto → MARK_ONLY). |
244
+ | `MEGACOMPACT_ALERT_WINDOW_MS` | `600000` | FP-rate rolling window (10 min). |
245
+ | `MEGACOMPACT_P95_BUDGET_MS` | `100` | Per-tier p95 latency budget; canary auto-disables on breach. |
246
+
247
+ See `docs/DEDUP_RUNBOOK.md` for incident response (SEV tiers, first-15-min
248
+ checklist, MARK_ONLY degrade) and `docs/RETENTION_POLICY.md` for TTL / soft-delete
249
+ / VACUUM.
250
+
251
+ ---
252
+
253
+ ## Reporting for testers (what to capture)
254
+
255
+ If you're testing pi-mega-compact, the maintainers need **local evidence**, not
256
+ guesswork. The store and logs are plain local files — never a network port.
257
+
258
+ 1. **Install + run it** (see [Installation](#install)).
259
+ 2. **Work a real session** until context fills past the gate (80%+) — you should
260
+ see the status chip flip to `● ready`, then `◐ armed`, a checkpoint persist,
261
+ and context visibly drop.
262
+ 3. **Resume and confirm recall:** restart pi, ask about something you worked on
263
+ earlier; relevant checkpoints should auto-inline (or use
264
+ `/mega-recall <topic>`).
265
+ 4. **Watch the live signal** while testing:
266
+ ```bash
267
+ tail -f ~/.pi/agent/extensions/pi-mega-compact/events.log | jq .
268
+ ```
269
+ Each line is `{ts, tier, result, latencyMs, falsePositive?}`.
270
+ 5. **Run the dashboard** (`/mega-dashboard`) and check the token gauge, store
271
+ stats, and live event stream.
272
+ 6. **Try `/mega-tier`** to see and switch compaction tiers.
273
+
274
+ ### What to include in a bug report
275
+
276
+ - Output of `/mega-status` (config + store stats).
277
+ - Output of `/mega-dashboard-status`.
278
+ - Your pi version + OS + Node version (`node -v`).
279
+ - A slice of `events.log` around the problem (the `result`/`tier` lines).
280
+ - `dashboard.json` from the state dir (aggregate metrics: hit rate, FP rate,
281
+ per-tier p95, storage bytes).
282
+ - If you suspect data loss or duplication: the checkpoint count and the
283
+ `sqlite.db` size, plus the output of the DR drill (below).
284
+
285
+ **Disaster-recovery drill** (validates the store against its JSON snapshots and
286
+ rebuilds if corrupt — see `docs/RETENTION_POLICY.md` §5):
287
+
288
+ ```bash
289
+ scripts/dedup-restore-drill.sh ~/.pi/agent/extensions/pi-mega-compact
290
+ ```
291
+
292
+ **Benchmark** (dedup hit rate, compression ratio, per-tier p95, storage at
293
+ 100 / 1K / 10K checkpoints):
294
+
295
+ ```bash
296
+ npm run build
297
+ node scripts/dedup-benchmark.mjs 100 1000 10000
298
+ ```
299
+
300
+ Open issues at: https://github.com/TheArchitectit/pi-mega-compact/issues
301
+
302
+ ---
303
+
304
+ ## Architecture & layout
305
+
306
+ ```
307
+ extensions/mega-compact.ts pi extension entry; wires src/ into pi lifecycle
308
+ src/adapt.ts the single pi↔engine message adapter (index-aligned)
309
+ src/engine.ts Layer 4: compactSession() Trident pipeline + recall()
310
+ src/vectorStore.ts Layer 3: local vector DB (add/search/dedupe + near-dup)
311
+ src/embedder.ts default TrigramEmbedder (deterministic, 512-dim)
312
+ src/httpEmbedder.ts BYO localhost embedder seam (MEGACOMPACT_EMBEDDING_URL)
313
+ src/store/sqlite.ts the "one store" — better-sqlite3 context_chunks + session_state (FTS5 trigram)
314
+ src/store/migrate.ts JSON → SQLite migration (legacy .checkpoints.json.gz retained)
315
+ src/store/backfill.ts resumable backfill orchestrator (L0/L1/L2/RAPTOR)
316
+ src/monitoring.ts local events.log + dashboard.json metrics + FP alerts
317
+ src/canary.ts sequential L0→L1→L2→RAPTOR rollout, auto-disable on p95 breach
318
+ src/config/dedup.ts single source of truth for ALL dedup tier flags + thresholds
319
+ src/store.ts state dir + JSON DR helpers + compression re-exports
320
+ src/compact.ts Layer 2: summarize / merge / autoCompactCheck
321
+ src/supersede.ts Layer 1: obsolete file-read pruning
322
+ src/boundary.ts drop-boundary guards (anchor floor + tool-pair)
323
+ src/tokens.ts deterministic token estimator
324
+ src/types.ts engine-internal types
325
+ ```
326
+
327
+ The `src/` directory is **pi-agnostic** and fully unit-tested (`node --test`).
328
+ The extension entry adapts between the engine and pi's runtime types.
329
+
330
+ ---
331
+
332
+ ## Development
333
+
334
+ ```bash
335
+ npm run build # tsc
336
+ npm test # build + node --test on dist/**/*.test.js
337
+ npm run lint # tsc --noEmit + guardrails-scan
338
+ npm run guardrails # regression_check + guardrails-scan
339
+ ```
340
+
341
+ The agent-guardrails suite (Four Laws, scope, secrets, regression) gates every
342
+ sprint.
343
+
344
+ ---
345
+
346
+ ## Status
347
+
348
+ - ✅ Sprint 1 — core engine (Layers 1–2, pure functions)
349
+ - ✅ Sprint 2 — local vector store (Layer 3)
350
+ - ✅ Sprint 3 — pi extension wiring (Layer 4 persist + trigger)
351
+ - ✅ Sprint 4 — unified recall layer (Layer 5: auto-inline + on-demand + sentinel)
352
+ - ✅ Sprint 5 — commands / UX / config polish (status chip, store stats, debug log)
353
+ - ✅ Sprint 6 — hardening, docs, release (`install.sh`, CHANGELOG, `v0.1.0`)
354
+ - ✅ Sprint 8 — SQLite storage backbone (`better-sqlite3`, one store) + compression v2
355
+ - ✅ Sprints 9–11 — L0 exact-hash + L1 MinHash/LSH near-dup dedup tiers
356
+ - ✅ Sprint 12 — L2 semantic cosine + MMR; BYO localhost embedder (`HttpEmbedder`)
357
+ - ✅ Sprint 13 — RAPTOR hierarchical pre-compression (shadow mode)
358
+ - ✅ Sprint 14 — full pipeline: flags, backfill, monitoring, canary rollout
359
+ - ✅ Sprint 15 — benchmarks, DR drill, docs, `v0.2.0`
360
+
361
+ See `SPRINT_PLAN.md` for the full breakdown and `PLAN.md` for architecture,
362
+ `RESEARCH.md` for the pi-API constraints that shaped it, `CHANGELOG.md` for
363
+ release notes.
364
+
365
+ ---
366
+
367
+ ## Acknowledgements
368
+
369
+ Algorithmic reference (reimplemented in TypeScript, not vendored): memory-mcp
370
+ (`MemoryCompactor` / `compact.py`), claw-code (`trident.rs` / `compact.rs`), and
371
+ neuralwatt-mcr (pi-extension mechanics). Attribution as design sources only.
372
+
373
+ ## License
374
+
375
+ [MIT](./LICENSE)
@@ -0,0 +1,160 @@
1
+ # Mega-Compact Dashboard
2
+
3
+ A lightweight local web dashboard for monitoring mega-compact's live state — compactions, context usage, checkpoints, and recall hits.
4
+
5
+ Zero npm dependencies. Uses only Node built-in modules (`http`, `fs`, `path`).
6
+
7
+ ## Quick Start
8
+
9
+ From a pi session with mega-compact loaded:
10
+
11
+ ```
12
+ /dashboard
13
+ ```
14
+
15
+ This will:
16
+ 1. Start a local HTTP server on a random port (3000–3999)
17
+ 2. Show a confirm dialog asking if you'd like to open it in your browser
18
+ 3. Write the dashboard URL to the terminal
19
+
20
+ ## Commands
21
+
22
+ | Command | Description |
23
+ |---|---|
24
+ | `/dashboard` | Start the dashboard server (or reuse if already running) |
25
+ | `/dashboard-stop` | Stop the running server |
26
+ | `/dashboard-status` | Show the current server status and URL |
27
+
28
+ ## Architecture
29
+
30
+ ```
31
+ ┌─────────────────────────────────────────────────────────────┐
32
+ │ pi session (mega-compact extension) │
33
+ │ ┌────────────────────────────────────────────────────────┐ │
34
+ │ │ DashboardEmitter │ │
35
+ │ │ • writes dashboard.json (full state snapshot) │ │
36
+ │ │ • appends to events.log (JSONL tail) │ │
37
+ │ └────────────────────────────────────────────────────────┘ │
38
+ │ │ writes after each compaction │
39
+ └───────────┼─────────────────────────────────────────────────┘
40
+
41
+ ┌─────────────────────────────────────────────────────────────┐
42
+ │ dashboard-server (detached child process) │
43
+ │ • GET / → single-page HTML dashboard │
44
+ │ • GET /api/snapshot → JSON snapshot (reads dashboard.json) │
45
+ │ • GET /api/events → SSE stream (watches events.log) │
46
+ └─────────────────────────────────────────────────────────────┘
47
+ ```
48
+
49
+ ### Data Files
50
+
51
+ All files are written to the extension's state directory
52
+ (`~/.pi/agent/extensions/pi-mega-compact/`):
53
+
54
+ | File | Format | Description |
55
+ |---|---|---|
56
+ | `dashboard.json` | JSON | Full state snapshot, rewritten after each compaction |
57
+ | `events.log` | JSONL | Append-only event log (compact_start, compact_end, checkpoint_persisted, recall_inject) |
58
+ | `port.pid` | JSON | Server port and PID for process management |
59
+ | `runner.mjs` | ESM script | Auto-generated launcher for the dashboard server |
60
+
61
+ ### Event Types
62
+
63
+ ```json
64
+ {"ts":"...","type":"compact_start","trigger":"auto|command","tier":"medium","sessionId":"..."}
65
+ {"ts":"...","type":"compact_end","trigger":"auto|command","durationMs":1234,"mode":"mega","fromTokens":100000,"toTokens":5000}
66
+ {"ts":"...","type":"checkpoint_persisted","checkpointId":"chk_1","totalCheckpoints":3}
67
+ {"ts":"...","type":"recall_inject","count":2,"totalTokens":1200,"sources":["chk_1","chk_2"]}
68
+ ```
69
+
70
+ ### Server Process
71
+
72
+ The server runs as a detached child process, independent of the pi session. It:
73
+ - Auto-discovers the state directory from the `port.pid` file
74
+ - Cleans up stale `port.pid` files from dead processes
75
+ - Supports `SIGHUP` for graceful shutdown
76
+ - Serves static HTML with no external dependencies
77
+
78
+ ## Browser UI
79
+
80
+ The dashboard is a single-page application that shows:
81
+
82
+ - **Status bar**: current tier, trigger state, context utilization
83
+ - **Compaction graph**: timeline of compaction events with token counts
84
+ - **Checkpoint list**: recent checkpoints with timestamps
85
+ - **Recall activity**: dedup hits and injection stats
86
+ - **Context gauge**: live token usage vs. threshold
87
+
88
+ The UI uses `EventSource` (SSE) for real-time updates — no polling required.
89
+
90
+ ## Development
91
+
92
+ ### Running tests
93
+
94
+ ```bash
95
+ npm run build && node --test dist/extensions/dashboard-server.test.js
96
+ ```
97
+
98
+ ### Manual testing
99
+
100
+ ```bash
101
+ # Start the server directly (for debugging)
102
+ node dist/extensions/dashboard-server.js
103
+
104
+ # Write a test snapshot
105
+ echo '{"updatedAt":"2025-01-01T00:00:00Z","tier":"medium","version":1}' > ~/.pi/agent/extensions/pi-mega-compact/dashboard.json
106
+
107
+ # Watch events in another terminal
108
+ tail -f ~/.pi/agent/extensions/pi-mega-compact/events.log | jq .
109
+ ```
110
+
111
+ ## Live Stats Widget
112
+
113
+ The extension displays a compact stats widget above the pi editor at all times:
114
+
115
+ ```
116
+ ⚡ medium │ 142k/200k tokens (71%) │ 3 chkpts │ 🤖 2 agents │ turn 5
117
+ ◐ armed │ dedup: 92% │ saved: 45k tok
118
+ ```
119
+
120
+ The widget shows:
121
+ - **Tier**: active compaction tier (low/medium/high/ultra/mega)
122
+ - **Token usage**: current / max context window and percentage
123
+ - **Checkpoints**: number of persisted checkpoints this session
124
+ - **Trigger state**: ○ idle (< gate %), ◐ armed (≥ gate %, below threshold), ● ready (≥ threshold)
125
+ - **Dedup hit rate**: percentage of compacted regions that were already stored
126
+ - **Tokens saved**: cumulative token savings from compaction
127
+ - **Active agents**: number of running sub-agents (shown when > 0)
128
+ - **Turn index**: current conversation turn number (shown when > 0)
129
+
130
+ The widget updates on every context event, session start, branch navigation, agent start/end, turn start/end, and compaction. It clears automatically on session shutdown.
131
+
132
+ ### Agent Tracking
133
+
134
+ The extension tracks active sub-agents in real-time:
135
+
136
+ | Event | Behavior |
137
+ |-------|----------|
138
+ | `agent_start` | Increments active agent count, updates widget |
139
+ | `agent_end` | Decrements active agent count, updates widget |
140
+ | `turn_start` | Tracks current turn index, updates widget |
141
+ | `turn_end` | Logs turn completion, updates widget |
142
+ | `session_start` | Resets agent count and turn counter |
143
+ | `session_shutdown` | Resets agent count and turn counter |
144
+
145
+ ## Security
146
+
147
+ - The server only listens on `127.0.0.1` (localhost)
148
+ - No authentication (local-only, not exposed to network)
149
+ - No write endpoints — all APIs are read-only
150
+ - No npm dependencies — only Node.js built-ins
151
+
152
+ ## Troubleshooting
153
+
154
+ **Port already in use**: The server picks a random port in 3000–3999. If all are taken, it will retry. Check `/dashboard-status` for the current port.
155
+
156
+ **Server won't start**: Check for stale `port.pid` files. Run `/dashboard-stop` to clean up, then try `/dashboard` again.
157
+
158
+ **No data showing**: The server reads `dashboard.json` and `events.log` from the state directory. These are created after the first compaction. If you haven't compacted yet, run `/megacompact` to trigger one.
159
+
160
+ **Browser doesn't open**: The server URL is always shown in the terminal. Copy it manually or use `xdg-open <url>` (Linux), `open <url>` (macOS), or `start <url>` (Windows).
@@ -0,0 +1,124 @@
1
+ /**
2
+ * dashboard-server.test.ts — unit tests for the standalone HTTP dashboard server.
3
+ *
4
+ * Tests the core logic (snapshot building, API responses) without requiring
5
+ * a live pi session or model.
6
+ */
7
+
8
+ import { test, describe } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // helpers
16
+ // ---------------------------------------------------------------------------
17
+
18
+ function tmpDir(): string {
19
+ return mkdtempSync(join(tmpdir(), "dashboard-test-"));
20
+ }
21
+
22
+ function writeSnapshot(dir: string, data: Record<string, unknown>): void {
23
+ writeFileSync(join(dir, "snapshot.json"), JSON.stringify(data, null, 2));
24
+ }
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Tests
28
+ // ---------------------------------------------------------------------------
29
+
30
+ describe("snapshot.json reading", () => {
31
+ test("returns valid snapshot when file exists", () => {
32
+ const dir = tmpDir();
33
+ const snapshot = {
34
+ updatedAt: new Date().toISOString(),
35
+ tier: "medium",
36
+ version: 1,
37
+ config: { activeTier: "medium" },
38
+ session: { id: "test-123", state: "running", persistedThisSession: true },
39
+ context: { tokens: 50000, percent: 50, contextWindow: 100000 },
40
+ trigger: { armed: true, ready: false, currentTokens: 50000, thresholdTokens: 100000 },
41
+ store: { checkpointCount: 3, totalTokenEstimate: 15000 },
42
+ };
43
+ writeSnapshot(dir, snapshot);
44
+
45
+ const content = readFileSync(join(dir, "snapshot.json"), "utf-8");
46
+ const parsed = JSON.parse(content);
47
+ assert.equal(parsed.tier, "medium");
48
+ assert.equal(parsed.session.id, "test-123");
49
+ assert.equal(parsed.context.percent, 50);
50
+ assert.equal(parsed.store.checkpointCount, 3);
51
+ rmSync(dir, { recursive: true });
52
+ });
53
+
54
+ test("handles missing snapshot.json gracefully", () => {
55
+ const dir = tmpDir();
56
+ const snapshotPath = join(dir, "snapshot.json");
57
+ assert.equal(existsSync(snapshotPath), false);
58
+ rmSync(dir, { recursive: true });
59
+ });
60
+ });
61
+
62
+ describe("dashboard.json (compact snapshot)", () => {
63
+ test("round-trips correctly", () => {
64
+ const dir = tmpDir();
65
+ const data = {
66
+ updatedAt: "2025-01-01T00:00:00.000Z",
67
+ tier: "mega",
68
+ version: 1,
69
+ config: { activeTier: "mega", thresholdTokens: 10000000 },
70
+ session: { id: "abc", state: "running", persistedThisSession: false },
71
+ context: { tokens: null, percent: null, contextWindow: 200000 },
72
+ trigger: { armed: false, ready: false, currentTokens: null, thresholdTokens: 10000000 },
73
+ store: { checkpointCount: 0, totalTokenEstimate: 0 },
74
+ };
75
+ writeFileSync(join(dir, "dashboard.json"), JSON.stringify(data));
76
+ const content = readFileSync(join(dir, "dashboard.json"), "utf-8");
77
+ const parsed = JSON.parse(content);
78
+ assert.equal(parsed.tier, "mega");
79
+ assert.equal(parsed.config.thresholdTokens, 10000000);
80
+ assert.equal(parsed.context.tokens, null);
81
+ rmSync(dir, { recursive: true });
82
+ });
83
+ });
84
+
85
+ describe("events.log (JSONL)", () => {
86
+ test("parses multiple JSONL lines", () => {
87
+ const dir = tmpDir();
88
+ const events = [
89
+ { ts: "2025-01-01T00:00:00.000Z", type: "compact_start", trigger: "auto", tier: "medium", sessionId: "s1" },
90
+ { ts: "2025-01-01T00:00:01.000Z", type: "compact_end", trigger: "auto", durationMs: 1500, mode: "mega", fromTokens: 100000, toTokens: 5000 },
91
+ { ts: "2025-01-01T00:00:02.000Z", type: "checkpoint_persisted", checkpointId: "chk_1", totalCheckpoints: 1 },
92
+ ];
93
+ writeFileSync(join(dir, "events.log"), events.map((e) => JSON.stringify(e)).join("\n") + "\n");
94
+
95
+ const lines = readFileSync(join(dir, "events.log"), "utf-8").trim().split("\n");
96
+ assert.equal(lines.length, 3);
97
+ const parsed = lines.map((l) => JSON.parse(l));
98
+ assert.equal(parsed[0].type, "compact_start");
99
+ assert.equal(parsed[1].durationMs, 1500);
100
+ assert.equal(parsed[2].checkpointId, "chk_1");
101
+ rmSync(dir, { recursive: true });
102
+ });
103
+
104
+ test("ignores empty lines", () => {
105
+ const dir = tmpDir();
106
+ writeFileSync(join(dir, "events.log"), '{"type":"test"}\n\n\n{"type":"test2"}\n');
107
+ const lines = readFileSync(join(dir, "events.log"), "utf-8").trim().split("\n").filter(Boolean);
108
+ assert.equal(lines.length, 2);
109
+ rmSync(dir, { recursive: true });
110
+ });
111
+ });
112
+
113
+ describe("port.pid file", () => {
114
+ test("round-trips port and pid", () => {
115
+ const dir = tmpDir();
116
+ const info = { port: 3847, pid: 12345 };
117
+ writeFileSync(join(dir, "port.pid"), JSON.stringify(info));
118
+ const content = readFileSync(join(dir, "port.pid"), "utf-8");
119
+ const parsed = JSON.parse(content);
120
+ assert.equal(parsed.port, 3847);
121
+ assert.equal(parsed.pid, 12345);
122
+ rmSync(dir, { recursive: true });
123
+ });
124
+ });