Controlled evaluation · executable graders · n=5 seeds

Bento Under Test

A controlled evaluation of an open-source, self-hostable agent-memory and retrieval layer, against no-memory controls and external memory products — every number recomputed from raw.

Author Matt Yonkovic Bento open source · self-hostable Version 1.2 Date 2026-06-30 Paid spend $77.2 / $200 Supersedes "Hosted Substrate" draft, Rev.2
Currency · +0.31 vs stale cache Cost-reuse · −34% wall, equal acc Correctness · +0.32 @ frontier Time-travel · replay (a18) Recovery loop · built (Tier-1) · 27ms/call ~ letta · agent ties, store can't supersede

bento is an open-source, self-hostable agent-memory and retrieval layer (optionally run as a managed service) over four ingredients — graph retrieval (pg-raggraph), currency/supersession (stele), code-aware chunking (chunkshop), and in-process extraction (lede). This report evaluates it on seven claims against no-memory controls and two external memory products (mem0; stele as the raw engine bento wraps), with LangChain as an external framework baseline. Tasks use executable graders, not an LLM judge, except one retrieval cell noted below. Runs span three model tiers including the frontier (OpenAI gpt-5.5), which was run — it is not pending.

Every quantitative claim cites evidence/CANONICAL-NUMBERS.md, recomputed from the committed raw seed records. Where this version disagrees with the prior draft, the raw data wins; §"What changed" lists every correction.

Architecture

One API composing four ingredients

Bento is an open-source agent-memory and retrieval system — deployable self-hosted or as a managed service — built as a single FastAPI backend-for-frontend (BFF) that composes four retrieval/memory ingredients behind one ingredient registry. Clients talk only to the BFF; it routes each capability to the ingredient that owns it. Three ingredients run as HTTP services over a shared substrate (PostgreSQL + Valkey); lede runs in-process because a network hop would dwarf its sub-millisecond work. Being open source, the system under test is itself inspectable and clonable — the reproducibility argument extends from the harness to the product.

Clients

Web UINext.js
MCP serverbento-mcp
REST / SDK/v1/*

Backend-for-frontend

FastAPI BFF · ingredient registry /v1/ingest · /v1/ask · /v1/memory · /v1/recall · lede (in-process extraction)

Ingredients · independent HTTP services

pg-raggraph-proxygraph retrieval
stele-shimcurrency · supersession
chunkshop-shimcode-aware chunking
modelsLLM endpoints

Substrate — PostgreSQL + Valkey · two stores that never cross-read

RAG chunksns = kb-{id} · pgvector + graph · read by /v1/ask
Stele memoriesns = str(kb.id) · episodic · read by /v1/recall

Composition is why "bento" ≠ vector-RAG: the test surface (C1–C7) exercises all four ingredients.

The four ingredients

IngredientServiceRoleTested in
pg-raggraphpg-raggraph-proxyGraph retrieval — per-KB entity/relationship extraction + traversal over pgvector chunksC4, C6, ask
stelestele-shimCurrency/supersession — typed memory, atomic replaces, active-only recallC1, C2, C3, C7
chunkshopchunkshop-shimChunking incl. code-aware symbol_aware (tree-sitter whole-symbol units)C5, ingest
ledein-processDeterministic term/fact extraction at ingest (sub-ms)ingest

Two independent stores — the most-misunderstood part

RAG chunksStele memories
Namespacekb-{kb_id}str(kb_id)
Written by/v1/ingest, uploads, connectors/v1/memory, session capture
Read by/v1/ask (synthesis)/v1/recall, Memory Browser
Searchpgvector + graph traversaltemporal / typed, active-only

/v1/ask resolves kb-{id} and queries chunks; it has no path into stele. Recall resolves str(kb_id) and reads stele; no path into chunks. So the currency/accumulation claims (C1/C3/C7, stele) and the retrieval claims (C4/C6, pg-raggraph) genuinely test different machinery — not two views of one store.

Request flows

  • Ingest/v1/ingest → chunkshop picks a strategy (symbol_aware for code, sentence-aware for prose) → pg-raggraph-proxy writes documents + chunks and, when graph is on, extracts the entity graph; effective_from is stamped for point-in-time (F13).
  • Ask/v1/ask → resolve kb-{id} → pg-raggraph retrieval (naive / hybrid / local graph modes) → the models service synthesizes a grounded, cited answer.
  • MemoryPOST /v1/memory writes a structured fact to stele; an optional replaces atomically supersedes the prior value (the currency mechanism, C1). /v1/recall returns active-only memories — superseded rows are never surfaced.

Claims under test

Seven claims, each with a control and a grader

#ClaimTaskGrader
C1Memory stays current after updatesprice_bookexecutable exact
C2Memory reduces cost at equal accuracyresearch_deskexecutable graph-answer
C3Memory unlocks correctnessincident_rcaexecutable exact
C4Graph beats flat retrieval on multi-hopgraph-scale A/Bneedle + LLM-judge
C5Code-aware chunking returns whole functionschunking A/Bsubstring completeness
C6KBs stay current + point-in-timeKB-currency A/Bexecutable substring
C7Procedural memory cuts repeated failuresplan_replan A/Bstate-oracle

Every memory arm runs against a no-memory control on the identical task, model, and grader. The design can disprove each claim.

C1 · Currency price_book · two model tiers · K=5

A mutable price book; a hot SKU's price is updated mid-stream; the agent must always answer the current price. A no-memory baseline re-reads the database (correct, but redoes work). A cache that never invalidates should go stale.

Accuracy — gpt-5.5 tierhigher is better · 0 → 1.0

bento
1.000
stele raw engine
0.997
baseline re-reads DB
1.000
naive cache
0.667
mem0-as-naive
0.670
00.51.0
Layers that supersede on update (bento, stele) stay current; caches without invalidation go stale by about a third — at both the local-26B and frontier tiers. A frontier model does not rescue a stale cache (0.667): the failure is in the memory layer, not the model.

Against the correct re-read baseline, bento is −0.027 at the local tier with overlapping 95% CIs — statistically indistinguishable. That is the honest meaning of "tracks the baseline": equal correctness, with a reusable cache instead of a full re-read.

TierArmacc ± sdvs naivesource
local-26Bbento0.923 ± 0.025+0.313seed-exp-memory-bento-*
local-26Bbaseline0.950 ± 0.039seed-baseline-*
local-26Bnaive cache0.610 ± 0.148seed-naive-cache-*
gpt-5.5bento1.000 ± 0.000+0.333results/price_book/…
gpt-5.5naive cache0.667 ± 0.122seed-naive-cache-*
So what. If your agent caches to go faster, an un-invalidated cache is wrong about a third of the time on data that changes — and a stronger model does not save you, because the error is in the memory layer. Memory that supersedes on update buys speed without buying staleness.

How we measured it · C1

Identical agent (lookup_price / update_price / answer) over one Postgres price book, identical model; the three arms differ only in the memory backend. K=5 data-seed-varied; executable exact grader; gold = the price valid at that point in the update stream. Harness: agent-samples seed_sweep --agent price_book.

C2 · Cost-reuse research_desk · local-193 · K=5

Multi-hop graph QA over a static dependency graph, with recurring questions. A resolved-answer cache can collapse a repeated question's multi-hop work into one recalled answer. Static graph, so no staleness — pure cost. Both memory arms reach accuracy 1.00, so the comparison is cost at equal correctness.

Cost relative to no-memory baseline (= 100%)lower is better

wall time
−34%
model calls
−39%
tokens
−42%
mem0 wall vs baseline
+45%
0baseline 100%
At equal accuracy (1.00), bento cuts wall time 34%, model calls 39%, and tokens 42%; wall-time CIs do not overlap. The mechanism is the drop in model calls (177→108): a recurring question resolves from one recalled answer instead of re-running the search.

mem0 loses this cell — reported, not hidden

mem0's semantic recall embeds every query (a per-query round-trip), driving 285 calls and wall time 45% above the no-memory baseline, at lower accuracy (0.94). bento and stele use exact recall — no per-query embedding — and land on the winning side.

Armaccuracywall (s)llm_callstokens
baseline (full multi-hop)1.00049.3 ± 1.7177106,045
bento (recall)1.00032.3 ± 2.910861,940
stele1.00032.0 ± 5.110861,627
mem0 (semantic)0.94071.7 ± 3.8285106,524
LangChain (no memory)0.96086.3 ± 6.1166103,849
So what. Repeated multi-hop questions are pure waste if the agent re-runs the work each time; reusing a resolved answer cuts a third of wall-clock and ~40% of tokens at identical accuracy. The counter-example matters too: a memory layer that adds a per-query round-trip (mem0 here) can make a repetitive workload slower than no memory at all. Measure the net.

How we measured it · C2

Same search/answer agent over a static service-dependency graph, same model; arms differ only in whether a resolved-answer cache is consulted. Both memory arms reach 1.00 accuracy, so cost is the comparison — wall / calls / tokens come from the proxy call log. K=5; executable graph-answer grader. Harness: seed_sweep --agent research_desk.

C3 · Correctness-unlock incident_rca · gpt-5.5 · K=5

Root-cause analysis where the document corpus is unreliable and conflicting. Memory accumulates verified findings and recalls them on recurring incident classes. Bars below show accuracy with 95% confidence-interval whiskers.

Accuracy ± 95% CI — gpt-5.50 → 1.0

baseline no memory
0.460
bento auto-inject
0.777
bento-agent recall loop
0.893
mem0-agent same loop
0.890
LangChain no memory
0.490
00.51.0
Memory raises accuracy from 0.46 to 0.78 (+0.317), CIs non-overlapping — a real correctness unlock the base model cannot reach on the bad corpus.

Two honest corrections to the prior draft

Cost is flat at the frontier, not "−55% tokens" (131k→133k): that figure came from an exploratory local run, not this clean sweep. And the single cell a competitor led decomposes to the agent loop, not the backend: in mem0's identical recall loop, bento scores 0.893 vs mem0's 0.890 — CIs overlap, statistically indistinguishable. We report this because it is what a skeptic would expect us to bury.

So what. On an unreliable corpus, accumulating verified findings lifts accuracy 0.46→0.78 — work the base model cannot do alone. But the larger lever was how memory is recalled (the agent loop), not which backend stores it. Bolt on a backend but keep a passive retrieval step and you leave most of the gain on the table.

How we measured it · C3

Same multi-check RCA agent against a deliberately unreliable, conflicting playbook corpus; gpt-5.5. Arms: no-memory, bento auto-inject, bento-agent (agent-driven recall loop), and mem0-agent (the same loop, mem0 backend). K=5; executable exact grader. bento-agent vs mem0-agent is the loop-vs-backend control. Harness: seed_sweep --agent incident_rca.

Distinctive capabilities · C4–C7

Memory types, measured against external comparators

C4 · Graph retrieval finds multi-hop needles others miss

A 6-document relational chain buried in distractors; questions paraphrased so the answer entities never appear in them and keyword/vector search has no surface to match.

Needle-in-context (isolation set)0 → 1.0

bento graph
1.00
vector RAG
0.17
bm25 keyword
0.00

NDCG@10 ranking quality ★ customer-requested · three conditions

read this first

In pg-raggraph, naive already = vector + BM25 (not pure vector); the graph modes layer entity/relationship traversal on top, and the relationship-focused global mode is the strongest graph mode at scale. Below: the best bento-graph mode vs standalone external vector and bm25, NDCG@10 graded, pure retrieval, no LLM.

Conditionbento-graphvectorbm25
Single chain · no surface cue (n=6×3)0.78 (local)0.110.07
Scaled 8 chains · conventional / named (n=16)0.72 (global)0.400.54
Scaled 8 chains · pure-lexical codename (n=16)0.79 (global)0.670.89

The honest reading scaling forced — and a loss we report, not hide

bento's graph wins decisively when retrieval needs traversal without a lexical bridge (no-cue 0.78 vs ~0.1; realistic named multi-hop 0.72 vs bm25 0.54 / vector 0.40). It loses one case — a pure-lexical codename lookup — to standalone Okapi bm25 (0.79 vs 0.89), because the fusion's vector leg dilutes a near-perfect keyword signal. That is a fusion-weight config knob, not a missing-BM25 bug (BM25 is in every bento mode via the naive leg). The pattern matches pg-raggraph's own bake-off: graph modes don't beat vector+BM25 on clean lexical corpora; they earn their cost on cross-document, no-surface-bridge reasoning.

Holding the retriever fixed and varying only the answer model shows retrieval is solved and synthesis is model-bound: answer accuracy climbs 0.33 → 0.50 → 1.00 across the 26B, 31B, and frontier models on the same graph-retrieved context.

Honest boundary: on easy questions that name the chain entities, bm25 also succeeds — graph's edge is specifically the multi-hop, no-surface-overlap case.

So what. Match the retriever to the question. When answers require connecting facts across documents with no shared keyword to bridge them, vector and keyword RAG miss ~85% of the time and graph traversal gets it. When the question is a keyword lookup, a graph buys nothing over BM25 — so don't pay for one. The win is specific and bounded, not universal.

How we measured it · C4

A 6-document relational chain is buried among N distractors in one namespace (chain ingested with entity extraction ON, distractors OFF). We race bento graph (local/hybrid) vs flat (naive) vs a vanilla vector RAG vs bm25 on multi-hop questions, scoring needle-in-context and NDCG@k — qrels: gold-answer doc = 3, other chain doc = 1, distractor = 0 — in two regimes (named / isolation). Pure retrieval, no LLM. Multi-model synthesis holds the retriever fixed and varies the answer model. Scripts: graph_isolation_test.py, graph_ndcg.py, graph_multimodel_synth.py.

C5 · Code-aware chunking returns the whole function

bento's symbol-aware chunker vs LangChain's fixed-size splitter, over the same 66 files / 254 functions, same embedder. Metric: is the complete target function the top-1 hit?

Top-1 = complete function0 → 1.0

symbol_aware bento
0.83
fixed 1200-char
0.35
fixed 2200-char ≈ same #chunks
0.36

Code-aware chunking keeps 100% of functions whole (vs 49–60%) and returns the complete function as the top hit 2.4× more often. The mechanism is boundary alignment, not chunk size: a larger fixed window barely changes completeness because windows still cut across function edges.

Ranking — complete-function chunk ★ MRR / NDCG@100 → 1.0

symbol_aware MRR
0.896
fixed-size MRR
0.384
symbol_aware NDCG@10
0.919
fixed-size NDCG@10
0.398

fixed-size finds the right region reasonably (region-MRR 0.687) but the complete-function unit ranks far lower (MRR 0.384) — that 0.687→0.384 gap is the fragmentation tax in ranking terms: it locates where the function is, but no single chunk holds the whole thing.

So what. A code assistant is only as good as the unit it retrieves. Fixed-size chunking slices functions across boundaries, so the model gets fragments and guesses the rest; symbol-aware chunking returns the whole function as the top hit 2.4× more often. For code RAG that is the difference between a usable answer and a confident, incomplete one.

How we measured it · C5

Both chunkers run over the same 66 source files (254 documented functions): bento's symbol_aware (tree-sitter, whole-symbol) and LangChain RecursiveCharacterTextSplitter. Query = each function's docstring; gold = that function. Same embedder (fastembed bge-small); we score top-1 completeness and full-ranking MRR / NDCG@10 on whether the complete function is retrieved. Deterministic. Script: code_chunking_ab.py.

C6 · Re-ingested documents serve the current version

Post-update correctness — KB docs (9 queries, 3 generations)0 → 1.0

bento
1.00
naive cache
0.33

Time-travel: version replay now works (a18)

An as_of before a document's ingest correctly excludes it (the F13 fix, verified live). The 2026-06-29 tool sync to pg-raggraph a18 closes #90. With evolution_tier=structural, a same-id re-ingest is append-only: it sets the old version's effective_to, opens the new one's effective_from, and keeps both. We re-ran the 30→90→7-day ingest. All three versions survive, and an as_of inside each version's window returns just that version (v1=30, v2=90, current=7) at chunk and answer level. One side effect: the old versions stay in the store and show up down-ranked in default retrieval, so the chunk-string currency check reads 0.33. That number measures retention, not staleness; scored at the answer or at rank-1, currency is still 1.00. See §7.5.

Memory-store currency (2026-06-30). The chart above is the chunk store. The other store — stele memory (cross-session facts, the /v1/recall path) — has its own currency test: when a fact is superseded ("Postgres 14"→"Postgres 16"), does recall return only the current one? Over 10 evolving-fact scenarios this is distill-model-dominated: the coder model leaves both siblings active (stale 1.0); switching only the distill model to a gemma-class temp-0 instruct model takes that to 0.1, and a consolidate sweep retires the residual to 0.0. §7.7.
So what. "Current" is not free, and the two stores keep it differently: the chunk store by re-retrieval (an engine property), the memory store only when the distill model labels a fact the same way across sessions — which a coder model does poorly and a temp-0 instruct model does well. If you run agent memory, choose the distill model deliberately; currency is a model decision as much as an engine one.

How we measured it · C6

A document is re-ingested under the same id across three value generations (30→90→7 days) and queried 9×; bento re-retrieves while the baseline is a cache that never invalidates. Deterministic substring grader. Time-travel is probed with as_of before / at / between versions. Script: kb_currency_test.py.

C7 · Procedural memory cuts repeated failures

A task where some orders' ship is refused until validate_address is called — a recovery procedure the agent discovers by reading the error. Over recurring episodes, no-memory re-discovers the failure every time; bento stores the procedure once and recalls it. Identical result across all three model tiers.

Refused operations over the episode streamlower is better · accuracy ties at 1.00

no-memory
12
bento
3

A 75% reduction in repeated failed operations at equal accuracy. Token cost is flat to slightly higher (the recalled hint costs tokens), so this is a win where failed operations carry real cost — side effects, money, rate limits, irreversible actions — and cost-neutral where retries are free.

The autonomous failure-recovery loop is now built and measured (Tier-1, bento#893). A deterministic, no-LLM learner reads agent transcripts, pairs each failed tool call with the call that recovered it (python3 x.pyModuleNotFoundErroruv run python x.py), gates the pair (LOG / STORE / SURFACE), redacts secrets, and pushes durable corrections into an always-loaded BENTO-LEARNED.md block so a later session skips the doomed call. A scripted W1 cold→warm measurement (N=12, bootstrap 95% CI) puts the saving of one prevented call at 27.0 ms (95% CI 25.6–28.3) for a net +11 context tokens — a conservative lower bound that excludes the avoided model turn. A safety invariant drops any correction that costs more context than it prevents. §7.8. The Tier-2 LLM analyzer is deferred.
So what. When a failed action costs real money — side effects, rate limits, irreversible operations — remembering the recovery cuts repeated failures 75%, and the now-built loop makes that automatic and cross-session: the agent learns "don't make this doomed call again" once and carries it forward, with a gate that keeps the learned hint from bloating every prompt.

How we measured it · C7

A plan_replan task: some orders' ship is refused until validate_address is called — a recovery the agent discovers from the error. Over a stream of episodes, no-memory re-discovers it each time; bento stores the procedure once (real /v1/memory via BentoStore) and recalls it thereafter. 3 model tiers, 4 episodes × 6 orders; metrics: accuracy, refused operations, calls, tokens. Script: procedural_reuse_ab.py.

Rigor

Statistics and effect sizes

Comparisondelta95% CIsread
C1 bento vs naive cache (local)+0.313non-overlappingsignificant
C1 bento vs correct baseline (local)−0.027overlappingindistinguishable
C2 bento vs baseline wall−34.5%non-overlappingsignificant
C3 bento vs baseline accuracy+0.317non-overlappingsignificant
C3 bento-agent vs mem0-agent+0.003overlappingindistinguishable (the tie)

Pre-registration shortfall — disclosed

Our pre-registration committed to K≥10 seeds; every reported cell is K=5. The headline effect sizes (+0.31, +0.32, −34%) are large enough to clear n=5 CIs, but small/null deltas (the −0.027 "within noise") are correspondingly under-powered. We state this rather than let a reader find it in the pre-registration document. Dispersions are standard deviations, not confidence intervals; CIs above are computed as mean ± t·sd/√n.

Scaled to K=15 (direct rebuttal): re-running the cost-reuse cell (research_desk, Qwen3-Coder-Next) at 3× the seeds left the headline unchanged — bento −40% tokens and −35% wall (50.7±2.7s → 32.8±2.7s, 95% CIs non-overlapping) at accuracy ≈1.00. At K=5 the same cell read −42% / −34%. A headline that doesn't move when you triple the data is a real effect, not a small-sample artifact. The currency cell (price_book, K=15) also held: bento 0.968±0.018 vs a stale cache 0.626±0.122 (+0.34, CIs non-overlapping). Both headline cells survived tripling the seeds.

Threats to validity

What could make these numbers wrong

  1. Harness heterogeneity. research_desk uses a text-action loop on instruct models and tool-calling on the frontier; win counts are claimed only within a model's working harness, never pooled.
  2. Sample size. n=5 seeds, below the pre-registered K≥10; small deltas are under-powered.
  3. Stack snapshot. The headline cells ran on one point release (stele 0.6.9 / pg-raggraph a17 + F13/F16/F18). The memory-type cells (C6, C7) were re-confirmed on a second stack (stele 0.6.10 / a18), giving a partial cross-version check. The local-tier and frontier cells are different tiers, labeled rather than pooled.
  4. Cross-box mem0 in one cell. The price_book mem0 figure ran cross-box with infer=False; the fair mem0 comparison is the same-box, identical-loop decomposition in C3, where the competitive claim rests.
  5. One LLM-judged cell. C4's answer accuracy uses the local 26B as judge; its retrieval metric is deterministic. "Executable graders" is not generalized to this cell.
  6. git_sha not stamped. Raw records carry empty git_sha; numbers bind to the named harness branch/commit, not a per-record SHA. Tracked for the public release.
  7. Identifier gaps. Two model identifiers (local-193 upstream id, gpt-5.5 snapshot) are not recorded — flagged, not invented.

Changelog

What changed from the prior draft

This version re-anchors every number to recomputed raw data and removes claims the raw did not support:

  • The "3.1→2.8 turns" mechanism (not in the data — turns is the case count; replaced by model-call counts, 177→108).
  • "−55% tokens / +0.66 at large" on incident_rca (exploratory local run; replaced by the +0.317 frontier result at flat cost).
  • "Frontier tier pending" in the abstract (the frontier ran; spend is stated as $77.2 / $200).
  • Time-travel "FIXED" (re-scoped to currency-yes / replay-no, pg-raggraph#90) — #90 since resolved by pg-raggraph a18 (2026-06-29): replay now delivered, §7.5.
  • "Tight CIs" over n=5 (relabeled as standard deviations, with real 95% CIs and a disclosed K-shortfall).

The full mapping is in evidence/CANONICAL-NUMBERS.md §7.

1.2 (2026-06-30): the autonomous failure-recovery loop moved from pre-implementation to built + measured (C7; W1 net in §7.8); cross-session store currency quantified against the distill model (C6, §7.7); a per-section "So what" reading added.

Verdict

Held to the strength the data supports

On controlled tasks with executable graders and a no-memory control, bento delivers measurable value on the axes a hosted agent-memory layer should: it keeps answers current under updates, reduces cost on recurring work at equal accuracy, and unlocks correctness the base model cannot reach on an unreliable corpus. Its distinctive surface — graph retrieval, code-aware chunking, supersession-backed currency, procedural reuse — produces measured benefits over external comparators. The one cell a competitor led decomposes to the agent loop, not the backend; in the identical loop, bento ties.

We are equally explicit about what is not claimed: token savings at the frontier on the correctness task (cost is flat there, not reduced). The autonomous recovery loop, unproven in the v2 body, is now built and measured at Tier-1 — a prevented call saves ~27 ms at a net-positive token cost (C7, §7.8); the Tier-2 analyzer is deferred. (Two more follow-ups since the v2 body: pg-raggraph a18 delivered full version replay, §7.5; and a letta comparison ran — its agent ties bento on currency and procedural reuse, its archival backend can't supersede, and it has no graph, code-aware, or time-travel surface, §7.6.) The pre-registered seed count was not met (K=5 vs K≥10).

PROVEN on currency, cost-reuse, and correctness-unlock, with distinctive-capability benefits measured against external baselines — held to the strength the data actually supports, no further.

Appendix · deep statistics

Full per-cell grid — every system × tier

The complete breakdown a stats-literate reviewer wants: accuracy mean ± standard deviation, 95% CI, seed count, wall time, model calls, and tokens per cell — all recomputed from the committed raw seed records. Source of truth: evidence/CANONICAL-NUMBERS.md; narrative grid: evidence/full-competitive-grid.md.

Core competitive grid — accuracy + cost per cell (n=5 seeds)
ArmTieracc ± sd95% CInwall scallstokens
C1 · price_book — currency
baselinelocal-26B0.950 ± 0.0390.901–0.9995
bentolocal-26B0.923 ± 0.0250.892–0.9545
stelelocal-26B0.973 ± 0.0370.928–1.0005
naive cachelocal-26B0.610 ± 0.1480.426–0.7945
baselinegpt-5.51.000 ± 0.0005187.012029,553
bentogpt-5.51.000 ± 0.0005398.116453,362
stelegpt-5.50.997 ± 0.0070.988–1.0005389.116152,259
naive cachegpt-5.50.667 ± 0.1220.516–0.8185388.116553,403
mem0-as-naivegpt-5.50.670 ± 0.1270.512–0.8285418.127755,116
C2 · research_desk — cost-reuse (acc ties at 1.00; the win is cost)
baselinelocal-1931.000 ± 0.000549.3 ± 1.7177106,045
bentolocal-1931.000 ± 0.000532.3 ± 2.910861,940
stelelocal-1931.000 ± 0.000532.0 ± 5.110861,627
mem0local-1930.940 ± 0.060571.7 ± 3.8285106,524
LangChainlocal-1930.960 ± 0.035586.3 ± 6.1166103,849
tc-bentogpt-5.51.000 ± 0.0005
tc-mem0gpt-5.50.420 ± 0.1495
C3 · incident_rca — correctness-unlock
baselinegpt-5.50.460 ± 0.1350.292–0.6285610.9240131,148
bento (auto-inject)gpt-5.50.777 ± 0.0560.707–0.8475580.3230132,723
bento-agentgpt-5.50.893 ± 0.0250.862–0.9245530.0183126,350
mem0-agentgpt-5.50.890 ± 0.0300.853–0.9275473.6259126,929
mem0-infragpt-5.50.770 ± 0.0790.672–0.8685
LangChaingpt-5.50.490 ± 0.1130.350–0.6305732.9242173,435
C4 · graph retrieval — full NDCG grid (single-chain + scaled 8-chain; all modes; pure retrieval)
RetrieverNDCG@5 (g)NDCG@10 (g)NDCG@10 (bin)
Single chain · no surface cue (6 q × 3, sd=0.000) — graph ≫ all
bento-local0.7570.7800.982
bento-global0.7570.7330.928
bento-naive (vector+BM25)0.0510.1590.231
vector0.0940.1050.127
bm250.0610.0660.115
Scaled 8 chains · conventional / named (16 q) — graph (global) beats all
bento-global0.6660.7160.943
bento-local / hybrid0.5200.5480.782
bento-naive (vector+BM25)0.5200.5300.768
bm250.4820.5380.741
vector0.2820.3960.591
Scaled 8 chains · pure-lexical codename (16 q) — bm25 wins (fusion-weight tunable)
bm250.8780.8861.000
bento-global0.7570.7860.993
bento-local / hybrid0.7370.7630.960
bento-naive (vector+BM25)0.7370.7460.943
vector0.5680.6740.829
C4 · multi-model synthesis — retriever fixed (graph), answer model varied (6 q)
Answer modelneedle-in-ctxanswer acctokens in/out
gemma-26B (local)1.000.333
gemma-31B (local)1.000.500
gpt-5.5 (frontier)1.001.0001414 / 411
C5 · code chunking — full metrics (66 files, 254 QA, deterministic)
Chunker#chunksintacttop-1 cmplMRR (cmpl)NDCG@10 (cmpl)MRR (region)
symbol_aware (bento)5881.0000.8350.8960.9190.896
fixed-size, 1200-char9400.4920.3460.3840.3980.687
fixed-size, 2000-char5390.6000.360
C6 · evolving KB (currency + time-travel) · C7 · procedural reuse (per model tier)
C6 · KB currency (9 q, 3 generations)post-update correcttime-travel proberesult
bento (re-retrieve)1.000as_of before ingestexcluded ✓
naive cache (no invalidation)0.333as_of = nowserves current ✓
as_of between versionsreplay ✓ (a18 #90, §7.5)
C7 · procedural (4 ep × 6 orders)armaccuracyrefused opscallstokens
gemma-26B (local)no-memory1.00125220,944
gemma-26B (local)bento1.0035222,984
gemma-31B (local)no-memory1.00125219,384
gemma-31B (local)bento1.0035220,323
gpt-5.5 (frontier)no-memory1.00125220,082
gpt-5.5 (frontier)bento1.0035222,350

Notes: C4/C5/C6 are deterministic (no seed variance — the NDCG runs show sd=0.000 across 3 repeats); C7 is identical across all three model tiers (the reuse benefit is behavioral, not model-bound). C7's token cost is flat-to-slightly-higher — the win is the −75% in refused operations, not tokens. Significance reads (95% CI overlap) are in §5; threats to validity in §6.