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 +
+
+
+ Workflow + n + MRR + R@5 + R@20 +
+ {HELD_OUT_WORKFLOWS.map((row) => ( +
+ {row.workflow} + + {row.cases} + + + {row.mrr.toFixed(3)} + + + {row.recall5.toFixed(3)} + + + {row.recall20.toFixed(3)} + +
+ ))} +
+
+ +

+ 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: +

+ +
+
+ Hypothetical-document expansion + held-out, {HELD_OUT.scored} cases +
+
+
+ Configuration + MRR + R@5 + R@20 + Latency +
+ {QUERY_EXPANSION.map((row) => ( +
+ {row.label} + + {row.mrr.toFixed(3)} + + + {row.recall5.toFixed(3)} + + + {row.recall20.toFixed(3)} + + + {(row.latencyMs / 1000).toFixed(1)}s + +
+ ))} +
+
+ +

+ 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. + 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. +
  2. +
+
+ +
+

References

+
    +
  1. + 1.{" "} + + Agent Retrieval Bench + + , commit {BENCHMARK.commit.slice(0, 12)}. +
  2. +
  3. + 2. Cormack, Clarke, and Buettcher, “Reciprocal Rank Fusion + Outperforms Condorcet and Individual Rank Learning Methods”, + SIGIR 2009. +
  4. +
  5. + 3. Synthetic Sciences,{" "} + + Delphi source and implementation documentation + + . +
  6. +
+
+
+
+
+ + + ); +} diff --git a/landing/src/app/blog/page.tsx b/landing/src/app/blog/page.tsx new file mode 100644 index 0000000..c33ed44 --- /dev/null +++ b/landing/src/app/blog/page.tsx @@ -0,0 +1,91 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { ARTICLE } from "@/lib/evidence"; +import { SiteFooter } from "@/components/SiteFooter"; +import { SiteNav } from "@/components/SiteNav"; + +export const metadata: Metadata = { + title: "Research blog · Delphi", + description: + "Field notes from Delphi on context engines, retrieval, evaluation, and developer agents.", + alternates: { + canonical: "https://trydelphi.ai/blog", + }, + openGraph: { + title: "Research blog · Delphi", + description: + "Field notes on context engines, retrieval, evaluation, and developer agents.", + url: "https://trydelphi.ai/blog", + }, +}; + +export default function BlogIndex() { + return ( + <> + +
+
+
+

+ Delphi research · field notes +

+

+ Work from the context layer. +

+

+ Experiments, failures, and engineering notes from building + context infrastructure for agents that have to finish real work. +

+
+
+ +
+
+
+
+

01

+

+ +
+ {ARTICLE.readingTime} +

+
+ +
+

+ + {ARTICLE.title} + +

+

+ {ARTICLE.dek} +

+ + Read field note → + +
+ +
+ {ARTICLE.labels.map((label) => ( + + {label} + + ))} +
+
+
+
+
+ + + ); +} 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 ( <> -