prism-mcp-server 7.7.1 → 7.8.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
@@ -10,9 +10,9 @@
10
10
 
11
11
  ![Prism Mind Palace Dashboard](docs/mind-palace-dashboard.png)
12
12
 
13
- **Your AI agent forgets everything between sessions. Prism fixes that.**
13
+ **Your AI agent forgets everything between sessions. Prism fixes that — then teaches it to think.**
14
14
 
15
- One command. Persistent memory. Local-first by default. Optional cloud power-ups.
15
+ Prism v7.8 is a true **Cognitive Architecture** inspired by human brain mechanics. Beyond flat vector search, your agent now forms principles from experience, follows causal trains of thought, and possesses the self-awareness to know when it lacks information. **Your agents don't just remember; they learn.**
16
16
 
17
17
  ```bash
18
18
  npx -y prism-mcp-server
@@ -28,6 +28,7 @@ Works with **Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Gem
28
28
  - [Setup Guides](#setup-guides)
29
29
  - [Universal Import: Bring Your History](#universal-import-bring-your-history)
30
30
  - [What Makes Prism Different](#what-makes-prism-different)
31
+ - [Cognitive Architecture (v7.8)](#-cognitive-architecture-v78)
31
32
  - [Data Privacy & Egress](#data-privacy--egress)
32
33
  - [Use Cases](#use-cases)
33
34
  - [What's New](#whats-new)
@@ -45,15 +46,17 @@ Works with **Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Gem
45
46
 
46
47
  Every time you start a new conversation with an AI coding assistant, it starts from scratch. You re-explain your architecture, re-describe your decisions, re-list your TODOs. Hours of context — gone.
47
48
 
48
- **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.
49
+ **Prism gives your agent a brain that persists — and then teaches it to reason.** Save what matters at the end of each session. Load it back instantly on the next one. But Prism goes far beyond storage: it consolidates raw experience into lasting principles, traverses causal chains to surface root causes, and knows when to say *"I don't know."*
49
50
 
50
- > 📌 **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.
51
+ > 📌 **Terminology:** Throughout this doc, **"Prism"** refers to the MCP server and cognitive memory 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.
51
52
 
52
- Prism has two pillars:
53
+ Prism has three pillars:
53
54
 
54
- 1. **🧠 Persistent Memory** — Memories are ranked like a human brain: recently and frequently accessed context surfaces first, while stale context fades naturally. The result is retrieval quality that no flat vector search can match. *(See [Scientific Foundation](#-scientific-foundation) for the ACT-R math.)*
55
+ 1. **🧠 Cognitive Memory** — Memories are ranked like a human brain: recently and frequently accessed context surfaces first, while stale context fades naturally via ACT-R activation decay. Raw experience consolidates into semantic principles through Hebbian learning. The result is retrieval quality that no flat vector search can match. *(See [Cognitive Architecture](#-cognitive-architecture-v78) and [Scientific Foundation](#-scientific-foundation).)*
55
56
 
56
- 2. **🏭 Autonomous Execution (Dark Factory)** — When you're ready, Prism can run coding tasks end-to-end with a fail-closed pipeline where an adversarial evaluator catches bugs the generator missedbefore you ever see the PR. *(See [Dark Factory](#-dark-factory--adversarial-autonomous-pipelines).)*
57
+ 2. **🔗 Multi-Hop Reasoning** — When your agent searches for "Error X", Prism doesn't just find logs mentioning "Error X". Spreading activation traverses the causal graph and brings back "Workaround Y", which is connected to "Architecture Decision Z" a literal train of thought. *(See [Cognitive Architecture](#-cognitive-architecture-v78).)*
58
+
59
+ 3. **🏭 Autonomous Execution (Dark Factory)** — When you're ready, Prism can run coding tasks end-to-end with a fail-closed pipeline where an adversarial evaluator catches bugs the generator missed — before you ever see the PR. *(See [Dark Factory](#-dark-factory--adversarial-autonomous-pipelines).)*
57
60
 
58
61
  ---
59
62
 
@@ -456,6 +459,71 @@ When you trigger a Dark Factory pipeline, Prism doesn't just run your task — i
456
459
 
457
460
  ---
458
461
 
462
+ ## 🧠 Cognitive Architecture (v7.8)
463
+
464
+ > *Prism v7.8 is our biggest leap forward yet. We have moved beyond flat vector search and implemented a true Cognitive Architecture inspired by human brain mechanics. With the new ACT-R Spreading Activation Engine, Episodic-to-Semantic memory consolidation, and Uncertainty-Aware Rejection Gates, Prism doesn't just store logs anymore — it forms principles, follows causal trains of thought, and possesses the self-awareness to know when it lacks information.*
465
+
466
+ Standard RAG (Retrieval-Augmented Generation) is now a commodity. Everyone has vector search. What turns a memory *storage* system into a memory *reasoning* system is the cognitive layer between storage and retrieval. Here is what Prism v7.8 builds on top of the vector foundation:
467
+
468
+ ### 1. The Agent Actually Learns (Episodic → Semantic Consolidation)
469
+
470
+ | | Standard RAG | Prism v7.8 |
471
+ |---|---|---|
472
+ | **Memory** | Giant, flat transcript of past events | Dual-memory: Episodic events + Semantic rules |
473
+ | **Recall** | Re-reads everything linearly | Retrieves distilled principles instantly |
474
+ | **Learning** | None — every session starts cold | Hebbian: confidence increases with repeated reinforcement |
475
+
476
+ **How it works:** When Prism compacts session history, it doesn't just summarize text — it extracts *principles*. Raw event logs ("We deployed v2.3 and the auth service crashed because the JWT secret was rotated") consolidate into a semantic rule ("JWT secrets must be rotated before deployment, not during"). These rules live in a dedicated `semantic_knowledge` table with `confidence` scores that increase every time the pattern is observed. **Your agent doesn't just remember what it did; it learns *how the world works* over time.** This is true Hebbian learning: neurons that fire together wire together.
477
+
478
+ ### 2. "Train of Thought" Reasoning (Spreading Activation & Causality)
479
+
480
+ | | Standard RAG | Prism v7.8 |
481
+ |---|---|---|
482
+ | **Search** | Cosine similarity to the query | Multi-hop graph traversal with lateral inhibition |
483
+ | **Scope** | Only finds things that *look like* the prompt | Follows causal chains across memories |
484
+ | **Root cause** | Missed entirely | Surfaced via `caused_by` / `led_to` edges |
485
+
486
+ **How it works:** When compacting memories, Prism extracts causal links (`caused_by`, `led_to`) and persists them as edges in the knowledge graph. At retrieval time, ACT-R spreading activation propagates through these edges with a damped fan effect (`1 / ln(fan + e)`) to prevent hub-flooding, lateral inhibition to suppress noise, and configurable hop depth. If you search for "Error X", the engine traverses the graph and brings back "Workaround Y" → "Architecture Decision Z" — a literal train of thought instead of a static search result.
487
+
488
+ ```
489
+ Query: "Why does the API timeout?"
490
+
491
+ ┌─────────────┼─────────────┐
492
+ ▼ ▼ ▼
493
+ [Memory: API [Memory: [Memory:
494
+ timeout error] DB pool rate limiter
495
+ exhaustion] misconfigured]
496
+ │ │
497
+ ▼ ▼
498
+ [Memory: [Memory:
499
+ caused_by → led_to →
500
+ connection connection
501
+ leak in v2.1] pool patch
502
+ in v2.2]
503
+ ```
504
+
505
+ ### 3. Self-Awareness & The End of Hallucinations (The Rejection Gate)
506
+
507
+ | | Standard RAG | Prism v7.8 |
508
+ |---|---|---|
509
+ | **Bad query** | Returns top-5 garbage results | Returns `rejected: true` with reason |
510
+ | **Confidence** | Always 100% confident (even when wrong) | Measures gap-distance and entropy |
511
+ | **Hallucination risk** | High — LLM gets garbage context | Low — LLM told "you don't know" |
512
+
513
+ **How it works:** The **Uncertainty-Aware Rejection Gate** operates on two signals: *similarity floor* (is the best match even remotely relevant?) and *gap distance* (is there meaningful separation between the top results, or are they all equally mediocre?). When both signals indicate low confidence, Prism returns a structured rejection — telling the LLM "I searched my memory, and I confidently do not know the answer" — instead of feeding it garbage context that causes hallucinations. In the current LLM landscape, **an agent that knows its own boundaries is a massive competitive advantage.**
514
+
515
+ ### 4. Block Amnesia Solved (Dynamic Fast Weight Decay)
516
+
517
+ | | Standard RAG | Prism v7.8 |
518
+ |---|---|---|
519
+ | **Decay** | Uniform (everything fades equally) | Dual-rate: episodic fades fast, semantic persists |
520
+ | **Core knowledge** | Forgotten over time | Permanently anchored via `is_rollup` flag |
521
+ | **Personality drift** | Common in long-lived agents | Prevented by Long-Term Context anchors |
522
+
523
+ **How it works:** Most memory systems decay everything at the same rate, meaning agents eventually forget their core system instructions as time passes. Prism applies ACT-R base-level activation decay (`B_i = ln(Σ t_j^(-d))`) with a **50% slower decay rate for semantic rollup nodes** (`ageModifier = 0.5` for `is_rollup` entries). The agent will naturally forget what it ate for breakfast (raw episodic chatter), but it will permanently remember its core personality, project rules, and hard-won architectural decisions. The result: Long-Term Context anchors that survive indefinitely.
524
+
525
+ ---
526
+
459
527
  ## 🔒 Data Privacy & Egress
460
528
 
461
529
  **Where is my data stored?**
@@ -623,8 +691,10 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
623
691
 
624
692
  ## 🆕 What's New
625
693
 
626
- > **Current release: v7.5.0**
694
+ > **Current release: v7.8.0 — Cognitive Architecture**
627
695
 
696
+ - 🧠 **v7.8.0 — Cognitive Architecture:** The biggest leap forward yet. Moved beyond flat vector search into a true cognitive architecture inspired by human brain mechanics. Episodic-to-Semantic memory consolidation (Hebbian learning), ACT-R Spreading Activation with multi-hop causal reasoning, Uncertainty-Aware Rejection Gate (your agent can say "I don't know"), and Dynamic Fast Weight Decay (semantic memories outlive episodic chatter by 2×). **Your agents don't just remember; they learn.** → [Cognitive Architecture](#-cognitive-architecture-v78)
697
+ - 🌐 **v7.7.0 — Cloud-Native SSE Transport:** Full unauthenticated and authenticated Server-Sent Events MCP support for seamless network deployments.
628
698
  - 🩺 **v7.5.0 — Intent Health Dashboard + Security Hardening:** Real-time 0–100 project health scoring (staleness × TODO load × decisions). 10 XSS injection vectors patched. Algorithm hardened with NaN guards and score ceiling.
629
699
  - ⚔️ **v7.4.0 — Adversarial Evaluation:** Split-brain anti-sycophancy pipeline. Generator and evaluator in isolated roles with evidence-bound findings.
630
700
  - 🏭 **v7.3.x — Dark Factory + Stability:** Fail-closed 3-gate execution pipeline. Dashboard stability and verification diagnostics.
@@ -635,7 +705,7 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
635
705
 
636
706
  ## ⚔️ How Prism Compares
637
707
 
638
- Standard memory servers (like Mem0, Zep, or the baseline Anthropic MCP) act as passive filing cabinets — they wait for the LLM to search them. **Prism is an active cognitive architecture.** Designed specifically for the **Model Context Protocol (MCP)**, Prism doesn't just store vectors; it manages the LLM's context window autonomously.
708
+ Standard memory servers (like Mem0, Zep, or the baseline Anthropic MCP) act as passive filing cabinets — they wait for the LLM to search them. **Prism is an active cognitive architecture.** Designed specifically for the **Model Context Protocol (MCP)**, Prism doesn't just store vectors it consolidates experience into principles, traverses causal graphs for multi-hop reasoning, and rejects queries it can't confidently answer.
639
709
 
640
710
  ### 📊 Feature-by-Feature Comparison
641
711
 
@@ -644,7 +714,7 @@ Standard memory servers (like Mem0, Zep, or the baseline Anthropic MCP) act as p
644
714
  | **Primary Interface** | **Native MCP** (Tools, Prompts, Resources) | REST API & Python/TS SDKs | REST API & Python/TS SDKs | Native MCP (Tools only) |
645
715
  | **Storage Engine** | **BYO SQLite or Supabase** | Managed Cloud / VectorDBs | Managed Cloud / Postgres | Local SQLite only |
646
716
  | **Context Assembly** | **Progressive (Quick/Std/Deep)** | Top-K Semantic Search | Top-K + Temporal Summaries | Basic Entity Search |
647
- | **Memory Mechanics** | **ACT-R Activation (recency×freq), SDM, HDC** | Basic Vector + Entity | Fading Temporal Graph | None (Infinite growth) |
717
+ | **Memory Mechanics** | **ACT-R Activation, Spreading Activation, Hebbian Consolidation, Rejection Gate** | Basic Vector + Entity | Fading Temporal Graph | None (Infinite growth) |
648
718
  | **Multi-Agent Sync** | **CRDT (Add-Wins / LWW)** | Cloud locks | Postgres locks | ❌ None (Data races) |
649
719
  | **Data Compression** | **TurboQuant (7x smaller vectors)** | ❌ Standard F32 Vectors | ❌ Standard Vectors | ❌ No Vectors |
650
720
  | **Observability** | **OTel Traces + Built-in PWA UI** | Cloud Dashboard | Cloud Dashboard | ❌ None |
@@ -883,7 +953,7 @@ Prism is a **stdio-based MCP server** that manages persistent agent memory. Here
883
953
  ```
884
954
  ┌──────────────────────────────────────────────────────────┐
885
955
  │ MCP Client (Claude Desktop / Cursor / Antigravity) │
886
- │ ↕ stdio (JSON-RPC)
956
+ │ ↕ stdio / SSE (JSON-RPC)
887
957
  ├──────────────────────────────────────────────────────────┤
888
958
  │ Prism MCP Server │
889
959
  │ │
@@ -894,8 +964,17 @@ Prism is a **stdio-based MCP server** that manages persistent agent memory. Here
894
964
  │ └──────┬───────┘ └──────────────┘ └────────────────┘ │
895
965
  │ ↕ │
896
966
  │ ┌────────────────────────────────────────────────────┐ │
967
+ │ │ Cognitive Engine (v7.8) │ │
968
+ │ │ • ACT-R Spreading Activation (multi-hop) │ │
969
+ │ │ • Episodic → Semantic Consolidation (Hebbian) │ │
970
+ │ │ • Uncertainty-Aware Rejection Gate │ │
971
+ │ │ • Dynamic Fast Weight Decay (dual-rate) │ │
972
+ │ │ • HDC Cognitive Routing (XOR binding) │ │
973
+ │ └──────┬─────────────────────────────────────────────┘ │
974
+ │ ↕ │
975
+ │ ┌────────────────────────────────────────────────────┐ │
897
976
  │ │ Storage Engine │ │
898
- │ │ Local: SQLite + FTS5 + TurboQuant vectors
977
+ │ │ Local: SQLite + FTS5 + TurboQuant + semantic_knowledge
899
978
  │ │ Cloud: Supabase + pgvector │ │
900
979
  │ └────────────────────────────────────────────────────┘ │
901
980
  │ ↕ │
@@ -924,6 +1003,8 @@ Prism is a **stdio-based MCP server** that manages persistent agent memory. Here
924
1003
  |-------|-----------|---------|
925
1004
  | **Session Ledger** | SQLite (append-only) | Immutable audit trail of all agent work |
926
1005
  | **Handoff State** | SQLite (upsert, versioned) | Live project context with OCC + CRDT merging |
1006
+ | **Semantic Knowledge** | SQLite (`semantic_knowledge`) | Hebbian-style distilled rules with confidence scoring |
1007
+ | **Memory Links** | SQLite (`memory_links`) | Causal graph edges (`caused_by`, `led_to`, `synthesized_from`) |
927
1008
  | **Keyword Search** | FTS5 virtual tables | Zero-dependency full-text search |
928
1009
  | **Semantic Search** | TurboQuant compressed vectors | 10× compressed 768-dim embeddings, three-tier retrieval |
929
1010
  | **Cloud Sync** | Supabase + pgvector | Optional multi-device/team sync |
@@ -943,7 +1024,7 @@ All platforms benefit from the **server-side fallback** (v5.2.1): if `session_lo
943
1024
 
944
1025
  ## 🧬 Scientific Foundation
945
1026
 
946
- Prism is evolving from smart session logging toward a **cognitive memory architecture** — grounded in real research, not marketing.
1027
+ Prism has evolved from smart session logging into a **cognitive memory architecture** — grounded in real research, not marketing. Every retrieval decision is backed by peer-reviewed models from cognitive psychology, neuroscience, and distributed computing.
947
1028
 
948
1029
  | Phase | Feature | Inspired By | Status |
949
1030
  |-------|---------|-------------|--------|
@@ -962,35 +1043,42 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
962
1043
  | **v6.1** | Prism-Port Vault Export — Obsidian/Logseq `.zip` with YAML frontmatter & `[[Wikilinks]]` | Data sovereignty, PKM interop | ✅ Shipped |
963
1044
  | **v6.1** | Cognitive Load & Semantic Search — dynamic graph thinning, search highlights | Contextual working memory | ✅ Shipped |
964
1045
  | **v6.2** | Synthesize & Prune — automated edge synthesis, graph pruning, SLO observability | Implicit associative memory | ✅ Shipped |
965
- | **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 |
1046
+ | **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) | ✅ Shipped |
966
1047
  | **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 |
967
1048
  | **v7.0** | Composite Retrieval Scoring — `0.7 × similarity + 0.3 × σ(activation)`; configurable via `PRISM_ACTR_WEIGHT_*` | Hybrid cognitive-neural retrieval models | ✅ Shipped |
968
1049
  | **v7.0** | AccessLogBuffer — in-memory batch-write buffer with 5s flush; prevents SQLite `SQLITE_BUSY` under parallel agents | Production reliability engineering | ✅ Shipped |
969
1050
  | **v7.3** | Dark Factory — 3-gate fail-closed EXECUTE pipeline (parse → type → scope) with structured JSON action contract | Industrial safety systems (defense-in-depth, fail-closed valves) | ✅ Shipped |
970
1051
  | **v7.2** | Verification-first harness — spec-freeze contract, rubric hash lock, multi-layer assertions, CLI `verify` commands | Programmatic verification systems + adversarial validation loops | ✅ Shipped |
971
1052
  | **v7.4** | Adversarial Evaluation — PLAN_CONTRACT + EVALUATE with isolated generator/evaluator roles, pre-committed rubrics, and evidence-bound findings | Anti-sycophancy research, adversarial ML evaluation frameworks | ✅ Shipped |
972
- | **v7.5** | Intent Health Dashboard — 3-signal scoring algorithm (staleness, TODO load, decisions), comprehensive XSS hardening (10 vectors), NaN/`Infinity` guards | Proactive monitoring, defense-in-depth security | ✅ Shipped |
1053
+ | **v7.5** | Intent Health Dashboard — 3-signal scoring (staleness × TODO × decisions) with NaN guards and score ceiling | Production observability, proactive monitoring | ✅ Shipped |
1054
+ | **v7.7** | Cloud-Native SSE Transport — full network-accessible MCP server via Server-Sent Events | Distributed systems, cloud-native architecture | ✅ Shipped |
1055
+ | **v7.8** | Episodic→Semantic Consolidation — raw event logs distilled into `semantic_knowledge` rules with confidence scoring and instance tracking | Hebbian learning ("neurons that fire together wire together"), sleep consolidation (neuroscience) | ✅ Shipped |
1056
+ | **v7.8** | Multi-Hop Causal Reasoning — spreading activation traverses `caused_by`/`led_to` edges with damped fan effect (`1/ln(fan+e)`) and lateral inhibition | ACT-R spreading activation (Anderson), Collins & Loftus (1975) | ✅ Shipped |
1057
+ | **v7.8** | Uncertainty-Aware Rejection Gate — dual-signal (similarity floor + gap distance) safety layer prevents hallucination from low-confidence retrievals | Metacognition research, uncertainty quantification | ✅ Shipped |
1058
+ | **v7.8** | Dynamic Fast Weight Decay — `is_rollup` semantic nodes decay 50% slower (`ageModifier = 0.5`) than episodic entries, creating Long-Term Context anchors | ACT-R base-level activation with differential decay rates | ✅ Shipped |
973
1059
  | **v7.x** | Affect-Tagged Memory — sentiment shapes what gets recalled | Affect-modulated retrieval (neuroscience) | 🔭 Horizon |
974
1060
  | **v8+** | Zero-Search Retrieval — no index, no ANN, just ask the vector | Holographic Reduced Representations | 🔭 Horizon |
975
1061
 
976
- > Informed by LeCun's "Why AI Systems Don't Learn" (Dupoux, LeCun, Malik) and Kanerva's SDM.
1062
+ > Informed by Anderson's ACT-R (Adaptive Control of Thought—Rational), Collins & Loftus spreading activation networks (1975), Kanerva's SDM (1988), Hebb's learning rule, and LeCun's "Why AI Systems Don't Learn" (Dupoux, LeCun, Malik).
977
1063
 
978
1064
  ---
979
1065
 
980
1066
  ## 📦 Milestones & Roadmap
981
1067
 
982
- > **Current: v7.5.0** — Intent Health Dashboard + XSS Hardening ([CHANGELOG](CHANGELOG.md))
1068
+ > **Current: v7.8.0** — Cognitive Architecture ([CHANGELOG](CHANGELOG.md))
983
1069
 
984
1070
  | Release | Headline |
985
1071
  |---------|----------|
986
- | **v7.5** | Intent Health scoring + 10 XSS patches |
987
- | **v7.4** | Adversarial Evaluation (anti-sycophancy) |
988
- | **v7.3** | Dark Factory fail-closed execution |
989
- | **v7.2** | Verification Harness |
990
- | **v7.1** | Task Router |
991
- | **v7.0** | ACT-R Activation Memory |
992
- | **v6.5** | HDC Cognitive Routing |
993
- | **v6.2** | Synthesize & Prune |
1072
+ | **v7.8** | 🧠 Cognitive Architecture Hebbian consolidation, multi-hop reasoning, rejection gate, dynamic decay |
1073
+ | **v7.7** | 🌐 Cloud-Native SSE Transport |
1074
+ | **v7.5** | 🩺 Intent Health Dashboard + Security Hardening |
1075
+ | **v7.4** | ⚔️ Adversarial Evaluation (anti-sycophancy) |
1076
+ | **v7.3** | 🏭 Dark Factory fail-closed execution |
1077
+ | **v7.2** | Verification Harness |
1078
+ | **v7.1** | 🚦 Task Router |
1079
+ | **v7.0** | 🧬 ACT-R Activation Memory |
1080
+ | **v6.5** | 🔮 HDC Cognitive Routing |
1081
+ | **v6.2** | 🧩 Synthesize & Prune |
994
1082
 
995
1083
  ### Future Tracks
996
1084
  - **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence.
@@ -1037,4 +1125,4 @@ MIT
1037
1125
 
1038
1126
  ---
1039
1127
 
1040
- <sub>**Keywords:** MCP server, Model Context Protocol, Claude Desktop memory, persistent session memory, AI agent memory, local-first, SQLite MCP, Mind Palace, time travel, visual memory, VLM image captioning, OpenTelemetry, GDPR, agent telepathy, multi-agent sync, behavioral memory, cursorrules, Ollama MCP, Brave Search MCP, TurboQuant, progressive context loading, knowledge management, LangChain retriever, LangGraph agent</sub>
1128
+ <sub>**Keywords:** MCP server, Model Context Protocol, Claude Desktop memory, persistent session memory, AI agent memory, cognitive architecture, ACT-R spreading activation, Hebbian learning, episodic semantic consolidation, multi-hop reasoning, uncertainty rejection gate, local-first, SQLite MCP, Mind Palace, time travel, visual memory, VLM image captioning, OpenTelemetry, GDPR, agent telepathy, multi-agent sync, behavioral memory, cursorrules, Ollama MCP, Brave Search MCP, TurboQuant, progressive context loading, knowledge management, LangChain retriever, LangGraph agent</sub>
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Apply ACT-R inspired Spreading Activation, Lateral Inhibition, and Fan Effect.
3
+ * It traverses the `memory_links` table over T iterations starting from the given anchors.
4
+ */
5
+ export async function applySpreadingActivation(db, anchors, options, userId) {
6
+ if (!options.enabled || anchors.length === 0)
7
+ return anchors;
8
+ const T = options.iterations ?? 3;
9
+ const S = options.spreadFactor ?? 0.8;
10
+ const softM = 20; // Soft lateral inhibition during propagation
11
+ const finalM = options.lateralInhibition ?? 7; // Final hard lateral inhibition
12
+ // State: current activation score for nodes.
13
+ let activeNodes = new Map();
14
+ for (const anchor of anchors) {
15
+ activeNodes.set(anchor.id, anchor.similarity || 1.0);
16
+ }
17
+ for (let t = 0; t < T; t++) {
18
+ const nextNodes = new Map();
19
+ // Preserve existing activation: a_i^(t+1) = a_i^(t) + incoming
20
+ for (const [id, score] of activeNodes.entries()) {
21
+ nextNodes.set(id, score);
22
+ }
23
+ const currentIds = Array.from(activeNodes.keys());
24
+ if (currentIds.length === 0)
25
+ break;
26
+ const placeholders = currentIds.map(() => '?').join(',');
27
+ // Fetch edges connected to active nodes with LIMIT to prevent explosion on hub nodes.
28
+ const edgeQuery = `
29
+ SELECT source_id, target_id, strength
30
+ FROM memory_links
31
+ WHERE source_id IN (${placeholders}) OR target_id IN (${placeholders})
32
+ LIMIT 200
33
+ `;
34
+ const edgeArgs = [...currentIds, ...currentIds];
35
+ const edgeRes = await db.execute({ sql: edgeQuery, args: edgeArgs });
36
+ // Compute out-degree (Fan Effect) directly from fetched edge rows
37
+ // instead of a separate SQL round-trip — halves query count per iteration.
38
+ const fanMap = new Map();
39
+ for (const row of edgeRes.rows) {
40
+ const src = row.source_id;
41
+ if (activeNodes.has(src)) {
42
+ fanMap.set(src, (fanMap.get(src) || 0) + 1);
43
+ }
44
+ }
45
+ for (const row of edgeRes.rows) {
46
+ const source = row.source_id;
47
+ const target = row.target_id;
48
+ const strength = Number(row.strength);
49
+ // Forward flow: Source is active, flows to Target
50
+ if (activeNodes.has(source)) {
51
+ const fan = fanMap.get(source) || 1;
52
+ // Dampened fan effect: instead of strict 1/fan, we use 1 / ln(fan + e)
53
+ const dampedFan = Math.log(fan + Math.E);
54
+ const flow = S * (strength * activeNodes.get(source) / dampedFan);
55
+ nextNodes.set(target, (nextNodes.get(target) || 0) + flow);
56
+ }
57
+ // Backward flow: Target is active, flows backward to Source with a heavier penalty
58
+ if (activeNodes.has(target)) {
59
+ const flow = (S * 0.5) * (strength * activeNodes.get(target));
60
+ nextNodes.set(source, (nextNodes.get(source) || 0) + flow);
61
+ }
62
+ }
63
+ // Soft lateral inhibition: Keep only top softM candidates to prevent explosion
64
+ const sorted = Array.from(nextNodes.entries()).sort((a, b) => b[1] - a[1]);
65
+ activeNodes = new Map(sorted.slice(0, softM));
66
+ }
67
+ // Final evaluation
68
+ const finalIds = Array.from(activeNodes.keys()).slice(0, finalM);
69
+ const anchorMap = new Map();
70
+ for (const a of anchors)
71
+ anchorMap.set(a.id, a);
72
+ const finalResults = [];
73
+ const missingIds = finalIds.filter(id => !anchorMap.has(id));
74
+ if (missingIds.length > 0) {
75
+ const placeholders = missingIds.map(() => '?').join(',');
76
+ const missingQuery = `
77
+ SELECT id, project, summary, session_date, decisions, files_changed
78
+ FROM session_ledger
79
+ WHERE id IN (${placeholders}) AND deleted_at IS NULL AND user_id = ?
80
+ `;
81
+ const missingRes = await db.execute({ sql: missingQuery, args: [...missingIds, userId] });
82
+ for (const row of missingRes.rows) {
83
+ anchorMap.set(row.id, {
84
+ id: row.id,
85
+ project: row.project,
86
+ summary: row.summary,
87
+ session_date: row.session_date,
88
+ decisions: row.decisions && typeof row.decisions === 'string' ? JSON.parse(row.decisions) : undefined,
89
+ files_changed: row.files_changed && typeof row.files_changed === 'string' ? JSON.parse(row.files_changed) : undefined,
90
+ similarity: 0.0 // Base similarity is 0 since it wasn't matched originally
91
+ });
92
+ }
93
+ }
94
+ // Compute Hybrid Score and return M nodes
95
+ for (const id of finalIds) {
96
+ if (anchorMap.has(id)) {
97
+ const node = anchorMap.get(id);
98
+ const activationScore = activeNodes.get(id) || 0;
99
+ node.activationScore = activationScore;
100
+ // Hybrid blend: 70% original match relevance, 30% activation structural energy
101
+ node.hybridScore = (node.similarity * 0.7) + (activationScore * 0.3);
102
+ finalResults.push(node);
103
+ }
104
+ }
105
+ // Sort descending by Hybrid Score
106
+ return finalResults.sort((a, b) => (b.hybridScore || 0) - (a.hybridScore || 0));
107
+ }
@@ -516,6 +516,42 @@ export class SqliteStorage {
516
516
  vector BLOB NOT NULL
517
517
  )
518
518
  `);
519
+ // ─── Phase 4 Migration: Semantic Knowledge Table ──────────────
520
+ //
521
+ // REVIEWER NOTE: Created to separate timeless semantic facts from
522
+ // chronological episodic ledger events, per Phase 4 consolidation.
523
+ await this.db.execute(`
524
+ CREATE TABLE IF NOT EXISTS semantic_knowledge (
525
+ id TEXT PRIMARY KEY,
526
+ project TEXT NOT NULL,
527
+ user_id TEXT NOT NULL DEFAULT '',
528
+ concept TEXT NOT NULL,
529
+ description TEXT NOT NULL DEFAULT '',
530
+ confidence REAL DEFAULT 0.5,
531
+ instances INTEGER DEFAULT 1,
532
+ related_entities TEXT DEFAULT '[]',
533
+ created_at TEXT DEFAULT (datetime('now')),
534
+ updated_at TEXT DEFAULT (datetime('now'))
535
+ )
536
+ `);
537
+ // v7.8 Migration: Rename legacy columns if upgrading from older schema
538
+ try {
539
+ await this.db.execute(`ALTER TABLE semantic_knowledge RENAME COLUMN rule TO description`);
540
+ }
541
+ catch { /* column already renamed or doesn't exist */ }
542
+ try {
543
+ await this.db.execute(`ALTER TABLE semantic_knowledge ADD COLUMN instances INTEGER DEFAULT 1`);
544
+ }
545
+ catch { /* column already exists */ }
546
+ try {
547
+ await this.db.execute(`ALTER TABLE semantic_knowledge ADD COLUMN related_entities TEXT DEFAULT '[]'`);
548
+ }
549
+ catch { /* column already exists */ }
550
+ try {
551
+ await this.db.execute(`ALTER TABLE semantic_knowledge ADD COLUMN updated_at TEXT DEFAULT (datetime('now'))`);
552
+ }
553
+ catch { /* column already exists */ }
554
+ await this.db.execute(`CREATE INDEX IF NOT EXISTS idx_semantic_project ON semantic_knowledge(project)`);
519
555
  // v6.5 Migration: Add prng_version column if missing
520
556
  try {
521
557
  await this.db.execute(`ALTER TABLE hdc_dictionary ADD COLUMN prng_version INTEGER DEFAULT 1`);
@@ -1341,6 +1377,21 @@ export class SqliteStorage {
1341
1377
  last_accessed_at: r.last_accessed_at,
1342
1378
  relevance: r.relevance,
1343
1379
  }));
1380
+ if (params.activation?.enabled) {
1381
+ // Normalise FTS5 ranks (more negative = better) into positive 0-1 similarity score approximation
1382
+ const mappedAnchors = results.map(r => ({
1383
+ id: r.id,
1384
+ project: r.project,
1385
+ summary: r.summary,
1386
+ similarity: 1.0 / (1.0 + Math.abs(r.relevance || 0)),
1387
+ session_date: r.session_date,
1388
+ decisions: r.decisions,
1389
+ files_changed: r.files_changed,
1390
+ }));
1391
+ const { applySpreadingActivation } = await import("../memory/spreadingActivation.js");
1392
+ const activated = await applySpreadingActivation(this.db, mappedAnchors, params.activation, params.userId);
1393
+ return { count: activated.length, results: activated };
1394
+ }
1344
1395
  return { count: results.length, results };
1345
1396
  }
1346
1397
  catch (err) {
@@ -1399,6 +1450,21 @@ export class SqliteStorage {
1399
1450
  importance: r.importance,
1400
1451
  last_accessed_at: r.last_accessed_at,
1401
1452
  }));
1453
+ if (params.activation?.enabled) {
1454
+ // Base similarity mapped as 1.0 for LIKE exact/partial matches
1455
+ const mappedAnchors = results.map(r => ({
1456
+ id: r.id,
1457
+ project: r.project,
1458
+ summary: r.summary,
1459
+ similarity: 1.0,
1460
+ session_date: r.session_date,
1461
+ decisions: r.decisions,
1462
+ files_changed: r.files_changed,
1463
+ }));
1464
+ const { applySpreadingActivation } = await import("../memory/spreadingActivation.js");
1465
+ const activated = await applySpreadingActivation(this.db, mappedAnchors, params.activation, params.userId);
1466
+ return { count: activated.length, results: activated };
1467
+ }
1402
1468
  return { count: results.length, results };
1403
1469
  }
1404
1470
  async searchMemory(params) {
@@ -1424,7 +1490,7 @@ export class SqliteStorage {
1424
1490
  args.push(params.limit);
1425
1491
  const sql = `
1426
1492
  SELECT l.id, l.project, l.summary, l.decisions, l.files_changed,
1427
- l.session_date, l.created_at,
1493
+ l.session_date, l.created_at, l.is_rollup, l.importance, l.last_accessed_at,
1428
1494
  (1 - vector_distance_cos(l.embedding, vector(?))) AS similarity
1429
1495
  FROM session_ledger l
1430
1496
  WHERE ${conditions.join(" AND ")}
@@ -1433,7 +1499,7 @@ export class SqliteStorage {
1433
1499
  `;
1434
1500
  const result = await this.db.execute({ sql, args });
1435
1501
  // Filter by similarity threshold and format results
1436
- return result.rows
1502
+ const baseResults = result.rows
1437
1503
  .filter(r => r.similarity >= params.similarityThreshold)
1438
1504
  .map(r => ({
1439
1505
  id: r.id,
@@ -1443,7 +1509,15 @@ export class SqliteStorage {
1443
1509
  session_date: (r.session_date || r.created_at),
1444
1510
  decisions: this.parseJsonColumn(r.decisions),
1445
1511
  files_changed: this.parseJsonColumn(r.files_changed),
1512
+ is_rollup: Boolean(r.is_rollup),
1513
+ importance: r.importance ?? 0,
1514
+ last_accessed_at: r.last_accessed_at || null,
1446
1515
  }));
1516
+ if (params.activation?.enabled) {
1517
+ const { applySpreadingActivation } = await import("../memory/spreadingActivation.js");
1518
+ return applySpreadingActivation(this.db, baseResults, params.activation, params.userId);
1519
+ }
1520
+ return baseResults;
1447
1521
  }
1448
1522
  catch (err) {
1449
1523
  // ─── TIER 2 FALLBACK: Asymmetric TurboQuant search in JS ───
@@ -1495,7 +1569,8 @@ export class SqliteStorage {
1495
1569
  }
1496
1570
  const fallbackSql = `
1497
1571
  SELECT id, project, summary, decisions, files_changed,
1498
- session_date, created_at, embedding_compressed, embedding_turbo_radius
1572
+ session_date, created_at, is_rollup, importance, last_accessed_at,
1573
+ embedding_compressed, embedding_turbo_radius
1499
1574
  FROM session_ledger
1500
1575
  WHERE ${fallbackConditions.join(" AND ")}
1501
1576
  `;
@@ -1517,6 +1592,9 @@ export class SqliteStorage {
1517
1592
  session_date: (row.session_date || row.created_at),
1518
1593
  decisions: this.parseJsonColumn(row.decisions),
1519
1594
  files_changed: this.parseJsonColumn(row.files_changed),
1595
+ is_rollup: Boolean(row.is_rollup),
1596
+ importance: row.importance ?? 0,
1597
+ last_accessed_at: row.last_accessed_at || null,
1520
1598
  });
1521
1599
  }
1522
1600
  }
@@ -1526,9 +1604,14 @@ export class SqliteStorage {
1526
1604
  }
1527
1605
  // Sort by similarity descending and limit
1528
1606
  scored.sort((a, b) => b.similarity - a.similarity);
1607
+ const baseResults = scored.slice(0, params.limit);
1529
1608
  debugLog(`[SqliteStorage] Tier-2 TurboQuant fallback: scored ${fallbackResult.rows.length} entries, ` +
1530
1609
  `${scored.length} above threshold`);
1531
- return scored.slice(0, params.limit);
1610
+ if (params.activation?.enabled) {
1611
+ const { applySpreadingActivation } = await import("../memory/spreadingActivation.js");
1612
+ return applySpreadingActivation(this.db, baseResults, params.activation, params.userId);
1613
+ }
1614
+ return baseResults;
1532
1615
  }
1533
1616
  catch (fallbackErr) {
1534
1617
  // Both tiers failed — return empty
@@ -3090,4 +3173,39 @@ export class SqliteStorage {
3090
3173
  override_reason: row.override_reason || undefined
3091
3174
  };
3092
3175
  }
3176
+ // ─── v7.5: Semantic Consolidation ────────────────────────────────
3177
+ async upsertSemanticKnowledge(data) {
3178
+ const existing = await this.db.execute({
3179
+ sql: `SELECT id, instances, confidence FROM semantic_knowledge WHERE project = ? AND concept = ? LIMIT 1`,
3180
+ args: [data.project, data.concept]
3181
+ });
3182
+ if (existing.rows.length > 0) {
3183
+ const row = existing.rows[0];
3184
+ const newConfidence = Math.min(1.0, row.confidence + 0.1);
3185
+ await this.db.execute({
3186
+ sql: `UPDATE semantic_knowledge SET instances = instances + 1, confidence = ?, updated_at = ? WHERE id = ?`,
3187
+ args: [newConfidence, new Date().toISOString(), row.id]
3188
+ });
3189
+ return row.id;
3190
+ }
3191
+ else {
3192
+ const id = crypto.randomUUID();
3193
+ await this.db.execute({
3194
+ sql: `INSERT INTO semantic_knowledge (id, project, user_id, concept, description, confidence, instances, related_entities, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3195
+ args: [
3196
+ id,
3197
+ data.project,
3198
+ data.userId || '',
3199
+ data.concept,
3200
+ data.description,
3201
+ 0.5,
3202
+ 1,
3203
+ JSON.stringify(data.related_entities || []),
3204
+ new Date().toISOString(),
3205
+ new Date().toISOString()
3206
+ ]
3207
+ });
3208
+ return id;
3209
+ }
3210
+ }
3093
3211
  }
@@ -1440,4 +1440,10 @@ export class SupabaseStorage {
1440
1440
  throw e;
1441
1441
  }
1442
1442
  }
1443
+ // ─── v7.5: Semantic Consolidation ────────────────────────────────
1444
+ async upsertSemanticKnowledge(data) {
1445
+ // For now we just implement graceful degradation/no-op on Supabase until the SQL is deployed.
1446
+ debugLog(`[SupabaseStorage] upsertSemanticKnowledge is not fully implemented in Supabase yet. Skipping for ${data.concept}.`);
1447
+ return crypto.randomUUID();
1448
+ }
1443
1449
  }
@@ -20,27 +20,31 @@ export function isCompactLedgerArgs(args) {
20
20
  // ─── LLM Summarization ────────────────────────────────────────
21
21
  async function summarizeEntries(entries) {
22
22
  const llm = getLLMProvider(); // throws if no API key configured
23
- const entriesText = entries.map((e, i) => `[${i + 1}] ${e.session_date || "unknown date"}: ${e.summary || "no summary"}\n` +
23
+ const entriesText = entries.map((e, i) => `[${i + 1}] ID: ${e.id || "N/A"} | Date: ${e.session_date || "unknown date"}: ${e.summary || "no summary"}\n` +
24
24
  (e.decisions?.length ? ` Decisions: ${e.decisions.join("; ")}\n` : "") +
25
25
  (e.files_changed?.length ? ` Files: ${e.files_changed.join(", ")}\n` : "")).join("\n");
26
26
  const prompt = (`You are compressing a session history log for an AI agent's persistent memory.\n\n` +
27
- `Analyze these ${entries.length} work sessions and produce THREE sections:\n\n` +
28
- `1. SUMMARY (max 300 words): A concise paragraph preserving key decisions, ` +
29
- `important file changes, error resolutions, and architecture changes. ` +
30
- `Omit routine operations and intermediate debugging steps.\n\n` +
31
- `2. PRINCIPLES (1-3 bullet points): Reusable lessons extracted from these sessions. ` +
32
- `These should be actionable engineering insights the agent can apply to future work. ` +
33
- `Format: "- [principle]"\n\n` +
34
- `3. PATTERNS (1-3 bullet points): Recurring behaviors, tools, or workflows observed. ` +
35
- `Format: "- [pattern]"\n\n` +
27
+ `Analyze these ${entries.length} work sessions and output a VALID JSON OBJECT matching this structure:\n` +
28
+ `{\n` +
29
+ ` "summary": "Concise paragraph preserving key decisions, important file changes, error resolutions, and architecture changes. Omit routine operations and intermediate debugging steps.",\n` +
30
+ ` "principles": [\n` +
31
+ ` { "concept": "Brief concept name", "description": "Reusable lesson extracted from sessions", "related_entities": ["tool", "tech"] }\n` +
32
+ ` ],\n` +
33
+ ` "causal_links": [\n` +
34
+ ` { "source_id": "Session ID that caused it", "target_id": "Session ID that was affected", "relation": "led_to" | "caused_by", "reason": "Explanation" }\n` +
35
+ ` ]\n` +
36
+ `}\n\n` +
36
37
  `Sessions to analyze:\n${entriesText}\n\n` +
37
- `Output format (follow exactly):\n` +
38
- `[summary paragraph]\n\n` +
39
- `Principles:\n` +
40
- `- ...\n\n` +
41
- `Patterns:\n` +
42
- `- ...`).substring(0, 30000);
43
- return llm.generateText(prompt);
38
+ `Respond ONLY with raw JSON.`).substring(0, 30000);
39
+ const response = await llm.generateText(prompt);
40
+ try {
41
+ const cleanJson = response.replace(/^```json\n?/, "").replace(/\n?```$/, "");
42
+ return JSON.parse(cleanJson);
43
+ }
44
+ catch (err) {
45
+ debugLog(`[compact_ledger] Failed to parse JSON from LLM: ${err}`);
46
+ return { summary: response, principles: [], causal_links: [] };
47
+ }
44
48
  }
45
49
  // ─── Main Handler ─────────────────────────────────────────────
46
50
  export async function compactLedgerHandler(args) {
@@ -126,19 +130,32 @@ export async function compactLedgerHandler(args) {
126
130
  for (let i = 0; i < oldEntries.length; i += COMPACTION_CHUNK_SIZE) {
127
131
  chunks.push(oldEntries.slice(i, i + COMPACTION_CHUNK_SIZE));
128
132
  }
129
- let finalSummary;
133
+ let finalSummaryText;
134
+ let finalPrinciples = [];
135
+ let finalCausalLinks = [];
130
136
  if (chunks.length === 1) {
131
- finalSummary = await summarizeEntries(chunks[0]);
137
+ const res = await summarizeEntries(chunks[0]);
138
+ finalSummaryText = typeof res === 'string' ? res : (res.summary || JSON.stringify(res));
139
+ finalPrinciples = res.principles || [];
140
+ finalCausalLinks = res.causal_links || [];
132
141
  }
133
142
  else {
134
143
  const chunkSummaries = await Promise.all(chunks.map(chunk => summarizeEntries(chunk)));
144
+ chunkSummaries.forEach(s => {
145
+ finalPrinciples.push(...(s.principles || []));
146
+ finalCausalLinks.push(...(s.causal_links || []));
147
+ });
135
148
  const metaEntries = chunkSummaries.map((s, i) => ({
149
+ id: `chunk-${i}`,
136
150
  session_date: `chunk ${i + 1}`,
137
- summary: s,
151
+ summary: s.summary,
138
152
  decisions: [],
139
153
  files_changed: [],
140
154
  }));
141
- finalSummary = await summarizeEntries(metaEntries);
155
+ const metaRes = await summarizeEntries(metaEntries);
156
+ finalSummaryText = typeof metaRes === 'string' ? metaRes : (metaRes.summary || JSON.stringify(metaRes));
157
+ finalPrinciples.push(...(metaRes.principles || []));
158
+ finalCausalLinks.push(...(metaRes.causal_links || []));
142
159
  }
143
160
  // Collect all unique keywords from rolled-up entries
144
161
  const allKeywords = [...new Set(oldEntries.flatMap((e) => e.keywords || []))];
@@ -148,7 +165,7 @@ export async function compactLedgerHandler(args) {
148
165
  const savedRollup = await storage.saveLedger({
149
166
  project: proj,
150
167
  user_id: PRISM_USER_ID,
151
- summary: `[ROLLUP of ${oldEntries.length} sessions] ${finalSummary}`,
168
+ summary: `[ROLLUP of ${oldEntries.length} sessions] ${finalSummaryText}`,
152
169
  keywords: allKeywords,
153
170
  files_changed: allFiles,
154
171
  decisions: [`Rolled up ${oldEntries.length} sessions on ${new Date().toISOString()}`],
@@ -173,6 +190,48 @@ export async function compactLedgerHandler(args) {
173
190
  debugLog(`[compact_ledger] Failed to create spawned_from link for ${rollupId}: ${err instanceof Error ? err.message : String(err)}`);
174
191
  }
175
192
  }));
193
+ // ── v7.5: Process semantic rules and causal links ──────────
194
+ for (const principle of finalPrinciples) {
195
+ if (!principle.concept || !principle.description)
196
+ continue;
197
+ try {
198
+ const semanticId = await storage.upsertSemanticKnowledge({
199
+ project: proj,
200
+ concept: principle.concept,
201
+ description: principle.description,
202
+ related_entities: principle.related_entities || [],
203
+ userId: PRISM_USER_ID,
204
+ });
205
+ await storage.createLink({
206
+ source_id: rollupId,
207
+ target_id: semanticId,
208
+ link_type: "related_to",
209
+ strength: 0.8,
210
+ metadata: JSON.stringify({ reason: "derived_principle" })
211
+ }, PRISM_USER_ID);
212
+ }
213
+ catch (err) {
214
+ debugLog(`[compact_ledger] Failed to upsert semantic knowledge: ${err instanceof Error ? err.message : String(err)}`);
215
+ }
216
+ }
217
+ for (const link of finalCausalLinks) {
218
+ if (!link.source_id || !link.target_id || !link.relation)
219
+ continue;
220
+ if (link.source_id.startsWith("chunk-") || link.target_id.startsWith("chunk-"))
221
+ continue;
222
+ try {
223
+ await storage.createLink({
224
+ source_id: link.source_id,
225
+ target_id: link.target_id,
226
+ link_type: link.relation,
227
+ strength: 0.9,
228
+ metadata: JSON.stringify({ reason: link.reason || "causal inference during compaction" })
229
+ }, PRISM_USER_ID);
230
+ }
231
+ catch (err) {
232
+ debugLog(`[compact_ledger] Failed to create causal link: ${err instanceof Error ? err.message : String(err)}`);
233
+ }
234
+ }
176
235
  }
177
236
  // Step 5: Archive old entries (soft-delete)
178
237
  for (const entry of oldEntries) {
@@ -63,7 +63,7 @@ export async function knowledgeSearchHandler(args) {
63
63
  throw new Error("Invalid arguments for knowledge_search");
64
64
  }
65
65
  // Phase 1: destructure enable_trace (defaults to false for backward compat)
66
- const { project, query, category, limit = 10, enable_trace = false } = args;
66
+ const { project, query, category, limit = 10, enable_trace = false, activation } = args;
67
67
  debugLog(`[knowledge_search] Searching: project=${project || "all"}, query="${query || ""}", category=${category || "any"}, limit=${limit}`);
68
68
  // Phase 1: Capture total start time for latency measurement
69
69
  const totalStart = performance.now();
@@ -79,6 +79,7 @@ export async function knowledgeSearchHandler(args) {
79
79
  queryText: query || null,
80
80
  limit: Math.min(limit, 50),
81
81
  userId: PRISM_USER_ID,
82
+ activation,
82
83
  });
83
84
  const storageMs = performance.now() - storageStart;
84
85
  const totalMs = performance.now() - totalStart;
@@ -121,7 +122,7 @@ export async function knowledgeSearchHandler(args) {
121
122
  // Mutate results to surface effective importance
122
123
  for (const r of data.results) {
123
124
  if (typeof r.importance === 'number' && r.importance > 0) {
124
- r.effective_importance = computeEffectiveImportance(r.importance, r.last_accessed_at, r.created_at);
125
+ r.effective_importance = computeEffectiveImportance(r.importance, r.last_accessed_at, r.created_at, Boolean(r.is_rollup));
125
126
  }
126
127
  }
127
128
  }
@@ -282,7 +283,7 @@ export async function sessionSearchMemoryHandler(args) {
282
283
  // When true, a MemoryTrace JSON block is appended as content[1].
283
284
  enable_trace = false,
284
285
  // v5.2: Context-Weighted Retrieval — biases search toward active work context
285
- context_boost = false, } = args;
286
+ context_boost = false, activation, } = args;
286
287
  debugLog(`[session_search_memory] Semantic search: query="${query}", ` +
287
288
  `project=${project || "all"}, limit=${limit}, threshold=${similarity_threshold}` +
288
289
  `${context_boost ? ", context_boost=ON" : ""}`);
@@ -374,6 +375,7 @@ export async function sessionSearchMemoryHandler(args) {
374
375
  limit: candidateLimit,
375
376
  similarityThreshold: similarity_threshold,
376
377
  userId: PRISM_USER_ID,
378
+ activation,
377
379
  });
378
380
  // Phase 1: Capture storage query latency and compute total
379
381
  const storageMs = performance.now() - storageStart;
@@ -444,7 +446,10 @@ export async function sessionSearchMemoryHandler(args) {
444
446
  const timestamps = accessTimestamps.length > 0
445
447
  ? accessTimestamps
446
448
  : [new Date(r.created_at || now)];
447
- const Bi = baseLevelActivation(timestamps, now, PRISM_ACTR_DECAY);
449
+ // Rollups represent consolidated semantic knowledge over many sessions.
450
+ // They should decay 50% slower than raw episodic chatter to retain long-term context.
451
+ const decayRate = r.is_rollup ? PRISM_ACTR_DECAY * 0.5 : PRISM_ACTR_DECAY;
452
+ const Bi = baseLevelActivation(timestamps, now, decayRate);
448
453
  // S_i: Candidate-scoped spreading activation
449
454
  const outboundLinks = linksMap.get(id) || [];
450
455
  const Si = candidateScopedSpreadingActivation(outboundLinks, candidateIdSet);
@@ -473,6 +478,29 @@ export async function sessionSearchMemoryHandler(args) {
473
478
  // This MUST happen after re-ranking but BEFORE recording access events,
474
479
  // so we only log access for results actually delivered to the LLM.
475
480
  results.splice(limit);
481
+ if (results.length > 0) {
482
+ const topScore = PRISM_ACTR_ENABLED ? results[0]._actr_composite : results[0].similarity;
483
+ const secondScore = results.length > 1 ? (PRISM_ACTR_ENABLED ? results[1]._actr_composite : results[1].similarity) : 0;
484
+ const gapDistance = (topScore || 0) - (secondScore || 0);
485
+ const fallbackThreshold = PRISM_HDC_POLICY_FALLBACK_THRESHOLD || 0.85;
486
+ const clarifyThreshold = PRISM_HDC_POLICY_CLARIFY_THRESHOLD || 0.95;
487
+ const gapThreshold = clarifyThreshold - fallbackThreshold;
488
+ if ((topScore || 0) < fallbackThreshold || (results.length > 1 && gapDistance < gapThreshold)) {
489
+ return {
490
+ content: [{
491
+ type: "text",
492
+ text: JSON.stringify({
493
+ results: [],
494
+ meta: {
495
+ rejected: true,
496
+ reason: `Uncertainty Rejection: topScore (${(topScore || 0).toFixed(3)}) < ${fallbackThreshold} OR gapDistance (${gapDistance.toFixed(3)}) < ${gapThreshold.toFixed(3)}.`
497
+ }
498
+ }, null, 2)
499
+ }],
500
+ isError: false
501
+ };
502
+ }
503
+ }
476
504
  // Fire-and-forget: record access events for the final delivered results
477
505
  // v7.0: Writes to both access_log buffer AND legacy last_accessed_at
478
506
  const finalIds = results.map((r) => r.id).filter(Boolean);
@@ -487,7 +515,7 @@ export async function sessionSearchMemoryHandler(args) {
487
515
  : "N/A";
488
516
  // Dynamic importance decay (uses ACT-R internally when enabled)
489
517
  const baseImportance = r.importance ?? 0;
490
- const effectiveImportance = computeEffectiveImportance(baseImportance, r.last_accessed_at, r.created_at);
518
+ const effectiveImportance = computeEffectiveImportance(baseImportance, r.last_accessed_at, r.created_at, Boolean(r.is_rollup));
491
519
  const importanceStr = baseImportance > 0
492
520
  ? ` Importance: ${effectiveImportance}${effectiveImportance !== baseImportance ? ` (base: ${baseImportance}, decayed)` : ""}\n`
493
521
  : "";
@@ -648,7 +648,7 @@ export async function sessionLoadContextHandler(args) {
648
648
  formattedContext += `\n⏳ Recent Sessions:\n` + d.recent_sessions.map((s) => {
649
649
  let impStr = "";
650
650
  if (typeof s.importance === 'number' && s.importance > 0) {
651
- const eff = computeEffectiveImportance(s.importance, s.last_accessed_at, s.created_at);
651
+ const eff = computeEffectiveImportance(s.importance, s.last_accessed_at, s.created_at, Boolean(s.is_rollup));
652
652
  impStr = ` [Imp: ${eff}]`;
653
653
  }
654
654
  return ` [${s.session_date?.split("T")[0]}]${impStr} ${s.summary}`;
@@ -188,6 +188,16 @@ export const KNOWLEDGE_SEARCH_TOOL = {
188
188
  description: "If true, returns a separate MEMORY TRACE content block with search strategy, " +
189
189
  "latency breakdown, and scoring metadata for explainability. Default: false.",
190
190
  },
191
+ activation: {
192
+ type: "object",
193
+ description: "Configuration for ACT-R inspired Spreading Activation. Use this to find structurally related memories beyond direct semantic/keyword hits.",
194
+ properties: {
195
+ enabled: { type: "boolean", description: "Enable spreading activation (default: false)." },
196
+ iterations: { type: "integer", description: "Number of propagation loops (default: 3)." },
197
+ spreadFactor: { type: "number", description: "Decay multiplier per step (default: 0.8)." },
198
+ lateralInhibition: { type: "integer", description: "Maximum nodes returned (default: 7)." },
199
+ }
200
+ },
191
201
  },
192
202
  required: ["query"],
193
203
  },
@@ -344,6 +354,16 @@ export const SESSION_SEARCH_MEMORY_TOOL = {
344
354
  "before embedding generation, naturally biasing results toward contextually relevant memories. " +
345
355
  "Useful when searching within a specific project context. Default: false.",
346
356
  },
357
+ activation: {
358
+ type: "object",
359
+ description: "Configuration for ACT-R inspired Spreading Activation. Use this to find structurally related memories beyond direct semantic/keyword hits.",
360
+ properties: {
361
+ enabled: { type: "boolean", description: "Enable spreading activation (default: false)." },
362
+ iterations: { type: "integer", description: "Number of propagation loops (default: 3)." },
363
+ spreadFactor: { type: "number", description: "Decay multiplier per step (default: 0.8)." },
364
+ lateralInhibition: { type: "integer", description: "Maximum nodes returned (default: 7)." },
365
+ }
366
+ },
347
367
  },
348
368
  required: ["query"],
349
369
  },
@@ -440,6 +460,8 @@ export function isKnowledgeSearchArgs(args) {
440
460
  return false;
441
461
  if (a.enable_trace !== undefined && typeof a.enable_trace !== "boolean")
442
462
  return false;
463
+ if (a.activation !== undefined && typeof a.activation !== "object")
464
+ return false;
443
465
  return true;
444
466
  }
445
467
  export function isSessionSaveLedgerArgs(args) {
@@ -509,6 +531,8 @@ export function isSessionSearchMemoryArgs(args) {
509
531
  return false;
510
532
  if (a.context_boost !== undefined && typeof a.context_boost !== "boolean")
511
533
  return false;
534
+ if (a.activation !== undefined && typeof a.activation !== "object")
535
+ return false;
512
536
  return true;
513
537
  }
514
538
  // ─── v1.5.0: Type guard for backfill embeddings ──────────────
@@ -46,7 +46,7 @@ import { PRISM_ACTR_ENABLED, PRISM_ACTR_DECAY, PRISM_ACTR_SIGMOID_MIDPOINT, PRIS
46
46
  * @param createdAtStr ISO string representing creation time (fallback).
47
47
  * @returns The effective importance score, rounded to 2 decimal places.
48
48
  */
49
- export function computeEffectiveImportance(baseImportance, lastAccessedStr, createdAtStr) {
49
+ export function computeEffectiveImportance(baseImportance, lastAccessedStr, createdAtStr, isRollup = false) {
50
50
  if (baseImportance <= 0)
51
51
  return baseImportance;
52
52
  const now = new Date();
@@ -64,7 +64,8 @@ export function computeEffectiveImportance(baseImportance, lastAccessedStr, crea
64
64
  // Use a single-timestamp proxy: treat last_accessed_at as one access event.
65
65
  // This gives a simplified B_i = ln(t^(-d)) = -d × ln(t)
66
66
  // The full multi-timestamp B_i runs in graphHandlers.ts search pipeline.
67
- const Bi = baseLevelActivation([referenceDate], now, PRISM_ACTR_DECAY);
67
+ const decayRate = isRollup ? PRISM_ACTR_DECAY * 0.5 : PRISM_ACTR_DECAY;
68
+ const Bi = baseLevelActivation([referenceDate], now, decayRate);
68
69
  // Normalize to (0, 1) via parameterized sigmoid
69
70
  const activationFactor = parameterizedSigmoid(Bi, PRISM_ACTR_SIGMOID_MIDPOINT, PRISM_ACTR_SIGMOID_STEEPNESS);
70
71
  // Scale importance by activation factor
@@ -148,3 +148,6 @@ export function getLLMProvider() {
148
148
  export function _resetLLMProvider() {
149
149
  providerInstance = null;
150
150
  }
151
+ export function _setLLMProviderForTest(mock) {
152
+ providerInstance = mock;
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "7.7.1",
3
+ "version": "7.8.0",
4
4
  "mcpName": "io.github.dcostenco/prism-mcp",
5
5
  "description": "The Mind Palace for AI Agents — adversarial evaluation (PLAN_CONTRACT→EVALUATE anti-sycophancy), fail-closed Dark Factory autonomous pipelines (3-gate parse→type→scope), persistent memory (SQLite/Supabase), ACT-R cognitive retrieval, behavioral learning & IDE rules sync, multi-agent Hivemind, time travel, visual dashboard. Zero-config local mode.",
6
6
  "module": "index.ts",