<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sara — long-term memory for coding agents</title>
<meta name="description" content="Your coding agent forgets everything tomorrow. Sara is the local, git-aware memory that makes it stop.">
<meta property="og:type" content="website">
<meta property="og:title" content="Sara — long-term memory for coding agents">
<meta property="og:description" content="Your coding agent forgets everything tomorrow. Sara is the local, git-aware memory that makes it stop.">
<meta property="og:url" content="https://abarbesgaard.github.io/Sara/">
<meta name="twitter:card" content="summary_large_image">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><circle cx='16' cy='16' r='11' fill='%23CCFF4D'/></svg>">
<style>
@property --ang { syntax:'<angle>'; initial-value:0deg; inherits:false; }
@property --sx { syntax:'<length>'; initial-value:50%; inherits:false; }
@property --sy { syntax:'<length>'; initial-value:0%; inherits:false; }
:root{
--bg:#060607;
--ink:#F6F6F8;
--mute:rgba(246,246,248,.56);
--dim:rgba(246,246,248,.34);
--faint:rgba(246,246,248,.13);
--line:rgba(255,255,255,.08);
--line-2:rgba(255,255,255,.14);
--glass:rgba(255,255,255,.026);
--lime:#CCFF4D;
--violet:#8B6CFF;
--cyan:#4DE3FF;
--rose:#FF6B8A;
--sans:-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI",Inter,system-ui,sans-serif;
--serif:ui-serif,"New York",Georgia,"Times New Roman",serif;
--mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,"Cascadia Mono",monospace;
}
*{box-sizing:border-box;margin:0;padding:0;}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;}
body{
background:var(--bg);color:var(--ink);font-family:var(--sans);
font-size:16px;line-height:1.55;letter-spacing:-.011em;
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
overflow-x:hidden;
}
::selection{background:var(--lime);color:#0a0a0a;}
.aurora{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;}
.aurora i{position:absolute;display:block;border-radius:50%;filter:blur(90px);opacity:.5;}
.a1{width:780px;height:620px;top:-260px;left:-160px;background:radial-gradient(circle,rgba(139,108,255,.5),transparent 65%);animation:drift1 26s ease-in-out infinite;}
.a2{width:700px;height:560px;top:-180px;right:-180px;background:radial-gradient(circle,rgba(77,227,255,.32),transparent 65%);animation:drift2 32s ease-in-out infinite;}
.a3{width:900px;height:520px;top:38%;left:30%;background:radial-gradient(circle,rgba(204,255,77,.14),transparent 65%);animation:drift3 38s ease-in-out infinite;}
@keyframes drift1{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(120px,90px) scale(1.15)}}
@keyframes drift2{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(-110px,120px) scale(1.1)}}
@keyframes drift3{0%,100%{transform:translate(-50%,0) scale(1)}50%{transform:translate(-40%,-120px) scale(1.2)}}
.grain{
position:fixed;inset:-200%;z-index:1;pointer-events:none;opacity:.035;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
animation:grain 8s steps(6) infinite;
}
@keyframes grain{0%{transform:translate(0,0)}20%{transform:translate(-4%,3%)}40%{transform:translate(3%,-4%)}60%{transform:translate(-2%,-3%)}80%{transform:translate(4%,2%)}100%{transform:translate(0,0)}}
.mesh{
position:fixed;inset:0;z-index:0;pointer-events:none;
background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);
background-size:64px 64px;opacity:.5;
-webkit-mask-image:radial-gradient(120% 90% at 50% 0%,#000 5%,transparent 62%);
mask-image:radial-gradient(120% 90% at 50% 0%,#000 5%,transparent 62%);
}
main{position:relative;z-index:2;}
.wrap{max-width:1240px;margin:0 auto;padding:0 clamp(1.2rem,4vw,2.5rem);}
section{padding:clamp(5rem,11vh,9rem) 0;position:relative;}
.display{
font-size:clamp(2.9rem,8.2vw,6.6rem);line-height:.95;letter-spacing:-.045em;
font-weight:640;text-wrap:balance;
}
.display em{
font-family:var(--serif);font-style:italic;font-weight:400;letter-spacing:-.03em;
background:linear-gradient(96deg,var(--lime),var(--cyan) 48%,var(--violet));
-webkit-background-clip:text;background-clip:text;color:transparent;
padding-right:.06em;
}
h2.display{font-size:clamp(2.3rem,5.6vw,4.4rem);}
.eyebrow{
font-family:var(--mono);font-size:.7rem;letter-spacing:.24em;text-transform:uppercase;
color:var(--dim);display:inline-flex;align-items:center;gap:.7rem;margin-bottom:1.6rem;
}
.eyebrow::before{content:"";width:22px;height:1px;background:var(--lime);opacity:.8;}
.lede{font-size:clamp(1.02rem,1.5vw,1.22rem);color:var(--mute);max-width:60ch;text-wrap:pretty;}
.lede strong{color:var(--ink);font-weight:600;}
code{
font-family:var(--mono);font-size:.85em;letter-spacing:-.01em;color:var(--lime);
background:rgba(204,255,77,.07);border:1px solid rgba(204,255,77,.16);
padding:.1em .4em;border-radius:6px;white-space:nowrap;
}
nav{
position:fixed;top:1rem;left:50%;transform:translateX(-50%);z-index:100;
display:flex;align-items:center;gap:.4rem;padding:.42rem .48rem .42rem 1.05rem;
border-radius:999px;border:1px solid var(--line);
background:rgba(10,10,12,.62);backdrop-filter:blur(22px) saturate(180%);
-webkit-backdrop-filter:blur(22px) saturate(180%);
box-shadow:0 10px 40px -12px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.07);
transition:transform .45s cubic-bezier(.32,.72,0,1),opacity .35s;
}
nav.hide{transform:translateX(-50%) translateY(-140%);opacity:0;}
nav .logo{font-weight:680;letter-spacing:-.03em;font-size:.98rem;margin-right:.7rem;}
nav .logo b{color:var(--lime);}
nav a{color:var(--mute);text-decoration:none;font-size:.855rem;padding:.4rem .7rem;border-radius:999px;transition:.2s;}
nav a:hover{color:var(--ink);background:rgba(255,255,255,.06);}
nav .go{
background:var(--ink);color:#08080a;font-weight:600;padding:.44rem .95rem;
}
nav .go:hover{background:var(--lime);color:#0a0a0a;}
@media(max-width:760px){nav .hidem{display:none;}}
.hero{padding-top:clamp(8rem,17vh,11rem);padding-bottom:clamp(3rem,6vh,5rem);}
.tagpill{
display:inline-flex;align-items:center;gap:.6rem;font-family:var(--mono);font-size:.715rem;
letter-spacing:.05em;color:var(--mute);border:1px solid var(--line);border-radius:999px;
padding:.4rem .9rem .4rem .55rem;background:var(--glass);margin-bottom:1.9rem;
backdrop-filter:blur(10px);
}
.tagpill .led{width:6px;height:6px;border-radius:50%;background:var(--lime);box-shadow:0 0 0 3px rgba(204,255,77,.16);animation:led 2.6s ease-in-out infinite;}
@keyframes led{0%,100%{opacity:1;box-shadow:0 0 0 3px rgba(204,255,77,.16)}50%{opacity:.55;box-shadow:0 0 0 6px rgba(204,255,77,0)}}
.hero .lede{margin:1.9rem 0 2.4rem;font-size:clamp(1.06rem,1.6vw,1.3rem);}
.actions{display:flex;flex-wrap:wrap;gap:.8rem;align-items:center;}
.btn{
position:relative;display:inline-flex;align-items:center;gap:.55rem;text-decoration:none;
font-size:.945rem;font-weight:600;padding:.82rem 1.35rem;border-radius:12px;
transition:transform .18s cubic-bezier(.32,.72,0,1),box-shadow .25s,background .2s;
will-change:transform;
}
.btn-p{background:var(--ink);color:#08080a;box-shadow:0 10px 34px -14px rgba(255,255,255,.55);}
.btn-p:hover{background:var(--lime);box-shadow:0 14px 44px -12px rgba(204,255,77,.6);}
.btn-s{color:var(--ink);border:1px solid var(--line-2);background:var(--glass);backdrop-filter:blur(10px);}
.btn-s:hover{border-color:rgba(255,255,255,.3);background:rgba(255,255,255,.06);}
.btn .arw{transition:transform .25s cubic-bezier(.32,.72,0,1);}
.btn:hover .arw{transform:translateX(3px);}
.cmdline{
display:flex;align-items:center;gap:.7rem;margin-top:1.5rem;max-width:640px;
font-family:var(--mono);font-size:.78rem;cursor:pointer;user-select:none;
border:1px solid var(--line);border-radius:12px;padding:.72rem .9rem;
background:rgba(255,255,255,.018);backdrop-filter:blur(10px);transition:.22s;
}
.cmdline:hover{border-color:var(--line-2);background:rgba(255,255,255,.04);}
.cmdline .pr{color:var(--violet);}
.cmdline .cm{color:var(--mute);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cmdline .cp{margin-left:auto;font-size:.66rem;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);flex:none;}
.stage{margin-top:clamp(3rem,6vw,4.5rem);perspective:1600px;}
.screen{
position:relative;border-radius:16px;overflow:hidden;
border:1px solid var(--line-2);
background:linear-gradient(180deg,rgba(18,18,22,.94),rgba(8,8,10,.97));
backdrop-filter:blur(24px);
box-shadow:0 50px 120px -40px rgba(0,0,0,1),0 0 0 1px rgba(255,255,255,.03),inset 0 1px 0 rgba(255,255,255,.08);
transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
transform-style:preserve-3d;transition:transform .5s cubic-bezier(.32,.72,0,1);
}
.screen::after{
content:"";position:absolute;inset:0;pointer-events:none;border-radius:16px;
background:radial-gradient(500px circle at var(--sx) var(--sy),rgba(255,255,255,.055),transparent 55%);
opacity:0;transition:opacity .35s;
}
.screen:hover::after{opacity:1;}
.chrome{
display:flex;align-items:center;gap:.45rem;padding:.7rem 1rem;
border-bottom:1px solid var(--line);background:rgba(255,255,255,.022);
}
.chrome i{width:10px;height:10px;border-radius:50%;display:block;}
.chrome .t{
margin-left:.8rem;font-family:var(--mono);font-size:.72rem;color:var(--dim);
display:flex;align-items:center;gap:.5rem;
}
.chrome .t b{color:var(--mute);font-weight:400;}
.tabs{margin-left:auto;display:flex;gap:.3rem;}
.tabs button{
font-family:var(--mono);font-size:.68rem;letter-spacing:.06em;color:var(--dim);
background:none;border:1px solid transparent;border-radius:7px;padding:.24rem .6rem;cursor:pointer;transition:.2s;
}
.tabs button:hover{color:var(--mute);}
.tabs button[aria-selected="true"]{color:var(--lime);border-color:rgba(204,255,77,.28);background:rgba(204,255,77,.08);}
.tty{
font-family:var(--mono);font-size:.812rem;line-height:1.82;padding:1.25rem 1.4rem 1.6rem;
min-height:376px;white-space:pre-wrap;word-break:break-word;
}
@media(max-width:820px){
.tty{min-height:0;font-size:.74rem;padding:1rem;}
.chrome{flex-wrap:wrap;}
.chrome .t{display:none;}
.tabs{margin-left:0;}
}
.tty .l{display:block;}
.p{color:var(--violet);} .c{color:var(--ink);} .o{color:var(--mute);}
.g{color:var(--lime);} .b{color:var(--cyan);} .r{color:var(--rose);} .d{color:var(--dim);} .y{color:#FFC978;}
.caret{display:inline-block;width:8px;height:1.05em;background:var(--lime);vertical-align:-3px;margin-left:1px;animation:cb 1s step-end infinite;}
@keyframes cb{0%,100%{opacity:1}50%{opacity:0}}
.ticker{
border-top:1px solid var(--line);border-bottom:1px solid var(--line);
padding:1.05rem 0;overflow:hidden;position:relative;
background:rgba(255,255,255,.012);
}
.ticker::before,.ticker::after{
content:"";position:absolute;top:0;bottom:0;width:16%;z-index:2;pointer-events:none;
}
.ticker::before{left:0;background:linear-gradient(90deg,var(--bg),transparent);}
.ticker::after{right:0;background:linear-gradient(270deg,var(--bg),transparent);}
.track{display:flex;gap:2.6rem;width:max-content;animation:slide 46s linear infinite;}
.ticker:hover .track{animation-play-state:paused;}
@keyframes slide{to{transform:translateX(-50%)}}
.track span{
font-family:var(--mono);font-size:.775rem;color:var(--dim);white-space:nowrap;
display:flex;align-items:center;gap:.55rem;letter-spacing:-.01em;
}
.track span::before{content:"";width:4px;height:4px;border-radius:50%;background:var(--faint);}
.head{max-width:900px;margin-bottom:clamp(2.6rem,5vw,4rem);}
.head .lede{margin-top:1.4rem;}
.chartcard{
border:1px solid var(--line);border-radius:20px;overflow:hidden;
background:linear-gradient(180deg,rgba(255,255,255,.032),rgba(255,255,255,.008));
backdrop-filter:blur(20px);box-shadow:inset 0 1px 0 rgba(255,255,255,.06);
}
.chartcard .top{
display:flex;flex-wrap:wrap;gap:1.4rem;align-items:center;
padding:1.15rem 1.5rem;border-bottom:1px solid var(--line);
}
.key{display:flex;align-items:center;gap:.55rem;font-family:var(--mono);font-size:.735rem;color:var(--mute);}
.key i{width:14px;height:2px;border-radius:2px;display:block;}
.key.k1 i{background:var(--rose);} .key.k2 i{background:var(--lime);}
.top .rt{margin-left:auto;font-family:var(--mono);font-size:.7rem;color:var(--dim);letter-spacing:.12em;text-transform:uppercase;}
#decay{display:block;width:100%;height:clamp(240px,32vw,340px);}
.chartcard .foot{
display:grid;grid-template-columns:1fr 1fr;gap:0;border-top:1px solid var(--line);
}
@media(max-width:720px){.chartcard .foot{grid-template-columns:1fr;}}
.chartcard .foot div{padding:1.15rem 1.5rem;font-size:.92rem;color:var(--mute);}
.chartcard .foot div:first-child{border-right:1px solid var(--line);}
@media(max-width:720px){.chartcard .foot div:first-child{border-right:0;border-bottom:1px solid var(--line);}}
.chartcard .foot b{display:block;font-family:var(--mono);font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;margin-bottom:.4rem;}
.foot .n1 b{color:var(--rose);} .foot .n2 b{color:var(--lime);}
.bento{
display:grid;grid-template-columns:repeat(6,1fr);gap:clamp(.7rem,1.1vw,1rem);
margin-top:clamp(2.4rem,4vw,3.2rem);
}
.cell{
position:relative;border:1px solid var(--line);border-radius:18px;overflow:hidden;
background:linear-gradient(180deg,rgba(255,255,255,.034),rgba(255,255,255,.007));
padding:1.6rem;backdrop-filter:blur(18px);
box-shadow:inset 0 1px 0 rgba(255,255,255,.06);
transition:border-color .3s,transform .4s cubic-bezier(.32,.72,0,1);
}
.cell::before{
content:"";position:absolute;inset:0;border-radius:18px;pointer-events:none;
background:radial-gradient(340px circle at var(--sx) var(--sy),rgba(204,255,77,.11),transparent 45%);
opacity:0;transition:opacity .35s;
}
.cell:hover{border-color:var(--line-2);transform:translateY(-2px);}
.cell:hover::before{opacity:1;}
.c-6{grid-column:span 6;} .c-4{grid-column:span 4;} .c-3{grid-column:span 3;} .c-2{grid-column:span 2;}
@media(max-width:900px){
.bento{grid-template-columns:repeat(2,1fr);}
.c-6,.c-4,.c-3,.c-2{grid-column:span 2;}
}
.cell h3{font-size:1.16rem;font-weight:640;letter-spacing:-.025em;margin-bottom:.55rem;}
.cell p{color:var(--mute);font-size:.935rem;text-wrap:pretty;}
.cell .tag{
font-family:var(--mono);font-size:.66rem;letter-spacing:.18em;text-transform:uppercase;
color:var(--dim);display:block;margin-bottom:1.1rem;
}
.big{font-size:clamp(2.6rem,5vw,3.6rem);font-weight:660;letter-spacing:-.05em;line-height:1;}
.big em{font-family:var(--serif);font-style:italic;font-weight:400;color:var(--lime);}
.meter{margin-top:1.2rem;display:flex;flex-direction:column;gap:.6rem;}
.mrow{display:flex;align-items:center;gap:.75rem;font-family:var(--mono);font-size:.72rem;color:var(--dim);}
.mrow .nm{width:34px;color:var(--mute);}
.mbar{flex:1;height:5px;border-radius:3px;background:rgba(255,255,255,.06);overflow:hidden;}
.mfill{display:block;height:100%;border-radius:3px;width:0;transition:width 1.3s cubic-bezier(.32,.72,0,1);}
.mrow.s .mfill{background:linear-gradient(90deg,var(--lime),rgba(204,255,77,.4));}
.mrow.l .mfill{background:linear-gradient(90deg,var(--cyan),rgba(77,227,255,.35));}
.mrow.w .mfill{background:linear-gradient(90deg,rgba(246,246,248,.4),rgba(246,246,248,.12));}
.mrow .vl{width:52px;text-align:right;}
.mini{
margin-top:1.1rem;font-family:var(--mono);font-size:.735rem;line-height:1.75;
background:rgba(0,0,0,.42);border:1px solid var(--line);border-radius:11px;
padding:.85rem .95rem;white-space:pre-wrap;overflow-x:auto;
}
.band{
position:relative;border-top:1px solid var(--line);border-bottom:1px solid var(--line);
overflow:hidden;background:radial-gradient(120% 130% at 50% 0%,rgba(139,108,255,.09),transparent 62%);
}
#graph{display:block;width:100%;height:clamp(400px,54vh,560px);}
.band .overlay{
position:absolute;inset:0;pointer-events:none;display:flex;flex-direction:column;
justify-content:space-between;padding:clamp(1.4rem,3vw,2.4rem);
}
.band .cap{
font-family:var(--mono);font-size:.7rem;letter-spacing:.18em;text-transform:uppercase;color:var(--dim);
}
.band .legend{display:flex;flex-wrap:wrap;gap:1.4rem;}
.band .legend span{display:flex;align-items:center;gap:.5rem;font-family:var(--mono);font-size:.715rem;color:var(--dim);}
.band .legend i{width:8px;height:8px;border-radius:50%;display:block;}
.cmp{width:100%;border-collapse:collapse;margin-top:2.4rem;}
.cmp th,.cmp td{padding:1.1rem 1.2rem;text-align:left;border-bottom:1px solid var(--line);vertical-align:top;font-size:.95rem;}
.cmp thead th{
font-family:var(--mono);font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;
font-weight:400;color:var(--dim);border-bottom-color:var(--line-2);
}
.cmp thead th:last-child{color:var(--lime);}
.cmp td:first-child{color:var(--ink);font-weight:560;width:24%;}
.cmp td:nth-child(2){color:var(--dim);}
.cmp td:last-child{color:var(--mute);}
.cmp tbody tr{transition:background .25s;}
.cmp tbody tr:hover{background:rgba(255,255,255,.022);}
@media(max-width:760px){
.cmp thead{display:none;}
.cmp tr{display:block;padding:1rem 0;border-bottom:1px solid var(--line);}
.cmp td{display:block;border:0;padding:.2rem 0;width:auto!important;}
.cmp td:nth-child(2)::before{content:"without · ";font-family:var(--mono);font-size:.68rem;color:var(--rose);letter-spacing:.1em;}
.cmp td:last-child::before{content:"sara · ";font-family:var(--mono);font-size:.68rem;color:var(--lime);letter-spacing:.1em;}
}
.steps{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(.8rem,1.2vw,1.1rem);margin-top:2.6rem;}
@media(max-width:860px){.steps{grid-template-columns:1fr;}}
.step{position:relative;min-width:0;display:flex;flex-direction:column;border:1px solid var(--line);border-radius:18px;padding:1.6rem;background:var(--glass);backdrop-filter:blur(14px);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);}
.step .no{
font-family:var(--mono);font-size:.7rem;letter-spacing:.2em;color:var(--lime);
display:block;margin-bottom:.9rem;
}
.step > p{margin-bottom:1.1rem;}
.step pre{
margin-top:auto;font-family:var(--mono);font-size:.755rem;line-height:1.8;
background:rgba(0,0,0,.42);border:1px solid var(--line);border-radius:11px;
padding:.8rem .9rem;overflow-x:auto;color:var(--lime);
}
.step pre .d{color:var(--dim);}
.final{text-align:center;}
.final .display{margin:0 auto;}
.final .lede{margin:1.8rem auto 2.6rem;}
.final .actions{justify-content:center;}
.figs{
display:grid;grid-template-columns:repeat(4,1fr);gap:1px;margin-top:clamp(3.4rem,6vw,5rem);
background:var(--line);border:1px solid var(--line);border-radius:18px;overflow:hidden;
}
@media(max-width:760px){.figs{grid-template-columns:repeat(2,1fr);}}
.fig{background:#08080a;padding:1.7rem 1rem;}
.fig b{display:block;font-size:2rem;font-weight:660;letter-spacing:-.045em;line-height:1;margin-bottom:.5rem;}
.fig span{font-family:var(--mono);font-size:.665rem;letter-spacing:.18em;text-transform:uppercase;color:var(--dim);}
footer{border-top:1px solid var(--line);padding:2.2rem 0;position:relative;z-index:2;}
footer .in{max-width:1240px;margin:0 auto;padding:0 clamp(1.2rem,4vw,2.5rem);display:flex;flex-wrap:wrap;gap:1rem;align-items:center;font-family:var(--mono);font-size:.735rem;color:var(--dim);}
footer a{color:var(--mute);text-decoration:none;}
footer a:hover{color:var(--lime);}
footer .rr{margin-left:auto;}
.rv{opacity:0;transform:translateY(22px);transition:opacity .8s cubic-bezier(.32,.72,0,1),transform .8s cubic-bezier(.32,.72,0,1);}
.rv.in{opacity:1;transform:none;}
.rv[data-d="1"]{transition-delay:.08s;} .rv[data-d="2"]{transition-delay:.16s;} .rv[data-d="3"]{transition-delay:.24s;}
.word{display:inline-block;opacity:0;transform:translateY(.42em) rotate(1.5deg);transition:opacity .7s cubic-bezier(.32,.72,0,1),transform .7s cubic-bezier(.32,.72,0,1);}
.word.in{opacity:1;transform:none;}
@media(prefers-reduced-motion:reduce){
*{animation:none!important;transition-duration:.01ms!important;}
.rv,.word{opacity:1!important;transform:none!important;}
html{scroll-behavior:auto;}
}
</style>
</head>
<body>
<div class="aurora"><i class="a1"></i><i class="a2"></i><i class="a3"></i></div>
<div class="mesh"></div>
<div class="grain"></div>
<nav id="nav">
<span class="logo">sara<b>.</b></span>
<a href="#problem" class="hidem">Problem</a>
<a href="#memory" class="hidem">Memory</a>
<a href="#proof" class="hidem">Proof</a>
<a href="#start" class="go">Install</a>
</nav>
<main>
<div class="wrap">
<header class="hero">
<span class="tagpill rv"><span class="led"></span> v1.1.1 · Rust · MIT · local-first</span>
<h1 class="display" id="h1">Your agent is brilliant <em>and amnesiac.</em></h1>
<p class="lede rv" data-d="1">You spend forty minutes explaining the codebase. It builds something great. Then the context window fills — and tomorrow you start from zero.<br><br><strong>Sara is the memory it never had.</strong> The plan, the findings and the proof of done, in one local SQLite file the agent drives itself over MCP.</p>
<div class="actions rv" data-d="2">
<a class="btn btn-p mag" href="#start">Get started <span class="arw">→</span></a>
<a class="btn btn-s mag" href="https://github.com/Abarbesgaard/Sara">View on GitHub</a>
</div>
<div class="cmdline rv" data-d="3" id="cmdline" title="click to copy">
<span class="pr">$</span>
<span class="cm">curl -fsSL https://raw.githubusercontent.com/Abarbesgaard/Sara/main/scripts/install.sh | sh</span>
<span class="cp" id="cphint">copy</span>
</div>
<div class="stage rv" data-d="3">
<div class="screen" id="screen">
<div class="chrome">
<i style="background:#FF5F57"></i><i style="background:#FEBC2E"></i><i style="background:#28C840"></i>
<span class="t"><b id="scene-title">tomorrow · a brand-new context window</b></span>
<div class="tabs" id="tabs">
<button data-s="0" aria-selected="true">recall</button>
<button data-s="1" aria-selected="false">validate</button>
<button data-s="2" aria-selected="false">learn</button>
</div>
</div>
<div class="tty" id="tty"></div>
</div>
</div>
</header>
</div>
<div class="ticker rv">
<div class="track" id="track"></div>
</div>
<div class="wrap">
<section id="problem">
<div class="head">
<span class="eyebrow rv">the tax you keep paying</span>
<h2 class="display rv" data-d="1">Every session <em>starts at zero.</em></h2>
<p class="lede rv" data-d="2">Forty minutes of context buys you <strong>one session</strong>. The gotcha discovered at 4pm dies at 6pm. Tomorrow your agent confidently makes the same mistake — and bills you to learn the same lesson again.</p>
</div>
<div class="chartcard rv" data-d="2">
<div class="top">
<span class="key k1"><i></i> a bare agent</span>
<span class="key k2"><i></i> with sara</span>
<span class="rt">effective knowledge · per session</span>
</div>
<canvas id="decay"></canvas>
<div class="foot">
<div class="n1"><b>sawtooth</b>Every session climbs from nothing and is wiped at the context limit. The ceiling never moves.</div>
<div class="n2"><b>staircase</b>Each session starts where the last one ended, because the knowledge is on disk — not in a window.</div>
</div>
</div>
</section>
</div>
<div class="wrap">
<section>
<div class="head">
<span class="eyebrow rv">what you actually get</span>
<h2 class="display rv" data-d="1">Three things that <em>survive.</em></h2>
</div>
<div class="bento">
<div class="cell c-3 spot rv">
<span class="tag">01 · the plan</span>
<h3>It outlives the window</h3>
<p>Steps, decisions, risks and full history live in SQLite. <code>next</code> hands the agent its first unfinished step — cold, a week later, mid-refactor, in a context window that has never seen your repo.</p>
<div class="mini"><span class="p">$</span> <span class="c">sara next 4f2a91c8</span>
<span class="o">step 3 of 6 · </span><span class="y">wire refresh into the middleware</span>
<span class="d"> 1 ✔ schema 2 ✔ token issuer</span></div>
</div>
<div class="cell c-3 spot rv" data-d="1">
<span class="tag">02 · the knowledge</span>
<h3>It compounds instead of evaporating</h3>
<p><code>learn</code> saves a finding. <code>recall</code> gets it back by tag, keyword, meaning — or by the very file about to be edited. Strength rises each time a memory proves useful.</p>
<div class="meter" id="meter">
<div class="mrow s"><span class="nm">m41</span><span class="mbar"><span class="mfill" data-w="92"></span></span><span class="vl">Strong</span></div>
<div class="mrow l"><span class="nm">m38</span><span class="mbar"><span class="mfill" data-w="58"></span></span><span class="vl">Linked</span></div>
<div class="mrow w"><span class="nm">m12</span><span class="mbar"><span class="mfill" data-w="21"></span></span><span class="vl">Weak</span></div>
</div>
</div>
<div class="cell c-2 spot rv">
<span class="tag">03 · the proof</span>
<div class="big">Done <em>means</em><br>proven.</div>
<p style="margin-top:1rem">Every acceptance criterion carries a command. <code>validate</code> runs them and refuses to stamp unless all pass.</p>
</div>
<div class="cell c-2 spot rv" data-d="1">
<span class="tag">local-first</span>
<div class="big">0</div>
<p style="margin-top:1rem">Files written into your repo. No account, no sync, no telemetry — one SQLite file you can <code>SELECT</code> from at 2am.</p>
</div>
<div class="cell c-2 spot rv" data-d="2">
<span class="tag">built for swarms</span>
<div class="big">n<em>+1</em></div>
<p style="margin-top:1rem">Tasks tie to git branches and writes are serialized, so parallel agents don't trample each other's state.</p>
</div>
</div>
</section>
</div>
<div class="wrap">
<section id="memory" style="padding-bottom:clamp(2.4rem,5vw,3.4rem)">
<div class="head">
<span class="eyebrow rv">how the memory works</span>
<h2 class="display rv" data-d="1">It doesn't store notes.<br>It grows a <em>graph.</em></h2>
<p class="lede rv" data-d="2">Memories that surface together get bonded — fire together, wire together. Retrieve one and you pull in its neighbourhood, so recall returns a <strong>context</strong>, not a list.</p>
</div>
</section>
</div>
<div class="band rv">
<canvas id="graph"></canvas>
<div class="overlay">
<span class="cap">memory graph · live</span>
<div class="legend">
<span><i style="background:#CCFF4D;box-shadow:0 0 10px #CCFF4D"></i> strong — recalled often</span>
<span><i style="background:#4DE3FF"></i> linked — bound to a file or task</span>
<span><i style="background:rgba(246,246,248,.3)"></i> weak — pruning candidate</span>
<span><i style="background:#8B6CFF"></i> bond — co-activation</span>
</div>
</div>
</div>
<div class="wrap">
<section id="proof">
<div class="head">
<span class="eyebrow rv">proof of done</span>
<h2 class="display rv" data-d="1">An agent that can't <em>mark its own homework.</em></h2>
<p class="lede rv" data-d="2">"I've completed the task" is the most expensive sentence in agentic coding. Sara doesn't take its word for it.</p>
</div>
<div class="screen rv" data-d="2" style="max-width:900px">
<div class="chrome">
<i style="background:#FF5F57"></i><i style="background:#FEBC2E"></i><i style="background:#28C840"></i>
<span class="t"><b>closing a task that isn't actually finished</b></span>
</div>
<div class="tty" style="min-height:0"><span class="p">$</span> <span class="c">sara validate 4f2a91c8</span>
<span class="o">running acceptance criteria…</span>
<span class="g">✔</span> <span class="o">token refresh covered by a test</span> <span class="d">cargo test refresh</span>
<span class="r">✘</span> <span class="o">no clippy warnings</span> <span class="d">cargo clippy -- -D warnings</span>
<span class="d">└─ error: unused variable `skew` — src/auth/login.rs:42</span>
<span class="r">refused to stamp · 1 of 2 criteria failed</span>
<span class="d">the task stays open. fix it, then validate again.</span></div>
</div>
<table class="cmp rv" data-d="2">
<thead><tr><th></th><th>A bare agent</th><th>With Sara</th></tr></thead>
<tbody>
<tr><td>Yesterday's decisions</td><td>Gone with the context window</td><td>Recalled before the first line of code</td></tr>
<tr><td>"It's done"</td><td>Asserted in prose</td><td>Proven by a command, or not stamped</td></tr>
<tr><td>The same gotcha</td><td>Rediscovered, billed twice</td><td>Surfaced automatically by file or tag</td></tr>
<tr><td>Handing off</td><td>Re-explain the whole thing</td><td><code>plan show</code> — a dependency-ordered brief</td></tr>
<tr><td>Where state lives</td><td>A chat log nobody rereads</td><td>Queryable SQLite that you own</td></tr>
<tr><td>Two agents at once</td><td>They trample each other</td><td>Branch-tied tasks, serialized writes</td></tr>
</tbody>
</table>
</section>
</div>
<div class="wrap">
<section id="start">
<div class="head">
<span class="eyebrow rv">wire it up</span>
<h2 class="display rv" data-d="1">Three commands. Then <em>never explain twice.</em></h2>
</div>
<div class="steps">
<div class="step rv"><span class="no">01 — INSTALL</span>
<h3>One binary</h3>
<p style="color:var(--mute);font-size:.935rem">No runtime, no daemon, no account.</p>
<pre>cargo install sara-tasks</pre>
</div>
<div class="step rv" data-d="1"><span class="no">02 — CLAIM THE REPO</span>
<h3>A git repo is a project</h3>
<p style="color:var(--mute);font-size:.935rem">Sara keeps its own database. Your repo stays untouched.</p>
<pre>cd ~/code/your-project
sara init</pre>
</div>
<div class="step rv" data-d="2"><span class="no">03 — HAND IT OVER</span>
<h3>44 typed MCP tools</h3>
<p style="color:var(--mute);font-size:.935rem">Point any MCP client at the server. The agent drives the loop itself.</p>
<pre>sara mcp</pre>
</div>
</div>
</section>
</div>
<div class="wrap">
<section class="final">
<h2 class="display rv">Stop re-explaining<br>your <em>codebase.</em></h2>
<p class="lede rv" data-d="1">Sara remembers the plan, the reasons, and what "done" actually means — so your agent shows up tomorrow already knowing.</p>
<div class="actions rv" data-d="2">
<a class="btn btn-p mag" href="https://github.com/Abarbesgaard/Sara">Read the docs <span class="arw">→</span></a>
<a class="btn btn-s mag" href="https://crates.io/crates/sara-tasks">cargo install sara-tasks</a>
</div>
<div class="figs rv" data-d="2">
<div class="fig"><b class="cnt" data-to="44">0</b><span>MCP tools</span></div>
<div class="fig"><b class="cnt" data-to="1">0</b><span>SQLite file</span></div>
<div class="fig"><b class="cnt" data-to="0">0</b><span>files in your repo</span></div>
<div class="fig"><b style="color:var(--lime)">MIT</b><span>licence</span></div>
</div>
</section>
</div>
</main>
<footer>
<div class="in">
<span>sara v1.1.1 — written in Rust</span>
<span class="rr"><a href="spqr/">the adeptus machinae rite</a> · <a href="https://github.com/Abarbesgaard/Sara">github.com/Abarbesgaard/Sara</a></span>
</div>
</footer>
<script>
"use strict";
const RM = matchMedia("(prefers-reduced-motion: reduce)").matches;
(function () {
const h = document.getElementById("h1");
const walk = (node) => {
[...node.childNodes].forEach(n => {
if (n.nodeType === 3) {
const frag = document.createDocumentFragment();
n.textContent.split(/(\s+)/).forEach(t => {
if (!t.trim()) return frag.appendChild(document.createTextNode(t));
const s = document.createElement("span");
s.className = "word"; s.textContent = t;
frag.appendChild(s);
});
n.replaceWith(frag);
} else if (n.nodeType === 1) walk(n);
});
};
walk(h);
const words = [...h.querySelectorAll(".word")];
words.forEach((w, i) => setTimeout(() => w.classList.add("in"), 90 + i * 55));
})();
if ("IntersectionObserver" in window && !RM) {
const io = new IntersectionObserver(es => es.forEach(e => {
if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); }
}), { threshold: .12, rootMargin: "0px 0px -60px 0px" });
document.querySelectorAll(".rv").forEach(el => io.observe(el));
} else document.querySelectorAll(".rv").forEach(el => el.classList.add("in"));
let lastY = 0;
addEventListener("scroll", () => {
const y = scrollY, n = document.getElementById("nav");
n.classList.toggle("hide", y > lastY && y > 420);
lastY = y;
}, { passive: true });
document.querySelectorAll(".spot,.screen").forEach(el => {
el.addEventListener("pointermove", e => {
const r = el.getBoundingClientRect();
el.style.setProperty("--sx", (e.clientX - r.left) + "px");
el.style.setProperty("--sy", (e.clientY - r.top) + "px");
});
});
(function () {
const s = document.getElementById("screen");
if (!s || RM) return;
const st = s.closest(".stage");
st.addEventListener("pointermove", e => {
const r = st.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width - .5;
const py = (e.clientY - r.top) / r.height - .5;
s.style.setProperty("--ry", (px * 4.5).toFixed(2) + "deg");
s.style.setProperty("--rx", (-py * 3.5).toFixed(2) + "deg");
});
st.addEventListener("pointerleave", () => {
s.style.setProperty("--ry", "0deg"); s.style.setProperty("--rx", "0deg");
});
})();
if (!RM) document.querySelectorAll(".mag").forEach(b => {
b.addEventListener("pointermove", e => {
const r = b.getBoundingClientRect();
b.style.transform = `translate(${((e.clientX - r.left) / r.width - .5) * 9}px,${((e.clientY - r.top) / r.height - .5) * 9}px)`;
});
b.addEventListener("pointerleave", () => b.style.transform = "");
});
const cl = document.getElementById("cmdline");
cl.addEventListener("click", () => {
const h = document.getElementById("cphint");
navigator.clipboard?.writeText(cl.querySelector(".cm").textContent).then(() => {
h.textContent = "copied ✓"; h.style.color = "var(--lime)";
setTimeout(() => { h.textContent = "copy"; h.style.color = ""; }, 1700);
}).catch(() => h.textContent = "select");
});
(function () {
const tools = ["add", "next", "step_done", "validate", "verify", "recall", "learn", "memories",
"forget", "promote", "link_memory", "prune_memories", "consolidate", "reflect",
"diagnose_memories", "reindex_embeddings", "info", "list", "modify", "steps", "check",
"annotate", "feedback", "resolve", "record_run", "link", "attach", "assignment",
"rationale", "dep", "start", "stop", "done", "plan_import", "plan_show", "tags"];
const t = document.getElementById("track");
const html = tools.map(x => `<span>${x}</span>`).join("");
t.innerHTML = html + html;
})();
const SCENES = [
{
title: "tomorrow · a brand-new context window",
lines: [
{ c: "sara recall --file src/auth/login.rs" },
{ w: 240 },
{ o: '<span class="d">3 memories · newest first</span>' },
{ o: '<span class="g">m41</span> <span class="y">[Strong]</span> <span class="o">validate_token() fails on clock skew > 30s.</span>' },
{ o: '<span class="o"> We allow 60s of drift on purpose — don\'t "fix" it.</span>' },
{ o: '<span class="b">m38</span> <span class="d">[Linked]</span> <span class="o">refresh flow is covered in tests/auth_e2e.rs</span>' },
{ o: '<span class="b">m22</span> <span class="d">[Linked]</span> <span class="o">never log a raw token — route it through LogSanitizer</span>' },
{ w: 460 },
{ c: "sara next 4f2a91c8" },
{ w: 200 },
{ o: '<span class="o">step 3 of 6 · </span><span class="y">wire refresh into the middleware</span>' },
{ o: '<span class="d"> 1 ✔ schema 2 ✔ token issuer</span>' },
{ w: 520 },
{ o: '' },
{ o: '<span class="g">it already knows the rules, the plan,</span>' },
{ o: '<span class="g">and exactly where it left off.</span>' },
{ o: '<span class="d">you have not typed a word of context.</span>' }
]
},
{
title: "proof, not prose",
lines: [
{ c: "sara validate 4f2a91c8" },
{ w: 300 },
{ o: '<span class="o">running acceptance criteria…</span>' },
{ o: ' <span class="g">✔</span> <span class="o">token refresh covered by a test</span> <span class="d">cargo test refresh</span>' },
{ w: 380 },
{ o: ' <span class="r">✘</span> <span class="o">no clippy warnings</span> <span class="d">cargo clippy -- -D warnings</span>' },
{ o: ' <span class="d">└─ unused variable `skew` — src/auth/login.rs:42</span>' },
{ w: 300 },
{ o: '' },
{ o: '<span class="r">refused to stamp · 1 of 2 criteria failed</span>' },
{ o: '<span class="d">the task stays open until the command is green.</span>' },
{ w: 600 },
{ o: '' },
{ o: '<span class="g">"done" is a command that passed,</span>' },
{ o: '<span class="g">never a sentence the model wrote.</span>' }
]
},
{
title: "a lesson learned once",
lines: [
{ c: 'sara learn --auto-files --tag auth \\' },
{ c2: ' "60s clock drift is deliberate — see RFC note in #412"' },
{ w: 320 },
{ o: '<span class="g">Learned m41</span> <span class="d">(5973ece2)</span>' },
{ o: '<span class="d"> files: src/auth/login.rs, src/auth/token.rs (from git diff)</span>' },
{ w: 300 },
{ o: '<span class="y">⚠ 1 existing memory overlaps this file</span>' },
{ o: '<span class="d"> m12 — "clock skew should be tightened to 5s"</span>' },
{ o: '<span class="d"> → supersede it, correct it, or confirm both stand.</span>' },
{ w: 620 },
{ o: '' },
{ o: '<span class="g">it noticed the contradiction before you did.</span>' },
{ o: '<span class="d">stale knowledge is the thing that poisons recall.</span>' }
]
}
];
const tty = document.getElementById("tty");
const titleEl = document.getElementById("scene-title");
let sceneIdx = 0, runToken = 0;
function playScene(idx) {
const token = ++runToken;
sceneIdx = idx;
const sc = SCENES[idx];
titleEl.textContent = sc.title;
[...document.querySelectorAll("#tabs button")].forEach((b, i) =>
b.setAttribute("aria-selected", String(i === idx)));
tty.innerHTML = "";
if (RM) {
sc.lines.forEach(l => {
if (l.c || l.c2) tty.insertAdjacentHTML("beforeend", `<span class="l">${l.c ? '<span class="p">$</span> ' : ""}<span class="c">${l.c || l.c2}</span></span>`);
else if (l.o !== undefined) tty.insertAdjacentHTML("beforeend", `<span class="l">${l.o || " "}</span>`);
});
return;
}
const caret = document.createElement("span");
caret.className = "caret";
let i = 0;
const step = () => {
if (token !== runToken) return;
if (i >= sc.lines.length) {
caret.remove();
setTimeout(() => { if (token === runToken) playScene((idx + 1) % SCENES.length); }, 5200);
return;
}
const ln = sc.lines[i++];
if (ln.w) return setTimeout(step, ln.w);
if (ln.o !== undefined) {
tty.insertAdjacentHTML("beforeend", `<span class="l">${ln.o || " "}</span>`);
return setTimeout(step, 120);
}
const text = ln.c || ln.c2;
const row = document.createElement("span");
row.className = "l";
row.innerHTML = ln.c ? '<span class="p">$</span> <span class="c"></span>' : '<span class="c"></span>';
tty.appendChild(row);
const tgt = row.querySelector(".c");
tgt.after(caret);
let n = 0;
const type = () => {
if (token !== runToken) return;
tgt.textContent = text.slice(0, ++n);
if (n < text.length) setTimeout(type, 22 + Math.random() * 30);
else setTimeout(step, 260);
};
type();
};
step();
}
document.getElementById("tabs").addEventListener("click", e => {
const b = e.target.closest("button");
if (b) playScene(+b.dataset.s);
});
playScene(0);
(function () {
const cv = document.getElementById("decay");
if (!cv) return;
const ctx = cv.getContext("2d");
let W, H, dpr = Math.min(devicePixelRatio || 1, 2), t = 0, started = false;
function size() {
W = cv.clientWidth; H = cv.clientHeight;
cv.width = W * dpr; cv.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
addEventListener("resize", size); size();
const PAD = { l: 16, r: 16, t: 26, b: 30 };
const SESSIONS = 6, STEPS = 40;
function bare(x) {
const f = (x * SESSIONS) % 1, peak = .55;
return f < .78 ? peak * (f / .78) : peak * (1 - (f - .78) / .22);
}
function sara(x) {
const s = x * SESSIONS, i = Math.floor(s), f = s % 1;
const fl = n => Math.min(.84, .15 + n * .14);
const floor = fl(i), next = fl(i + 1), peak = Math.min(.97, floor + .22);
return f < .72 ? floor + (peak - floor) * (f / .72) : peak + (next - peak) * ((f - .72) / .28);
}
function line(fn, prog, color, glow, fill) {
const w = W - PAD.l - PAD.r, h = H - PAD.t - PAD.b;
const pts = [];
const N = Math.floor(STEPS * SESSIONS * prog);
for (let k = 0; k <= N; k++) {
const x = k / (STEPS * SESSIONS);
pts.push([PAD.l + x * w, PAD.t + h - fn(x) * h]);
}
if (pts.length < 2) return;
if (fill) {
const gr = ctx.createLinearGradient(0, PAD.t, 0, H - PAD.b);
gr.addColorStop(0, fill); gr.addColorStop(1, "rgba(0,0,0,0)");
ctx.beginPath();
ctx.moveTo(pts[0][0], H - PAD.b);
pts.forEach(p => ctx.lineTo(p[0], p[1]));
ctx.lineTo(pts[pts.length - 1][0], H - PAD.b);
ctx.closePath(); ctx.fillStyle = gr; ctx.fill();
}
ctx.beginPath();
pts.forEach((p, k) => k ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1]));
ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.lineJoin = "round"; ctx.lineCap = "round";
ctx.shadowBlur = glow; ctx.shadowColor = color;
ctx.stroke(); ctx.shadowBlur = 0;
const last = pts[pts.length - 1];
ctx.beginPath(); ctx.arc(last[0], last[1], 3.4, 0, 7); ctx.fillStyle = color;
ctx.shadowBlur = 12; ctx.shadowColor = color; ctx.fill(); ctx.shadowBlur = 0;
}
function draw() {
ctx.clearRect(0, 0, W, H);
const w = W - PAD.l - PAD.r, h = H - PAD.t - PAD.b;
ctx.font = '10px ui-monospace, SFMono-Regular, Menlo, monospace';
for (let s = 0; s <= SESSIONS; s++) {
const x = PAD.l + (s / SESSIONS) * w;
ctx.strokeStyle = "rgba(255,255,255,.045)";
ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(x, PAD.t - 8); ctx.lineTo(x, H - PAD.b); ctx.stroke();
if (s < SESSIONS) {
ctx.fillStyle = "rgba(246,246,248,.26)";
ctx.fillText("session " + (s + 1), x + 8, H - PAD.b + 18);
}
}
ctx.strokeStyle = "rgba(255,255,255,.1)";
ctx.beginPath(); ctx.moveTo(PAD.l, H - PAD.b); ctx.lineTo(W - PAD.r, H - PAD.b); ctx.stroke();
const prog = Math.min(1, t / 210);
line(sara, prog, "#CCFF4D", 14, "rgba(204,255,77,.13)");
line(bare, prog, "#FF6B8A", 10, "rgba(255,107,138,.09)");
if (started && t < 260) t++;
if (t >= 260) t = 0;
requestAnimationFrame(draw);
}
new IntersectionObserver(es => es.forEach(e => { if (e.isIntersecting) started = true; }),
{ threshold: .3 }).observe(cv);
draw();
})();
(function () {
const cv = document.getElementById("graph");
if (!cv) return;
const ctx = cv.getContext("2d");
let W, H, dpr = Math.min(devicePixelRatio || 1, 2), nodes = [], links = [], hover = null, mx = -999, my = -999;
const DATA = [
["clock skew 60s is deliberate", "s"], ["LogSanitizer for tokens", "s"], ["refresh flow e2e", "l"],
["RLS on public.users", "l"], ["n+1 in list_orders()", "l"], ["migration 014 is destructive", "s"],
["retry budget = 3", "w"], ["webhook ordering", "l"], ["idempotency keys", "l"],
["feature flag: billing_v2", "w"], ["cache invalidation on write", "l"], ["SQLITE_BUSY under swarms", "s"],
["prefer savepoints", "l"], ["display-id race", "s"], ["embed on learn only", "w"],
["BEGIN IMMEDIATE for writes", "s"], ["tags beat free-text recall", "l"], ["auth cookies are SameSite=Lax", "l"],
["never trust the 200 from /pay", "s"], ["seed data lives in fixtures/", "w"], ["clippy -D warnings in CI", "l"],
["branch tie prevents recompaction", "s"], ["provisional memories decay", "w"], ["one idea per memory", "l"],
["WAL mode: readers never block", "s"], ["import/mod.rs holds a txn", "l"], ["absolute paths on file links", "l"],
["strength rises on recall hit", "l"], ["prune at 90 days unlinked", "w"], ["44 typed MCP tools", "s"]
];
const COL = { s: "#CCFF4D", l: "#4DE3FF", w: "rgba(246,246,248,.34)" };
function seed() {
const cols = Math.max(4, Math.round(Math.sqrt(DATA.length * (W / Math.max(H, 1)) * 1.4)));
const rows = Math.ceil(DATA.length / cols);
const padX = 46, padY = 92;
nodes = DATA.map(([label, k], i) => {
const cx = i % cols, cy = Math.floor(i / cols);
return {
label, k, r: k === "s" ? 5.4 : k === "l" ? 4 : 2.9,
x: padX + (cx + .5 + (Math.random() - .5) * .82) / cols * (W - padX * 2),
y: padY + (cy + .5 + (Math.random() - .5) * .82) / rows * (H - padY * 2),
vx: (Math.random() - .5) * .16, vy: (Math.random() - .5) * .16, pulse: 0
};
});
links = [];
const seen = new Set();
nodes.forEach((n, i) => {
const near = nodes
.map((m, j) => ({ m, j, d: Math.hypot(m.x - n.x, m.y - n.y) }))
.filter(o => o.j !== i).sort((a, b) => a.d - b.d)
.slice(0, n.k === "s" ? 3 : n.k === "l" ? 2 : 1);
near.forEach(o => {
const key = i < o.j ? `${i}-${o.j}` : `${o.j}-${i}`;
if (seen.has(key)) return;
seen.add(key); links.push({ a: n, b: o.m });
});
});
}
function size() {
W = cv.clientWidth; H = cv.clientHeight;
cv.width = W * dpr; cv.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
if (!nodes.length) seed();
}
addEventListener("resize", size); size();
cv.addEventListener("pointermove", e => {
const r = cv.getBoundingClientRect();
mx = e.clientX - r.left; my = e.clientY - r.top;
hover = null; let best = 26 * 26;
nodes.forEach(n => { const d = (n.x - mx) ** 2 + (n.y - my) ** 2; if (d < best) { best = d; hover = n; } });
cv.style.cursor = hover ? "pointer" : "default";
});
cv.addEventListener("pointerleave", () => { hover = null; mx = my = -999; });
function fire(n) {
n.pulse = 1;
links.forEach(l => { if (l.a === n) l.b.pulse = Math.max(l.b.pulse, .72); if (l.b === n) l.a.pulse = Math.max(l.a.pulse, .72); });
}
cv.addEventListener("click", () => fire(hover || nodes[(Math.random() * nodes.length) | 0]));
let auto = 0;
function draw() {
ctx.clearRect(0, 0, W, H);
links.forEach(l => {
const d = Math.hypot(l.b.x - l.a.x, l.b.y - l.a.y);
if (d > 260) return;
const heat = Math.max(l.a.pulse, l.b.pulse);
const al = (1 - d / 260) * (.3 + heat * .7);
ctx.strokeStyle = `rgba(139,108,255,${al.toFixed(3)})`;
ctx.lineWidth = .9 + heat * 1.8;
ctx.beginPath(); ctx.moveTo(l.a.x, l.a.y); ctx.lineTo(l.b.x, l.b.y); ctx.stroke();
});
nodes.forEach(n => {
if (!RM) {
n.x += n.vx; n.y += n.vy;
if (n.x < 30 || n.x > W - 30) n.vx *= -1;
if (n.y < 62 || n.y > H - 62) n.vy *= -1;
const dx = n.x - mx, dy = n.y - my, d2 = dx * dx + dy * dy;
if (d2 < 12000 && d2 > 1) { const f = (1 - d2 / 12000) * .5; n.x += dx / Math.sqrt(d2) * f; n.y += dy / Math.sqrt(d2) * f; }
}
n.pulse *= .968;
const c = COL[n.k];
ctx.shadowBlur = (n.k === "s" ? 16 : 9) + n.pulse * 30;
ctx.shadowColor = c; ctx.fillStyle = c;
ctx.beginPath(); ctx.arc(n.x, n.y, n.r + n.pulse * 3.6, 0, 7); ctx.fill();
ctx.shadowBlur = 0;
if (hover === n || n.pulse > .3) {
ctx.font = '11.5px ui-monospace, SFMono-Regular, Menlo, monospace';
const w = ctx.measureText(n.label).width;
const bx = Math.min(n.x + 12, W - w - 26), by = n.y - 10;
ctx.fillStyle = "rgba(6,6,7,.9)";
ctx.strokeStyle = "rgba(255,255,255,.14)"; ctx.lineWidth = 1;
ctx.beginPath(); ctx.roundRect(bx, by, w + 16, 21, 6); ctx.fill(); ctx.stroke();
ctx.fillStyle = "#F6F6F8"; ctx.fillText(n.label, bx + 8, by + 14.5);
}
});
if (!RM && ++auto > 170) { auto = 0; fire(nodes[(Math.random() * nodes.length) | 0]); }
requestAnimationFrame(draw);
}
draw();
})();
new IntersectionObserver(es => es.forEach(e => {
if (!e.isIntersecting) return;
e.target.querySelectorAll(".mfill").forEach(f => f.style.width = f.dataset.w + "%");
}), { threshold: .4 }).observe(document.getElementById("meter"));
new IntersectionObserver(es => es.forEach(e => {
if (!e.isIntersecting) return;
e.target.querySelectorAll(".cnt").forEach(c => {
const to = +c.dataset.to; let n = 0;
if (to === 0) { c.textContent = "0"; return; }
const iv = setInterval(() => { n += Math.max(1, Math.ceil(to / 22)); if (n >= to) { n = to; clearInterval(iv); } c.textContent = n; }, 34);
});
}), { threshold: .4 }).observe(document.querySelector(".figs"));
</script>
</body>
</html>