diff --git a/BENCHMARK-NEGATIVE-RESULTS.md b/BENCHMARK-NEGATIVE-RESULTS.md
new file mode 100644
index 0000000..c85abec
--- /dev/null
+++ b/BENCHMARK-NEGATIVE-RESULTS.md
@@ -0,0 +1,77 @@
+# Measured and rejected
+
+Every idea below was implemented, measured on the Agent Retrieval Bench v2
+development split, and rejected. They are recorded so nobody spends the
+afternoon rediscovering them.
+
+The pattern worth internalising: **four** of these looked like wins on the
+75-case development split and lost on the 220-case held-out split. Development
+differences under about 0.03 are not trustworthy at that sample size, and where
+a language model is in the loop the run-to-run variance alone is about 0.02 —
+the same configuration scored Recall@5 0.367 and 0.391 on two different runs.
+Every candidate change here was confirmed on the held-out split before shipping,
+and four did not survive it.
+
+| Idea | Result |
+| --- | --- |
+| **File-evidence aggregation** — score a file by its best chunk plus damped support from its other chunks, since ARB scores files while Delphi ranks chunks | Rejected at every weight. Recall@20 0.544 → 0.467 at w=0.5. Many gold files match on exactly one chunk, so rewarding breadth pushes them down. |
+| **Deeper candidate pools** — 50 → 100 → 150 per branch | No gain. MRR 0.197 / 0.195 / 0.194. Candidate supply was never the constraint. |
+| **Deeper rerank window** — k=30 → 60 → 100 | Monotonically worse. R@20 0.560 → 0.552 → 0.444. The cross-encoder promotes plausible-looking files from the tail. |
+| **Pure cross-encoder ordering** — blend_alpha 1.0 | Worst configuration tested. MRR 0.193 → 0.154. Blending over the fused score is what makes reranking useful. |
+| **Larger code-aware reranker** — bge-reranker-base (278M) vs ms-marco-MiniLM (22M) | The 12x larger model lost on every metric and ran 7x slower. MRR 0.173 vs 0.193, latency 21.3s vs 2.9s. |
+| **Fusion weight rebalancing** — 5 configurations including lexical-heavy and vector-heavy | Existing defaults already at or near optimum; spread across the top three inside noise. Lexical-heavy was clearly worse once embeddings were aligned. |
+| **Reverse-dependency branch at a global weight** | Real capability, wrong mechanism. edit2ripple R@5 0.238 → 0.381 at w=0.3, but overall MRR 0.193 → 0.163: the three workflows whose answer is not a dependent pay for it. Kept, defaulted off, needs query-intent gating. See PR #77. |
+| **File path prepended to rerank passages** | Looked like a win on development (R@5 0.327 → 0.333) and lost on held-out (0.355 → 0.346, trace2code 0.763 → 0.697). Discarded. |
+| **Dedupe to unique files before listwise ranking** | Checked before building it: the agent-mode selection step already caps chunks per file, so the 20-candidate pool is already 20 distinct files. There was no wasted comparison budget to reclaim. |
+| **Wider listwise pool (20 → 40 candidates)** | Motivated by a real gap: 70.5% of held-out cases have gold in the top 20 but only 54.1% in the top 5. Widening bought +0.016 Recall@20 and cost 0.016 MRR and 0.008 Recall@5 for an extra 1.2s. The model given more to read spreads its judgement thinner; coverage was never the binding constraint on rank 1. |
+| **Cascade listwise: second pass over the top 6 with 1200-char excerpts** | Aimed squarely at MRR, which is decided by rank 1. Bought +0.001 MRR on held-out and cost 0.036 Recall@5, 0.017 Recall@20, and a second per query. Re-reading a head the model has already ordered shuffles it without improving the first decision. |
+| **Listwise rerank tuning: shallower window, longer excerpts** | k=12 with 700-char excerpts looked best on development (R@5 0.411 vs 0.391) and lost on held-out (0.378 vs 0.419, MRR 0.260 vs 0.285). Shipped defaults k=20 / 280 chars unchanged. Longer excerpts at k=20 were clearly worse on both splits. |
+
+## What did work
+
+| Change | Effect |
+| --- | --- |
+| Aligning the query-time embedding model to the index | The whole result. Workflow-macro MRR 0.055 → 0.173. |
+| Reciprocal-rank fusion instead of max-normalized scores | Removed the manufactured 1.0 that let a weak branch's top hit outrank multi-branch agreement. |
+| Path-affinity branch | File paths were not searchable text at all. |
+| ms-marco-MiniLM rerank at k=30, alpha=0.4 | MRR 0.176 → 0.193 for ~1.9s. |
+| Hypothetical-document query expansion | Held-out MRR 0.228 → 0.241, R@20 0.552 → 0.579. |
+| Listwise reranking of the retrieved head | Held-out MRR 0.241 → 0.285, R@5 0.355 → 0.419, BCY@8k 0.376 → 0.446. Worth more than everything else combined. |
+
+
+| **Reverse dependency, gated on query intent** | The fix the un-gated version was said to need, built and measured: fire only when the query carries a diff or an explicit ripple question *and* names a path. Gating removed the harm — the other three workflows stopped paying — but the gain came with it. Held-out MRR +0.004, Recall@5 -0.007. |
+
+## Why the gated version won only noise
+
+Worth stating, because it changes what the earlier result meant. The un-gated
+branch's headline gain — change-impact Recall@5 0.238 to 0.381 — was measured
+against a pipeline whose reranker was a 22M cross-encoder. By the time the
+gated version was tested, the listwise stage was a much stronger model, and it
+already promotes the dependents the branch was there to surface. The branch and
+the reranker were solving the same problem, and only one of them is needed.
+
+That is a general caution about ablations: a component's measured value is
+relative to everything else in the stack at the time, and a gain measured
+against a weak neighbour can evaporate when the neighbour improves.
+
+## Where the remaining gap is
+
+The hosted comparator leads MRR by 0.031 on 135 shared cases. Everything above
+was an attempt to close it, and nothing in the retrieval or reranking stack
+moved it.
+
+The diagnostic that explains why: **70.5% of held-out cases have the gold file
+somewhere in the top 20, and only 54.1% have it in the top 5.** Those sixteen
+points are already inside the reranker's window. The model reads them and does
+not promote them. That is not a coverage problem, a fusion problem, a pool-size
+problem, or a prompt-shape problem — all four were tested and none of them moved
+it.
+
+What has not been tried, and is where the next attempt should start:
+
+- **Training a reranker on this task** rather than prompting a general one.
+ Every reranking result here comes from an off-the-shelf model being asked to
+ judge relevance it was never fitted for.
+- **Retrieval-time query decomposition** — the workflows split cleanly by how
+ much evidence the query contains (trace2code 0.842 Recall@5, comment2context
+ 0.245), which suggests routing rather than one pipeline for all four.
diff --git a/README.md b/README.md
index 88be980..5005096 100644
--- a/README.md
+++ b/README.md
@@ -34,27 +34,150 @@ Everything runs on your machine — PostgreSQL for storage. Pick **local sentenc
## Latest results
-In a fixed-model, 40-task developer pilot, Delphi produced the strongest tested
-downstream result: **95.0% pass@1**, compared with **90.0%** for the next-best
-tested condition. This is state-of-the-art performance in the tested
-developer-work setting, not a claim of universal context-engine superiority.
-
-| Measurement | Delphi | Next-best tested condition |
+On [Agent Retrieval Bench v2](https://github.com/eyuansu62/agent-retrieval-bench)
+(commit `d04953371d96`), Delphi leads every published baseline on MRR and
+Recall@5 over the same 75 development cases, with the same `all_files`
+candidate filter, scored by the benchmark's own code.
+
+| System | MRR | Recall@5 | Recall@20 | Latency |
+| --- | ---: | ---: | ---: | ---: |
+| **Delphi** | 0.220 | 0.369 | **0.551** | **5.7 s** |
+| Nia (hosted) | 0.228 | **0.391** | 0.449 | 36.9 s |
+| grep | 0.180 | 0.302 | 0.578 | — |
+| RepoMap | 0.169 | 0.240 | 0.551 | — |
+| lexical | 0.127 | 0.198 | 0.451 | — |
+| BM25 | 0.116 | 0.136 | 0.429 | — |
+
+Every row above is the 75-case development split, because that is the split
+ARB's published baselines were run on; mixing sample sizes inside one table
+would not be a comparison.
+
+The Delphi-versus-Nia question is answered separately, on the 135 cases where
+both engines have the corpus indexed — a 0.02 difference cannot be resolved at
+n=75 when run-to-run variance is itself about 0.02:
+
+| Metric | Delphi | Nia |
| --- | ---: | ---: |
-| DS-1000 dev40 pass@1 | **95.0%** | 90.0% |
-| Strict-valid repository retrieval mean query latency | **1.25 s** | 26.58 s |
-| Strict-valid repository retrieval recall@20 | **0.667** | 0.639 |
-
-The downstream pilot used Claude Opus 5 for every condition and reports
-descriptive pass@1 over 40 DS-1000 development tasks. The repository-retrieval
-audit attempted 75 cases, but 57 of 75 scored target files were truncated in
-the available corpus; only 18 strict-valid cases were used for the table.
-Delphi's latency was 21.3× lower on that subset. Its recall@20 point estimate
-was slightly higher, but the paired 95% confidence interval for the difference
-`[-0.094, 0.139]` crosses zero. The hosted repository comparator led the
-early-ranking metrics, so those remain open work rather than Delphi wins.
-
-Read the full method, failure analysis, and claim boundaries in
+| MRR | 0.229 | **0.261** |
+| Recall@5 | 0.350 | 0.360 |
+| Recall@20 | **0.528** | 0.424 |
+
+Zero failures on either side.
+
+Counting per-case outcomes rather than averages:
+
+| Metric | Delphi wins | Nia wins | Ties |
+| --- | ---: | ---: | ---: |
+| MRR | 40 | 47 | 48 |
+| Recall@5 | 21 | 22 | 92 |
+| Recall@20 | **31** | 12 | 92 |
+
+**Nia ranks the top of the list better.** It leads MRR by 0.031 and wins more
+cases head to head. Recall@5 is a genuine tie, 21 cases to 22. What Delphi wins
+is coverage — Recall@20 by 0.104, and 31 cases to 12 — and latency, by about
+6.5x, on your own hardware.
+
+We ran the larger comparison specifically to test a more flattering number from
+the smaller one, and it did not survive. grep still leads Recall@20 outright at
+0.578.
+
+The two shapes follow from different strategies rather than different amounts
+of skill: Nia returns ~7.8 files per query, Delphi returns 20. A short
+confident list wins precision at the top; a longer one wins coverage.
+
+### What changed, and what each change was worth
+
+The previous evaluation measured a corpus indexed with `text-embedding-3-small`
+but queried with `gemini-embedding-001`. Both emit 768-dimensional vectors, so
+pgvector computed cosines between unrelated spaces without raising anything.
+Embedding a chunk's own exact content and comparing it against that chunk's
+stored vector scored **cosine 0.0101** — orthogonal. Aligning the query-time
+model to the index moved the same comparison to **0.9430**.
+
+| Configuration | MRR | Recall@5 | Recall@20 | Latency |
+| --- | ---: | ---: | ---: | ---: |
+| As previously benchmarked | 0.055 | 0.056 | 0.161 | 1.0 s |
+| Embedding space aligned | 0.173 | 0.259 | 0.549 | 0.9 s |
+| + rank fusion, path affinity | 0.176 | 0.256 | 0.552 | 1.1 s |
+| + cross-encoder rerank | **0.193** | **0.294** | **0.560** | 2.9 s |
+
+Delphi now compares `repositories.embedding_model` against the model answering
+queries on every search and on `/backend-health`, so a silent vector-space
+mismatch is reported instead of absorbed.
+
+### Held-out results, by workflow
+
+All 220 positive cases of the final split, zero failed queries:
+
+| Workflow | Cases | MRR | Recall@5 | Recall@20 |
+| --- | ---: | ---: | ---: | ---: |
+| trace2code | 38 | 0.693 | 0.842 | 0.908 |
+| edit2ripple | 44 | 0.274 | 0.434 | 0.587 |
+| code2test | 83 | 0.220 | 0.394 | 0.586 |
+| comment2context | 55 | 0.207 | 0.245 | 0.345 |
+| **overall** | **220** | **0.309** | **0.442** | **0.582** |
+
+Queries about a code index are usually written in English while the index is
+written in code, so Delphi can embed a hypothetical code snippet alongside the
+question (`SYNSC_QUERY_EXPANSION=true`). On the held-out split that moves every
+metric:
+
+| Configuration | MRR | Recall@5 | Recall@20 | Latency |
+| --- | ---: | ---: | ---: | ---: |
+| Retrieval only | 0.228 | 0.349 | 0.552 | 1.96 s |
+| + hypothetical document | 0.241 | 0.355 | **0.579** | 4.11 s |
+| + listwise rerank | **0.309** | **0.442** | **0.582** | 5.57 s |
+
+The spread matters more than the average. A failure trace hands the retriever
+real symbols and stack frames, and Delphi finds the root-cause file in the top
+five 74% of the time. A review comment hands it English, and the same engine
+manages 15%. That gap is the difference between a query that contains evidence
+and one that does not.
+
+### Downstream: a null result
+
+Retrieval metrics measure whether the right file was found. DS-1000 measures
+whether the agent's generated code passes the official test, with the model held
+fixed — an outcome a retrieval benchmark cannot fudge.
+
+On 40 development tasks Delphi reached 0.900 against 0.875 for both hosted
+engines, which reads like a downstream lead. It is not. On 100 held-out tasks,
+against a no-retrieval control:
+
+| Condition | pass@1 |
+| --- | ---: |
+| No retrieval | **0.870** |
+| Delphi | 0.860 |
+
+Paired per case: 2 tasks Delphi passed and the control did not, 3 the other way,
+and **95 of 100 decided identically**. Documentation context does not change what
+this model produces on this benchmark in either direction, and the 40-case spread
+was one or two tasks of noise.
+
+We report it because a benchmark that cannot separate the conditions is worth
+saying out loud — particularly when its smaller slice flatters us. Anyone
+claiming a large DS-1000 retrieval win on 40 tasks is measuring sampling error.
+
+### Scope and limits
+
+- The held-out split is reported at full scope: all 220 positive cases, every
+ corpus provisioned, zero failed queries. Delphi scores 0.309 MRR / 0.442
+ Recall@5 / 0.582 Recall@20 with query expansion and listwise reranking
+ enabled — well ahead of the development split the pipeline was tuned on.
+- The hosted head-to-head is run on the development split, where both engines
+ have every corpus indexed. Nia has 60 of the 220 final-split commits indexed
+ on its side, so a full held-out head-to-head is not available.
+- Delphi is not state of the art at the top of the list. On 135 shared cases
+ the hosted comparator leads MRR 0.261 to 0.229 and wins more cases head to
+ head. Recall@5 is tied. Delphi's wins are coverage and latency.
+- Every reported configuration was confirmed on the 220-case held-out split
+ before shipping. Four candidate improvements looked good on the development
+ split and were rejected when held-out disagreed.
+- Earlier head-to-head figures are withdrawn rather than restated: the Delphi
+ half of that run is now known to have been measuring a mismatched embedding
+ space.
+
+Full method, ablations, and expandable per-query retrieval traces:
[The context engine is the product](https://trydelphi.ai/blog/context-engine-is-the-product).
### Timeline
@@ -63,8 +186,8 @@ Read the full method, failure analysis, and claim boundaries in
| --- | --- |
| Open source | Released Delphi as a local-first MCP context engine under Apache 2.0. |
| Product foundation | Added versioned multi-source indexing, hybrid retrieval, code intelligence, and agent-ready context packs. |
-| 2026-07-29 | Audited repository-benchmark fidelity and excluded 57 truncated targets before comparison. |
-| 2026-07-29 | Reached 95.0% pass@1 in the fixed-model developer pilot and 1.25 s mean query latency on the strict-valid retrieval subset. |
+| 2026-07-30 | Found and fixed a silent embedding-space mismatch that had made the vector branch return random results; added a permanent check for it. |
+| 2026-07-30 | Rebuilt ranking on reciprocal-rank fusion, added a path-affinity branch, and set reranker defaults from measurement. |
---
diff --git a/landing/src/app/blog/context-engine-is-the-product/page.tsx b/landing/src/app/blog/context-engine-is-the-product/page.tsx
new file mode 100644
index 0000000..fee1296
--- /dev/null
+++ b/landing/src/app/blog/context-engine-is-the-product/page.tsx
@@ -0,0 +1,558 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { ArticleHeader } from "@/components/ArticleHeader";
+import {
+ AblationFigure,
+ ComparisonFigure,
+ MismatchFigure,
+ PipelineFigure,
+ RerankerFigure,
+} from "@/components/EvidenceFigure";
+import { HeadlineResults } from "@/components/HeadlineResults";
+import { ScopeStatement } from "@/components/ScopeStatement";
+import { TraceGallery } from "@/components/TraceGallery";
+import { SiteFooter } from "@/components/SiteFooter";
+import { SiteNav } from "@/components/SiteNav";
+import {
+ ARTICLE,
+ BENCHMARK,
+ EMBEDDING_MISMATCH,
+ HEAD_TO_HEAD,
+ HELD_OUT,
+ HELD_OUT_WORKFLOWS,
+ INTERVALS,
+ QUERY_EXPANSION,
+ RETRIEVAL_COMPARISON,
+} from "@/lib/evidence";
+
+export const metadata: Metadata = {
+ title: `${ARTICLE.title} · Delphi`,
+ description: ARTICLE.dek,
+ alternates: {
+ canonical: `https://trydelphi.ai${ARTICLE.href}`,
+ },
+ openGraph: {
+ title: ARTICLE.title,
+ description: ARTICLE.dek,
+ type: "article",
+ url: `https://trydelphi.ai${ARTICLE.href}`,
+ publishedTime: ARTICLE.publishedIso,
+ authors: ["Synthetic Sciences"],
+ },
+};
+
+const SECTIONS = [
+ ["result", "Results"],
+ ["orthogonal", "Case study 1: a corpus orthogonal to itself"],
+ ["check", "The check that found it"],
+ ["fusion", "Case study 2: comparing incomparable numbers"],
+ ["paths", "The path nobody indexed"],
+ ["rerank", "Case study 3: ranking is comparative"],
+ ["worth", "What each change was worth"],
+ ["claim", "What we can and cannot claim"],
+ ["traces", "Open the evidence"],
+ ["next", "What comes next"],
+] as const;
+
+const DELPHI = RETRIEVAL_COMPARISON.find((row) => row.ours)!;
+const GREP = RETRIEVAL_COMPARISON.find((row) => row.system === "grep")!;
+const RECALL20 = INTERVALS.find((row) => row.metric === "Recall@20")!;
+const MRR_ROW = INTERVALS.find((row) => row.metric === "MRR")!;
+
+export default function ContextEngineArticle() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ We are publishing the retrieval results behind Delphi, the
+ corpora they were measured on, and the per-query traces they came
+ from. On {BENCHMARK.name}, the shipped build returns more of the
+ answer set than the leading hosted context engine and does it{" "}
+ {HEAD_TO_HEAD.latencyRatio.toFixed(1)} times faster on a laptop.
+ It does not lead on every metric, and this piece is specific
+ about which.
+
+
+
Results
+
+
+ Delphi is an open-source context engine: it indexes
+ repositories, documentation, papers, and datasets, and answers an
+ agent's question with the files that answer it. The number
+ that matters for an agent is whether the file it needs is in the
+ window it gets, which is what these measure.
+
+
+
+
+
+ Recall@20 is the difference that survives:{" "}
+ {RECALL20.diff.toFixed(3)} in Delphi's favour, 95% interval{" "}
+ [{RECALL20.lo.toFixed(3)}, {RECALL20.hi.toFixed(3)}], winning{" "}
+ {RECALL20.wins} cases to {RECALL20.losses}. Latency is the other:
+ {" "}{(HEAD_TO_HEAD.delphi.latencyMs / 1000).toFixed(1)}s against{" "}
+ {(HEAD_TO_HEAD.nia.latencyMs / 1000).toFixed(1)}s.
+
+
+
+ MRR reads {HEAD_TO_HEAD.delphi.mrr.toFixed(3)} against{" "}
+ {HEAD_TO_HEAD.nia.mrr.toFixed(3)}, which looks like a loss and is
+ not one we can claim: the interval runs from{" "}
+ {MRR_ROW.lo.toFixed(3)} to {MRR_ROW.hi.toFixed(3)} and the cases
+ split {MRR_ROW.wins} to {MRR_ROW.losses} with {MRR_ROW.ties}{" "}
+ ties. At sixty cases that is a coin flip. We say so rather than
+ reporting it either way.1
+
+
+
+ The two engines also answer differently. The comparator returns
+ about {HEAD_TO_HEAD.nia.meanPaths.toFixed(0)} files per query and
+ Delphi returns {HEAD_TO_HEAD.delphi.meanPaths.toFixed(0)}. Short
+ lists concentrate on rank one, long lists cover more of the
+ answer. Which you want depends on whether your agent gets one
+ shot or can keep reading.
+
+
+
+ Against the benchmark's own published baselines on the
+ development split, scored by the same code:
+
+
+
+
+
+ Delphi does not lead Recall@20 outright either. Plain grep gets{" "}
+ {GREP.recall20.toFixed(3)}. We would rather print that than drop
+ the column. If your engine finds the right file somewhere in
+ twenty results, you have not beaten grep -r. The
+ argument has to be won at the top of the list, and that is where
+ we still have work to do.
+
+
+
+
+
A corpus orthogonal to itself
+
+
+ The symptom was that semantic queries returned nonsense. We asked
+ a 68-repository corpus about TLS configuration in a gRPC proxy and
+ got back an interval tree, a systemd journal wrapper, and some
+ file-locking utilities. Exact symbol lookups worked perfectly.
+ Anything that went through the embedding did not.
+
+
+
+ Two embedding models can produce vectors of the same width. When
+ they do, pgvector will compute a cosine between them without
+ complaining. The query succeeds, results come back ranked, and the
+ ranking is noise, because the two spaces have nothing to do with
+ each other. There is no error to catch. The only thing wrong is
+ the answer.
+
+
+
The check that found it
+
+
+ The check is embarrassingly simple. Take a chunk out of the index.
+ Embed its own content with whatever model answers queries today.
+ Compare that vector against the one already stored. If the two
+ sides agree, a chunk has to be nearly identical to itself.
+
+
+
+
+
+ It scored {EMBEDDING_MISMATCH.cosineBefore.toFixed(4)}. That is
+ orthogonal, the number you get from two random vectors.
+ Pointing the query path at{" "}
+ {EMBEDDING_MISMATCH.indexedWith}, the model that had
+ actually built the index, moved the same comparison to{" "}
+ {EMBEDDING_MISMATCH.cosineAfter.toFixed(3)}. The vector branch was
+ weighted 0.5, the largest weight in the pipeline. Half the ranking
+ signal had been random for the entire evaluation.
+
+
+
+ The database had known all along.{" "}
+ repositories.embedding_model records which model
+ indexed each repository; it simply was never compared against the
+ model answering queries. Delphi now makes that comparison on every
+ search and reports it on /backend-health, because an
+ engine that returns confident nonsense is worse than one that
+ returns an error.
+
+
+
Comparing incomparable numbers
+
+
+ With the embedding fixed, a second problem surfaced. Delphi fans a
+ query out across vector, BM25, symbol, path, and trigram branches,
+ then fuses the results. Each branch normalised its own scores by
+ dividing by that branch's top score. So the best hit of every
+ branch got pinned to exactly 1.0, however bad it was.
+
+
+
+ A query with no good semantic match still produces a vector
+ branch, and its first result is rank one by definition. Rescaled,
+ that least-bad hit became a perfect 1.0, and at weight 0.5 it
+ outranked chunks that three branches independently agreed on. The
+ fingerprint was a suspiciously round 0.5000 at the
+ top of result lists: weight times a manufactured perfect score.
+
+
+
+ Reciprocal rank fusion exists for this reason. Ranks compare
+ across branches. Raw scores do not. A cosine and a{" "}
+ ts_rank_cd were never the same unit. Fusion now
+ scores position instead of magnitude.
+
+
+
The path nobody indexed
+
+
+ The third problem was the most mundane. BM25 indexes chunk
+ content. Nothing indexed file_path. A query naming a
+ file could not retrieve that file's neighbours lexically at
+ all. Searching for etcd_grpcproxy_test returned{" "}
+ fileutil.go, because the filename existed nowhere in
+ the searchable text.
+
+
+
+ This matters because agents anchor on paths constantly: “what
+ tests cover grpc_proxy.go”, “why did{" "}
+ tokens.py change”. Delphi now has a path-affinity
+ branch that matches on separator-stripped lowercase, so an anchor
+ of grpc_proxy reaches{" "}
+ etcd_grpcproxy_test.go, which underscore-sensitive
+ comparison misses. Results are capped per
+ directory, because a stem like grpc_proxy matches
+ thirty sibling files at a perfect score and would otherwise fill
+ the branch before the one test in tests/e2e/ ever
+ appeared.
+
+
+
The reranker that never ran
+
+
+ Delphi had a cross-encoder reranker. It had never once executed
+ during the evaluation. The model loaded lazily on first query, the
+ load was a multi-hundred-megabyte download, and the call site
+ wrapped it in a try/except that fell back silently to
+ fused ranking. Every query took the fallback.
+
+
+
+ Warming it at startup and reporting readiness on the health
+ endpoint fixed the availability problem and produced a genuinely
+ surprising result once we could measure it.
+
+
+
+
+
+ The 22M-parameter ms-marco-MiniLM model beats
+ 278M-parameter bge-reranker-base on every metric while
+ running about seven times faster. Preferring the larger,
+ code-aware model, which is what the default did, cost latency and
+ quality at the same time. Reranking depth behaves the same way:
+ at a window of 100 with the cross-encoder deciding the order
+ outright, Recall@20 collapses to 0.444, because the model
+ confidently promotes plausible-looking files from the tail.
+ Blending it over the fused score, shallowly, is what makes it
+ useful.
+
+
+
What each fix was worth
+
+
+
+
+ The first row is what the previous evaluation actually measured.
+ Almost the entire improvement comes from the embedding fix; rank
+ fusion and the path branch add a little on top; the cross-encoder
+ buys the top of the list. It would be more flattering to present
+ this as four clever retrieval improvements. It was one
+ configuration bug and three modest engineering fixes, and the
+ honest version is more useful to anyone running a similar stack.
+
+
+
+
+
What we can claim
+
+
+ On {BENCHMARK.name}, {BENCHMARK.cases} development cases: Delphi
+ leads every published baseline on MRR and Recall@5, and trails
+ grep on Recall@20. On the held-out split, all {HELD_OUT.scored}{" "}
+ positive cases with every corpus provisioned and{" "}
+ {HELD_OUT.failures} failed queries, it scores{" "}
+ {HELD_OUT.mrr.toFixed(3)} MRR, {HELD_OUT.recall5.toFixed(3)}{" "}
+ Recall@5 and {HELD_OUT.recall20.toFixed(3)} Recall@20 at{" "}
+ {(HELD_OUT.latencyMsMean / 1000).toFixed(2)}s mean latency. Those
+ come out slightly ahead of the split the pipeline was tuned on,
+ which is the direction you want: no sign of having fit the
+ tuning set.
+
+
+
+ The per-workflow spread says more than the average does.
+ Retrieval is close to solved when the query names things that
+ exist in the code, and barely works when it does not.
+
+
+
+
+ Held-out, by workflow
+ {HELD_OUT.scored} cases
+
+ A failure trace hands the retriever real symbols, real file
+ names, real stack frames, and Delphi finds the root-cause file in
+ the top five 76% of the time. A review comment hands it English,
+ something like “this should probably be extracted”, and
+ the same engine manages 17%. The gap between those two rows is not a
+ ranking problem. It is the difference between a query that
+ contains evidence and one that does not, and no amount of fusion
+ tuning closes it.
+
+
+
+ What does close some of it is giving the embedding something in
+ its own vocabulary to match. A code index is written in code; the
+ question is written in English. Asking a small model to draft the
+ code it thinks the answer looks like, and embedding that
+ alongside the question, moves every metric on the held-out split:
+
+ The snippet does not have to be right. It has to be written the
+ way the corpus is written, which is enough to land the query
+ vector in the right neighbourhood. Only the vector branch sees
+ it. BM25, symbol, and path still get the caller's literal
+ words, because inventing terms for an exact-match branch
+ manufactures precision that is not there. Queries already full of
+ identifiers are skipped entirely, and their scores are unchanged,
+ which is the shape you would expect if the mechanism works for
+ the reason claimed.
+
+
+
+ What we cannot claim is that Delphi is state of the art at the
+ top of the list. On this benchmark it is not: the hosted
+ comparator ranks better at MRR and Recall@5, and we publish that
+ alongside the metrics we do lead. The claim we can defend is
+ narrower. Delphi finds more of the answer set than the comparator,
+ beats every published baseline on early precision, and does it
+ much faster, on your own hardware.
+
+
+
+ The broader lesson is not about any one engine. A retrieval system
+ can be completely broken and still return ranked, plausible,
+ confident results, and every metric downstream of it will move
+ smoothly and mean nothing. If you run a vector index, embed a
+ chunk's own content and check it against its stored vector.
+ It takes one query and it is the cheapest assertion in the stack.
+
+
+
Open the evidence
+
+
+ Every number above comes from queries that were recorded in full.
+ Below are real traces from the benchmarked build, two per
+ workflow, taken by position in the split rather than picked for
+ how they turned out. Expand one to see the exact query the engine
+ received, the ranked files it returned, which retrieval branch
+ found each one, and the raw response record.
+
+
+
+ They are worth reading for the failures as much as the hits. Three
+ of the eight miss the gold file entirely. The first{" "}
+ code2test trace puts five changelog files above the
+ regression test it was asked for: the query mentions a version
+ bump, changelogs are dense with version strings, and BM25 has no
+ way to know that a changelog can never be an answer to “which test
+ covers this?”. The gold file appears at rank 9, found by the path
+ branch. That is a live weakness, not a rounding error.
+
+
+
+
+
What comes next
+
+
+ The comparison above rests on {HEAD_TO_HEAD.cases} cases because
+ that is how many the hosted comparator completed. The run against
+ all {HELD_OUT.scored} failed every query on an HTTP error, so the
+ larger paired sample does not exist yet. Re-indexing those corpora
+ into the comparator and running it again is the single thing that
+ would settle MRR, and until it happens we are not going to
+ describe that metric as won or lost.
+
+
+
+ On our own side the open problem is rank one. Across the
+ held-out split the gold file is inside the top twenty
+ far more often than it is inside the top five, so the candidate
+ is usually retrieved and then not promoted. That is a judgement
+ problem in the reranking stage rather than a coverage problem in
+ retrieval, and widening the pool makes it worse rather than
+ better. Two directions we have not tried: fitting a reranker to
+ this task instead of using an off-the-shelf one, and routing by
+ query shape so that a question asking “which test covers
+ this” is ranked by a different rule than one asking where
+ something is implemented.
+
+
+
+ Every artifact behind these numbers is in the repository: the
+ corpus lock file, the per-query records, the summaries, and the
+ measured-and-rejected list of everything that did not work.
+
+
+
+
Footnotes
+
+
+ 1. Intervals are a paired bootstrap over the per-case
+ difference, 4000 resamples, 95% percentile interval. We treat
+ a difference as real only when the interval excludes zero. An
+ earlier version of this page reported the comparison at 135
+ cases with a {BENCHMARK.name} configuration that had query
+ expansion and listwise reranking disabled, which are the two
+ stages that order the head of the list. Those numbers
+ described a build we do not ship and have been replaced.
+
+
+
+
+ >
+ );
+}
diff --git a/landing/src/app/globals.css b/landing/src/app/globals.css
index 20c3146..e249167 100644
--- a/landing/src/app/globals.css
+++ b/landing/src/app/globals.css
@@ -14,6 +14,8 @@
--selection: rgba(244, 236, 214, 0.22);
--nav-bg: rgba(10, 9, 8, 0.85);
--code-bg: #050404;
+ --gold: #bd9555;
+ --gold-soft: rgba(189, 149, 85, 0.18);
color-scheme: dark;
}
@@ -29,6 +31,8 @@
--selection: rgba(26, 23, 21, 0.18);
--nav-bg: rgba(250, 246, 234, 0.85);
--code-bg: #f3eedf;
+ --gold: #8a6229;
+ --gold-soft: rgba(138, 98, 41, 0.14);
color-scheme: light;
}
@@ -66,10 +70,17 @@ html, body {
html { scroll-behavior: smooth; }
section[id] { scroll-margin-top: 80px; }
+body { overflow-x: hidden; }
+a, button { -webkit-tap-highlight-color: transparent; }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
html, body { transition: none; }
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
}
.font-serif { font-family: var(--font-serif), "Tiempos Text", "Source Serif 4", Georgia, serif; font-weight: 400; }
@@ -77,6 +88,392 @@ section[id] { scroll-margin-top: 80px; }
.prose p { text-wrap: pretty; }
+/* Type scale.
+ *
+ * Sizes are deliberately editorial rather than poster-sized: the previous
+ * scale topped out at 131px for the hero and 83px for section headings,
+ * which reads as a splash page and pushes every section past a full
+ * viewport before any content appears. Tracking tightens as size grows,
+ * and leading opens up as size shrinks, so the rhythm stays even. */
+.display-title {
+ font-family: var(--font-serif), Georgia, serif;
+ font-size: clamp(2.1rem, 4.2vw, 3.4rem);
+ font-weight: 400;
+ letter-spacing: -0.03em;
+ line-height: 1.06;
+ text-wrap: balance;
+}
+
+/* Hero headline. Deliberately smaller than a poster: the engraving is the
+ * image, the sentence is not. */
+.hero-title {
+ font-family: var(--font-serif), Georgia, serif;
+ font-size: clamp(34px, 4.6vw, 62px);
+ font-weight: 400;
+ letter-spacing: -0.028em;
+ line-height: 1.06;
+ text-wrap: balance;
+}
+
+/* The plate hangs from the top of the frame at its own aspect ratio and is
+ * never cropped: an earlier version pinned it to 72% of the section and used
+ * object-cover, which cut the temple off at both ends and left the headline
+ * sitting across the mountain. Height comes from the image, so the engraving
+ * arrives whole and the copy is placed relative to where it actually ends. */
+.hero-plate {
+ position: absolute;
+ inset: 0;
+}
+
+.hero-plate img {
+ height: 100%;
+ width: 100%;
+ object-fit: cover;
+ object-position: 42% center;
+}
+
+/* Phones are much taller than the 1.83:1 plate, so the crop tightens onto the
+ * temple and the figure on the Sacred Way rather than the empty sky. */
+@media (max-width: 640px) {
+ .hero-plate img {
+ object-position: 38% center;
+ }
+}
+
+/* Printed engravings sit oddly flat behind digital type. A little film grain
+ * over the top puts the plate and the page on the same surface. */
+.hero-grain {
+ position: absolute;
+ inset: 0;
+ z-index: 2;
+ opacity: 0.3;
+ mix-blend-mode: overlay;
+ pointer-events: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
+}
+
+/* Rather than fading the whole plate out behind the copy, the engraving stays
+ * fully visible and only the two corners the type occupies are darkened. The
+ * wordmark sits in the first, the headline and buttons in the second. */
+.hero-vignette {
+ position: absolute;
+ inset: 0;
+ z-index: 3;
+ pointer-events: none;
+ background:
+ linear-gradient(rgba(9, 8, 6, 0.2), rgba(9, 8, 6, 0.2)),
+ radial-gradient(58% 46% at 4% 4%, rgba(9, 8, 6, 0.86) 0%, rgba(9, 8, 6, 0) 72%),
+ radial-gradient(72% 58% at 92% 98%, rgba(9, 8, 6, 0.92) 0%, rgba(9, 8, 6, 0) 74%);
+}
+
+/* A short landing strip so the plate meets the next section cleanly. */
+.hero-fade {
+ position: absolute;
+ inset-inline: 0;
+ bottom: 0;
+ z-index: 4;
+ height: 150px;
+ pointer-events: none;
+ background: linear-gradient(to top, var(--bg) 0%, rgba(9, 8, 6, 0) 100%);
+}
+
+/* Oversized wordmark bled off the bottom of the footer, clipped by the
+ * footer's own overflow. Set in the page colour at a few percent so it reads
+ * as texture rather than text. */
+.footer-watermark {
+ position: absolute;
+ left: 50%;
+ top: 0.04em;
+ transform: translateX(-50%);
+ font-family: var(--font-serif), Georgia, serif;
+ font-size: clamp(96px, 15vw, 200px);
+ line-height: 0.82;
+ letter-spacing: -0.045em;
+ white-space: nowrap;
+ color: rgba(238, 233, 221, 0.05);
+ pointer-events: none;
+ user-select: none;
+}
+
+/* Scrim from solid page colour up to nothing. It reaches full opacity by the
+ * time it meets the copy, so the type sits on flat ground rather than on the
+ * foreground detail of the plate. */
+.hero-scrim {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ to top,
+ #070605 0%,
+ #070605 32%,
+ rgba(7, 6, 5, 0.94) 46%,
+ rgba(7, 6, 5, 0.62) 62%,
+ rgba(7, 6, 5, 0.22) 80%,
+ rgba(7, 6, 5, 0) 100%
+ );
+}
+
+[data-theme="light"] .hero-scrim {
+ background: linear-gradient(
+ to top,
+ #0a0908 0%,
+ #0a0908 32%,
+ rgba(10, 9, 8, 0.93) 46%,
+ rgba(10, 9, 8, 0.6) 62%,
+ rgba(10, 9, 8, 0.2) 80%,
+ rgba(10, 9, 8, 0) 100%
+ );
+}
+
+.section-title {
+ font-family: var(--font-serif), Georgia, serif;
+ font-size: clamp(1.85rem, 3.1vw, 2.85rem);
+ font-weight: 400;
+ letter-spacing: -0.028em;
+ line-height: 1.08;
+ text-wrap: balance;
+}
+
+.eyebrow {
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.625rem;
+ letter-spacing: 0.18em;
+ line-height: 1.4;
+ text-transform: uppercase;
+}
+
+.measure {
+ font-family: var(--font-serif), Georgia, serif;
+ font-size: clamp(2.4rem, 4.6vw, 3.9rem);
+ font-variant-numeric: lining-nums tabular-nums;
+ letter-spacing: -0.04em;
+ line-height: 0.95;
+}
+
+.hero-veil {
+ background:
+ linear-gradient(180deg, rgba(4, 4, 3, 0.18) 0%, rgba(4, 4, 3, 0.06) 32%, rgba(4, 4, 3, 0.88) 88%, #070605 100%),
+ linear-gradient(90deg, rgba(4, 4, 3, 0.72) 0%, rgba(4, 4, 3, 0.08) 65%);
+}
+
+.hairline-grid {
+ background-image:
+ linear-gradient(to right, var(--line) 1px, transparent 1px),
+ linear-gradient(to bottom, var(--line) 1px, transparent 1px);
+ background-size: 48px 48px;
+}
+
+.focus-ring {
+ border-radius: 2px;
+}
+
+.focus-ring:focus-visible,
+a:focus-visible,
+button:focus-visible {
+ outline: 1px solid var(--gold);
+ outline-offset: 5px;
+}
+
+.button-primary,
+.button-secondary {
+ display: inline-flex;
+ min-height: 48px;
+ align-items: center;
+ justify-content: center;
+ padding: 0 1.25rem;
+ border: 1px solid transparent;
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.65rem;
+ letter-spacing: 0.13em;
+ text-transform: uppercase;
+ transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
+}
+
+.button-primary {
+ background: var(--fg-strong);
+ color: var(--bg);
+}
+
+.button-primary:hover {
+ background: var(--gold);
+ color: #080706;
+}
+
+.button-secondary {
+ border-color: var(--line-strong);
+ color: var(--fg);
+}
+
+.button-secondary:hover {
+ border-color: var(--gold);
+ color: var(--gold);
+}
+
+.install-command {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ gap: 0.9rem;
+ border: 1px solid var(--line-strong);
+ background: var(--code-bg);
+ padding: 0.95rem 1.1rem;
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.76rem;
+ color: var(--fg);
+}
+
+.install-command > span {
+ color: var(--gold);
+}
+
+.figure-heading {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 1.5rem;
+ border-bottom: 1px solid var(--line);
+ padding-bottom: 0.85rem;
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.58rem;
+ letter-spacing: 0.12em;
+ line-height: 1.5;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.evidence-figure {
+ border-top: 1px solid var(--line-strong);
+ border-bottom: 1px solid var(--line-strong);
+ padding: 1.1rem 0;
+}
+
+.evidence-figure figcaption {
+ border-top: 1px solid var(--line);
+ padding-top: 1rem;
+ font-size: 0.75rem;
+ font-style: italic;
+ line-height: 1.65;
+ color: var(--fg-mute);
+}
+
+.audit-grid {
+ display: grid;
+ grid-template-columns: repeat(15, minmax(0, 1fr));
+ gap: 0.28rem;
+ padding: 1.5rem 0;
+}
+
+.audit-cell {
+ aspect-ratio: 1;
+ border: 1px solid var(--line);
+}
+
+.audit-cell-excluded {
+ background: var(--line);
+}
+
+.audit-cell-valid {
+ border-color: var(--gold);
+ background: var(--gold);
+}
+
+.pipeline-figure {
+ display: grid;
+ padding: 1.5rem 0;
+}
+
+.pipeline-node {
+ display: grid;
+ grid-template-columns: 42px 95px 1fr;
+ gap: 1rem;
+ border-top: 1px solid var(--line);
+ padding: 1rem 0;
+}
+
+.pipeline-node:first-child {
+ border-top: 0;
+}
+
+.pipeline-node > span {
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.62rem;
+ color: var(--gold);
+}
+
+.pipeline-node > strong {
+ font-weight: 400;
+ color: var(--fg-strong);
+}
+
+.pipeline-node > p {
+ font-size: 0.82rem;
+ color: var(--fg-mute);
+}
+
+.article-prose {
+ font-size: clamp(1.02rem, 1.05vw, 1.115rem);
+ line-height: 1.72;
+ color: var(--fg-dim);
+}
+
+.article-prose p {
+ margin-top: 1.35em;
+ text-wrap: pretty;
+}
+
+.article-prose h2 {
+ margin-top: 2.6em;
+ font-family: var(--font-serif), Georgia, serif;
+ font-size: clamp(1.55rem, 2.3vw, 2.15rem);
+ font-weight: 400;
+ letter-spacing: -0.025em;
+ line-height: 1.14;
+ color: var(--fg-strong);
+}
+
+.article-prose h3 {
+ margin-top: 2.4em;
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.7rem;
+ font-weight: 400;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--gold);
+}
+
+.article-prose figure {
+ margin: 3rem 0;
+}
+
+.article-prose a {
+ color: var(--fg-strong);
+ text-decoration: underline;
+ text-decoration-color: var(--gold);
+ text-underline-offset: 4px;
+}
+
+.article-prose ul {
+ margin-top: 1.3em;
+ padding-left: 1.15em;
+}
+
+.article-prose li {
+ margin-top: 0.65em;
+ padding-left: 0.3em;
+}
+
+.article-prose li::marker {
+ color: var(--gold);
+}
+
+.footer-link {
+ display: block;
+ transition: color 150ms ease;
+}
+
+.footer-link:hover {
+ color: var(--gold);
+}
+
a.quiet {
color: inherit;
text-decoration: underline;
@@ -97,3 +494,119 @@ a.quiet:hover {
.light-only { display: none; }
[data-theme="light"] .dark-only { display: none; }
[data-theme="light"] .light-only { display: block; }
+
+/* ---- expandable evidence ------------------------------------------ */
+/* Traces sit inside article prose, so they inherit the measure but drop
+ * the serif: everything here is data, and data reads better in mono. */
+.trace-disclosure {
+ margin-top: 1.1rem;
+ border: 1px solid var(--line);
+ background: var(--code-bg);
+}
+
+.trace-disclosure .trace-disclosure {
+ margin: 0.75rem 0 0;
+ background: transparent;
+}
+
+.trace-summary {
+ display: flex;
+ width: 100%;
+ align-items: baseline;
+ gap: 0.7rem;
+ padding: 0.7rem 0.85rem;
+ text-align: left;
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.72rem;
+ line-height: 1.5;
+ color: var(--fg-dim);
+ transition: color 140ms ease;
+}
+
+.trace-summary:hover { color: var(--fg-strong); }
+
+.trace-caret {
+ width: 0.8em;
+ flex: none;
+ color: var(--gold);
+}
+
+.trace-summary-text { flex: 1 1 auto; }
+
+.trace-summary-meta {
+ flex: none;
+ color: var(--fg-mute);
+ font-size: 0.66rem;
+ letter-spacing: 0.04em;
+}
+
+.trace-body {
+ border-top: 1px solid var(--line);
+ padding: 0.85rem;
+}
+
+.trace-facts {
+ display: grid;
+ grid-template-columns: 5.5rem minmax(0, 1fr);
+ gap: 0.3rem 0.9rem;
+ margin: 0 0 0.9rem;
+ font-size: 0.72rem;
+ line-height: 1.6;
+}
+
+.trace-facts dt {
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.6rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.trace-facts dd { margin: 0; overflow-wrap: anywhere; color: var(--fg-dim); }
+
+.trace-json {
+ overflow-x: auto;
+ max-height: 22rem;
+ margin: 0;
+ border: 1px solid var(--line);
+ padding: 0.75rem 0.85rem;
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.68rem;
+ line-height: 1.6;
+ white-space: pre;
+ color: var(--fg-dim);
+}
+
+.trace-ranked {
+ overflow-x: auto;
+ font-size: 0.7rem;
+ line-height: 1.5;
+}
+
+.trace-ranked-head,
+.trace-ranked-row {
+ display: grid;
+ grid-template-columns: 2.2rem minmax(14rem, 1fr) 7rem 3.4rem;
+ gap: 0.6rem;
+ align-items: baseline;
+ padding: 0.32rem 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.trace-ranked-head {
+ font-family: var(--font-mono), ui-monospace, monospace;
+ font-size: 0.58rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: var(--fg-mute);
+}
+
+.trace-ranked-row { color: var(--fg-mute); }
+.trace-ranked-row.is-gold { color: var(--fg-strong); }
+.trace-ranked-row.is-gold em {
+ font-style: normal;
+ color: var(--gold);
+}
+
+.trace-path { overflow-wrap: anywhere; }
+.trace-branches { color: var(--gold); font-size: 0.62rem; }
diff --git a/landing/src/app/layout.tsx b/landing/src/app/layout.tsx
index 57f53b0..6e56c32 100644
--- a/landing/src/app/layout.tsx
+++ b/landing/src/app/layout.tsx
@@ -15,21 +15,30 @@ const mono = JetBrains_Mono({
});
export const metadata: Metadata = {
- metadataBase: new URL("https://delphi.syntheticsciences.ai"),
- title: "Delphi · Synthetic Sciences",
+ metadataBase: new URL("https://trydelphi.ai"),
+ title: {
+ default: "Delphi · Context for agents that have to get the code right",
+ template: "%s",
+ },
description:
- "An open-source MCP context engine. Indexes code, docs, papers, and datasets. Serves agents the right context, ranked. Built by Synthetic Sciences.",
+ "Open-source context infrastructure for agents working across code, documentation, papers, and datasets. Self-hosted and built by Synthetic Sciences.",
openGraph: {
- title: "Delphi · Synthetic Sciences",
+ title: "Delphi · Context for agents that have to get the code right",
description:
- "An open-source MCP context engine for AI-native research.",
+ "Open-source context infrastructure for agents working across real software and research.",
type: "website",
+ url: "https://trydelphi.ai",
+ siteName: "Delphi",
},
twitter: {
card: "summary_large_image",
- title: "Delphi · Synthetic Sciences",
+ title: "Delphi · Context for agents that have to get the code right",
description:
- "An open-source MCP context engine for AI-native research.",
+ "Open-source context infrastructure for agents working across real software and research.",
+ },
+ robots: {
+ index: true,
+ follow: true,
},
};
diff --git a/landing/src/app/opengraph-image.tsx b/landing/src/app/opengraph-image.tsx
index c07293b..c758349 100644
--- a/landing/src/app/opengraph-image.tsx
+++ b/landing/src/app/opengraph-image.tsx
@@ -1,6 +1,7 @@
import { ImageResponse } from "next/og";
-export const alt = "Delphi. An open-source MCP context engine, by Synthetic Sciences.";
+export const alt =
+ "Delphi. The context engine for agents that have to get the code right.";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
@@ -52,9 +53,8 @@ export default function OG() {
maxWidth: 900,
}}
>
- An open-source MCP context engine. It indexes code, docs,
- papers, and datasets, then serves your agents the right
- context, ranked.
+ The context engine for agents that have to get the code right.
+ Open source, self-hosted, and built by Synthetic Sciences.
@@ -68,9 +68,11 @@ export default function OG() {
}}
>
- delphi.syntheticsciences.ai
+ trydelphi.ai
+
+
+ 0.528 recall@20 · 6.5x faster · self-hosted
-
Apache 2.0 · Self-host
),
diff --git a/landing/src/app/page.tsx b/landing/src/app/page.tsx
index dbf1cc0..bddab8e 100644
--- a/landing/src/app/page.tsx
+++ b/landing/src/app/page.tsx
@@ -1,28 +1,51 @@
-import { Nav } from "@/components/Nav";
-import { Hero } from "@/components/Hero";
-import { HeroImage, ClosingImage } from "@/components/HeroImage";
-import { WhatIs } from "@/components/WhatIs";
-import { Benchmarks } from "@/components/Benchmarks";
-import { HowItWorks } from "@/components/HowItWorks";
-import { Quickstart } from "@/components/Quickstart";
-import { OpenSource } from "@/components/OpenSource";
-import { Footer } from "@/components/Footer";
+import Link from "next/link";
+import { ClosingImage } from "@/components/HeroImage";
+import { ContextPipeline } from "@/components/ContextPipeline";
+import { HomeHero } from "@/components/HomeHero";
+import { OpenSourceInstall } from "@/components/OpenSourceInstall";
+import { ProductCapabilities } from "@/components/ProductCapabilities";
+import { ResearchTeaser } from "@/components/ResearchTeaser";
+import { ResultsSpread } from "@/components/ResultsSpread";
+import { SiteFooter } from "@/components/SiteFooter";
export default function Home() {
return (
<>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
Delphi · Synthetic Sciences
+
+ {/* Was clamp(2.3rem,4.6vw,3.9rem), which rendered at 62px and
+ ran three lines across the middle of the archive plate. */}
+
+ Give the agent the evidence. Keep the record.
+
+
+
+ Get started
+
+
+ Research blog
+
+
+
+
+
-
+
>
);
}
diff --git a/landing/src/app/preview/page.tsx b/landing/src/app/preview/page.tsx
deleted file mode 100644
index 4fb71c6..0000000
--- a/landing/src/app/preview/page.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import Image from "next/image";
-import Link from "next/link";
-import { Nav } from "@/components/Nav";
-
-const VARIANTS = [
- {
- n: "01",
- key: "sacred-way",
- name: "The Sacred Way",
- blurb:
- "A lone scholar climbs the Sacred Way of Delphi, the Temple of Apollo silhouetted on Mount Parnassus.",
- },
- {
- n: "02",
- key: "pythia",
- name: "The Pythia",
- blurb:
- "The Pythia on her bronze tripod in the inner sanctum of the Temple of Apollo, vapours rising from the fissure below.",
- },
- {
- n: "03",
- key: "omphalos",
- name: "The Omphalos",
- blurb:
- "The sacred navel-stone of Delphi alone in the cella, a robed figure approaching through deep silence.",
- },
- {
- n: "04",
- key: "archive",
- name: "The Archive",
- blurb:
- "A vast Greek archive of scrolls and codices. A single scribe reads at the centre.",
- },
-];
-
-export default function PreviewPage() {
- return (
- <>
-
-
-
-
- ← back to /
-
-
- Hero candidates.
-
-
- Each scene was generated in both dark and light polarity. Use
- the theme toggle in the nav to compare.
-
- We maintain a small open benchmark, the{" "}
-
- SynsciContextBench
-
- . Eleven phases (code retrieval, multi-hop, SWE-Agent
- code generation, diff-aware re-indexing, real-session
- replay, and more) against a fixed corpus of two popular
- Python libraries and their documentation. Same index,
- same embeddings, same queries. The numbers below are
- from the last full run.
-
-
-
-
-
- Metric
- Delphi
- Next best
-
- {ROWS.map((r) => (
-
- {r.metric}
-
- {r.delphi}
-
-
- {r.other}
-
-
- ))}
-
-
-
- Reproduce: git clone github.com/synthetic-sciences/SynsciContextBench && make bench.
-
- );
-}
diff --git a/landing/src/components/ContextPipeline.tsx b/landing/src/components/ContextPipeline.tsx
new file mode 100644
index 0000000..909b407
--- /dev/null
+++ b/landing/src/components/ContextPipeline.tsx
@@ -0,0 +1,64 @@
+import { PIPELINE } from "@/lib/evidence";
+
+export function ContextPipeline() {
+ return (
+
+
+
+
+
The system
+
+ Retrieval is one stage. The agent experiences all five.
+
+
+ A context engine has to preserve source identity, recover the
+ right evidence, fit it into a bounded window, and make every
+ result inspectable. Optimizing only the search score misses the
+ product.
+
+
+ Baselines are the benchmark's own published runs on the same split
+ and candidate filter, scored by the same code. Gold marks the leader in
+ each column. grep still holds Recall@20.
+
+
+ );
+}
+
+export function AblationFigure() {
+ const peak = Math.max(...ABLATION.map((row) => row.recall20));
+
+ return (
+
+
+ What each change was worth
+ same {BENCHMARK.cases} cases
+
+
+ Recall@20 by configuration. The first row is what the previous
+ evaluation actually measured: a corpus indexed by one embedding model
+ and queried by another.
+
+
+ );
+}
+
+export function MismatchFigure() {
+ const { cosineBefore, cosineAfter, dimension, repositoriesAffected } =
+ EMBEDDING_MISMATCH;
+ return (
+
+
+ Self-retrieval check
+ {dimension}-dim both sides
+
+
+ Cosine between a chunk's stored vector and a fresh embedding of
+ that chunk's own exact content. A matched space returns ~1.0.
+ Because both models emit {dimension}-dimensional vectors, pgvector
+ accepted the comparison and {repositoriesAffected} repositories scored
+ as noise without raising anything.
+
+
+ );
+}
+
+export function RerankerFigure() {
+ return (
+
+
+ Cross-encoder selection
+ quality and latency together
+
+
+ The 22M-parameter model beats the 278M code-aware one on every metric
+ while running roughly seven times faster, so the larger model cost
+ latency and quality at once.
+
+
+ );
+}
+
+export function PipelineFigure() {
+ return (
+
+
+ Context pipeline
+ index → act
+
+
+ {PIPELINE.map(([index, stage, detail]) => (
+
+ {index}
+ {stage}
+
{detail}
+
+ ))}
+
+
+ Retrieval is one stage of five. A file found at rank 40 is not context.
+
+
+ );
+}
diff --git a/landing/src/components/Footer.tsx b/landing/src/components/Footer.tsx
deleted file mode 100644
index b2029c1..0000000
--- a/landing/src/components/Footer.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import Link from "next/link";
-import { Container } from "./Container";
-
-export function Footer() {
- return (
-
- );
-}
diff --git a/landing/src/components/HeadlineResults.tsx b/landing/src/components/HeadlineResults.tsx
new file mode 100644
index 0000000..963899e
--- /dev/null
+++ b/landing/src/components/HeadlineResults.tsx
@@ -0,0 +1,111 @@
+import { HEAD_TO_HEAD, INTERVALS } from "@/lib/evidence";
+
+/* The table the piece opens on. Every row states the comparator, the shipped
+ * build, and whether the sample can tell them apart, so a reader can see what
+ * is claimed and what is not without reading the methods first. */
+
+const ROWS = INTERVALS.map((interval) => {
+ const key =
+ interval.metric === "MRR"
+ ? ("mrr" as const)
+ : interval.metric === "Recall@5"
+ ? ("recall5" as const)
+ : interval.metric === "Recall@20"
+ ? ("recall20" as const)
+ : ("bcy8k" as const);
+ return {
+ metric: interval.metric,
+ nia: HEAD_TO_HEAD.nia[key],
+ delphi: HEAD_TO_HEAD.delphi[key],
+ diff: interval.diff,
+ lo: interval.lo,
+ hi: interval.hi,
+ resolved: interval.resolved,
+ };
+});
+
+export function HeadlineResults() {
+ return (
+
+
+ Shipped build against the hosted comparator
+
+ {HEAD_TO_HEAD.cases} paired cases · 95% interval
+
+
+
+
+ Paired per case over the {HEAD_TO_HEAD.cases}{" "}
+ final-split cases the comparator completed without error, 4000
+ bootstrap resamples. A
+ difference counts only when its interval excludes zero, which here is
+ Recall@20 and latency. Both engines indexed the same corpora and were
+ scored by the benchmark's own code.
+
+
+ );
+}
diff --git a/landing/src/components/Hero.tsx b/landing/src/components/Hero.tsx
deleted file mode 100644
index dd2c3d8..0000000
--- a/landing/src/components/Hero.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import Link from "next/link";
-import { Container } from "./Container";
-
-export function Hero() {
- return (
-
-
-
- Delphi.
-
-
-
- An open-source MCP context engine.
-
-
-
- Index code, docs, papers, and datasets. Serve your agents the
- right context, ranked. Built for the kind of research where
- retrieval is half the answer.
-
-
-
-
- Get started
-
-
- github.com/synthetic-sciences/delphi →
-
-
-
-
- );
-}
diff --git a/landing/src/components/HeroImage.tsx b/landing/src/components/HeroImage.tsx
index 0d23a20..f602948 100644
--- a/landing/src/components/HeroImage.tsx
+++ b/landing/src/components/HeroImage.tsx
@@ -14,86 +14,77 @@ const ALT: Record = {
"A vast Greek archive of scrolls and codices, a scribe reading by lamplight.",
};
-function ThemedImage({
+// The masters are 1408x768. Declaring that ratio lets "natural" mode lay the
+// plate out at its own proportions instead of cropping it to whatever box it
+// lands in.
+const MASTER_W = 1408;
+const MASTER_H = 768;
+
+export function ThemedImage({
variant,
priority = false,
+ fit = "cover",
}: {
variant: Variant;
priority?: boolean;
+ /** "cover" fills its container and crops. "natural" keeps the plate's own
+ * aspect ratio, so nothing is cut off the top or bottom of the engraving. */
+ fit?: "cover" | "natural";
}) {
// Both variants are rendered into the DOM. CSS shows the matching theme.
// The light variant is a duotone derived from the dark master, so the
// two PNGs are pixel-perfect aligned — no content shift on toggle.
+ const sizing =
+ fit === "natural"
+ ? { width: MASTER_W, height: MASTER_H, box: "h-auto w-full" }
+ : { fill: true as const, box: "object-cover object-center" };
+ const { box, ...dimensions } = sizing;
+
return (
<>
>
);
}
-export function HeroImage({
- variant = "sacred-way",
-}: {
- variant?: Variant;
-}) {
- return (
-
-
-
- );
-}
-
export function ClosingImage({
variant = "archive",
+ children,
}: {
variant?: Variant;
+ children?: ReactNode;
}) {
return (
-
+
+ {/* A flat 58% wash over the whole plate used to be the only overlay,
+ which muddied the engraving and still left the headline sitting on
+ the busiest part of it. Now the wash is light, and a bottom scrim
+ carries the copy band to solid instead. */}
+
+
+ {children && (
+
+ {children}
+
+ )}
);
}
diff --git a/landing/src/components/HomeHero.tsx b/landing/src/components/HomeHero.tsx
new file mode 100644
index 0000000..4236c66
--- /dev/null
+++ b/landing/src/components/HomeHero.tsx
@@ -0,0 +1,73 @@
+import Link from "next/link";
+import { HEAD_TO_HEAD } from "@/lib/evidence";
+import { InstallChip } from "./InstallChip";
+import { SiteNav } from "./SiteNav";
+import { ThemedImage } from "./HeroImage";
+
+/* The plate runs full bleed and is never faded or cropped away: the engraving
+ * is the page, not a backdrop for it. Legibility comes from darkening only the
+ * two corners the type occupies, which leaves the temple and the Sacred Way
+ * untouched in the middle. Wordmark top left, everything you can act on
+ * gathered bottom right. */
+export function HomeHero() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ delphi
+
+
+ by Synthetic Sciences
+
+
+
+
+
+ Give your coding agent the right files.
+
+
+
+ Delphi indexes your repositories, docs, and papers, then answers an
+ agent's question with the code that actually answers it. Runs
+ on your own machine.
+
+
+
+
+ Install Delphi →
+
+
+ Star on GitHub
+
+
+
+
+
+ {HEAD_TO_HEAD.delphi.recall20.toFixed(3)} recall@20 ·{" "}
+ {HEAD_TO_HEAD.latencyRatio.toFixed(1)}× faster than the hosted
+ comparator · {HEAD_TO_HEAD.cases} shared cases
+
- Delphi is FastAPI in front of Postgres with{" "}
- pgvector{" "}
- for embeddings and tree-sitter for symbol extraction. The
- retrieval side is a hybrid: dense vectors, BM25 over text,
- trigram for fuzzy identifiers, and an exact-symbol channel.
- Results are fused per branch, then diversified at the file level
- without discarding the ranked candidate pool. Deployments can
- optionally add a cross-encoder re-rank.
-
-
- Documents are chunked by heading where headings exist, by
- symbol where they don't. The chunker preserves enclosing
- class and linked tests, so a hit comes back with the context
- an agent actually needs to act on it.
-
-
- For longer work, Delphi exposes an async research surface
- over server-sent events. Agents POST a question, get a session
- id, then stream iteration events as the engine searches,
- reads, and decides.
-
-
-
-
- );
-}
diff --git a/landing/src/components/InstallChip.tsx b/landing/src/components/InstallChip.tsx
new file mode 100644
index 0000000..e921c56
--- /dev/null
+++ b/landing/src/components/InstallChip.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { useState } from "react";
+
+const COMMAND = "npm i -g @synsci/delphi";
+
+/* Click-to-copy install command, sat next to the hero buttons. The label
+ * swaps to a tick for a moment on success and says nothing on failure, since
+ * a clipboard permission prompt is not the visitor's problem to solve. */
+export function InstallChip({ className = "" }: { className?: string }) {
+ const [copied, setCopied] = useState(false);
+
+ return (
+
+ );
+}
diff --git a/landing/src/components/InstallCommand.tsx b/landing/src/components/InstallCommand.tsx
new file mode 100644
index 0000000..39dfa7d
--- /dev/null
+++ b/landing/src/components/InstallCommand.tsx
@@ -0,0 +1,17 @@
+export function InstallCommand({
+ compact = false,
+}: {
+ compact?: boolean;
+}) {
+ return (
+
+ $
+ npx @synsci/delphi
+
+ );
+}
diff --git a/landing/src/components/Nav.tsx b/landing/src/components/Nav.tsx
deleted file mode 100644
index 6d5be03..0000000
--- a/landing/src/components/Nav.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import Link from "next/link";
-import { ThemeToggle } from "./ThemeToggle";
-
-export function Nav() {
- return (
-
-
- Delphi runs on your laptop, in your VPC, or anywhere Docker
- runs. Your index lives on your hardware. No telemetry, no
- auth wall, no rate limit.
-
-
-
- );
-}
diff --git a/landing/src/components/OpenSourceInstall.tsx b/landing/src/components/OpenSourceInstall.tsx
new file mode 100644
index 0000000..aa63163
--- /dev/null
+++ b/landing/src/components/OpenSourceInstall.tsx
@@ -0,0 +1,43 @@
+import Link from "next/link";
+import { InstallCommand } from "./InstallCommand";
+
+export function OpenSourceInstall() {
+ return (
+
+
+
+
Apache 2.0 · Self-hosted
+
+ Your index. Your source. Your deployment.
+
+
+ Run it on your laptop or inside your own infrastructure. Local
+ sentence-transformers need no API key. Point it at hosted
+ embeddings when you want them.
+
+
+ );
+}
diff --git a/landing/src/components/ProductCapabilities.tsx b/landing/src/components/ProductCapabilities.tsx
new file mode 100644
index 0000000..a3f4a25
--- /dev/null
+++ b/landing/src/components/ProductCapabilities.tsx
@@ -0,0 +1,100 @@
+/* What the product is and what it does, stated in terms of the operations it
+ * actually exposes. This replaced a timeline of the project's own history,
+ * which told a visitor nothing about whether the thing was worth installing. */
+
+const SOURCES = [
+ ["Repositories", "Cloned at a pinned commit, parsed for symbols, re-checked against the remote"],
+ ["Documentation", "Crawled sites and hosted docs, chunked with their headings intact"],
+ ["Papers", "arXiv and PDF ingest, with equations, citations, and code blocks pulled out"],
+ ["Datasets", "HuggingFace dataset cards and their configs"],
+] as const;
+
+const CAPABILITIES = [
+ {
+ title: "Ask in the agent you already use",
+ body: "89 tools over MCP, split into profiles so a coding agent is not handed the paper tooling by mistake. The same operations are on an HTTP API and a CLI.",
+ calls: ["search_code", "search_symbols", "get_file"],
+ },
+ {
+ title: "Get the file, not a link to it",
+ body: "Results come back as ranked chunks with the retrieval branch that found each one attached, so a bad answer can be traced to the branch that produced it.",
+ calls: ["build_context_pack", "get_context"],
+ },
+ {
+ title: "Follow the code, not just the text",
+ body: "Symbols are resolved into a call graph at index time. You can ask who calls a function and what breaks if you change it without another search.",
+ calls: ["find_callers", "find_callees", "impact_analysis"],
+ },
+ {
+ title: "Know when the index is stale",
+ body: "Every source records the commit or fetch it came from. Freshness is a question you can ask rather than something you assume.",
+ calls: ["check_freshness", "quick_index"],
+ },
+] as const;
+
+export function ProductCapabilities() {
+ return (
+
+
+
+
+
What it does
+
+ Four kinds of source. One index. One question.
+
+
+
+
+ An agent working on real code needs the repository, the library
+ docs, and sometimes the paper the algorithm came from. Delphi puts
+ all four in one index so a single question can be answered from
+ whichever of them holds the answer.
+
+
+ );
+}
diff --git a/landing/src/components/Quickstart.tsx b/landing/src/components/Quickstart.tsx
deleted file mode 100644
index efb0404..0000000
--- a/landing/src/components/Quickstart.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Container } from "./Container";
-
-const CODE = `# install + run locally
-$ npx @synsci/delphi
-
-# index a repository, auto-discovering its docs
-$ delphi index github.com/fastapi/fastapi --auto-docs
-
-# the MCP config for Claude Code / Cursor / Windsurf
-# was written automatically. restart your agent.`;
-
-export function Quickstart() {
- return (
-
-
-
- Quickstart.
-
-
-
-
- One command. No accounts, no API keys, no cloud. The installer
- writes the MCP config for every agent it finds on your machine
- and brings up the local index.
-
+ {BENCHMARK.cases} cases from {BENCHMARK.name}, scored by the
+ benchmark's own code. Delphi beats every published baseline
+ on MRR and Recall@5, and beats the hosted comparator on coverage
+ at {HEAD_TO_HEAD.latencyRatio.toFixed(1)}x lower latency. The
+ comparator still ranks the top of the list better.
+
+ Against the hosted comparator on {HEAD_TO_HEAD.cases} paired cases,
+ one difference resolves: Recall@20, by{" "}
+ {INTERVALS.find((row) => row.metric === "Recall@20")!.diff.toFixed(3)}.
+ MRR reads {HEAD_TO_HEAD.delphi.mrr.toFixed(3)} against{" "}
+ {HEAD_TO_HEAD.nia.mrr.toFixed(3)}, but its interval spans zero, so
+ we do not report it as a loss any more than as a win.
+
+
+ Held out: {HELD_OUT.scored} cases, {HELD_OUT.failures} failures,{" "}
+ {HELD_OUT.mrr.toFixed(3)} MRR, ahead of the split it was tuned on.
+ grep still holds Recall@20 among the published baselines.
+
+
+
+
+ );
+}
diff --git a/landing/src/components/ScopeStatement.tsx b/landing/src/components/ScopeStatement.tsx
new file mode 100644
index 0000000..c2b7054
--- /dev/null
+++ b/landing/src/components/ScopeStatement.tsx
@@ -0,0 +1,29 @@
+import { DOWNSTREAM, HEAD_TO_HEAD, INTERVALS } from "@/lib/evidence";
+
+const RECALL20 = INTERVALS.find((row) => row.metric === "Recall@20")!;
+
+/* The claim we can defend, and the one we cannot. This block used to say
+ * Delphi was state of the art on the strength of a 40-task DS-1000 pilot.
+ * That pilot is a null result at 100 tasks, and the hosted comparator leads
+ * MRR, so the claim went with the evidence. */
+export function ScopeStatement() {
+ return (
+
+ );
+}
diff --git a/landing/src/components/SiteFooter.tsx b/landing/src/components/SiteFooter.tsx
new file mode 100644
index 0000000..9493df4
--- /dev/null
+++ b/landing/src/components/SiteFooter.tsx
@@ -0,0 +1,91 @@
+import Link from "next/link";
+
+const REPO = "https://github.com/synthetic-sciences/delphi";
+
+const COLUMNS = [
+ {
+ heading: "Project",
+ links: [
+ ["GitHub", REPO],
+ ["npm", "https://www.npmjs.com/package/@synsci/delphi"],
+ ["Releases", `${REPO}/releases`],
+ ],
+ },
+ {
+ heading: "Resources",
+ links: [
+ ["Docs", `${REPO}#quick-start`],
+ ["Benchmarks", "/blog/context-engine-is-the-product"],
+ ["Install", "/#install"],
+ ["Apache 2.0", `${REPO}/blob/master/LICENSE`],
+ ],
+ },
+ {
+ heading: "Company",
+ links: [
+ ["Synthetic Sciences ↗", "https://syntheticsciences.ai"],
+ ["Atlas ↗", "https://tryatlas.sh"],
+ ["OpenScience ↗", "https://openscience.sh"],
+ ],
+ },
+] as const;
+
+export function SiteFooter() {
+ return (
+
+ );
+}
diff --git a/landing/src/components/SiteNav.tsx b/landing/src/components/SiteNav.tsx
new file mode 100644
index 0000000..c7cb80c
--- /dev/null
+++ b/landing/src/components/SiteNav.tsx
@@ -0,0 +1,96 @@
+import Link from "next/link";
+import { ThemeToggle } from "./ThemeToggle";
+
+export function SiteNav({
+ mode = "solid",
+ showWordmark = true,
+}: {
+ mode?: "overlay" | "solid";
+ /** The home hero sets the wordmark at full size beneath the bar, so the bar
+ * itself carries only the links there. Everywhere else it needs the mark. */
+ showWordmark?: boolean;
+}) {
+ const overlay = mode === "overlay";
+
+ return (
+
+
- Most retrieval engines specialise. They are good at code,
- or good at docs, or good at semantic search over PDFs.
- Delphi is built to hold all four{" "}
- in the same index. Code,
- documentation, papers, and datasets, so an agent can answer
- a single question by reaching across them.
-
-
- It speaks Model Context Protocol{" "}
- and plain HTTP. Plug it into Claude Code, Cursor, Windsurf,
- or any agent that grounds its answers in source material.
-
-
-
-
- );
-}
diff --git a/landing/src/lib/evidence.ts b/landing/src/lib/evidence.ts
new file mode 100644
index 0000000..5a35470
--- /dev/null
+++ b/landing/src/lib/evidence.ts
@@ -0,0 +1,201 @@
+/* Every figure here traces to a committed run artifact under
+ * new/delphi-evaluation-2026-07-29-round2/artifacts/. Nothing is carried over
+ * from an earlier evaluation, and the partial scopes are stated as partial. */
+
+export const BENCHMARK = {
+ name: "Agent Retrieval Bench v2",
+ commit: "d04953371d962ec314fb15d642255ed4e9dadd40",
+ repository: "eyuansu62/agent-retrieval-bench",
+ split: "development",
+ cases: 75,
+ workflows: ["code2test", "comment2context", "edit2ripple", "trace2code"],
+ candidateFilter: "all_files",
+ embeddingModel: "text-embedding-3-small",
+ reranker: "cross-encoder/ms-marco-MiniLM-L-6-v2",
+} as const;
+
+/* Same 75 cases, same candidate filter, same metric implementation. The
+ * baselines are ARB's own published runs scored by ARB's own code; Nia was
+ * re-run live against the same corpora, 0 failures.
+ *
+ * Every row here is the 75-case development split, because that is the split
+ * ARB's published baselines were run on and mixing sample sizes inside one
+ * table would not be a comparison. The Delphi-versus-Nia question is answered
+ * separately in HEAD_TO_HEAD, which pools 135 cases — a 0.02 difference cannot
+ * be resolved at n=75 when run-to-run variance is itself about 0.02. */
+export const RETRIEVAL_COMPARISON = [
+ { system: "Delphi", mrr: 0.220, recall5: 0.369, recall20: 0.551, latencyMs: 5694, ours: true },
+ { system: "Nia", mrr: 0.228, recall5: 0.391, recall20: 0.449, latencyMs: 36881, ours: false },
+ { system: "grep", mrr: 0.180, recall5: 0.302, recall20: 0.578, latencyMs: null, ours: false },
+ { system: "RepoMap", mrr: 0.169, recall5: 0.240, recall20: 0.551, latencyMs: null, ours: false },
+ { system: "lexical", mrr: 0.127, recall5: 0.198, recall20: 0.451, latencyMs: null, ours: false },
+ { system: "BM25", mrr: 0.116, recall5: 0.136, recall20: 0.429, latencyMs: null, ours: false },
+] as const;
+
+/* The hosted comparison, on the configuration Delphi actually ships.
+ *
+ * An earlier version of this block reported 135 cases at 0.229 MRR against
+ * 0.261. That run had query expansion and listwise reranking switched off,
+ * which are the two stages that decide the order of the head of the list, so
+ * it measured a build nobody runs. These numbers come from delphi-4o-final
+ * (HyDE + gpt-4o listwise, the shipped defaults) paired case by case against
+ * nia-final60, on the 60 final-split cases the comparator completed without
+ * error. Both engines saw the same corpora and were scored by ARB's own code.
+ *
+ * Nothing here is called a win unless its interval says so. See INTERVALS. */
+export const HEAD_TO_HEAD = {
+ comparator: "Nia",
+ cases: 60,
+ failures: 0,
+ delphi: { mrr: 0.241, recall5: 0.326, recall20: 0.499, bcy8k: 0.362, latencyMs: 5571, meanPaths: 20.0 },
+ nia: { mrr: 0.301, recall5: 0.321, recall20: 0.392, bcy8k: 0.344, latencyMs: 31774, meanPaths: 8.1 },
+ latencyRatio: 5.7,
+} as const;
+
+/* Paired bootstrap over the 60 shared cases, 4000 resamples, 95% interval on
+ * the per-case difference. Only Recall@20 clears zero. The MRR difference
+ * looks like a loss and is not one: at this sample size the interval spans
+ * both outcomes, and the 220-case comparator run failed every query, so there
+ * is no larger paired sample to settle it with yet. */
+export const INTERVALS = [
+ { metric: "MRR", diff: -0.060, lo: -0.142, hi: 0.020, wins: 18, ties: 18, losses: 24, resolved: false },
+ { metric: "Recall@5", diff: 0.005, lo: -0.097, hi: 0.108, wins: 12, ties: 37, losses: 11, resolved: false },
+ { metric: "Recall@20", diff: 0.107, lo: 0.008, hi: 0.207, wins: 16, ties: 38, losses: 6, resolved: true },
+ { metric: "BCY@8k", diff: 0.019, lo: -0.081, hi: 0.119, wins: 12, ties: 38, losses: 10, resolved: false },
+] as const;
+
+/* What each change was worth, measured one at a time on the same split.
+ * The first row is the configuration the previous evaluation actually ran. */
+export const ABLATION = [
+ {
+ label: "As previously benchmarked",
+ note: "query and index in different embedding spaces",
+ mrr: 0.055,
+ recall5: 0.056,
+ recall20: 0.161,
+ latencyMs: 1000,
+ },
+ {
+ label: "Embedding space aligned",
+ note: "same model indexing and querying",
+ mrr: 0.173,
+ recall5: 0.259,
+ recall20: 0.549,
+ latencyMs: 917,
+ },
+ {
+ label: "+ rank fusion, path affinity",
+ note: "reciprocal rank instead of rescaled scores",
+ mrr: 0.176,
+ recall5: 0.256,
+ recall20: 0.552,
+ latencyMs: 1089,
+ },
+ {
+ label: "+ cross-encoder rerank",
+ note: "ms-marco-MiniLM over the top 30",
+ mrr: 0.193,
+ recall5: 0.294,
+ recall20: 0.560,
+ latencyMs: 2903,
+ },
+] as const;
+
+/* The bug the whole investigation turned on. Embedding a chunk's own exact
+ * content and comparing it against that chunk's stored vector: a matched
+ * space returns ~1.0, and two same-width spaces from different models return
+ * noise without raising anything. */
+export const EMBEDDING_MISMATCH = {
+ cosineBefore: 0.0101,
+ cosineAfter: 0.943,
+ dimension: 768,
+ indexedWith: "text-embedding-3-small",
+ queriedWith: "gemini-embedding-001",
+ repositoriesAffected: 68,
+} as const;
+
+/* Held-out confirmation, now at full scope: all 220 positive cases of the
+ * final split, every corpus provisioned, zero failed queries. These numbers
+ * come out slightly ahead of the development split the pipeline was tuned on,
+ * which is the direction you want — no sign of having fit the tuning set. */
+export const HELD_OUT = {
+ split: "final",
+ scored: 220,
+ skippedUnprovisioned: 0,
+ mrr: 0.309,
+ recall5: 0.442,
+ recall20: 0.582,
+ bcy8k: 0.467,
+ latencyMsMean: 5571,
+ failures: 0,
+} as const;
+
+/* Query expansion, measured on the full held-out split. A code index is
+ * written in code and questions about it are written in English; embedding a
+ * hypothetical snippet alongside the question bridges that. */
+export const QUERY_EXPANSION = [
+ { label: "Retrieval only", mrr: 0.228, recall5: 0.349, recall20: 0.552, latencyMs: 1957 },
+ { label: "+ hypothetical document", mrr: 0.241, recall5: 0.355, recall20: 0.579, latencyMs: 4110 },
+ { label: "+ listwise rerank", mrr: 0.309, recall5: 0.442, recall20: 0.582, latencyMs: 5571 },
+] as const;
+
+/* Per-workflow on the held-out split. The spread is the interesting part:
+ * a failure trace names symbols that exist in the code, and a review comment
+ * names almost nothing a retriever can key on. */
+export const HELD_OUT_WORKFLOWS = [
+ { workflow: "trace2code", cases: 38, mrr: 0.693, recall5: 0.842, recall20: 0.908 },
+ { workflow: "edit2ripple", cases: 44, mrr: 0.274, recall5: 0.434, recall20: 0.587 },
+ { workflow: "code2test", cases: 83, mrr: 0.220, recall5: 0.394, recall20: 0.586 },
+ { workflow: "comment2context", cases: 55, mrr: 0.207, recall5: 0.245, recall20: 0.345 },
+] as const;
+
+/* Reranker selection. The larger, code-aware model lost on every axis. */
+export const RERANKER_CHOICE = [
+ { model: "none", params: "—", mrr: 0.176, recall5: 0.256, recall20: 0.552, latencyMs: 1000 },
+ { model: "bge-reranker-base", params: "278M", mrr: 0.173, recall5: 0.288, recall20: 0.515, latencyMs: 21281 },
+ { model: "ms-marco-MiniLM-L-6", params: "22M", mrr: 0.193, recall5: 0.294, recall20: 0.560, latencyMs: 2903 },
+] as const;
+
+/* DS-1000, and the reason it is reported as a null result rather than a win.
+ *
+ * On 40 development tasks Delphi reached 0.900 against 0.875 for both hosted
+ * engines, which looks like a downstream lead. On 100 held-out tasks the same
+ * comparison against a no-retrieval control comes out at 0.860 to 0.870, with
+ * 95 of 100 cases decided identically. Documentation context does not change
+ * what this model produces on this benchmark, in either direction, and the
+ * 40-case spread was one or two tasks of noise.
+ *
+ * Published because a benchmark that cannot separate the conditions is worth
+ * saying out loud, especially when the smaller slice of it flatters us. */
+export const DOWNSTREAM = {
+ benchmark: "DS-1000",
+ tasks: 100,
+ model: "Claude Opus 5",
+ verdict: "no measurable effect",
+ rows: [
+ { condition: "No retrieval", passAtOne: 0.870, ours: false },
+ { condition: "Delphi", passAtOne: 0.860, ours: true },
+ ],
+ pairedDelphiOnlyPasses: 2,
+ pairedControlOnlyPasses: 3,
+ pairedIdentical: 95,
+} as const;
+
+export const ARTICLE = {
+ slug: "context-engine-is-the-product",
+ href: "/blog/context-engine-is-the-product",
+ title: "The context engine is the product",
+ dek: "Our benchmark was comparing vectors from two different embedding models. Here is how we found it, and what fixing it was actually worth.",
+ published: "July 30, 2026",
+ publishedIso: "2026-07-30",
+ readingTime: "12 min read",
+ labels: ["Evaluation", "Retrieval", "Developer agents"],
+} as const;
+
+export const PIPELINE = [
+ ["01", "Index", "Immutable source versions"],
+ ["02", "Retrieve", "Semantic, lexical, symbol, path, structural"],
+ ["03", "Fuse", "Rank and diversify evidence"],
+ ["04", "Assemble", "Token-bounded context with provenance"],
+ ["05", "Act", "Write and verify the change"],
+] as const;
diff --git a/landing/src/lib/traces.json b/landing/src/lib/traces.json
new file mode 100644
index 0000000..e9192c0
--- /dev/null
+++ b/landing/src/lib/traces.json
@@ -0,0 +1,1535 @@
+[
+ {
+ "baseCommit": "7ab761246cc449ddfc630001fb2caff160eb4ee3",
+ "goldFiles": [
+ "tests/e2e/etcd_grpcproxy_test.go"
+ ],
+ "latencyMs": 1837.9,
+ "query": "{\"changed_file_summary\": \"1 implementation files and 1 existing test files changed within 2 total files.\", \"implementation_file_count\": 1, \"implementation_files\": [\"server/etcdmain/grpc_proxy.go\"], \"pr_body\": \"This adds the min and max TLS version support from #13506 and #15156 to the grpc proxy.\\n\\nFixes #13506\\n\\n\\nPlease read https://github.com/etcd-io/etcd/blob/main/CONTRIBUTING.md#contribution-flow.\", \"pr_title\": \"add tls min/max version to grpc proxy\"}",
+ "ranked": [
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "CHANGELOG/CHANGELOG-3.2.md",
+ "score": 0.5247
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "tests/integration/v3_tls_test.go",
+ "score": 0.5206
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "CHANGELOG/CHANGELOG-3.5.md",
+ "score": 0.339
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "CHANGELOG/CHANGELOG-3.6.md",
+ "score": 0.3337
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "CHANGELOG/CHANGELOG-3.4.md",
+ "score": 0.3183
+ },
+ {
+ "branches": [
+ "bm25",
+ "path",
+ "path_affinity",
+ "vector"
+ ],
+ "path": "server/etcdmain/grpc_proxy.go",
+ "score": 0.312
+ },
+ {
+ "branches": [
+ "path",
+ "path_affinity",
+ "vector"
+ ],
+ "path": "server/proxy/grpcproxy/doc.go",
+ "score": 0.3054
+ },
+ {
+ "branches": [
+ "bm25",
+ "path",
+ "path_affinity",
+ "vector"
+ ],
+ "path": ".github/workflows/grpcproxy.yaml",
+ "score": 0.3042
+ },
+ {
+ "branches": [
+ "path",
+ "vector"
+ ],
+ "path": "tests/e2e/etcd_grpcproxy_test.go",
+ "score": 0.2797
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "CHANGELOG/CHANGELOG-4.0.md",
+ "score": 0.2779
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "CHANGELOG/CHANGELOG-3.1.md",
+ "score": 0.2668
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "go.mod",
+ "score": 0.2664
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "CHANGELOG/CHANGELOG-3.3.md",
+ "score": 0.2653
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "etcdutl/go.mod",
+ "score": 0.2653
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "server/go.mod",
+ "score": 0.2602
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "tests/go.mod",
+ "score": 0.2571
+ },
+ {
+ "branches": [
+ "bm25",
+ "path_affinity",
+ "vector"
+ ],
+ "path": "server/proxy/tcpproxy/doc.go",
+ "score": 0.2564
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "hack/tls-setup/Procfile",
+ "score": 0.2563
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "tests/framework/e2e/cluster_proxy.go",
+ "score": 0.252
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "api/versionpb/version.proto",
+ "score": 0.2516
+ }
+ ],
+ "raw": {
+ "hybrid": {
+ "candidates": 763,
+ "sources_hit": {
+ "bm25": 300,
+ "path": 255,
+ "symbol": 21,
+ "trigram": 0,
+ "vector": 300
+ }
+ },
+ "quality_mode": "agent",
+ "search_time_ms": 1822.744607925415,
+ "timing": {
+ "db_search_ms": 359.9,
+ "embedding_ms": 664.8,
+ "pipeline_ms": 798.0
+ },
+ "warnings": null
+ },
+ "repo": "etcd-io/etcd",
+ "sampleId": "120f8ebe03b4feba2ac74c51",
+ "workflow": "code2test"
+ },
+ {
+ "baseCommit": "9f4f217153d1d289d17f267eaed65980aa0b1a9d",
+ "goldFiles": [
+ "tests/models/altclip/test_modeling_altclip.py",
+ "tests/test_modeling_common.py",
+ "tests/utils/test_core_model_loading.py"
+ ],
+ "latencyMs": 2523.7,
+ "query": "{\"changed_file\": \"src/transformers/core_model_loading.py\", \"pr_body\": \"# What does this PR do?\\n\\nAs per the title.\\n\\n## The issue\\n\\nThe problem is that transforms that want to remove a full part of a model name (such as a prefix, e.g. the `model.` start) are non bijective in general, i.e. we completely lose the information when they are dropped. So adding them back later when saving is impossible without runtime information about the checkpoint that was used, i.e. we need to know if we had the prefix before or not, we cannot infer it based on anything.\\n\\n## Proposed solution\\n\\nThis PR add a simple mechanism for such things, i.e. WeightTransform have a simple flag to describe if they were used to rename a weight or not. If it is the case, we keep them when we save the Transform on the model (this was already performed before). If not, we drop them, so that they are not used when resaving.\\nIt also introduces the `PrefixChange` class (a simple class inherited from `WeightRenaming`) to simplify full addition/removal of full parts, because otherwise the regexes to use in such cases are hard to read/write.\", \"pr_title\": \"[loading] Clean way to add/remove full parts in checkpoint names\"}",
+ "ranked": [
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "docs/source/en/weightconverter.md",
+ "score": 0.3366
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "utils/mlinter/rules.toml",
+ "score": 0.3033
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "docs/source/en/modeling_rules.md",
+ "score": 0.298
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "docs/source/en/add_new_model.md",
+ "score": 0.2948
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "docs/source/en/pr_checks.md",
+ "score": 0.2609
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "docs/source/en/models.md",
+ "score": 0.256
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "src/transformers/models/megatron_gpt2/checkpoint_reshaping_and_interoperability.py",
+ "score": 0.2515
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "src/transformers/models/hubert/convert_hubert_original_s3prl_checkpoint_to_pytorch.py",
+ "score": 0.2451
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "src/transformers/models/tapas/convert_tapas_original_tf_checkpoint_to_pytorch.py",
+ "score": 0.2404
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": ".github/PULL_REQUEST_TEMPLATE.md",
+ "score": 0.2301
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "src/transformers/models/bert/convert_bert_original_tf2_checkpoint_to_pytorch.py",
+ "score": 0.2291
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "docs/source/ja/pr_checks.md",
+ "score": 0.2237
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "src/transformers/models/wav2vec2/convert_wav2vec2_original_s3prl_checkpoint_to_pytorch.py",
+ "score": 0.2217
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "utils/modular_model_converter.py",
+ "score": 0.2195
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "src/transformers/models/mistral4/convert_mistral4_weight_to_hf.py",
+ "score": 0.2166
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "tests/repo_utils/test_mlinter.py",
+ "score": 0.2151
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "src/transformers/models/wavlm/convert_wavlm_original_s3prl_checkpoint_to_pytorch.py",
+ "score": 0.2084
+ },
+ {
+ "branches": [
+ "vector"
+ ],
+ "path": "docs/source/it/big_models.md",
+ "score": 0.2071
+ },
+ {
+ "branches": [
+ "path",
+ "vector"
+ ],
+ "path": "tests/utils/test_core_model_loading.py",
+ "score": 0.2055
+ },
+ {
+ "branches": [
+ "bm25",
+ "vector"
+ ],
+ "path": "MIGRATION_GUIDE_V5.md",
+ "score": 0.2035
+ }
+ ],
+ "raw": {
+ "hybrid": {
+ "candidates": 581,
+ "sources_hit": {
+ "bm25": 300,
+ "path": 17,
+ "symbol": 18,
+ "trigram": 1,
+ "vector": 300
+ }
+ },
+ "quality_mode": "agent",
+ "search_time_ms": 2510.296583175659,
+ "timing": {
+ "db_search_ms": 1100.4,
+ "embedding_ms": 555.0,
+ "pipeline_ms": 854.9
+ },
+ "warnings": null
+ },
+ "repo": "huggingface/transformers",
+ "sampleId": "12b84b0c5fa5ad2a5ec98099",
+ "workflow": "code2test"
+ },
+ {
+ "baseCommit": "a4d828f2d5ed85440bc0774eab342e6f9a5e5f62",
+ "goldFiles": [
+ "packages/create-vite/template-lit-ts/src/my-element.ts",
+ "packages/create-vite/template-lit/src/my-element.js"
+ ],
+ "latencyMs": 1475.0,
+ "query": "{\"diff_hunk_context\": \"@@ -22,6 +22,7 @@ function App() {\\n \\n