Files
echo/echo-memory-performance-brief.html
T
2026-06-22 23:55:19 -05:00

223 lines
13 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ECHO Memory — Performance Update</title>
<style>
:root{
--ink:#16181d; --ink-soft:#3a3f4a; --muted:#6b7280;
--line:#e4e7ec; --line-soft:#eef1f4;
--bg:#ffffff; --panel:#f7f8fa; --panel-2:#f0f4f9;
--accent:#2f6df6; --accent-deep:#1b3a8f; --accent-soft:#e8f0ff;
--good:#0f9d6b; --good-soft:#e6f6ef; --bad:#d64545; --bad-soft:#fdecec;
--warn:#b7791f; --code-bg:#0f1320; --code-ink:#e6e9f2;
--radius:14px;
}
*{box-sizing:border-box;}
html{-webkit-print-color-adjust:exact; print-color-adjust:exact;}
body{
margin:0; color:var(--ink); background:#eceef1;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
line-height:1.55; font-size:15px;
}
.page{
max-width:880px; margin:32px auto; background:var(--bg);
padding:0 0 56px; border-radius:18px; overflow:hidden;
box-shadow:0 10px 40px rgba(20,24,35,.10);
}
.hero{
background:linear-gradient(135deg,#101a3a 0%,#1b3a8f 55%,#2f6df6 100%);
color:#fff; padding:48px 56px 40px; position:relative;
}
.hero .kicker{font-size:12px; letter-spacing:.18em; text-transform:uppercase; opacity:.82; margin:0 0 14px;}
.hero h1{font-size:38px; line-height:1.08; margin:0 0 10px; font-weight:750; letter-spacing:-.5px;}
.hero .sub{font-size:16.5px; opacity:.92; max-width:640px; margin:0 0 22px;}
.hero .meta{display:flex; flex-wrap:wrap; gap:10px 26px; font-size:13px; opacity:.92; border-top:1px solid rgba(255,255,255,.18); padding-top:16px;}
.hero .meta b{font-weight:650;}
.pill{display:inline-block; background:rgba(255,255,255,.16); border:1px solid rgba(255,255,255,.28); padding:3px 11px; border-radius:999px; font-size:12px; font-weight:600; margin-right:6px;}
.body{padding:8px 56px 0;}
section{padding:28px 0; border-bottom:1px solid var(--line-soft);}
section:last-child{border-bottom:none;}
h2{font-size:13px; letter-spacing:.14em; text-transform:uppercase; color:var(--accent-deep); margin:0 0 4px; font-weight:700;}
.h2sub{font-size:22px; font-weight:700; letter-spacing:-.3px; margin:0 0 16px; color:var(--ink);}
h3{font-size:15.5px; margin:20px 0 7px; font-weight:680;}
p{margin:0 0 13px; color:var(--ink-soft);}
strong{color:var(--ink);}
code{background:var(--panel-2); padding:1px 6px; border-radius:5px; font-family:"SF Mono",ui-monospace,Menlo,Consolas,monospace; font-size:12.5px; color:var(--accent-deep);}
.lede{font-size:16.5px; color:var(--ink); background:var(--accent-soft); border-left:4px solid var(--accent); padding:15px 19px; border-radius:0 10px 10px 0; margin:0;}
.metrics{display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin:4px 0 6px;}
.metric{background:var(--panel); border:1px solid var(--line); border-radius:var(--radius); padding:16px 13px; text-align:center;}
.metric .num{font-size:30px; font-weight:780; letter-spacing:-1px; line-height:1; color:var(--accent-deep);}
.metric .num.good{color:var(--good);}
.metric .lbl{font-size:11.5px; color:var(--muted); margin-top:7px; line-height:1.35;}
.grid{display:grid; gap:13px;}
.g2{grid-template-columns:repeat(2,1fr);}
.card{background:var(--panel); border:1px solid var(--line); border-radius:var(--radius); padding:15px 16px;}
.card h4{margin:0 0 5px; font-size:14.5px; font-weight:680;}
.card p{margin:0; font-size:13px; color:var(--muted); line-height:1.5;}
table{width:100%; border-collapse:collapse; font-size:13px; margin:8px 0 6px;}
th,td{text-align:left; padding:9px 11px; border-bottom:1px solid var(--line);}
th{background:var(--panel-2); font-size:11px; letter-spacing:.05em; text-transform:uppercase; color:var(--ink-soft); font-weight:680;}
td.n{font-variant-numeric:tabular-nums; text-align:right; white-space:nowrap;}
tr td:first-child{font-weight:600; color:var(--ink);}
.delta{font-weight:700; color:var(--good);}
.muted-row td{color:var(--muted);}
/* bar chart */
.bars{margin:14px 0 4px;}
.bar{display:flex; align-items:center; gap:12px; margin:0 0 9px;}
.bar .name{flex:0 0 168px; font-size:12.5px; color:var(--ink-soft); text-align:right;}
.bar .track{flex:1; background:var(--panel-2); border-radius:7px; height:26px; position:relative; overflow:hidden;}
.bar .fill{height:100%; border-radius:7px; background:linear-gradient(90deg,#2f6df6,#1b3a8f); display:flex; align-items:center; justify-content:flex-end; padding-right:9px; color:#fff; font-size:12px; font-weight:700;}
.bar .fill.old{background:linear-gradient(90deg,#e87f7f,#d64545);}
.bar .fill.best{background:linear-gradient(90deg,#23b783,#0f9d6b);}
.note{background:var(--good-soft); border:1px solid #bfe6d4; border-radius:10px; padding:12px 16px; font-size:12.5px; color:#0c5c43; margin:14px 0 0;}
.warn{background:#fff6e6; border:1px solid #f0d9a8; color:#7a5712;}
.foot{padding:22px 56px 0; color:var(--muted); font-size:11.5px; line-height:1.5;}
.foot b{color:var(--ink-soft);}
@media print{
body{background:#fff;}
.page{margin:0; box-shadow:none; border-radius:0; max-width:none; padding-bottom:24px;}
@page{ size:A4; margin:13mm; }
section,.metrics,.bars,table,.grid{break-inside:avoid;}
.hero{border-radius:0;}
}
</style>
</head>
<body>
<div class="page">
<div class="hero">
<p class="kicker">ECHO Memory · Performance Update</p>
<h1>Full-vault operations: from timing out to sub-second</h1>
<p class="sub">Releases v1.1.0 and v1.2.0 rebuilt the vault's network layer and entity
resolution. Long operations that used to exceed the agent tool timeout and drop the
session now complete in under a second — and shortened entity names resolve instead of
spawning duplicates.</p>
<div class="meta">
<span><span class="pill">v1.1.0</span><span class="pill">v1.2.0</span></span>
<span><b>Prepared</b> 2026-06-22</span>
<span><b>Vault</b> 186 notes · 119 entities</span>
<span><b>Endpoint</b> echoapi.alwisp.com</span>
</div>
</div>
<div class="body">
<section>
<p class="lede">Scripts that sweep the whole vault — <code>sweep.py</code>,
<code>vault_lint.py</code>, recall rebuild — were making hundreds of <em>serial</em>
requests, each opening a fresh TLS connection, and re-reading every note 23 times.
On the constrained agent sandbox that pushed a single pass past the ~120-second tool
timeout, so the process was killed mid-run and the session/thread was dropped. The
fix attacks the cause on three axes; the headline change is reliability:
<strong>a full-vault pass that used to fail now finishes in well under a second.</strong></p>
</section>
<section>
<h2>At a glance</h2>
<div class="metrics">
<div class="metric"><div class="num">4.5&times;</div><div class="lbl">faster per request<br>(connection reuse)</div></div>
<div class="metric"><div class="num">~2&times;</div><div class="lbl">added by concurrency<br>(full-vault scale)</div></div>
<div class="metric"><div class="num">23&times;</div><div class="lbl">fewer requests<br>(single-pass cache)</div></div>
<div class="metric"><div class="num good">&lt;1 s</div><div class="lbl">full-vault pass<br>(was: timeout)</div></div>
</div>
<p style="margin-top:6px">These factors compound. A full-vault pass pays
<strong>4.5&times;</strong> less per request, issues <strong>~23&times;</strong> fewer
of them, and runs the rest <strong>~2&times;</strong> in parallel — a modeled
<strong>~1827&times;</strong> reduction in total request-time, before counting the
higher connection latency of the production sandbox where the old path actually failed.</p>
</section>
<section>
<h2>What changed</h2>
<div class="h2sub">Three structural fixes, one resolution overhaul</div>
<div class="grid g2">
<div class="card"><h4>Connection reuse (keep-alive)</h4><p>One persistent
connection per thread, reused across every request — instead of a fresh TCP+TLS
handshake per file. The single biggest win; benefits every operation
(load, capture, recall) for free.</p></div>
<div class="card"><h4>Concurrent bulk reads</h4><p>A new <code>read_many()</code>
fans GETs across a thread pool (8 workers). I/O-bound work, so it parallelizes
cleanly; the win grows with vault size and network latency.</p></div>
<div class="card"><h4>Single-pass shared cache</h4><p>Each note is fetched
<em>once</em> and reused across all passes — eliminating the 23&times; re-reads and
the per-link-target re-fetch storm in <code>sweep</code>.</p></div>
<div class="card"><h4>Entity resolution (v1.2.0)</h4><p>Shortened names now resolve
via aliases + a fuzzy fallback, so a mention like &ldquo;echo memory&rdquo; finds the
existing project instead of silently creating a duplicate note.</p></div>
</div>
</section>
<section>
<h2>Measured — per request</h2>
<div class="h2sub">Connection reuse is the dominant win</div>
<p>Micro-benchmark: 25 real notes, best of 3 runs, against the live endpoint.</p>
<div class="bars">
<div class="bar"><div class="name">Fresh connection / GET <em>(old)</em></div>
<div class="track"><div class="fill old" style="width:100%">30.3 ms</div></div></div>
<div class="bar"><div class="name">Pooled keep-alive (serial)</div>
<div class="track"><div class="fill" style="width:21.8%">6.6 ms</div></div></div>
<div class="bar"><div class="name">Pooled + concurrent <em>(new)</em></div>
<div class="track"><div class="fill best" style="width:18.8%">5.7 ms</div></div></div>
</div>
<table>
<tr><th>Mode</th><th class="n">ms / request</th><th class="n">vs old</th></tr>
<tr><td>Fresh connection per GET (old)</td><td class="n">30.3</td><td class="n">1.0&times;</td></tr>
<tr><td>Pooled keep-alive, serial</td><td class="n">6.6</td><td class="n delta">4.5&times;</td></tr>
<tr><td>Pooled + concurrent (new default)</td><td class="n">5.7</td><td class="n delta">5.3&times;</td></tr>
</table>
</section>
<section>
<h2>Measured — full vault (186 notes)</h2>
<div class="h2sub">Serial vs concurrent, both pooled</div>
<table>
<tr><th>Operation</th><th class="n">serial (1 worker)</th><th class="n">concurrent (8)</th><th class="n">speedup</th></tr>
<tr><td>vault_lint.py</td><td class="n">1.77 s</td><td class="n">0.90 s</td><td class="n delta">2.0&times;</td></tr>
<tr><td>sweep.py (plan)</td><td class="n">1.64 s</td><td class="n">0.85 s</td><td class="n delta">1.9&times;</td></tr>
<tr><td>sweep.py --apply</td><td class="n">&mdash;</td><td class="n">1.07 s</td><td class="n">&mdash;</td></tr>
<tr class="muted-row"><td>single GET · doctor</td><td class="n">&mdash;</td><td class="n">0.28 s · 0.21 s</td><td class="n">&mdash;</td></tr>
</table>
<div class="note warn"><strong>Before the fix:</strong> the same passes exceeded the
~120-second agent tool ceiling and were terminated mid-run, dropping the session.
The numbers above are the <em>new</em> floor; the practical change is
<strong>fails&nbsp;&rarr;&nbsp;completes</strong>.</div>
</section>
<section>
<h2>Efficiency — entity resolution (v1.2.0)</h2>
<div class="h2sub">Fewer duplicates, less wasted work</div>
<p>Resolution was exact-match only, so a shortened or expanded name returned
<em>nothing</em> — and the writer would create a parallel note. The active project
slugged <code>echo</code> was invisible to the mention &ldquo;echo memory.&rdquo; Now:</p>
<table>
<tr><th>Lookup</th><th>Before</th><th>After</th></tr>
<tr><td>echo memory · echo plugin · echo-memory</td><td><span class="delta" style="color:var(--bad)">no match</span></td><td class="delta">&rarr; projects/active/echo.md</td></tr>
<tr><td>non-aliased shortened name</td><td><span style="color:var(--bad);font-weight:700">no match</span></td><td class="delta">ranked candidates</td></tr>
<tr><td>update under a new name</td><td>second slug, same note</td><td class="delta">canonical slug + alias learned</td></tr>
</table>
<p style="margin-top:10px">Aliases are auto-derived from titles, learned from mentions on
update, and stored in note frontmatter (folded back into the index on sweep) — so the
graph gets <em>better</em> at resolution over time, while exact-match safety prevents
wrong auto-merges. The payoff is avoided duplicate entities and the cleanup/merge passes
they trigger.</p>
</section>
</div>
<div class="foot">
<p><b>Methodology.</b> Measured on the maintainer's workstation against the live
<code>echoapi.alwisp.com</code> endpoint (low latency). The production agent sandbox has
higher per-request connection latency, so the connection-reuse gain — and the reliability
fix — is larger there than the local figures show. The ~1827&times; compound is a model of
the three measured factors (4.5&times; reuse &times; ~2&times; concurrency &times; ~23&times; fewer reads),
not a single end-to-end timing. Stdlib-only; no new dependencies.</p>
<p><b>Artifacts.</b> echo-memory v1.2.0 &mdash; <code>echo-memory-1.2.0.plugin</code>.
33 automated tests pass; vault-health linter clean of regressions.</p>
</div>
</div>
</body>
</html>