feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control

First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.

Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
  lateral PFC as the domain-general inhibitory controller; SIF
  compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
  Rac1 GTPase as the active synaptic destabilization mechanism.

What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
  subtracts a `0.15 × count` penalty (saturating at 80%) from
  retrieval scores during hybrid search. Distinct from delete
  (removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
  consolidation loop, walks `memory_connections` edges from
  recently-suppressed seeds, applies attenuated FSRS decay to
  co-activated neighbors. You don't just forget Jake — you fade
  the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
  within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
  + partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
  are active. 3D graph nodes dim to 20% opacity when suppressed.
  New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
  `Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
  so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.

Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.

Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:

* Root cause: node glow `SpriteMaterial` had no `map`, so
  `THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
  blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
  edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
  as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
  density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
  to brand violet `0x8b5cf6` with higher opacity. Added explicit
  `scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
  invariant in (shared texture singleton, depthWrite:false, scale
  ×6, bloom magic numbers via source regex, starfield presence).

Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages

Closes #31
This commit is contained in:
Sam Valladares 2026-04-14 17:30:30 -05:00
parent 95bde93b49
commit 8178beb961
359 changed files with 8277 additions and 3416 deletions

View file

@ -0,0 +1,6 @@
import"../chunks/Bzak7iHL.js";import{p as ze,s as I,c as Ae,g as e,a as Pe,d as a,e as r,h as b,r as t,i as Qe,t as y,f as ge,u as se,j as qe}from"../chunks/nyjtQ1Ok.js";import{d as Be,a as q,s as o}from"../chunks/C4L78yoI.js";import{a as c,f as m,c as De}from"../chunks/B0IenmM-.js";import{i as k}from"../chunks/B17metm1.js";import{e as ie,i as ne}from"../chunks/BilMa3tw.js";import{r as ye}from"../chunks/BBOOwRwQ.js";import{s as oe}from"../chunks/BgOFZ9jq.js";import{s as Ke}from"../chunks/D2QmVWrb.js";import{b as de}from"../chunks/BOVu8he9.js";import{a as X}from"../chunks/BcuCGYSa.js";var Re=m('<button><span class="text-xl"> </span> <span class="font-medium"> </span> <span class="text-[10px] text-muted text-center"> </span></button>'),Ue=m('<div class="p-3 glass rounded-xl !border-synapse/20"><div class="text-[10px] text-synapse-glow mb-1 uppercase tracking-wider">Source</div> <p class="text-sm text-text"> </p> <div class="flex gap-2 mt-1.5 text-[10px] text-muted"><span> </span> <span> </span></div></div>'),Ve=m('<div class="p-3 glass rounded-xl !border-dream/20"><div class="text-[10px] text-dream-glow mb-1 uppercase tracking-wider">Target</div> <p class="text-sm text-text"> </p> <div class="flex gap-2 mt-1.5 text-[10px] text-muted"><span> </span> <span> </span></div></div>'),Ge=m(`<div class="space-y-3"><span class="text-xs text-dim font-medium">Target Memory <span class="text-muted"> </span></span> <div class="flex gap-2"><input type="text" placeholder="Search for the target memory..." class="flex-1 px-4 py-2.5 bg-white/[0.03] border border-synapse/10 rounded-xl text-text text-sm
placeholder:text-muted focus:outline-none focus:border-dream/40 transition backdrop-blur-sm"/> <button class="px-4 py-2.5 bg-dream/20 border border-dream/40 text-dream-glow text-sm rounded-xl hover:bg-dream/30 transition">Find</button></div></div> <!>`,1),He=m('<div class="text-center py-8 text-dim"><div class="text-lg animate-pulse mb-2">◎</div> <p> </p></div>'),Je=m('<span class="px-1.5 py-0.5 bg-white/[0.04] rounded"> </span>'),Le=m("<span> </span>"),We=m("<span> </span>"),Xe=m("<span> </span>"),Ye=m('<span class="text-synapse-glow"> </span>'),Ze=m('<div class="p-3 glass-subtle rounded-xl flex items-start gap-3 hover:bg-white/[0.03] transition"><div class="w-6 h-6 rounded-full bg-synapse/15 text-synapse-glow text-xs flex items-center justify-center flex-shrink-0 mt-0.5"></div> <div class="flex-1 min-w-0"><p class="text-sm text-text line-clamp-2"> </p> <div class="flex flex-wrap gap-3 mt-1.5 text-xs text-muted"><!> <!> <!> <!> <!></div></div></div>'),et=m('<div class="space-y-4"><div class="flex items-center justify-between"><h2 class="text-sm text-bright font-semibold"> </h2></div> <div class="space-y-2"></div></div>'),tt=m('<div class="text-center py-8 text-dim"><div class="text-3xl mb-3 opacity-20"></div> <p>No connections found for this query.</p></div>'),rt=m('<div><div class="text-xs text-dim mb-1.5 capitalize"> </div> <div class="h-2 bg-deep rounded-full overflow-hidden"><div></div></div> <div class="text-xs text-muted mt-1"> </div></div>'),at=m('<div class="grid grid-cols-4 gap-3"></div>'),st=m('<div class="mt-4 p-4 glass rounded-xl"><div class="flex items-center gap-3 mb-4"><span class="text-3xl text-bright font-bold"> </span> <span> </span></div> <!></div>'),it=m(`<div class="p-6 max-w-5xl mx-auto space-y-8"><h1 class="text-xl text-bright font-semibold">Explore Connections</h1> <div class="grid grid-cols-3 gap-2"></div> <div class="space-y-3"><span class="text-xs text-dim font-medium">Source Memory</span> <div class="flex gap-2"><input type="text" placeholder="Search for a memory to explore from..." class="flex-1 px-4 py-2.5 bg-white/[0.03] border border-synapse/10 rounded-xl text-text text-sm
placeholder:text-muted focus:outline-none focus:border-synapse/40 transition backdrop-blur-sm"/> <button class="px-4 py-2.5 bg-synapse/20 border border-synapse/40 text-synapse-glow text-sm rounded-xl hover:bg-synapse/30 transition">Find</button></div></div> <!> <!> <!> <div class="pt-8 border-t border-synapse/10"><h2 class="text-lg text-bright font-semibold mb-4">Importance Scorer</h2> <p class="text-xs text-muted mb-3">4-channel neuroscience scoring: novelty, arousal, reward, attention</p> <textarea placeholder="Paste any text to score its importance..." class="w-full h-24 px-4 py-3 bg-white/[0.03] border border-synapse/10 rounded-xl text-text text-sm
placeholder:text-muted resize-none focus:outline-none focus:border-synapse/40 transition backdrop-blur-sm"></textarea> <button class="mt-2 px-4 py-2 bg-dream/20 border border-dream/40 text-dream-glow text-sm rounded-xl hover:bg-dream/30 transition">Score</button> <!></div></div>`);function ft(he,we){ze(we,!0);let V=I(""),G=I(""),F=I(null),C=I(null),B=I(Ae([])),$=I("associations"),O=I(!1),H=I(""),D=I(null);const le={associations:{icon:"",desc:"Spreading activation find related memories via graph traversal"},chains:{icon:"",desc:"Build reasoning path from source to target memory"},bridges:{icon:"",desc:"Find connecting memories between two concepts"}};async function ve(){if(e(V).trim()){b(O,!0);try{const s=await X.search(e(V),1);s.results.length>0&&(b(F,s.results[0],!0),await Y())}catch{}finally{b(O,!1)}}}async function pe(){if(e(G).trim()){b(O,!0);try{const s=await X.search(e(G),1);s.results.length>0&&(b(C,s.results[0],!0),e(F)&&await Y())}catch{}finally{b(O,!1)}}}async function Y(){if(e(F)){b(O,!0);try{const s=(e($)==="chains"||e($)==="bridges")&&e(C)?e(C).id:void 0,i=await X.explore(e(F).id,e($),s);b(B,i.results||i.nodes||i.chain||i.bridges||[],!0)}catch{b(B,[],!0)}finally{b(O,!1)}}}async function ke(){e(H).trim()&&b(D,await X.importance(e(H)),!0)}function Se(s){b($,s,!0),e(F)&&Y()}var Z=it(),ee=a(r(Z),2);ie(ee,20,()=>["associations","chains","bridges"],ne,(s,i)=>{var d=Re(),_=r(d),h=r(_,!0);t(_);var f=a(_,2),p=r(f,!0);t(f);var n=a(f,2),g=r(n,!0);t(n),t(d),y(w=>{oe(d,1,`flex flex-col items-center gap-1 p-3 rounded-xl text-sm transition
${e($)===i?"glass !border-synapse/30 text-synapse-glow":"glass-subtle text-dim hover:bg-white/[0.03]"}`),o(h,le[i].icon),o(p,w),o(g,le[i].desc)},[()=>i.charAt(0).toUpperCase()+i.slice(1)]),q("click",d,()=>Se(i)),c(s,d)}),t(ee);var te=a(ee,2),ce=a(r(te),2),J=r(ce);ye(J);var Fe=a(J,2);t(ce),t(te);var xe=a(te,2);{var Te=s=>{var i=Ue(),d=a(r(i),2),_=r(d,!0);t(d);var h=a(d,2),f=r(h),p=r(f,!0);t(f);var n=a(f,2),g=r(n);t(n),t(h),t(i),y((w,z)=>{o(_,w),o(p,e(F).nodeType),o(g,`${z??""}% retention`)},[()=>e(F).content.slice(0,200),()=>(e(F).retentionStrength*100).toFixed(0)]),c(s,i)};k(xe,s=>{e(F)&&s(Te)})}var me=a(xe,2);{var $e=s=>{var i=Ge(),d=ge(i),_=r(d),h=a(r(_)),f=r(h);t(h),t(_);var p=a(_,2),n=r(p);ye(n);var g=a(n,2);t(p),t(d);var w=a(d,2);{var z=u=>{var E=Ve(),x=a(r(E),2),K=r(x,!0);t(x);var M=a(x,2),S=r(M),A=r(S,!0);t(S);var T=a(S,2),P=r(T);t(T),t(M),t(E),y((Q,j)=>{o(K,Q),o(A,e(C).nodeType),o(P,`${j??""}% retention`)},[()=>e(C).content.slice(0,200),()=>(e(C).retentionStrength*100).toFixed(0)]),c(u,E)};k(w,u=>{e(C)&&u(z)})}y(()=>o(f,`(for ${e($)??""})`)),q("keydown",n,u=>u.key==="Enter"&&pe()),de(n,()=>e(G),u=>b(G,u)),q("click",g,pe),c(s,i)};k(me,s=>{(e($)==="chains"||e($)==="bridges")&&s($e)})}var ue=a(me,2);{var Ee=s=>{var i=De(),d=ge(i);{var _=p=>{var n=He(),g=a(r(n),2),w=r(g);t(g),t(n),y(()=>o(w,`Exploring ${e($)??""}...`)),c(p,n)},h=p=>{var n=et(),g=r(n),w=r(g),z=r(w);t(w),t(g);var u=a(g,2);ie(u,21,()=>e(B),ne,(E,x,K)=>{var M=Ze(),S=r(M);S.textContent=K+1;var A=a(S,2),T=r(A),P=r(T,!0);t(T);var Q=a(T,2),j=r(Q);{var L=l=>{var v=Je(),N=r(v,!0);t(v),y(()=>o(N,e(x).nodeType)),c(l,v)};k(j,l=>{e(x).nodeType&&l(L)})}var R=a(j,2);{var ae=l=>{var v=Le(),N=r(v);t(v),y(U=>o(N,`Score: ${U??""}`),[()=>Number(e(x).score).toFixed(3)]),c(l,v)};k(R,l=>{e(x).score&&l(ae)})}var W=a(R,2);{var Ie=l=>{var v=We(),N=r(v);t(v),y(U=>o(N,`Similarity: ${U??""}`),[()=>Number(e(x).similarity).toFixed(3)]),c(l,v)};k(W,l=>{e(x).similarity&&l(Ie)})}var be=a(W,2);{var je=l=>{var v=Xe(),N=r(v);t(v),y(U=>o(N,`${U??""}% retention`),[()=>(Number(e(x).retention)*100).toFixed(0)]),c(l,v)};k(be,l=>{e(x).retention&&l(je)})}var Ce=a(be,2);{var Oe=l=>{var v=Ye(),N=r(v,!0);t(v),y(()=>o(N,e(x).connectionType)),c(l,v)};k(Ce,l=>{e(x).connectionType&&l(Oe)})}t(Q),t(A),t(M),y(()=>o(P,e(x).content)),c(E,M)}),t(u),t(n),y(()=>o(z,`${e(B).length??""} Connections Found`)),c(p,n)},f=p=>{var n=tt();c(p,n)};k(d,p=>{e(O)?p(_):e(B).length>0?p(h,1):p(f,!1)})}c(s,i)};k(ue,s=>{e(F)&&s(Ee)})}var _e=a(ue,2),re=a(r(_e),4);Qe(re);var fe=a(re,2),Me=a(fe,2);{var Ne=s=>{const i=se(()=>e(D).channels),d=se(()=>Number(e(D).composite||e(D).compositeScore||0));var _=st(),h=r(_),f=r(h),p=r(f,!0);t(f);var n=a(f,2),g=r(n,!0);t(n),t(h);var w=a(h,2);{var z=u=>{var E=at();ie(E,21,()=>Object.entries(e(i)),ne,(x,K)=>{var M=se(()=>qe(e(K),2));let S=()=>e(M)[0],A=()=>e(M)[1];var T=rt(),P=r(T),Q=r(P,!0);t(P);var j=a(P,2),L=r(j);t(j);var R=a(j,2),ae=r(R,!0);t(R),t(T),y(W=>{o(Q,S()),oe(L,1,`h-full rounded-full transition-all duration-500
${S()==="novelty"?"bg-synapse":S()==="arousal"?"bg-dream":S()==="reward"?"bg-recall":"bg-amber-400"}`),Ke(L,`width: ${A()*100}%`),o(ae,W)},[()=>A().toFixed(2)]),c(x,T)}),t(E),c(u,E)};k(w,u=>{e(i)&&u(z)})}t(_),y(u=>{o(p,u),oe(n,1,`px-2 py-1 rounded-lg text-xs ${e(d)>.6?"bg-recall/20 text-recall border border-recall/30":"bg-white/[0.04] text-dim border border-subtle/20"}`),o(g,e(d)>.6?"SAVE":"SKIP")},[()=>e(d).toFixed(2)]),c(s,_)};k(Me,s=>{e(D)&&s(Ne)})}t(_e),t(Z),q("keydown",J,s=>s.key==="Enter"&&ve()),de(J,()=>e(V),s=>b(V,s)),q("click",Fe,ve),de(re,()=>e(H),s=>b(H,s)),q("click",fe,ke),c(he,Z),Pe()}Be(["click","keydown"]);export{ft as component};