prism-mcp-server 6.5.2 → 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 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`. *(Note: The MCP server automatically starts this UI on port 3000 when connected. If you have a Next.js/React app running, port 3000 might already be in use.)*
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.
@@ -391,16 +417,78 @@ Soft/hard delete (Art. 17), full export in JSON, Markdown, or Obsidian vault `.z
391
417
 
392
418
  **Morning Briefings** — After 4+ hours away, Prism auto-synthesizes a 3-bullet action plan from your last sessions.
393
419
 
420
+ ### Claude Code: Parallel Explore Agent Workflows
421
+
422
+ When you need to quickly map a large auth system, launch multiple `Explore` subagents in parallel and merge their findings:
423
+
424
+ ```text
425
+ Run 3 Explore agents in parallel.
426
+ 1) Map auth architecture
427
+ 2) List auth API endpoints
428
+ 3) Find auth test coverage gaps
429
+ Research only, no code changes.
430
+ Return a merged summary.
431
+ ```
432
+
433
+ Then continue a specific thread with a follow-up message to the selected agent, such as deeper refresh-token edge-case analysis.
434
+
394
435
  ---
395
436
 
396
437
  ## 🆕 What's New
397
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
+
398
487
  ### v6.5.1 — Dashboard Project-Load Hotfix ✅
399
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.
400
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.
401
490
 
402
491
  ### v6.5 — HDC Cognitive Routing ✅
403
- > **Current stable release.** The Mind Palace gains a brain-inspired routing engine.
404
492
 
405
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`.
406
494
  - 🎛️ **Per-Project Threshold Overrides** — Fallback and clarify thresholds are configurable per-project and persisted via the existing `getSetting`/`setSetting` contract (no new migrations).
@@ -472,7 +560,7 @@ Standard memory servers (like Mem0, Zep, or the baseline Anthropic MCP) act as p
472
560
  | **Primary Interface** | **Native MCP** (Tools, Prompts, Resources) | REST API & Python/TS SDKs | REST API & Python/TS SDKs | Native MCP (Tools only) |
473
561
  | **Storage Engine** | **BYO SQLite or Supabase** | Managed Cloud / VectorDBs | Managed Cloud / Postgres | Local SQLite only |
474
562
  | **Context Assembly** | **Progressive (Quick/Std/Deep)** | Top-K Semantic Search | Top-K + Temporal Summaries | Basic Entity Search |
475
- | **Memory Mechanics** | **Ebbinghaus Decay, SDM, HDC** | Basic Vector + Entity | Fading Temporal Graph | None (Infinite growth) |
563
+ | **Memory Mechanics** | **ACT-R Activation (recency×freq), SDM, HDC** | Basic Vector + Entity | Fading Temporal Graph | None (Infinite growth) |
476
564
  | **Multi-Agent Sync** | **CRDT (Add-Wins / LWW)** | Cloud locks | Postgres locks | ❌ None (Data races) |
477
565
  | **Data Compression** | **TurboQuant (7x smaller vectors)** | ❌ Standard F32 Vectors | ❌ Standard Vectors | ❌ No Vectors |
478
566
  | **Observability** | **OTel Traces + Built-in PWA UI** | Cloud Dashboard | Cloud Dashboard | ❌ None |
@@ -486,7 +574,7 @@ Standard memory servers (like Mem0, Zep, or the baseline Anthropic MCP) act as p
486
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.
487
575
 
488
576
  #### 2. Academic-Grade Cognitive Computer Science
489
- 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)**. This makes Prism vastly more efficient on a local machine than running a giant Postgres/pgvector instance.
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.
490
578
 
491
579
  #### 3. True Multi-Agent Coordination (CRDTs)
492
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.
@@ -609,6 +697,14 @@ Requires `PRISM_ENABLE_HIVEMIND=true`.
609
697
 
610
698
  ## Environment Variables
611
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
+
612
708
  <details>
613
709
  <summary><strong>Full variable reference</strong></summary>
614
710
 
@@ -637,6 +733,11 @@ Requires `PRISM_ENABLE_HIVEMIND=true`.
637
733
  | `PRISM_SCHOLAR_MAX_ARTICLES_PER_RUN` | No | Max articles per Scholar run (default: `3`) |
638
734
  | `PRISM_HDC_ENABLED` | No | `"true"` (default) to enable HDC cognitive routing pipeline |
639
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`) |
640
741
 
641
742
  </details>
642
743
 
@@ -726,6 +827,10 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
726
827
  | **v6.1** | Prism-Port Vault Export — Obsidian/Logseq `.zip` with YAML frontmatter & `[[Wikilinks]]` | Data sovereignty, PKM interop | ✅ Shipped |
727
828
  | **v6.1** | Cognitive Load & Semantic Search — dynamic graph thinning, search highlights | Contextual working memory | ✅ Shipped |
728
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 |
729
834
  | **v7.x** | Affect-Tagged Memory — sentiment shapes what gets recalled | Affect-modulated retrieval (neuroscience) | 🔭 Horizon |
730
835
  | **v8+** | Zero-Search Retrieval — no index, no ANN, just ask the vector | Holographic Reduced Representations | 🔭 Horizon |
731
836
 
@@ -740,10 +845,13 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
740
845
  ### v6.2: The "Synthesize & Prune" Phase ✅
741
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.
742
847
 
743
- ### v6.5: Cognitive Architecture (Primary)
744
- Full Superposed Memory (SDM) + Hyperdimensional Computing (HDC/VSA) becomes the next major implementation phase, targeting compositional memory states and faster associative retrieval at scale.
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.
745
853
 
746
- ### After v6.5: Future Tracks
854
+ ### Future Tracks
747
855
  - **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence, making surfaced context more behaviorally useful.
748
856
  - **v8+: Zero-Search Retrieval** — Direct vector-addressed recall (“just ask the vector”) reduces retrieval indirection and moves Prism toward truly native associative memory.
749
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
+ }