prism-mcp-server 6.5.3 → 7.0.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.
- package/README.md +135 -42
- package/dist/backgroundScheduler.js +26 -1
- package/dist/config.js +22 -0
- package/dist/dashboard/authUtils.js +150 -0
- package/dist/dashboard/server.js +50 -48
- package/dist/storage/sqlite.js +135 -2
- package/dist/storage/supabase.js +77 -0
- package/dist/storage/supabaseMigrations.js +123 -0
- package/dist/tools/graphHandlers.js +98 -12
- package/dist/tools/ledgerHandlers.js +2 -2
- package/dist/utils/accessLogBuffer.js +159 -0
- package/dist/utils/actrActivation.js +197 -0
- package/dist/utils/cognitiveMemory.js +86 -25
- package/dist/utils/tracing.js +26 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Works with **Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Gem
|
|
|
24
24
|
- [Quick Start](#-quick-start)
|
|
25
25
|
- [The Magic Moment](#-the-magic-moment)
|
|
26
26
|
- [Setup Guides](#-setup-guides)
|
|
27
|
+
- [Universal Import](#-universal-import-bring-your-history)
|
|
27
28
|
- [What Makes Prism Different](#-what-makes-prism-different)
|
|
28
29
|
- [Use Cases](#-use-cases)
|
|
29
30
|
- [What's New](#-whats-new)
|
|
@@ -43,6 +44,10 @@ Every time you start a new conversation with an AI coding assistant, it starts f
|
|
|
43
44
|
|
|
44
45
|
**Prism gives your agent a brain that persists.** Save what matters at the end of each session. Load it back instantly on the next one. Your agent remembers what it did, what it learned, and what's left to do.
|
|
45
46
|
|
|
47
|
+
> 📌 **Terminology:** Throughout this doc, **"Prism"** refers to the MCP server and storage engine. **"Mind Palace"** refers to the visual dashboard UI at `localhost:3000` — your window into the agent's brain. They work together; the dashboard is optional.
|
|
48
|
+
|
|
49
|
+
**Starting in v7.0**, Prism doesn't just *store* memories — it **ranks them like a human brain.** The ACT-R activation model (from cognitive science) means memories that were accessed recently and frequently surface first, while stale context fades naturally. Combine that with candidate-scoped spreading activation and you get retrieval quality that no flat vector search can match.
|
|
50
|
+
|
|
46
51
|
---
|
|
47
52
|
|
|
48
53
|
## 🚀 Quick Start
|
|
@@ -63,7 +68,28 @@ Add to your MCP client config (`claude_desktop_config.json`, `.cursor/mcp.json`,
|
|
|
63
68
|
|
|
64
69
|
> **Note on Windows/Restricted Shells:** If your MCP client complains that `npx` is not found, use the absolute path to your node binary (e.g. `C:\Program Files\nodejs\npx.cmd`) or install globally with caution.
|
|
65
70
|
|
|
66
|
-
**That's it.** Restart your client. All tools are available. Dashboard at `http://localhost:3000`.
|
|
71
|
+
**That's it.** Restart your client. All tools are available. The **Mind Palace Dashboard** (the visual UI for your agent's brain) starts automatically at `http://localhost:3000`. You don't need to keep a tab open — the dashboard runs in the background and the MCP tools work with or without it.
|
|
72
|
+
|
|
73
|
+
<details>
|
|
74
|
+
<summary>Port 3000 already in use? (Next.js / Vite / etc.)</summary>
|
|
75
|
+
|
|
76
|
+
Add `PRISM_DASHBOARD_PORT` to your MCP config env block:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"prism-mcp": {
|
|
82
|
+
"command": "npx",
|
|
83
|
+
"args": ["-y", "prism-mcp-server"],
|
|
84
|
+
"env": { "PRISM_DASHBOARD_PORT": "3001" }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then open `http://localhost:3001` instead.
|
|
91
|
+
</details>
|
|
92
|
+
|
|
67
93
|
|
|
68
94
|
### Capability Matrix
|
|
69
95
|
|
|
@@ -77,7 +103,7 @@ Add to your MCP client config (`claude_desktop_config.json`, `.cursor/mcp.json`,
|
|
|
77
103
|
| Semantic vector search | ❌ | ✅ `GOOGLE_API_KEY` |
|
|
78
104
|
| Morning Briefings | ❌ | ✅ `GOOGLE_API_KEY` |
|
|
79
105
|
| Auto-compaction | ❌ | ✅ `GOOGLE_API_KEY` |
|
|
80
|
-
| Web Scholar research | ❌ | ✅ `BRAVE_API_KEY` + `FIRECRAWL_API_KEY` (or `TAVILY_API_KEY`) |
|
|
106
|
+
| Web Scholar research | ❌ | ✅ [`BRAVE_API_KEY`](#environment-variables) + [`FIRECRAWL_API_KEY`](#environment-variables) (or `TAVILY_API_KEY`) |
|
|
81
107
|
| VLM image captioning | ❌ | ✅ Provider key |
|
|
82
108
|
|
|
83
109
|
> 🔑 The core Mind Palace works **100% offline** with zero API keys. Cloud keys unlock intelligence features. See [Environment Variables](#environment-variables).
|
|
@@ -183,39 +209,6 @@ Add to your Continue `config.json` or Cline MCP settings:
|
|
|
183
209
|
|
|
184
210
|
</details>
|
|
185
211
|
|
|
186
|
-
### Migration
|
|
187
|
-
|
|
188
|
-
<details>
|
|
189
|
-
<summary><strong>Migrating Existing History (Claude, Gemini, OpenAI)</strong></summary>
|
|
190
|
-
|
|
191
|
-
Prism can ingest months of historical sessions from other tools to give your Mind Palace a massive head start. Import via the **CLI** or directly from the [Mind Palace Dashboard](#-mind-palace-dashboard) Import tab (file picker + manual path + dry-run toggle).
|
|
192
|
-
|
|
193
|
-
#### Supported Formats
|
|
194
|
-
* **Claude Code** (`.jsonl` logs) — Automatically handles streaming chunk deduplication and `requestId` normalization.
|
|
195
|
-
* **Gemini** (JSON history arrays) — Supports large-file streaming for 100MB+ exports.
|
|
196
|
-
* **OpenAI** (JSON chat completion history) — Normalizes disparate tool-call structures into the unified Ledger schema.
|
|
197
|
-
|
|
198
|
-
#### How to Run
|
|
199
|
-
|
|
200
|
-
**Option 1 — CLI:**
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
# Ingest Claude Code history
|
|
204
|
-
npx -y prism-mcp-server universal-import --format claude --path ~/path/to/claude_log.jsonl --project my-project
|
|
205
|
-
|
|
206
|
-
# Dry run (verify mapping without saving)
|
|
207
|
-
npx -y prism-mcp-server universal-import --format gemini --path ./gemini_history.json --dry-run
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**Option 2 — Dashboard:** Open `localhost:3000`, navigate to the **Import** tab, select the format and file, and click Import. Supports dry-run preview. See the [dashboard screenshot](#-mind-palace-dashboard) above.
|
|
211
|
-
|
|
212
|
-
#### Key Features
|
|
213
|
-
* **OOM-Safe Streaming:** Processes massive log files line-by-line using `stream-json`.
|
|
214
|
-
* **Idempotent Dedup:** Content-hash prevents duplicate imports on re-run (`skipCount` reported).
|
|
215
|
-
* **Chronological Integrity:** Uses timestamp fallbacks and `requestId` sorting to ensure your memory timeline is accurate.
|
|
216
|
-
* **Smart Context Mapping:** Extracts `cwd`, `gitBranch`, and tool usage patterns into searchable metadata.
|
|
217
|
-
|
|
218
|
-
</details>
|
|
219
212
|
|
|
220
213
|
<details>
|
|
221
214
|
<summary><strong>Claude Code — Lifecycle Autoload (.clauderules)</strong></summary>
|
|
@@ -329,6 +322,39 @@ Then add to your MCP config:
|
|
|
329
322
|
|
|
330
323
|
---
|
|
331
324
|
|
|
325
|
+
## 📥 Universal Import — Bring Your History
|
|
326
|
+
|
|
327
|
+
Switching to Prism? Don't leave months of AI session history behind. Prism can **ingest historical sessions from Claude Code, Gemini, and OpenAI** and give your Mind Palace an instant head start — no manual re-entry required.
|
|
328
|
+
|
|
329
|
+
Import via the **CLI** or directly from the Mind Palace Dashboard (**Import** tab → file picker + dry-run toggle).
|
|
330
|
+
|
|
331
|
+
### Supported Formats
|
|
332
|
+
* **Claude Code** (`.jsonl` logs) — Automatically handles streaming chunk deduplication and `requestId` normalization.
|
|
333
|
+
* **Gemini** (JSON history arrays) — Supports large-file streaming for 100MB+ exports.
|
|
334
|
+
* **OpenAI** (JSON chat completion history) — Normalizes disparate tool-call structures into the unified Ledger schema.
|
|
335
|
+
|
|
336
|
+
### How to Import
|
|
337
|
+
|
|
338
|
+
**Option 1 — CLI:**
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# Ingest Claude Code history
|
|
342
|
+
npx -y prism-mcp-server universal-import --format claude --path ~/path/to/claude_log.jsonl --project my-project
|
|
343
|
+
|
|
344
|
+
# Dry run (verify mapping without saving)
|
|
345
|
+
npx -y prism-mcp-server universal-import --format gemini --path ./gemini_history.json --dry-run
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Option 2 — Dashboard:** Open `localhost:3000`, navigate to the **Import** tab, select the format and file, and click Import. Supports dry-run preview.
|
|
349
|
+
|
|
350
|
+
### Why It's Safe to Re-Run
|
|
351
|
+
* **OOM-Safe Streaming:** Processes massive log files line-by-line using `stream-json`.
|
|
352
|
+
* **Idempotent Dedup:** Content-hash prevents duplicate imports on re-run (`skipCount` reported).
|
|
353
|
+
* **Chronological Integrity:** Uses timestamp fallbacks and `requestId` sorting to preserve your memory timeline.
|
|
354
|
+
* **Smart Context Mapping:** Extracts `cwd`, `gitBranch`, and tool usage patterns into searchable metadata.
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
332
358
|
## ✨ What Makes Prism Different
|
|
333
359
|
|
|
334
360
|
|
|
@@ -357,7 +383,7 @@ A gorgeous glassmorphism UI at `localhost:3000` that lets you see exactly what y
|
|
|
357
383
|
Powered by a pure TypeScript port of Google's TurboQuant (inspired by Google's ICLR research), Prism compresses 768-dim embeddings from **3,072 bytes → ~400 bytes** — enabling decades of session history on a standard laptop. No native modules. No vector database required.
|
|
358
384
|
|
|
359
385
|
### 🐝 Multi-Agent Hivemind
|
|
360
|
-
Multiple agents (dev, QA, PM) can work on the same project with **role-isolated memory**. Agents discover each other automatically, share context in real-time via Telepathy sync, and see a team roster during context loading.
|
|
386
|
+
Multiple agents (dev, QA, PM) can work on the same project with **role-isolated memory**. Agents discover each other automatically, share context in real-time via Telepathy sync, and see a team roster during context loading. → [Multi-agent setup example](examples/multi-agent-hivemind/)
|
|
361
387
|
|
|
362
388
|
### 🖼️ Visual Memory
|
|
363
389
|
Save UI screenshots, architecture diagrams, and bug states to a searchable vault. Images are auto-captioned by a VLM (Claude Vision / GPT-4V / Gemini) and become semantically searchable across sessions.
|
|
@@ -410,12 +436,59 @@ Then continue a specific thread with a follow-up message to the selected agent,
|
|
|
410
436
|
|
|
411
437
|
## 🆕 What's New
|
|
412
438
|
|
|
439
|
+
### v7.0.0 — ACT-R Activation Memory ✅
|
|
440
|
+
> **Current stable release.** Memory retrieval now uses a scientifically-grounded cognitive model.
|
|
441
|
+
|
|
442
|
+
- 🧠 **ACT-R Base-Level Activation** — `B_i = ln(Σ t_j^(-d))` computes recency × frequency activation per memory. Recent, frequently-accessed memories surface first; cold memories fade to near-zero. Based on Anderson's *Adaptive Control of Thought—Rational* (ACM, 2025).
|
|
443
|
+
- 🔗 **Candidate-Scoped Spreading Activation** — `S_i = Σ(W × strength)` for links within the current search result set only. Prevents "God node" centrality from dominating rankings (Rule #5).
|
|
444
|
+
- 📐 **Parameterized Sigmoid Normalization** — Calibrated `σ(x) = 1/(1 + e^(-k(x - x₀)))` with midpoint at -2.0 maps the natural ACT-R activation range (-10 to +5) into discriminating (0, 1) scores.
|
|
445
|
+
- 🏗️ **Composite Retrieval Scoring** — `Score = 0.7 × similarity + 0.3 × σ(activation)` — similarity dominates, activation re-ranks. Fully configurable weights via `PRISM_ACTR_WEIGHT_*` env vars.
|
|
446
|
+
- ⚡ **AccessLogBuffer** — In-memory write buffer with 5-second batch flush prevents SQLite `SQLITE_BUSY` contention under parallel agent tool calls. Deduplicates within flush windows.
|
|
447
|
+
- 🗂️ **Access Log Infrastructure** — New `memory_access_log` table with `logAccess()`, `getAccessLog()`, `pruneAccessLog()` across both SQLite and Supabase backends. Creation seeds initial access (zero cold-start penalty).
|
|
448
|
+
- 🧹 **Background Access Log Pruning** — Scheduler automatically prunes access logs exceeding retention window (default: 90 days). Configurable via `PRISM_ACTR_ACCESS_LOG_RETENTION_DAYS`.
|
|
449
|
+
- 🧪 **49-Test ACT-R Suite** — Pure-function unit tests covering base-level activation, spreading activation, sigmoid normalization, composite scoring, AccessLogBuffer lifecycle, deduplication, chunking, and edge cases.
|
|
450
|
+
- 📊 **705 Tests** — 32 suites, all passing, zero regressions.
|
|
451
|
+
|
|
452
|
+
<details>
|
|
453
|
+
<summary><strong>🔬 Live Example: v6.5 vs v7.0 Retrieval Behavior</strong></summary>
|
|
454
|
+
|
|
455
|
+
Consider an agent searching for "OAuth migration" with 3 memories in the result set:
|
|
456
|
+
|
|
457
|
+
| Memory | Cosine Similarity | Last Accessed | Access Count (30d) |
|
|
458
|
+
|--------|:-:|:-:|:-:|
|
|
459
|
+
| A: "PKCE flow decision" | 0.82 | 2 hours ago | 12× |
|
|
460
|
+
| B: "OAuth library comparison" | 0.85 | 14 days ago | 2× |
|
|
461
|
+
| C: "Auth middleware refactor" | 0.81 | 30 minutes ago | 8× |
|
|
462
|
+
|
|
463
|
+
**v6.5 (pure similarity):** B > A > C — the stale library comparison wins because it has the highest cosine score, even though the agent hasn't looked at it in two weeks.
|
|
464
|
+
|
|
465
|
+
**v7.0 (ACT-R re-ranking):**
|
|
466
|
+
|
|
467
|
+
| Memory | Similarity (0.7×) | ACT-R σ(B+S) (0.3×) | **Composite** |
|
|
468
|
+
|--------|:-:|:-:|:-:|
|
|
469
|
+
| A | 0.574 | 0.3 × 0.94 = 0.282 | **0.856** |
|
|
470
|
+
| C | 0.567 | 0.3 × 0.91 = 0.273 | **0.840** |
|
|
471
|
+
| B | 0.595 | 0.3 × 0.12 = 0.036 | **0.631** |
|
|
472
|
+
|
|
473
|
+
**Result:** The actively-used PKCE decision (A) and the just-touched middleware (C) surface above the stale comparison (B). The agent gets the context it's *actually working with*, not just the closest embedding.
|
|
474
|
+
|
|
475
|
+
</details>
|
|
476
|
+
|
|
477
|
+
### v6.5.3 — Auth Hardening ✅
|
|
478
|
+
- 🔒 **Rate Limiting** — Login endpoint (`POST /api/auth/login`) protected by sliding-window rate limiter (5 attempts per 60s per IP). Resets on success.
|
|
479
|
+
- 🔒 **CORS Hardening** — Dynamic `Origin` echo with `Allow-Credentials` when auth enabled (replaces wildcard `*`).
|
|
480
|
+
- 🚪 **Logout Endpoint** — `POST /api/auth/logout` invalidates session server-side and clears client cookie.
|
|
481
|
+
- 🧪 **42-Test Auth Suite** — Unit + HTTP integration tests covering `safeCompare`, `generateToken`, `isAuthenticated`, `createRateLimiter`, login/logout lifecycle, rate limiting, and CORS.
|
|
482
|
+
- 🏗️ **Auth Module Extraction** — Decoupled auth logic from `server.ts` closures into testable `authUtils.ts`.
|
|
483
|
+
|
|
484
|
+
### v6.5.2 — SDM/HDC Test Hardening ✅
|
|
485
|
+
- 🧪 **37 New Edge-Case Tests** — Hardened the cognitive routing pipeline (HDC engine, PolicyGateway, StateMachine, SDM engine) with boundary condition tests. 571 → 608 total tests.
|
|
486
|
+
|
|
413
487
|
### v6.5.1 — Dashboard Project-Load Hotfix ✅
|
|
414
488
|
- 🩹 **Project Selector Recovery** — Fixed a startup path where the dashboard selector could stay stuck on "Loading projects..." when Supabase env vars were unresolved placeholders.
|
|
415
489
|
- 🔄 **Safe Backend Fallback** — If Supabase is requested but env is invalid/unresolved, Prism now auto-falls back to local SQLite so `/api/projects` and dashboard boot remain operational.
|
|
416
490
|
|
|
417
491
|
### v6.5 — HDC Cognitive Routing ✅
|
|
418
|
-
> **Current stable release.** The Mind Palace gains a brain-inspired routing engine.
|
|
419
492
|
|
|
420
493
|
- 🧠 **Hyperdimensional Cognitive Routing** — New `session_cognitive_route` tool composes the agent's current state, role, and action into a single 768-dim binary hypervector via XOR binding, then resolves it to a semantic concept via Hamming distance. Three-outcome policy gateway: `direct` / `clarify` / `fallback`.
|
|
421
494
|
- 🎛️ **Per-Project Threshold Overrides** — Fallback and clarify thresholds are configurable per-project and persisted via the existing `getSetting`/`setSetting` contract (no new migrations).
|
|
@@ -487,7 +560,7 @@ Standard memory servers (like Mem0, Zep, or the baseline Anthropic MCP) act as p
|
|
|
487
560
|
| **Primary Interface** | **Native MCP** (Tools, Prompts, Resources) | REST API & Python/TS SDKs | REST API & Python/TS SDKs | Native MCP (Tools only) |
|
|
488
561
|
| **Storage Engine** | **BYO SQLite or Supabase** | Managed Cloud / VectorDBs | Managed Cloud / Postgres | Local SQLite only |
|
|
489
562
|
| **Context Assembly** | **Progressive (Quick/Std/Deep)** | Top-K Semantic Search | Top-K + Temporal Summaries | Basic Entity Search |
|
|
490
|
-
| **Memory Mechanics** | **
|
|
563
|
+
| **Memory Mechanics** | **ACT-R Activation (recency×freq), SDM, HDC** | Basic Vector + Entity | Fading Temporal Graph | None (Infinite growth) |
|
|
491
564
|
| **Multi-Agent Sync** | **CRDT (Add-Wins / LWW)** | Cloud locks | Postgres locks | ❌ None (Data races) |
|
|
492
565
|
| **Data Compression** | **TurboQuant (7x smaller vectors)** | ❌ Standard F32 Vectors | ❌ Standard Vectors | ❌ No Vectors |
|
|
493
566
|
| **Observability** | **OTel Traces + Built-in PWA UI** | Cloud Dashboard | Cloud Dashboard | ❌ None |
|
|
@@ -501,7 +574,7 @@ Standard memory servers (like Mem0, Zep, or the baseline Anthropic MCP) act as p
|
|
|
501
574
|
Mem0 and Zep are APIs that *can* be wrapped into an MCP server. Prism was built *for* MCP from day one. Instead of wasting tokens on "search" tool calls, Prism uses **MCP Prompts** (`/resume_session`) to inject context *before* the LLM thinks, and **MCP Resources** (`memory://project/handoff`) to attach live, subscribing context.
|
|
502
575
|
|
|
503
576
|
#### 2. Academic-Grade Cognitive Computer Science
|
|
504
|
-
The giants use standard RAG (Retrieval-Augmented Generation). Prism uses biological and academic models of memory: **TurboQuant** for extreme vector compression, **Ebbinghaus curves** for importance decay, and **Sparse Distributed Memory (SDM)**.
|
|
577
|
+
The giants use standard RAG (Retrieval-Augmented Generation). Prism uses biological and academic models of memory: **ACT-R base-level activation** (`B_i = ln(Σ t_j^(-d))`) for recency–frequency re-ranking, **TurboQuant** for extreme vector compression, **Ebbinghaus curves** for importance decay, and **Sparse Distributed Memory (SDM)**. The result is retrieval quality that follows how human memory actually works — not just nearest-neighbor cosine distance. And all of it runs on a laptop without a Postgres/pgvector instance.
|
|
505
578
|
|
|
506
579
|
#### 3. True Multi-Agent Coordination (CRDTs)
|
|
507
580
|
If Cursor (Agent A) and Claude Desktop (Agent B) try to update a Mem0 or standard SQLite database at the exact same time, you get a race condition and data loss. Prism uses **Optimistic Concurrency Control (OCC) with CRDT OR-Maps** — mathematically guaranteeing that simultaneous agent edits merge safely. Enterprise-grade distributed systems on a local machine.
|
|
@@ -624,6 +697,14 @@ Requires `PRISM_ENABLE_HIVEMIND=true`.
|
|
|
624
697
|
|
|
625
698
|
## Environment Variables
|
|
626
699
|
|
|
700
|
+
> **🚦 TL;DR — Just want the best experience fast?** Set these three keys and you're done:
|
|
701
|
+
> ```
|
|
702
|
+
> GOOGLE_API_KEY=... # Unlocks: semantic search, Morning Briefings, auto-compaction
|
|
703
|
+
> BRAVE_API_KEY=... # Unlocks: Web Scholar research + Brave Answers
|
|
704
|
+
> FIRECRAWL_API_KEY=... # Unlocks: Web Scholar deep scraping (or use TAVILY_API_KEY instead)
|
|
705
|
+
> ```
|
|
706
|
+
> **Zero keys = zero problem.** Core session memory, keyword search, time travel, and the full dashboard work 100% offline. Cloud keys are optional power-ups.
|
|
707
|
+
|
|
627
708
|
<details>
|
|
628
709
|
<summary><strong>Full variable reference</strong></summary>
|
|
629
710
|
|
|
@@ -652,6 +733,11 @@ Requires `PRISM_ENABLE_HIVEMIND=true`.
|
|
|
652
733
|
| `PRISM_SCHOLAR_MAX_ARTICLES_PER_RUN` | No | Max articles per Scholar run (default: `3`) |
|
|
653
734
|
| `PRISM_HDC_ENABLED` | No | `"true"` (default) to enable HDC cognitive routing pipeline |
|
|
654
735
|
| `PRISM_HDC_EXPLAINABILITY_ENABLED` | No | `"true"` (default) to include convergence/distance/ambiguity in cognitive route responses |
|
|
736
|
+
| `PRISM_ACTR_ENABLED` | No | `"true"` (default) to enable ACT-R activation re-ranking on semantic search |
|
|
737
|
+
| `PRISM_ACTR_DECAY` | No | ACT-R decay parameter `d` (default: `0.5`). Higher values = faster recency drop-off |
|
|
738
|
+
| `PRISM_ACTR_WEIGHT_SIMILARITY` | No | Composite score similarity weight (default: `0.7`) |
|
|
739
|
+
| `PRISM_ACTR_WEIGHT_ACTIVATION` | No | Composite score ACT-R activation weight (default: `0.3`) |
|
|
740
|
+
| `PRISM_ACTR_ACCESS_LOG_RETENTION_DAYS` | No | Days before access logs are pruned by background scheduler (default: `90`) |
|
|
655
741
|
|
|
656
742
|
</details>
|
|
657
743
|
|
|
@@ -741,6 +827,10 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
|
|
|
741
827
|
| **v6.1** | Prism-Port Vault Export — Obsidian/Logseq `.zip` with YAML frontmatter & `[[Wikilinks]]` | Data sovereignty, PKM interop | ✅ Shipped |
|
|
742
828
|
| **v6.1** | Cognitive Load & Semantic Search — dynamic graph thinning, search highlights | Contextual working memory | ✅ Shipped |
|
|
743
829
|
| **v6.2** | Synthesize & Prune — automated edge synthesis, graph pruning, SLO observability | Implicit associative memory | ✅ Shipped |
|
|
830
|
+
| **v7.0** | ACT-R Base-Level Activation — `B_i = ln(Σ t_j^(-d))` recency×frequency re-ranking over similarity candidates | Anderson's ACT-R (Adaptive Control of Thought—Rational, ACM 2025) | ✅ Shipped |
|
|
831
|
+
| **v7.0** | Candidate-Scoped Spreading Activation — `S_i = Σ(W × strength)` bounded to search result set; prevents God-node dominance | Spreading activation networks (Collins & Loftus, 1975) | ✅ Shipped |
|
|
832
|
+
| **v7.0** | Composite Retrieval Scoring — `0.7 × similarity + 0.3 × σ(activation)`; configurable via `PRISM_ACTR_WEIGHT_*` | Hybrid cognitive-neural retrieval models | ✅ Shipped |
|
|
833
|
+
| **v7.0** | AccessLogBuffer — in-memory batch-write buffer with 5s flush; prevents SQLite `SQLITE_BUSY` under parallel agents | Production reliability engineering | ✅ Shipped |
|
|
744
834
|
| **v7.x** | Affect-Tagged Memory — sentiment shapes what gets recalled | Affect-modulated retrieval (neuroscience) | 🔭 Horizon |
|
|
745
835
|
| **v8+** | Zero-Search Retrieval — no index, no ANN, just ask the vector | Holographic Reduced Representations | 🔭 Horizon |
|
|
746
836
|
|
|
@@ -755,10 +845,13 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
|
|
|
755
845
|
### v6.2: The "Synthesize & Prune" Phase ✅
|
|
756
846
|
Shipped in v6.2.0. Edge synthesis, graph pruning with SLO observability, temporal decay heatmaps, active recall prompt generation, and full dashboard metrics integration.
|
|
757
847
|
|
|
758
|
-
### v6.5: Cognitive Architecture
|
|
759
|
-
Full Superposed Memory (SDM) + Hyperdimensional Computing (HDC/VSA)
|
|
848
|
+
### v6.5: Cognitive Architecture ✅
|
|
849
|
+
Shipped. Full Superposed Memory (SDM) + Hyperdimensional Computing (HDC/VSA) cognitive routing pipeline. Compositional memory states via XOR binding, Hamming resolution, and policy-gated routing (direct / clarify / fallback). 705 tests passing.
|
|
850
|
+
|
|
851
|
+
### v7.0: ACT-R Activation Memory ✅
|
|
852
|
+
Shipped. Scientifically-grounded retrieval re-ranking via ACT-R base-level activation (`B_i = ln(Σ t_j^(-d))`), candidate-scoped spreading activation, parameterized sigmoid normalization, composite scoring, and zero-cold-start access log infrastructure. 49 dedicated unit tests, 705 total passing.
|
|
760
853
|
|
|
761
|
-
###
|
|
854
|
+
### Future Tracks
|
|
762
855
|
- **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence, making surfaced context more behaviorally useful.
|
|
763
856
|
- **v8+: Zero-Search Retrieval** — Direct vector-addressed recall (“just ask the vector”) reduces retrieval indirection and moves Prism toward truly native associative memory.
|
|
764
857
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* storage backends during maintenance windows.
|
|
18
18
|
*/
|
|
19
19
|
import { getStorage } from "./storage/index.js";
|
|
20
|
-
import { PRISM_USER_ID, PRISM_SCHOLAR_ENABLED, PRISM_SCHOLAR_INTERVAL_MS, PRISM_GRAPH_PRUNING_ENABLED, PRISM_GRAPH_PRUNE_MIN_STRENGTH, PRISM_GRAPH_PRUNE_PROJECT_COOLDOWN_MS, PRISM_GRAPH_PRUNE_SWEEP_BUDGET_MS, PRISM_GRAPH_PRUNE_MAX_PROJECTS_PER_SWEEP, } from "./config.js";
|
|
20
|
+
import { PRISM_USER_ID, PRISM_SCHOLAR_ENABLED, PRISM_SCHOLAR_INTERVAL_MS, PRISM_GRAPH_PRUNING_ENABLED, PRISM_GRAPH_PRUNE_MIN_STRENGTH, PRISM_GRAPH_PRUNE_PROJECT_COOLDOWN_MS, PRISM_GRAPH_PRUNE_SWEEP_BUDGET_MS, PRISM_GRAPH_PRUNE_MAX_PROJECTS_PER_SWEEP, PRISM_ACTR_ENABLED, PRISM_ACTR_ACCESS_LOG_RETENTION_DAYS, } from "./config.js";
|
|
21
21
|
import { debugLog } from "./utils/logger.js";
|
|
22
22
|
import { runWebScholar } from "./scholar/webScholar.js";
|
|
23
23
|
import { getAllActiveSdmProjects, getSdmEngine } from "./sdm/sdmEngine.js";
|
|
@@ -100,6 +100,8 @@ export const DEFAULT_SCHEDULER_CONFIG = {
|
|
|
100
100
|
graphPruneProjectCooldownMs: PRISM_GRAPH_PRUNE_PROJECT_COOLDOWN_MS,
|
|
101
101
|
graphPruneSweepBudgetMs: PRISM_GRAPH_PRUNE_SWEEP_BUDGET_MS,
|
|
102
102
|
graphPruneMaxProjectsPerSweep: PRISM_GRAPH_PRUNE_MAX_PROJECTS_PER_SWEEP,
|
|
103
|
+
enableAccessLogPrune: PRISM_ACTR_ENABLED,
|
|
104
|
+
accessLogRetentionDays: PRISM_ACTR_ACCESS_LOG_RETENTION_DAYS,
|
|
103
105
|
};
|
|
104
106
|
// ─── Scheduler State ─────────────────────────────────────────
|
|
105
107
|
let schedulerInterval = null;
|
|
@@ -147,6 +149,7 @@ export function startScheduler(config) {
|
|
|
147
149
|
cfg.enableSdmFlush && "SdmFlush",
|
|
148
150
|
cfg.enableEdgeSynthesis && "EdgeSynthesis",
|
|
149
151
|
cfg.enableGraphPruning && "GraphPruning",
|
|
152
|
+
cfg.enableAccessLogPrune && "AccessLogPrune",
|
|
150
153
|
].filter(Boolean).join(", ");
|
|
151
154
|
console.error(`[Scheduler] ⏰ Started (interval=${formatDuration(cfg.intervalMs)}, tasks=[${enabledTasks}])`);
|
|
152
155
|
return () => {
|
|
@@ -249,6 +252,7 @@ export async function runSchedulerSweep(cfg = DEFAULT_SCHEDULER_CONFIG) {
|
|
|
249
252
|
durationMs: 0,
|
|
250
253
|
minStrength: cfg.graphPruneMinStrength,
|
|
251
254
|
},
|
|
255
|
+
accessLogPrune: { ran: false, deleted: 0 },
|
|
252
256
|
},
|
|
253
257
|
};
|
|
254
258
|
debugLog("[Scheduler] 🔄 Sweep starting...");
|
|
@@ -612,6 +616,24 @@ export async function runSchedulerSweep(cfg = DEFAULT_SCHEDULER_CONFIG) {
|
|
|
612
616
|
// Non-critical — don't let metrics failure break the scheduler
|
|
613
617
|
}
|
|
614
618
|
}
|
|
619
|
+
// ── Task 9: ACT-R Access Log Prune (v7.0) ──────────────────
|
|
620
|
+
// Removes stale rows from memory_access_log older than the
|
|
621
|
+
// configured retention window (default: 90 days). Prevents
|
|
622
|
+
// unbounded table growth from the ACT-R frequency tracking.
|
|
623
|
+
if (cfg.enableAccessLogPrune) {
|
|
624
|
+
try {
|
|
625
|
+
result.tasks.accessLogPrune.ran = true;
|
|
626
|
+
const deleted = await storage.pruneAccessLog(cfg.accessLogRetentionDays);
|
|
627
|
+
result.tasks.accessLogPrune.deleted = deleted;
|
|
628
|
+
if (deleted > 0) {
|
|
629
|
+
debugLog(`[Scheduler] Access log prune: deleted ${deleted} stale entries (>${cfg.accessLogRetentionDays}d)`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
result.tasks.accessLogPrune.error = err instanceof Error ? err.message : String(err);
|
|
634
|
+
debugLog(`[Scheduler] Access log prune error (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
615
637
|
}
|
|
616
638
|
finally {
|
|
617
639
|
clearInterval(heartbeatInterval);
|
|
@@ -657,6 +679,9 @@ export async function runSchedulerSweep(cfg = DEFAULT_SCHEDULER_CONFIG) {
|
|
|
657
679
|
if (result.tasks.edgeSynthesis.ran && result.tasks.edgeSynthesis.projectsSynthesized > 0) {
|
|
658
680
|
parts.push(`Synthesis:${result.tasks.edgeSynthesis.newLinks} links in ${result.tasks.edgeSynthesis.projectsSynthesized} projects`);
|
|
659
681
|
}
|
|
682
|
+
if (result.tasks.accessLogPrune.ran && result.tasks.accessLogPrune.deleted > 0) {
|
|
683
|
+
parts.push(`AccessLog:${result.tasks.accessLogPrune.deleted} stale entries pruned`);
|
|
684
|
+
}
|
|
660
685
|
const summaryLine = parts.length > 0
|
|
661
686
|
? parts.join(" | ")
|
|
662
687
|
: "no maintenance actions needed";
|
package/dist/config.js
CHANGED
|
@@ -209,3 +209,25 @@ export const PRISM_GRAPH_PRUNE_MIN_STRENGTH = parseFloat(process.env.PRISM_GRAPH
|
|
|
209
209
|
export const PRISM_GRAPH_PRUNE_PROJECT_COOLDOWN_MS = parseInt(process.env.PRISM_GRAPH_PRUNE_PROJECT_COOLDOWN_MS || "600000", 10);
|
|
210
210
|
export const PRISM_GRAPH_PRUNE_SWEEP_BUDGET_MS = parseInt(process.env.PRISM_GRAPH_PRUNE_SWEEP_BUDGET_MS || "30000", 10);
|
|
211
211
|
export const PRISM_GRAPH_PRUNE_MAX_PROJECTS_PER_SWEEP = parseInt(process.env.PRISM_GRAPH_PRUNE_MAX_PROJECTS_PER_SWEEP || "25", 10);
|
|
212
|
+
// ─── v7.0: ACT-R Cognitive Memory Activation ────────────────
|
|
213
|
+
// Scientifically-grounded retrieval re-ranking based on the ACT-R
|
|
214
|
+
// cognitive architecture. Replaces simple Ebbinghaus decay with
|
|
215
|
+
// a composite similarity + activation model.
|
|
216
|
+
/** Master switch for ACT-R activation-based re-ranking. */
|
|
217
|
+
export const PRISM_ACTR_ENABLED = process.env.PRISM_ACTR_ENABLED === "true";
|
|
218
|
+
/** ACT-R decay parameter d in t^(-d). Higher = faster forgetting. (Paper default: 0.5) */
|
|
219
|
+
export const PRISM_ACTR_DECAY = parseFloat(process.env.PRISM_ACTR_DECAY || "0.5");
|
|
220
|
+
/** Weight of cosine similarity in composite score. (Default: 0.7 — similarity dominates) */
|
|
221
|
+
export const PRISM_ACTR_WEIGHT_SIMILARITY = parseFloat(process.env.PRISM_ACTR_WEIGHT_SIMILARITY || "0.7");
|
|
222
|
+
/** Weight of activation boost in composite score. (Default: 0.3 — activation re-ranks) */
|
|
223
|
+
export const PRISM_ACTR_WEIGHT_ACTIVATION = parseFloat(process.env.PRISM_ACTR_WEIGHT_ACTIVATION || "0.3");
|
|
224
|
+
/** Sigmoid midpoint: activation value that maps to 0.5 boost. (Default: -2.0) */
|
|
225
|
+
export const PRISM_ACTR_SIGMOID_MIDPOINT = parseFloat(process.env.PRISM_ACTR_SIGMOID_MIDPOINT || "-2.0");
|
|
226
|
+
/** Sigmoid steepness k. Higher = sharper discrimination. (Default: 1.0) */
|
|
227
|
+
export const PRISM_ACTR_SIGMOID_STEEPNESS = parseFloat(process.env.PRISM_ACTR_SIGMOID_STEEPNESS || "1.0");
|
|
228
|
+
/** Max access log entries per entry for base-level activation. (Default: 50) */
|
|
229
|
+
export const PRISM_ACTR_MAX_ACCESSES_PER_ENTRY = parseInt(process.env.PRISM_ACTR_MAX_ACCESSES_PER_ENTRY || "50", 10);
|
|
230
|
+
/** AccessLogBuffer flush interval in milliseconds. (Default: 5000ms) */
|
|
231
|
+
export const PRISM_ACTR_BUFFER_FLUSH_MS = parseInt(process.env.PRISM_ACTR_BUFFER_FLUSH_MS || "5000", 10);
|
|
232
|
+
/** Days to retain access log entries before pruning. (Default: 90) */
|
|
233
|
+
export const PRISM_ACTR_ACCESS_LOG_RETENTION_DAYS = parseInt(process.env.PRISM_ACTR_ACCESS_LOG_RETENTION_DAYS || "90", 10);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard authentication helpers extracted from server.ts for direct unit testing.
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes } from "crypto";
|
|
5
|
+
// ─────────────────────────────────────────────────────────────────
|
|
6
|
+
// TIMING-SAFE COMPARISON
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────
|
|
8
|
+
/**
|
|
9
|
+
* Timing-safe string comparison to prevent timing attacks.
|
|
10
|
+
*
|
|
11
|
+
* Returns false immediately for different lengths (this leaks length
|
|
12
|
+
* information, but credential length is not considered secret in
|
|
13
|
+
* HTTP Basic Auth where the header is visible).
|
|
14
|
+
*
|
|
15
|
+
* For equal-length strings, iterates ALL characters regardless of
|
|
16
|
+
* where mismatches occur, accumulating XOR differences.
|
|
17
|
+
*/
|
|
18
|
+
export function safeCompare(a, b) {
|
|
19
|
+
if (a.length !== b.length)
|
|
20
|
+
return false;
|
|
21
|
+
let result = 0;
|
|
22
|
+
for (let i = 0; i < a.length; i++) {
|
|
23
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
24
|
+
}
|
|
25
|
+
return result === 0;
|
|
26
|
+
}
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────
|
|
28
|
+
// TOKEN GENERATION
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Generate a random 64-character hex session token.
|
|
32
|
+
*/
|
|
33
|
+
export function generateToken() {
|
|
34
|
+
return randomBytes(32).toString("hex");
|
|
35
|
+
}
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────
|
|
37
|
+
// AUTHENTICATION CHECK
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Check if a request is authenticated against the provided config.
|
|
41
|
+
*
|
|
42
|
+
* Returns true if:
|
|
43
|
+
* 1. Auth is disabled (authEnabled === false) → pass-through
|
|
44
|
+
* 2. Request has a valid, non-expired session cookie
|
|
45
|
+
* 3. Request has valid Basic Auth credentials
|
|
46
|
+
*
|
|
47
|
+
* Side effect: expired session tokens are lazily cleaned up when
|
|
48
|
+
* encountered, preventing unbounded memory growth.
|
|
49
|
+
*/
|
|
50
|
+
export function isAuthenticated(req, config) {
|
|
51
|
+
if (!config.authEnabled)
|
|
52
|
+
return true;
|
|
53
|
+
// Check session cookie first
|
|
54
|
+
const cookies = req.headers.cookie || "";
|
|
55
|
+
const match = cookies.match(/prism_session=([a-f0-9]{64})/);
|
|
56
|
+
if (match) {
|
|
57
|
+
const token = match[1];
|
|
58
|
+
const expiry = config.activeSessions.get(token);
|
|
59
|
+
if (expiry && expiry > Date.now())
|
|
60
|
+
return true;
|
|
61
|
+
// Expired or unknown — lazy cleanup
|
|
62
|
+
if (expiry)
|
|
63
|
+
config.activeSessions.delete(token);
|
|
64
|
+
}
|
|
65
|
+
// Check Basic Auth header
|
|
66
|
+
const authHeader = req.headers.authorization || "";
|
|
67
|
+
if (authHeader.startsWith("Basic ")) {
|
|
68
|
+
try {
|
|
69
|
+
const decoded = Buffer.from(authHeader.slice(6), "base64").toString("utf-8");
|
|
70
|
+
const colonIndex = decoded.indexOf(":");
|
|
71
|
+
if (colonIndex === -1)
|
|
72
|
+
return false;
|
|
73
|
+
const user = decoded.slice(0, colonIndex);
|
|
74
|
+
const pass = decoded.slice(colonIndex + 1);
|
|
75
|
+
return safeCompare(user, config.authUser) && safeCompare(pass, config.authPass);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Malformed Base64 — reject
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────
|
|
85
|
+
// RATE LIMITER
|
|
86
|
+
// ─────────────────────────────────────────────────────────────────
|
|
87
|
+
/**
|
|
88
|
+
* Creates a sliding-window rate limiter.
|
|
89
|
+
*
|
|
90
|
+
* Returns an object with:
|
|
91
|
+
* - isAllowed(key): boolean — check and record an attempt
|
|
92
|
+
* - reset(key): void — clear attempts for a key
|
|
93
|
+
* - clear(): void — clear all state (for testing)
|
|
94
|
+
*
|
|
95
|
+
* The limiter automatically prunes stale entries on each check
|
|
96
|
+
* to prevent unbounded memory growth from unique IPs.
|
|
97
|
+
*/
|
|
98
|
+
export function createRateLimiter(opts) {
|
|
99
|
+
const store = new Map();
|
|
100
|
+
let lastPrune = Date.now();
|
|
101
|
+
// Prune stale entries every 5 minutes to prevent memory leaks
|
|
102
|
+
const PRUNE_INTERVAL_MS = 5 * 60 * 1000;
|
|
103
|
+
function pruneStale(now) {
|
|
104
|
+
if (now - lastPrune < PRUNE_INTERVAL_MS)
|
|
105
|
+
return;
|
|
106
|
+
lastPrune = now;
|
|
107
|
+
for (const [key, entry] of store) {
|
|
108
|
+
// Keep only timestamps within the window
|
|
109
|
+
entry.timestamps = entry.timestamps.filter(t => now - t < opts.windowMs);
|
|
110
|
+
if (entry.timestamps.length === 0) {
|
|
111
|
+
store.delete(key);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
/**
|
|
117
|
+
* Check if an attempt from the given key is allowed.
|
|
118
|
+
* Records the attempt and returns false if rate limit exceeded.
|
|
119
|
+
*/
|
|
120
|
+
isAllowed(key) {
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
pruneStale(now);
|
|
123
|
+
let entry = store.get(key);
|
|
124
|
+
if (!entry) {
|
|
125
|
+
entry = { timestamps: [] };
|
|
126
|
+
store.set(key, entry);
|
|
127
|
+
}
|
|
128
|
+
// Filter to only timestamps within the window
|
|
129
|
+
entry.timestamps = entry.timestamps.filter(t => now - t < opts.windowMs);
|
|
130
|
+
if (entry.timestamps.length >= opts.maxAttempts) {
|
|
131
|
+
return false; // Rate limited
|
|
132
|
+
}
|
|
133
|
+
entry.timestamps.push(now);
|
|
134
|
+
return true;
|
|
135
|
+
},
|
|
136
|
+
/** Reset attempts for a specific key */
|
|
137
|
+
reset(key) {
|
|
138
|
+
store.delete(key);
|
|
139
|
+
},
|
|
140
|
+
/** Clear all state — useful for testing */
|
|
141
|
+
clear() {
|
|
142
|
+
store.clear();
|
|
143
|
+
lastPrune = Date.now();
|
|
144
|
+
},
|
|
145
|
+
/** Get current store size — for testing */
|
|
146
|
+
get size() {
|
|
147
|
+
return store.size;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|