From 766001fc5a29c3843acc4010a1b7bef5b3dc3095 Mon Sep 17 00:00:00 2001 From: Griswald Brooks Date: Mon, 29 Jun 2026 10:29:07 -0400 Subject: [PATCH 1/3] =?UTF-8?q?blog:=20Two=20Memories=20=E2=80=94=20Honcho?= =?UTF-8?q?=20vs=20Hindsight=20teardown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source-level comparison of two agent-memory systems, descending from design philosophy to schema. Adds 3 files (1 post + 2 static assets); no template or global-CSS changes. All widget styles namespaced .tm-*. Co-Authored-By: Claude Opus 4.8 (1M context) --- content/blog/2026-06-27-two-memories.md | 228 ++++++++++++++++++++++++ static/blog/two-memories/widgets.css | 93 ++++++++++ static/blog/two-memories/widgets.js | 200 +++++++++++++++++++++ 3 files changed, 521 insertions(+) create mode 100644 content/blog/2026-06-27-two-memories.md create mode 100644 static/blog/two-memories/widgets.css create mode 100644 static/blog/two-memories/widgets.js diff --git a/content/blog/2026-06-27-two-memories.md b/content/blog/2026-06-27-two-memories.md new file mode 100644 index 0000000..32798b4 --- /dev/null +++ b/content/blog/2026-06-27-two-memories.md @@ -0,0 +1,228 @@ ++++ +title = "Two Memories: Honcho vs Hindsight, all the way down" +date = 2026-06-27 +[taxonomies] +tags = ["agent-memory", "llm", "honcho", "hindsight", "systems"] +categories = ["engineering"] ++++ + + + +If you ask an LLM to compare [Honcho](https://github.com/plastic-labs/honcho) and [Hindsight](https://github.com/vectorize-io/hindsight), you get mush. Both "give your agent long-term memory." Both "go beyond RAG." Both "reason." The mush isn't the model's fault — it's downstream of docs that are themselves vague, and a marketing layer that wants every system to sound like the same magic. + +So I cloned both repos and read down to the schema. The two systems are **not** two implementations of one idea. They're two opposing bets about *where intelligence should live in a memory system* — and that single disagreement propagates cleanly from design philosophy all the way down to their `CREATE TABLE` statements. One puts the intelligence in a model. The other puts it in the structure. Everything else follows. + +What follows is a descent. Each layer is the same question asked further down: how does the system decide what's true, what to keep, what to surface, and how to store it. Amber is Honcho; teal is Hindsight. + +## Layer 00 — The split + +Honcho (Plastic Labs) models **people**. Its thesis is "memory as reasoning": it treats remembering as a logical-reasoning task and builds a theory-of-mind representation of each participant. Tellingly, *both* users and agents are "peers" — it's fundamentally a social-cognition engine. + +Hindsight (Vectorize, with Virginia Tech and The Washington Post) models **the world and the agent's own experience**, with a hard wall between *evidence* and *inference*. The architecture exists to keep "what the agent saw" separate from "what the agent believes," over a queryable timeline. + +> One bets on weights. The other bets on structure. Read every layer below as that same wager, restated. + +## Layer 01 — The reasoning engine + +This is the cleanest expression of the split, and the first place the docs mislead. + +Honcho trains a model. **Neuromancer XR** is a fine-tune of Qwen3-8B on a hand-curated dataset (~10k social-reasoning traces) mapping conversation turns to atomic conclusions along a certainty spectrum. Crucially it's trained *once*, by Plastic Labs, ahead of time. Your conversations run *inference* through frozen weights — no gradients touch your data. The "continual learning" is rows accumulating in Postgres, not weight updates. (Swap Neuromancer for GLM-5 or Grok and you keep all your memory — proof it lives in the DB, not the model.) + +Hindsight trains nothing. Its reasoning layer, CARA, is a prompt-and-orchestration framework over *any* model you configure. It's LLM-based at several stages — fact extraction, a cross-encoder reranker, response generation — all swappable modules. The intelligence is in the pipeline and the prompts, not a specialized weight set. + +
+
+ Neuromancer XR (Qwen3-8B fine-tune) + generic backbone (open-source 20B+) +
+
+
+

Fig 1 · published benchmarks   Honcho's win comes from a small specialized model; Hindsight's from a generic backbone inside a structured pipeline. These are the numbers each project reports — different benchmarks, not a head-to-head — so read them as "what each bet buys," not a leaderboard.

+ +So the privacy story is the ordinary one (your text sits in their Postgres unless you self-host), not "my data is in a model." And the engineering story is the deep one: **Honcho put a brain in the box; Hindsight built a very good filing system and rents a brain by the call.** + +## Layer 02 — Representation + +What is a single "memory"? They answer in opposite directions. + +Honcho: atomic conclusions. A memory is an *atomic conclusion* about a peer, placed on a strict spectrum of logical certainty — `explicit` (stated), `deductive` (necessarily follows), `inductive` (likely pattern), `abductive` (best explanation). Conclusions are recomposable and link to the premises they came from. + +Hindsight: narrative facts. A memory is a *coarse narrative fact* (2–5 per conversation, each covering a whole exchange), filed into one of four networks by epistemic role — `world`, `experience`, `opinion`, `observation`. + +Note the inversion: Honcho makes facts **atomic and recomposable** so a reasoner can scaffold them; Hindsight keeps them **chunky and narrative** so retrieval is robust to where you split the conversation. And neither stores its "types" as separate tables — both use a single fact table with a discriminator column. The four networks are a `WHERE fact_type = …`, not four databases (more at Layer 07). + +## Layer 03 — Retrieval + +Honcho retrieves with an agent. The Dialectic is the one tool-using agent on the synchronous path; it loops over its tools until it has enough context, with a tunable reasoning budget (`minimal → max`, priced per query). Retrieval is a decision the model makes. + +Hindsight retrieves with a fixed pipeline. TEMPR runs four retrievers in parallel — semantic vector, BM25, graph, temporal — fuses them with Reciprocal Rank Fusion, reranks with a cross-encoder, trims to a token budget. No agent decides; the arithmetic does. + +
+

Fig 2 · the two recall paths   left, an adaptive tool loop; right, a deterministic four-arm fusion. Adaptive vs predictable — the same trade reappears in the ranking math at Layer 06.

+ +The RRF core is textbook, constant and all: + +```python +# hindsight · search/fusion.py — four arms, fixed priority +def reciprocal_rank_fusion(result_lists, k=60): + # score(d) = Σ_arms 1 / (k + rank(d)) + ... +``` + +But right below it sits `interleave_fusion` — an admission against the tidy formula. RRF *sums* reciprocal ranks, so a near-duplicate that's rank #1 in *one* arm but absent from the others gets averaged down and dropped below the budget — the exact failure that makes consolidation create duplicates. Their fix is a round-robin that guarantees every arm's top hit a slot. The elegant formalism had a hole; they patched it with an inelegant interleave. You only find that in the source. + +## Layer 04 — Belief and contradiction + +When new information contradicts old, the systems disagree about what "confidence" even is. + +Hindsight is numeric. An opinion is a tuple `(text, c, τ)` with `c ∈ [0,1]`. New evidence is classified and the scalar is nudged: + +```text +c' = min(c + α, 1) # reinforce +c' = max(c − α, 0) # weaken +c' = max(c − 2α, 0) # contradict ← costs double +c' = c # neutral +``` + +That `2α` is the entire deconfliction philosophy in one coefficient: damped but responsive, so beliefs don't oscillate on a single example. And the evidence/inference wall is enforced *in the database* — the CHECK constraint at Layer 07. + +Honcho refuses numbers. It deliberately avoids "arbitrary numerical tokens for certainty," using natural-language tiers plus reasoning traces. Contradiction is handled by re-reasoning: the deduction specialist treats a changed fact as a knowledge update, writes a dated update observation with explicit premises, and **deletes the stale one**. Reconciliation is generative and offline, not arithmetic and online. + +A quiet convergence worth noting: both end up *deleting* superseded beliefs — Honcho via a soft-delete in its deduction agent, Hindsight via an LLM-driven move to a tombstone table. They reach "prune the past" from opposite directions: one through agent judgment, one through a consolidation engine. + +## Layer 05 — The dreamer, and surprisal + +This is the find that most repays reading the source. Honcho's docs mention "reasoning trees," so I expected a logic graph in `src/dreamer/trees/`. It isn't. It's a set of spatial nearest-neighbor structures — random-projection trees, cover trees, LSH — whose only job is to compute **geometric surprisal** over observation embeddings. + +Before the dreamer spends a single expensive reasoning call, it decides *what's worth thinking about* using information theory. It builds a tree over the cloud of a peer's existing observations and scores each by how improbable its path through that tree is: + +```text +# honcho · dreamer/trees/rptree.py +S(x) = −log P(path to x) = Σ −log( n_child / n_parent ) +``` + +An observation landing in a dense, well-trodden region has a high-probability path → *low* surprisal → redundant. One that forces traversal down sparse branches → *high* surprisal → novel or anomalous. The pipeline keeps only the top slice and feeds *those* to the deductive / inductive / abductive specialists. + +
+
+
+ + +
+
+

Fig 3 · surprisal, illustrated   Each drop lands an observation in embedding space. Near the crowd → low surprisal (dim, skipped). Out in empty space → high surprisal (bright, sent to the reasoners). This is the throttle that keeps "dreaming" affordable: spend scarce reasoning only on the surprising tail.

+ +So Honcho's most sophisticated code is a mechanism for *not* reasoning about most things. That tells you exactly what it's bound by: **reasoning compute**. The whole architecture rations an expensive, scarce resource and spends it only where novelty says it'll pay off. (The actual premise-linked "reasoning tree," by the way, lives elsewhere — it's the mandatory `source_ids` linkage the deduction agent writes on every conclusion. Two different "trees" sharing a module; no wonder the docs confuse.) + +## Layer 06 — The recency–relevance arbitration + +The very bottom of retrieval: in a tie, do you get the recent answer or the relevant one? Here the split is almost too clean. + +Honcho has no recency formula at all. Grep the whole repo for `recency_boost`, `half_life`, `time_decay` — zero hits. Instead it exposes three separate primitives as tools and lets the Dialectic agent *choose*: cosine-only semantic, `created_at`-only recent, or `times_derived`-then-recency for reinforced observations. Recency-vs-relevance is an agent decision, not a blend. + +Hindsight has a closed-form score. Relevance is the multiplicative base; recency, temporal proximity, and proof-count are *bounded* modifiers on top: + +```python +# hindsight · search/reranking.py +combined = CE_norm * recency_boost * temporal_boost * proof_count_boost +recency_boost = 1 + alpha * (recency - 0.5) # alpha=0.2 → range [0.9, 1.1] +# combined envelope: max ≈ +21%, min ≈ −19% → recency can NEVER trump relevance +``` + +Drag `α` below and watch the January "I love Python" fact and the June "Rust won me over" fact resolve. Recency is a tiebreaker, not a trump — and there's an explicit guardrail so that when the cross-encoder gives no signal, the order doesn't collapse into a pure recency sort. + +
+
+
+
+
+ + α = 0.20
+
+ + 0.00
+
+
+
+
+

Fig 4 · recency as a bounded modifier   The shaded band is the ±envelope recency can move a result. With relevance tied, June wins by the recency margin — repeatably, every run. Give January a big enough relevance edge and recency can't flip it. That determinism is also why this function is trivially unit-testable; an agent tool-loop is not.

+ +## The trace — one fact, both pipelines + +Concretely: the user says *"I love Python"* in January, then *"honestly, Rust has won me over"* in June. Watch the same contradiction move through both systems. + +
+
+ + + + step 0 / 6 +
+
+

Honchosurprisal → re-reason → delete

+

Hindsightbitemporal retrieve + decay

+
+
+ +The deepest practical difference surfaces here: what each does with the *past*. Honcho prunes the stale "loves Python" observation in favor of a current-truth update — great for "what's true now," weaker for "what did they prefer in February?" Hindsight keeps the January fact as valid history with bitemporal timestamps — heavier, but it answers point-in-time and "how did this belief evolve" queries natively. + +## Layer 07 — Storage: the schema is the architecture + +The DDL confirms everything above. Count the storage systems first: Hindsight is one database doing everything; Honcho is two (optionally three). + +**Honcho — ~11 tables.** PostgreSQL (relational + pgvector HNSW + `tsvector` FTS + a JSONB graph + a `queue` table) plus Redis as a hot cache, plus an optional external vector store (Turbopuffer / LanceDB). Plus a separate deriver worker process. + +**Hindsight — ~19 tables.** PostgreSQL *only*: pluggable ANN (pgvector / pgvectorscale-DiskANN / vchord / scann), BM25 via `pgroonga`/`pg_search`, the graph as real edge tables, queues as tables. There's even an in-process embedded mode. No Redis. + +Three physical divergences tell the whole story. + +**The graph.** Hindsight normalizes it: `memory_links(from_unit_id, to_unit_id, link_type, weight)` is a real edge table, plus `entities`, `unit_entities`, `entity_cooccurrences`. Honcho denormalizes it into a single JSONB column (`source_ids`) with a GIN index — the graph lives *inside* the fact rows. + +**Confidence is a database constraint.** Hindsight's evidence/inference firewall isn't convention; it's DDL. A world or experience fact is *forbidden* a confidence score; an opinion is *required* one: + +```sql +-- hindsight · alembic/…initial_schema.py +CHECK ( + (fact_type = 'opinion' AND confidence_score IS NOT NULL) OR + (fact_type = 'observation') OR + (fact_type NOT IN ('opinion','observation') AND confidence_score IS NULL) +) +``` + +Honcho has no confidence column at all — consistent with its "no numeric certainty" stance. The `level` enum *is* the certainty representation. + +**History.** Hindsight keeps append-only provenance — `observation_history`, `observation_sources`, `invalidated_memory_units`, `audit_log` — built for "how did this evolve" and audit. Honcho compresses all of it into a `times_derived` counter, a `source_ids` array, and a `deleted_at` soft-delete flag. + +The operational corollary: Honcho's footprint is Postgres + Redis + a long-running deriver worker + the model calls. Hindsight is a single Postgres you can even embed in-process. On a RAM-constrained box, that asymmetry can matter more than any algorithm above it. + +## The same bet, at every layer + +| layer | Honcho | Hindsight | +|---|---|---| +| philosophy | models people · reasoning-centric | models world + experience · structure-centric | +| engine | Neuromancer XR — fine-tuned Qwen3-8B | CARA — generic swappable LLMs + prompts | +| unit | atomic conclusion · explicit→abductive | narrative fact · world/exp/opinion/obs | +| retrieval | Dialectic agent loop · budgeted | 4-arm fusion · RRF k=60 + rerank | +| certainty | linguistic tiers · no number | scalar c∈[0,1] · CHECK-enforced | +| contradiction | re-reason offline + delete | c−2α + LLM supersede + tombstone | +| consolidation | surprisal-gated dreaming | proof-count + history tables | +| ranking | no formula · agent picks tool | CE × recency × temporal × proof | +| graph | JSONB adjacency + GIN | normalized edge + entity tables | +| storage | Postgres + Redis (+ ext vec) | Postgres only (or embedded) | +| license | AGPL-3.0 | MIT | + +## Verdict + +Neither is "better." They optimize different axes, and your use case picks for you. + +Reach for Honcho when the job is **knowing who someone is right now** and reasoning well about it — personalization, theory-of-mind, an agent that should anticipate needs. Its bias toward current-truth-plus-provenance fits, and the surprisal gate keeps dream cost bounded. The cost: a heavier runtime and ranking behavior you can't pin down with a deterministic test. + +Reach for Hindsight when you need an **auditable timeline** — "what did this customer believe in Q1 vs Q3," explainable recall, mission-critical recall over many sessions. Bitemporal facts + an evidence/inference firewall + a deterministic, unit-testable ranking function are the win. The cost: a heavier retrieval stack you have to tune, over generic models. + +> They're not two takes on one idea. They're two answers to one question: put the intelligence in the model, or put it in the structure. Everything from the philosophy to the `CHECK` constraint is that single choice, restated. + +--- + +*Written from source — every claim traces to a file, a migration, or a paragraph in the paper, not a docs page. Primary sources: [plastic-labs/honcho](https://github.com/plastic-labs/honcho) (AGPL-3.0), [vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) (MIT), and [arXiv:2512.12818](https://arxiv.org/abs/2512.12818), "Hindsight is 20/20," Latimer et al.* + + diff --git a/static/blog/two-memories/widgets.css b/static/blog/two-memories/widgets.css new file mode 100644 index 0000000..dc34300 --- /dev/null +++ b/static/blog/two-memories/widgets.css @@ -0,0 +1,93 @@ +/* ============================================================ + two-memories — scoped interactive figure styles + Everything is namespaced under .tm-fig / #tm-* so nothing + leaks into the site's global stylesheet. Figures render as + dark "instrument" panels inside the light article. + ============================================================ */ +.tm-fig{ + --tm-honcho:#F2B05E; + --tm-honcho-dim:#7a5a32; + --tm-hindsight:#4FD0C9; + --tm-hindsight-dim:#2c6864; + --tm-ink:#11151C; + --tm-panel:#171D28; + --tm-line:#2A323F; + --tm-text:#E9E6DC; + --tm-muted:#9AA3B2; + --tm-faint:#6B7384; + background:linear-gradient(180deg,var(--tm-panel),var(--tm-ink)); + border:1px solid var(--tm-line); + border-radius:14px; + padding:22px; + margin:30px 0 12px; + color:var(--tm-text); + font-family:'IBM Plex Mono',ui-monospace,monospace; +} +.tm-cap{ + font-family:'IBM Plex Mono',ui-monospace,monospace; + font-size:13px;line-height:1.55;color:#6a6a6a;margin:0 0 26px; +} +.tm-cap b{color:#222;font-weight:700;text-transform:uppercase;letter-spacing:.04em;font-size:11.5px} +.tm-fig .tm-legend{display:flex;gap:20px;flex-wrap:wrap;font-size:12px;color:var(--tm-muted);margin:0 0 16px} +.tm-fig .tm-legend span{display:inline-flex;align-items:center;gap:8px} +.tm-dot{width:10px;height:10px;border-radius:50%;display:inline-block} +.tm-dot.h{background:var(--tm-honcho);box-shadow:0 0 12px var(--tm-honcho)} +.tm-dot.s{background:var(--tm-hindsight);box-shadow:0 0 12px var(--tm-hindsight)} +.tm-fig svg{display:block;width:100%;height:auto} +.tm-fig svg text{font-family:'IBM Plex Mono',ui-monospace,monospace} + +/* buttons */ +.tm-btn{ + font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:13.5px;font-weight:700; + background:#1C2330;color:var(--tm-text);border:1px solid var(--tm-line);border-radius:9px; + padding:9px 16px;cursor:pointer;transition:.16s} +.tm-btn:hover{border-color:var(--tm-muted);transform:translateY(-1px)} +.tm-btn.pri{background:var(--tm-honcho);color:#1a130a;border-color:var(--tm-honcho)} +.tm-btn:disabled{opacity:.4;cursor:default;transform:none} +.tm-controls{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:16px;justify-content:center} + +/* recency lab */ +.tm-recency{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:center} +@media(max-width:620px){.tm-recency{grid-template-columns:1fr}} +.tm-ctrl{display:flex;align-items:center;gap:12px;margin:14px 0;font-size:13px;color:var(--tm-muted)} +.tm-ctrl input[type=range]{-webkit-appearance:none;appearance:none;height:4px;border-radius:3px; + background:var(--tm-line);outline:none;flex:1} +.tm-ctrl input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;border-radius:50%; + background:var(--tm-hindsight);cursor:pointer;box-shadow:0 0 12px var(--tm-hindsight-dim)} +.tm-ctrl input[type=range]::-moz-range-thumb{width:18px;height:18px;border:0;border-radius:50%; + background:var(--tm-hindsight);cursor:pointer} +.tm-read{font-size:13px;color:var(--tm-text)} +.tm-read b{color:var(--tm-hindsight)} +.tm-verdict{margin-top:16px;border:1px solid var(--tm-line);border-radius:11px;padding:14px 16px; + font-size:12.5px;line-height:1.7;background:var(--tm-ink)} +.tm-verdict .win{color:var(--tm-hindsight);font-weight:700} + +/* trace */ +.tm-trace-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:18px} +@media(max-width:620px){.tm-trace-grid{grid-template-columns:1fr}} +.tm-col{border:1px solid var(--tm-line);border-radius:12px;background:#141A24;overflow:hidden} +.tm-col>h4{margin:0;padding:13px 16px;border-bottom:1px solid var(--tm-line); + font-family:'IBM Plex Sans',sans-serif;font-weight:700;font-size:14.5px; + display:flex;justify-content:space-between;align-items:center} +.tm-col.h>h4{color:var(--tm-honcho)} +.tm-col.s>h4{color:var(--tm-hindsight)} +.tm-col>h4 .mech{font-size:10px;letter-spacing:.06em;color:var(--tm-faint);text-transform:uppercase;font-weight:400} +.tm-steps{padding:8px 0} +.tm-step{padding:12px 16px;border-left:2px solid transparent;opacity:.3;transition:.4s;display:flex;gap:12px} +.tm-step.on{opacity:1;background:rgba(255,255,255,.025)} +.tm-col.h .tm-step.on{border-left-color:var(--tm-honcho)} +.tm-col.s .tm-step.on{border-left-color:var(--tm-hindsight)} +.tm-step .i{font-size:11px;color:var(--tm-faint);padding-top:2px;min-width:20px} +.tm-step .x{font-size:14px;line-height:1.5;color:#cfcabb;font-family:'IBM Plex Sans',sans-serif} +.tm-step .x b{font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:500} +.tm-col.h .tm-step .x b{color:var(--tm-honcho)} +.tm-col.s .tm-step .x b{color:var(--tm-hindsight)} +.tm-stepn{font-size:12.5px;color:var(--tm-muted)} + +@media(prefers-reduced-motion:reduce){ + .tm-fig *{animation:none!important;transition:none!important} +} + +/* inline prose emphasis — legible on the light article background */ +.tm-h{color:#B5710A;font-weight:600} +.tm-s{color:#0E8C84;font-weight:600} diff --git a/static/blog/two-memories/widgets.js b/static/blog/two-memories/widgets.js new file mode 100644 index 0000000..4a4308a --- /dev/null +++ b/static/blog/two-memories/widgets.js @@ -0,0 +1,200 @@ +/* ============================================================ + two-memories — interactive figures (vanilla, no deps) + Progressive enhancement: if this fails to load, the prose and + fenced code in the post still convey everything. All IDs are + namespaced tm-*. Respects prefers-reduced-motion. + ============================================================ */ +(function () { + "use strict"; + var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + var NS = "http://www.w3.org/2000/svg"; + function el(t, a) { var e = document.createElementNS(NS, t); a = a || {}; for (var k in a) e.setAttribute(k, a[k]); return e; } + function byId(id) { return document.getElementById(id); } + + var HONCHO = "#F2B05E", HONCHO_DIM = "#7a5a32", HIND = "#4FD0C9"; + + /* ---------- benchmark bars ---------- */ + (function bench() { + var host = byId("tm-bench"); if (!host) return; + var data = [ + { label: "Neuromancer XR · LoCoMo", val: 86.9, c: HONCHO, note: "vs 80.0 Claude 4 Sonnet baseline" }, + { label: "base Qwen3-8B · LoCoMo", val: 69.6, c: HONCHO_DIM, note: "the un-tuned backbone" }, + { label: "Hindsight 20B · LongMemEval", val: 83.6, c: HIND, note: "up from 39.0 full-context, same backbone" }, + { label: "Hindsight (large) · LongMemEval", val: 91.4, c: HIND, note: "scaled backbone" }, + { label: "Hindsight (large) · LoCoMo", val: 89.6, c: HIND, note: "vs 75.8 strongest prior open system" } + ]; + var W = Math.min(host.offsetWidth || 640, 760), rowH = 58, pad = 8, max = 100; + var svg = el("svg", { viewBox: "0 0 " + W + " " + (data.length * rowH + pad), width: "100%" }); + data.forEach(function (d, i) { + var y = i * rowH + pad, trackY = y + 24, bw = W; + var lab = el("text", { x: 0, y: y + 15, fill: "#cfcabb", "font-size": 12.5 }); lab.textContent = d.label; svg.appendChild(lab); + svg.appendChild(el("rect", { x: 0, y: trackY, width: bw, height: 14, rx: 7, fill: "#1c2330" })); + var bar = el("rect", { x: 0, y: trackY, width: 0, height: 14, rx: 7, fill: d.c }); svg.appendChild(bar); + var note = el("text", { x: 6, y: trackY + 44, fill: "#6B7384", "font-size": 11 }); note.textContent = d.note; svg.appendChild(note); + var num = el("text", { x: 0, y: trackY + 11, fill: "#0f1219", "font-size": 11, "font-weight": 700, "text-anchor": "end" }); svg.appendChild(num); + var target = bw * d.val / max; + var obs = new IntersectionObserver(function (e) { + if (e[0].isIntersecting) { + var t0 = null, dur = 900; + (function anim(ts) { + if (!t0) t0 = ts; var p = reduce ? 1 : Math.min((ts - t0) / dur, 1), e2 = 1 - Math.pow(1 - p, 3); + bar.setAttribute("width", target * e2); num.setAttribute("x", Math.max(target * e2 - 6, 26)); + num.textContent = (d.val * e2).toFixed(1); if (p < 1) requestAnimationFrame(anim); + })(); + obs.disconnect(); + } + }, { threshold: 0.35 }); + obs.observe(host); + }); + host.appendChild(svg); + })(); + + /* ---------- retrieval diagram ---------- */ + (function ret() { + var host = byId("tm-ret"); if (!host) return; + var W = Math.min(host.offsetWidth || 640, 760), H = 300; + var svg = el("svg", { viewBox: "0 0 " + W + " " + H, width: "100%" }); + var midX = W / 2 - 14; + function lbl(x, y, t, c, sz, anchor, weight) { var e = el("text", { x: x, y: y, fill: c, "font-size": sz || 11, "text-anchor": anchor || "middle", "font-weight": weight || 500 }); e.textContent = t; svg.appendChild(e); } + function box(x, y, w, h, c, t) { svg.appendChild(el("rect", { x: x, y: y, width: w, height: h, rx: 8, fill: "none", stroke: c, "stroke-width": 1.3, opacity: .9 })); lbl(x + w / 2, y + h / 2 + 4, t, c); } + var defs = el("defs"); + [["tmah", HONCHO], ["tmas", HIND]].forEach(function (p) { var m = el("marker", { id: p[0], markerWidth: 7, markerHeight: 7, refX: 6, refY: 3, orient: "auto" }); m.appendChild(el("path", { d: "M0,0 L6,3 L0,6 Z", fill: p[1] })); defs.appendChild(m); }); + svg.appendChild(defs); + svg.appendChild(el("line", { x1: W / 2, y1: 14, x2: W / 2, y2: H - 14, stroke: "#2a323f", "stroke-dasharray": "3 5" })); + lbl(midX / 2, 30, "HONCHO · Dialectic", HONCHO, 12, "middle", 700); + box(midX / 2 - 70, 52, 140, 34, HONCHO, "query"); + box(midX / 2 - 70, 118, 140, 34, HONCHO, "LLM agent"); + box(midX / 2 - 70, 184, 140, 34, HONCHO, "tools: search · recent"); + svg.appendChild(el("path", { d: "M " + (midX / 2) + " 86 L " + (midX / 2) + " 118", stroke: HONCHO, "stroke-width": 1.3, "marker-end": "url(#tmah)" })); + svg.appendChild(el("path", { d: "M " + (midX / 2) + " 152 L " + (midX / 2) + " 184", stroke: HONCHO, "stroke-width": 1.3, "marker-end": "url(#tmah)" })); + svg.appendChild(el("path", { d: "M " + (midX / 2 - 70) + " 201 C " + (midX / 2 - 130) + " 201, " + (midX / 2 - 130) + " 135, " + (midX / 2 - 70) + " 135", stroke: HONCHO, "stroke-width": 1.3, fill: "none", "marker-end": "url(#tmah)", "stroke-dasharray": "4 4" })); + lbl(midX / 2 - 120, 170, "loop", HONCHO, 10); + box(midX / 2 - 70, 250, 140, 30, HONCHO, "answer"); + svg.appendChild(el("path", { d: "M " + (midX / 2) + " 218 L " + (midX / 2) + " 250", stroke: HONCHO, "stroke-width": 1.3, "marker-end": "url(#tmah)" })); + var sx = W / 2 + (W / 2) / 2; + lbl(sx, 30, "HINDSIGHT · TEMPR", HIND, 12, "middle", 700); + ["semantic", "bm25", "graph", "temporal"].forEach(function (a, i) { + var seg = (W / 2 - 36) / 4, x = W / 2 + 18 + i * seg; + box(x, 52, seg - 6, 30, HIND, a); + svg.appendChild(el("path", { d: "M " + (x + (seg - 6) / 2) + " 82 L " + sx + " 118", stroke: HIND, "stroke-width": 1, opacity: .7 })); + }); + box(sx - 70, 118, 140, 30, HIND, "RRF k=60"); + svg.appendChild(el("path", { d: "M " + sx + " 148 L " + sx + " 178", stroke: HIND, "stroke-width": 1.3, "marker-end": "url(#tmas)" })); + box(sx - 70, 178, 140, 30, HIND, "cross-encoder"); + svg.appendChild(el("path", { d: "M " + sx + " 208 L " + sx + " 238", stroke: HIND, "stroke-width": 1.3, "marker-end": "url(#tmas)" })); + box(sx - 70, 238, 140, 30, HIND, "token budget → facts"); + host.appendChild(svg); + })(); + + /* ---------- surprisal ---------- */ + (function surp() { + var host = byId("tm-surprisal"); if (!host) return; + var W = Math.min(host.offsetWidth || 640, 760), H = 320; + var svg = el("svg", { viewBox: "0 0 " + W + " " + H, width: "100%" }); host.appendChild(svg); + var clusters = [[W * .32, H * .42], [W * .6, H * .62], [W * .5, H * .32]]; + var pts = []; + function gauss() { var u = 0, v = 0; while (!u) u = Math.random(); while (!v) v = Math.random(); return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); } + function nearest(p) { var m = 1e9; for (var i = 0; i < pts.length; i++) { var q = pts[i]; if (q === p) continue; var d = (q.x - p.x) * (q.x - p.x) + (q.y - p.y) * (q.y - p.y); if (d < m) m = d; } return Math.sqrt(m); } + function render(flash) { + while (svg.firstChild) svg.removeChild(svg.firstChild); + var g = el("text", { x: 12, y: H - 12, fill: "#6B7384", "font-size": 11 }); g.textContent = "embedding space (2-D projection)"; svg.appendChild(g); + pts.forEach(function (p) { + var s = nearest(p), high = s > 55; + var col = high ? HONCHO : (p.base ? "#3a4250" : "#6b7384"); + var r = p.base ? 3.4 : 5; + svg.appendChild(el("circle", { cx: p.x, cy: p.y, r: r, fill: high ? col : "none", stroke: col, "stroke-width": 1.6, opacity: high ? 1 : .65 })); + if (p === flash) { + var ring = el("circle", { cx: p.x, cy: p.y, r: 6, fill: "none", stroke: high ? HONCHO : "#6b7384", "stroke-width": 1.5 }); + svg.appendChild(ring); + if (!reduce) { + ring.appendChild(el("animate", { attributeName: "r", from: 6, to: 34, dur: "1s", fill: "freeze" })); + ring.appendChild(el("animate", { attributeName: "opacity", from: .9, to: 0, dur: "1s", fill: "freeze" })); + } + var t = el("text", { x: p.x + 12, y: p.y + 4, fill: high ? HONCHO : "#6b7384", "font-size": 11, "font-weight": 600 }); + t.textContent = high ? "S=" + s.toFixed(0) + " · novel → reason" : "S=" + s.toFixed(0) + " · redundant → skip"; + svg.appendChild(t); + } + }); + } + function seed() { pts = []; clusters.forEach(function (c) { for (var i = 0; i < 14; i++) pts.push({ x: c[0] + gauss() * 28, y: c[1] + gauss() * 24, base: true }); }); render(); } + var add = byId("tm-surp-add"), rst = byId("tm-surp-reset"); + if (add) add.onclick = function () { + var p; + if (Math.random() < .5) { var c = clusters[Math.floor(Math.random() * 3)]; p = { x: c[0] + gauss() * 26, y: c[1] + gauss() * 22, base: false }; } + else { p = { x: 60 + Math.random() * (W - 120), y: 40 + Math.random() * (H - 100), base: false }; } + pts.push(p); render(p); + }; + if (rst) rst.onclick = seed; + seed(); + })(); + + /* ---------- recency lab ---------- */ + (function recency() { + var host = byId("tm-recency-svg"); if (!host) return; + var W = Math.min(host.offsetWidth || 400, 420), H = 230; + var svg = el("svg", { viewBox: "0 0 " + W + " " + H, width: "100%" }); host.appendChild(svg); + var aEl = byId("tm-alpha"), aV = byId("tm-alpha-val"), rEl = byId("tm-rel"), rV = byId("tm-rel-val"), verdict = byId("tm-recency-verdict"); + function draw() { + while (svg.firstChild) svg.removeChild(svg.firstChild); + var alpha = parseFloat(aEl.value), rel = parseFloat(rEl.value); + aV.textContent = alpha.toFixed(2); rV.textContent = (rel >= 0 ? "+" : "") + rel.toFixed(2); + var baseCE = 0.6, janCE = baseCE + rel, junCE = baseCE, janR = 0.0, junR = 1.0; + var jan = janCE * (1 + alpha * (janR - 0.5)), jun = junCE * (1 + alpha * (junR - 0.5)); + var top = 24, bot = H - 40, mid = (top + bot) / 2; + var bandTop = mid - (alpha / 0.6) * 70, bandBot = mid + (alpha / 0.6) * 70; + svg.appendChild(el("rect", { x: 40, y: bandTop, width: W - 80, height: bandBot - bandTop, fill: HIND, opacity: .07, rx: 4 })); + svg.appendChild(el("line", { x1: 40, y1: mid, x2: W - 40, y2: mid, stroke: "#2a323f", "stroke-dasharray": "3 5" })); + var t1 = el("text", { x: 44, y: bandTop - 6, fill: "#6B7384", "font-size": 10 }); t1.textContent = "±recency envelope"; svg.appendChild(t1); + var maxv = 0.95, minv = 0.35, scale = (bot - top) / (maxv - minv); + function bar(x, v, col, label, sub) { + var hgt = (v - minv) * scale, y = bot - hgt; + svg.appendChild(el("rect", { x: x - 34, y: y, width: 68, height: hgt, rx: 6, fill: col, opacity: .85 })); + var tv = el("text", { x: x, y: y - 8, fill: col, "font-size": 12, "font-weight": 700, "text-anchor": "middle" }); tv.textContent = v.toFixed(3); svg.appendChild(tv); + var tl = el("text", { x: x, y: bot + 16, fill: "#cfcabb", "font-size": 11, "text-anchor": "middle" }); tl.textContent = label; svg.appendChild(tl); + var ts = el("text", { x: x, y: bot + 30, fill: "#6B7384", "font-size": 9.5, "text-anchor": "middle" }); ts.textContent = sub; svg.appendChild(ts); + } + bar(W * 0.36, jan, HONCHO_DIM, "Jan · Python", "older fact"); + bar(W * 0.64, jun, HIND, "Jun · Rust", "newer fact"); + var winner = jun > jan ? "Rust (June)" : "Python (January)"; + var margin = Math.abs(jun - jan) / Math.max(jun, jan) * 100; + verdict.innerHTML = 'recall returns: ' + winner + '
' + + 'Jan score = ' + jan.toFixed(3) + '   Jun score = ' + jun.toFixed(3) + '
' + + 'margin ' + margin.toFixed(1) + '%  ·  ' + + (rel > 0.0001 ? ('relevance edge to January ' + (rel >= alpha ? 'overrides recency' : 'still losing to recency')) : 'relevance tied → recency decides'); + } + aEl.addEventListener("input", draw); rEl.addEventListener("input", draw); draw(); + })(); + + /* ---------- trace stepper ---------- */ + (function trace() { + var H = [ + ["Jan message stored", 'Deriver emits an explicit observation: "user loves Python" (+ deductive: "is a programmer"). No conflict yet.'], + ["Jun message stored", 'Deriver emits "user now prefers Rust." Two observations coexist; nothing reconciled.'], + ["Dream cycle fires", "Surprisal builds a tree over the peer's observation cloud. The Rust point sits in a sparse region → high surprisal → selected."], + ["Deduction specialist", "Discovery tools surface both facts. Recognizes a knowledge update: preference changed over time."], + ["Reconcile + prune", 'Writes a dated update with source_ids + premises; soft-deletes the stale "loves Python" observation.'], + ["Query: prefers?", 'Dialectic tool-loops, finds the current observation → answers "Rust, as of June", chain traceable via premises.'] + ]; + var S = [ + ["Jan retained", "TEMPR extracts narrative fact f1 (τ≈Jan, entities {User,Python}); embedded, entity-linked."], + ["Jun retained", "f2 (τ≈Jun, entities {User,Rust,Python}). Shared Python entity → hard w=1.0 edge f1↔f2."], + ["Consolidation", "New evidence classified contradict vs the Python opinion → c′ = max(c−2α, 0); confidence collapses with repetition."], + ["Supersede", 'Old observation marked superseded → moved to invalidated_memory_units; new "prefers Rust" written with history.'], + ["Recall · 4 arms", "Semantic finds f1,f2; temporal + recency push f2 above f1; RRF fuses, cross-encoder reranks."], + ["Reflect → answer", 'CARA answers "Rust, as of June". f1 stays valid history — "used to prefer Python" still queryable.'] + ]; + var hHost = byId("tm-trace-h"), sHost = byId("tm-trace-s"); if (!hHost || !sHost) return; + function build(host, arr) { arr.forEach(function (s, i) { var d = document.createElement("div"); d.className = "tm-step"; d.dataset.i = i; d.innerHTML = '' + String(i + 1).padStart(2, "0") + '' + s[0] + '
' + s[1] + '
'; host.appendChild(d); }); } + build(hHost, H); build(sHost, S); + var total = H.length, cur = 0, timer = null; + var stepn = byId("tm-trace-stepn"), playBtn = byId("tm-trace-play"); + function paint() { var n = document.querySelectorAll("#tm-trace-h .tm-step, #tm-trace-s .tm-step"); n.forEach(function (x) { x.classList.toggle("on", +x.dataset.i < cur); }); stepn.textContent = "step " + cur + " / " + total; } + function step() { if (cur < total) { cur++; paint(); } if (cur >= total) stop(); } + function play() { if (timer) { stop(); return; } playBtn.textContent = "❚❚ Pause"; if (cur >= total) cur = 0; timer = setInterval(step, 1100); } + function stop() { clearInterval(timer); timer = null; playBtn.textContent = "▶ Play"; } + playBtn.onclick = play; + byId("tm-trace-step").onclick = function () { stop(); step(); }; + byId("tm-trace-reset").onclick = function () { stop(); cur = 0; paint(); }; + paint(); + })(); +})(); From 66b9fadf86d71c51a7cf1382174e013992d00211 Mon Sep 17 00:00:00 2001 From: Griswald Brooks Date: Mon, 29 Jun 2026 21:29:22 -0400 Subject: [PATCH 2/3] fix: load feather-icons async so it never blocks page render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feather.min.js was a parser-blocking +