ContextNest vs Amazon Bedrock Knowledge Bases (vector + GraphRAG), v3.1
We compared ContextNest's search (ctx) with Amazon Bedrock Knowledge Bases, vector and GraphRAG, on four datasets: BEIR SciFact (300 queries), BEIR NFCorpus (323), MultiHop-RAG (529 with evidence) and VersionedDocs, a corpus of 180 Kubernetes documentation pages with their previous versions (541 questions, plus 155 paraphrases). The protocol was frozen before any Bedrock result; every later change is a dated amendment, and all of them are listed below. Both sides used the same embedder (Amazon Titan Text Embeddings v2), the same reranker (Cohere Rerank 3.5) and the same rerank budget. Answers were written by Claude Sonnet 4.6 and graded by one primary judge (Claude Opus 4.6) for every arm, checked by gpt-oss-120b; judges never saw the retrieved context. ctx with passage rerank scored higher NDCG@10 than Bedrock's best fair setup on SciFact, NFCorpus and VersionedDocs, each statistically significant. On MultiHop-RAG the two were level and the pre-registered parity rule was narrowly not met; answer accuracy there was 65.7% for ctx and 66.0% for Bedrock. Bedrock GraphRAG, at its best result count, trailed every other arm on retrieval and answered 39.0% of MultiHop-RAG questions correctly. With old copies stored next to new ones, Bedrock put a superseded version in the top 10 for 86% of VersionedDocs questions; ContextNest's versioned nest did so for none. SciFact and NFCorpus were used to choose ctx defaults, and MultiHop-RAG was used to fix ctx lexical scoring, so VersionedDocs is the only held-out comparison. Every ctx arm re-run so far on the released ctx 3.4.0 reproduced the release-candidate numbers exactly, with identical top-10 lists (keyword search on every dataset; hybrid on SciFact, NFCorpus and VersionedDocs; hybrid + passage rerank on SciFact). The remaining hybrid + rerank re-runs (NFCorpus, MultiHop-RAG, VersionedDocs) are in progress; any difference will be published as an update (A20). An audit found that only 278 of the 541 VersionedDocs questions describe a real change of fact (A21), so every VersionedDocs result is reported on both the full set and the validated subset; the conclusions hold on both, and on the validated subset Bedrock with rerank gave a stale answer to 17.4% of questions against 0.8% for ctx.
Released software. The engine changes this benchmark prompted shipped in @promptowl/contextnest-cli@3.4.0. Every ctx arm re-run so far on the released ctx 3.4.0 reproduced the release-candidate numbers exactly, with identical top-10 lists (keyword search on every dataset; hybrid on SciFact, NFCorpus and VersionedDocs; hybrid + passage rerank on SciFact). The remaining hybrid + rerank re-runs (NFCorpus, MultiHop-RAG, VersionedDocs) are in progress; any difference will be published as an update (A20). Before those changes (section 8), ctx 3.0.0 scored 0.604 NDCG@10 on SciFact.
- What changed since v2 and why
- Executive results
- Method
- Results: full tables
- GraphRAG analysis
- Governance results
- Cost
- What we fixed in ContextNest because of this
- Limitations
- Reproduce it
- Annex: the v2 report (2026-10-02), unchanged
1. What changed since v2 and why
The v2 report tested one question on 30 questions over 11 short documents. The reviewer's verdict was that it was "not a defensible conclusion". This table maps each request to what v3 does about it.
| Request | v2 (2026-10-02) | v3 (this paper) |
|---|---|---|
| "a defensible conclusion" | No pre-registration; small synthetic corpus; our own questions | Protocol frozen 2026-10-13 before any Bedrock result. Parity defined in advance. 21 dated amendments, each disclosed with the numbers it affects. All arms published, including the ones ctx loses. Raw per-query runs and the scripts that produce every table. |
| "more data" | 30 questions on 6 documents | 4 datasets: 300 + 323 + 529 + 541 retrieval queries, plus 155 paraphrases, over 5,183 / 3,633 / 609 / 180 (+180 previous) documents. Answer grading on 300 MultiHop-RAG and 250 VersionedDocs questions per arm. |
| "MRR, NDCG, mAP" | Hit rates with Wilson intervals | NDCG@10 (primary), MRR@10, MAP@10, Recall@20. Headline differences carry a 95% bootstrap CI and a paired randomization p-value. |
| "modern models" (no Amazon Nova) | Nova Pro for answers and GraphRAG extraction; Mistral Large 3 judge | No Nova anywhere. GraphRAG extraction: Claude Haiku 4.5. Answers: Claude Sonnet 4.6. Judges: Claude Opus 4.6 (primary), gpt-oss-120b (check). Reranker: Cohere Rerank 3.5. Embeddings: Titan Text Embeddings v2 (Bedrock Knowledge Bases rejected Cohere Embed v4; A16). |
| "at least parity" with Bedrock's GraphRAG | GraphRAG on single-fact lookups, where a graph does little work | GraphRAG on MultiHop-RAG, a published multi-hop benchmark and its intended case, and on VersionedDocs, at five result counts. Parity rule: the 95% CI of the per-query NDCG@10 difference (ctx − Bedrock) lies above −0.02. Also compared against Bedrock vector with rerank, which turned out to be Bedrock's strongest arm. |
| "the same LLM-as-a-judge for both" | One judge, from a third model family | One answer model and one primary judge for every arm. The judge sees the question, the gold answer and the system's answer, never the retrieved context. A second judge from another family gives Cohen's κ. |
| "other measurements to compare graphs" | None | Evidence-Complete@k (all gold evidence articles in the top k), Recall@20, distinct documents returned per query, Stale@10, indexing time and cost. |
2. Executive results
Retrieval: headline comparison per dataset (NDCG@10)
"Bedrock best" is the strongest fair Bedrock arm on that dataset: vector + Cohere Rerank 3.5 (B2) on the BEIR and MultiHop-RAG sets, and vector with a status=current metadata filter (B1-filter) on VersionedDocs, since B2 there returns superseded versions in 90% of top-10 lists. "ctx" is ctx hybrid + passage rerank (C3p).
| Dataset | Role for ctx | Bedrock best | ctx (C3p) | Difference [95% CI] | p | Parity rule | GraphRAG (best count) |
|---|---|---|---|---|---|---|---|
| SciFact | development (A7) | 0.734 | 0.770 | +0.035 [+0.015, +0.058] | 0.0008 | met; ctx better | not run |
| NFCorpus | development (A7) | 0.341 | 0.361 | +0.020 [+0.007, +0.033] | 0.0013 | met; ctx better | not run |
| MultiHop-RAG | dev for lexical (A8); C3p post-hoc (A17) | 0.795 | 0.787 | −0.008 [−0.025, +0.009] | 0.3482 | not met | 0.529 10 results |
| VersionedDocs | held-out | 0.827 | 0.902 | +0.075 [+0.047, +0.102] | 0.0000 | met; ctx better | 0.594 5 results |
| VersionedDocs, paraphrased | held-out | 0.723 | 0.770 | +0.048 [−0.001, +0.096] | 0.0620 | met; not significant | — |
| VersionedDocs, validated items (278) | held-out (A21) | 0.821 | 0.898 | +0.077 [+0.038, +0.117] | 0.0000 | met; ctx better | — |
| VersionedDocs, paraphrased, validated (91) | held-out (A21) | 0.742 | 0.790 | +0.048 [−0.022, +0.116] | 0.1924 | not met | — |
GraphRAG was not run on the BEIR sets (A9). p = paired randomization test, as reported by make_results.py (0.0000 means no permutation reached the observed difference). Source: results/final.md.
MultiHop-RAG, plainly: the lower CI bound is −0.025 and the rule needs it above −0.020, so parity is not established there. Neither is a Bedrock advantage (p = 0.3482). The answer-level result on the same dataset is a tie.
Answers (Claude Sonnet 4.6 answers; Claude Opus 4.6 judge)
| Dataset (sample) | Arm | Correct | Stale | Missing | Wrong |
|---|---|---|---|---|---|
| MultiHop-RAG (300) | Bedrock vector + rerank (B2) | 66.0% | 0.0% | 25.7% | 5.3% |
| ctx, engine passages (C3pass) | 65.7% | 0.0% | 25.7% | 5.3% | |
| Bedrock GraphRAG, 10 results | 39.0% | 0.0% | 54.7% | 3.7% | |
| VersionedDocs (250) | Nest feeding Bedrock (F1) | 74.4% | 1.2% | 14.8% | 8.8% |
| ctx, engine passages (C3pass) | 73.2% | 2.0% | 17.2% | 6.4% | |
| Bedrock vector + rerank (B2) | 64.0% | 13.6% | 5.6% | 4.8% | |
| Bedrock GraphRAG, 5 results | 54.8% | 12.0% | 20.4% | 6.4% |
Hedged answers make up the remainder of each row; full rows, including the superseded whole-document ctx arm, are in section 4. Judge agreement (Cohen's κ, primary vs check): 0.7462 on MultiHop-RAG, 0.8781 on VersionedDocs.
Stale versions (VersionedDocs, old and new copies both present)
| Arm | Stale@10 | Stale answers |
|---|---|---|
| Bedrock vector, no filter (B1) | 0.863 | not graded |
| Bedrock vector + rerank (B2) | 0.900 | 13.6% |
| Bedrock GraphRAG, 5 results | 0.708 | 12.0% |
Bedrock + status=current filter (B1-filter) | 0.000 | not graded |
| Nest feeding Bedrock (F1) | 0.000 | 1.2% |
| ctx, versioned nest (C3p / C3pass) | 0.000 | 2.0% |
| ctx, old copies stored as plain files (flat C1) | 0.861 | not graded |
Validated items only (A21). On the 278 questions whose answer really changed, the pattern is the same and the stale-answer gap is wider:
| Arm | Stale@10 | Correct answers | Stale answers |
|---|---|---|---|
| Bedrock vector, no filter (B1) | 0.903 | not graded | not graded |
| Bedrock vector + rerank (B2) | 0.946 | 56.8% | 17.4% |
| Bedrock GraphRAG, 5 results | 0.763 | 45.5% | 16.7% |
Bedrock + status=current filter (B1-filter) | 0.000 | not graded | not graded |
| Nest feeding Bedrock (F1) | 0.000 | 73.5% | 0.8% |
| ctx, versioned nest (C3p / C3pass) | 0.000 | 77.3% | 0.8% |
| ctx, old copies stored as plain files (flat C1) | 0.910 | not graded | not graded |
Answer rates are on the 132 questions of the 250-question answer sample that pass the audit; Stale@10 on all 278.
Stale@10 = share of questions whose top 10 contains the superseded version of the gold page. The flat ctx arm leaks like Bedrock does: the advantage comes from versioning, not from the search engine.
3. Method
Datasets
| Dataset | Docs | Queries scored | Relevance | Role for ctx |
|---|---|---|---|---|
| BEIR SciFact | 5,183 | 300 | binary | development set for ctx defaults (A7) |
| BEIR NFCorpus | 3,633 | 323 | graded | development set for ctx defaults (A7) |
| MultiHop-RAG | 609 | 529 (of a frozen 600-query sample, seed 13; 71 no-evidence queries excluded from retrieval, A1) | gold evidence list | development set for ctx lexical scoring (A8); passage rerank post-hoc (A17) |
| VersionedDocs | 180 current + 180 previous | 541, plus 155 paraphrases | gold page; gold and stale facts | held-out |
| BEIR FiQA-2018 | 57,638 | — | binary | pre-registered as held-out; not run (see Limitations) |
VersionedDocs. Built from public git history of kubernetes/website content/en/docs: current 51675a5 (2026-10-13) against previous fb49b3e (2024-10-12). 180 pages exist in both with a changed fact-bearing sentence (A2). A model generated 1,250 questions in two passes; a validator kept 541 (56.7% dropped), requiring that the gold fact appears only in the current page and the stale fact only in the previous one, and rejecting questions that copy the page's wording. 155 strict paraphrases (no 4+-letter word from the page except names) have a query-term overlap of 0.353 against 0.796 for their originals (A14). The model's questions are a known risk; see Limitations.
Question validity (A21). A 50-item spot-check found that many generated items do not test a real change: the "change" is rewording, a typo fix or a rename of the same thing, or the gold or stale fact does not answer the question. All 541 items were then audited by Claude Sonnet 4.6 against four criteria (natural single-answer question answered by the current page; gold fact answers it; stale fact is what the previous page said; the answer genuinely changed). 278 of 541 pass (91 of the 155 paraphrases). The audit agrees with the spot-check on 39 of 50 items overall and 44 of 50 on "changed"; the spot-check was also model-made, not a human hand-check. NDCG is defined by the page a question is about, so it stays meaningful on every item; the answer-level stale label is only meaningful on valid items. Every VersionedDocs table is reported on both sets.
Arms
| Arm | What it is |
|---|---|
| BM25 | Reference: BM25 with stemming. |
| B1 | Bedrock Knowledge Base, vector, S3 Vectors store, Titan v2 (1024-d), default chunking, no reranker. |
| B2 | B1 + Cohere Rerank 3.5 over its top 100 chunks, one call per query. |
| B3 | Bedrock Knowledge Base GraphRAG on Neptune Analytics (16 m-NCU), CHUNK_ENTITY_EXTRACTION with Claude Haiku 4.5, Titan v2. Run at 5, 10, 20, 50 and 100 results (A18). MultiHop-RAG and VersionedDocs only (A9). |
| B1-filter | VersionedDocs: B1 with a status=current metadata filter on every object. |
| F1 | VersionedDocs: a Bedrock vector Knowledge Base (Titan v2) fed only the nest's current published snapshot. |
| C1 | ctx lexical search (BM25, Porter stemming). |
| C2 | ctx hybrid: lexical + Titan v2 embeddings, weighted reciprocal rank fusion 1 : 2. |
| C3p | C2 + Cohere Rerank 3.5 over the best ~300-token passage of each of the top 100 documents, one call of ≤100 passages per query: the same budget as B2 (A17). |
| C3 (superseded) | C2 + rerank over whole documents (top 50). Published for transparency. |
| flat C1 / C2 | VersionedDocs: ctx with previous versions stored as separate plain files, as Bedrock stores them. |
All arms return a ranked list of document ids; chunk hits collapse to their parent document at their best rank. Every arm received identical text (frontmatter stripped; title + body). The ctx arms were measured on the release candidate (engine commit 9e1ab02) and re-run on the published @promptowl/contextnest-cli@3.4.0 (A20; status in section 8).
Models
| Role | Model | Note |
|---|---|---|
| Embeddings, both sides | Amazon Titan Text Embeddings v2, 1024-d | Cohere Embed v4 was rejected by Bedrock Knowledge Bases (A16) |
| Rerank, both sides | Cohere Rerank 3.5 | Same budget: one call of ≤100 items per query |
| GraphRAG extraction | Claude Haiku 4.5 | AWS-documented GraphRAG extraction model (A3) |
| Answers, every arm | Claude Sonnet 4.6, temperature 0 | 6,000-character context budget, filled in rank order (A4, A19) |
| Primary judge, every arm | Claude Opus 4.6 | Labels CORRECT / STALE / HEDGED / MISSING / WRONG; never sees context |
| Check judge | OpenAI gpt-oss-120b | Cohen's κ against the primary is reported |
The answer model is the same for every arm, so a same-family bias in the judge would apply to every arm alike and cancel in arm-to-arm differences.
Metrics and statistics
- Primary: NDCG@10. Secondary: MRR@10, MAP@10, Recall@20 (cross-system; Bedrock's
retrievecaps at 100 chunks, so Recall@100 is local-only, A5). - Headline difference per dataset: 95% bootstrap CI of the per-query NDCG@10 difference and a paired randomization test.
- Parity, defined before results: the CI's lower bound lies above −0.02.
- VersionedDocs: Stale@10. MultiHop-RAG: Evidence-Complete@k (A11).
- Answers: share of answers per label, judge agreement as Cohen's κ.
Protocol amendments
The full text is in PROTOCOL.md. Every amendment that changes a reported number is marked.
| # | Date | Change | Affects reported numbers |
|---|---|---|---|
| A1 | 10-13 | MultiHop-RAG: 71 no-evidence queries in the frozen 600 are scored for answers only; retrieval uses 529. | Yes: MultiHop query count |
| A2 | 10-13 | VersionedDocs: 180 qualifying pages, not ~200; validator not loosened. | Yes: corpus size |
| A3 | 10-13 | GraphRAG extraction = Claude Haiku 4.5 (AWS-supported). It was accepted; no Nova fallback fired. | Yes: all GraphRAG numbers |
| A4 | 10-13 | Same 6,000-character answer context budget for every arm. | Yes: all answer numbers |
| A5 | 10-13 | Cross-system recall reported at 20, not 100. | Yes: recall columns |
| A6 | 10-13 | Embeddings at 1024 dimensions on both sides. | Yes |
| A7 | 10-13 | SciFact and NFCorpus become development sets for ctx defaults (stemming, boosts, fusion weight 1 : 2). | Yes: SciFact and NFCorpus are not held-out |
| A8 | 10-13 | MultiHop-RAG inspection found MiniSearch's coordination factor; removing it makes ctx lexical a plain BM25 sum and boosts go to 1. MultiHop-RAG is reclassified as a development set for ctx lexical scoring. | Yes: MultiHop ctx numbers are not held-out |
| A9 | 10-14 | Cost scope: GraphRAG on MultiHop-RAG and VersionedDocs only; FiQA local only; answers on a 300-question MultiHop subset. | Yes: which arms exist |
| A10 | 10-14 | Model lineup: Opus 4.6 primary judge for every arm; gpt-oss-120b check judge; Mistral and DeepSeek dropped. | Yes: all answer numbers |
| A11 | 10-14 | Evidence-Complete@k added for MultiHop-RAG. | Yes: new metric (section 5) |
| A12 | 10-14 | Budget: hard cap $140 enforced by the harness ledger. | No |
| A13 | 10-14 | Merged a parallel lab; Cohere Embed v4 fusion re-check; 1 : 2 kept. Preliminary Cohere numbers only. | No (preliminary context only) |
| A14 | 10-14 | VersionedDocs final: 541 questions, 155 strict paraphrases. | Yes: query counts |
| A15 | 10-14 | Answers only on the arms the claims rest on; VersionedDocs answers on a stratified 250; F1 becomes a vector KB on the current snapshot. | Yes: answer samples; F1 design |
| A16 | 10-14 | Primary embedder = Titan v2 on both sides, because Bedrock KB rejected Cohere Embed v4. | Yes: all embedding arms |
| A17 | 10-14 | Prompted by B2's MultiHop-RAG score: ctx reranks ~300-token passages, same budget as B2. C3p on MultiHop-RAG is post-hoc; VersionedDocs is the held-out test. | Yes: C3p everywhere; MultiHop C3p is post-hoc |
| A18 | 10-14 | GraphRAG result-count sensitivity: reported at every tested count, best count as headline. The MultiHop-RAG graph was rebuilt with the same configuration for the sweep. | Yes: GraphRAG headline (favours Bedrock) |
| A19 | 10-14 | Answer harness correction: ctx context had been whole documents truncated at 6,000 characters. Corrected arm C3pass uses the passages the engine returns. Both published; C3pass is the ctx figure. | Yes: ctx answer numbers |
| A20 | 10-28 | ctx arms re-run on the released contextnest-cli@3.4.0; every finished arm is identical to the release candidate (three rerank arms still running). Four reporting fixes: GraphRAG extraction per document, the first Neptune graph added to the spend total, paraphrase Stale@10 now measured, README embedder. | Yes: ctx provenance; paraphrase Stale@10; cost total |
| A21 | 10-28 | Post-hoc item-validity audit of all 541 VersionedDocs questions (Claude Sonnet 4.6, calibrated on a 50-item spot-check): 278 valid. Every VersionedDocs result reported on the full and the validated set. | Yes: new validated-subset rows; full-set rows unchanged |
4. Results: full tables
Every number in this section is copied from results/final.md, which make_results.py generates from the raw runs in runs/.
SciFact (300 queries, 5,183 documents), development set
| Arm | NDCG@10 | MRR@10 | MAP@10 | Recall@20 |
|---|---|---|---|---|
| BM25 (reference) | 0.690 | 0.656 | 0.642 | 0.866 |
| Bedrock KB vector, Titan v2 (B1) | 0.682 | 0.646 | 0.636 | 0.863 |
| Bedrock KB vector + Cohere Rerank 3.5 (B2) | 0.734 | 0.708 | 0.696 | 0.883 |
| ctx keyword (C1) | 0.662 | 0.630 | 0.617 | 0.849 |
| ctx hybrid, Titan v2 (C2) | 0.728 | 0.697 | 0.682 | 0.907 |
| ctx hybrid + passage rerank (C3p) | 0.770 | 0.741 | 0.731 | 0.927 |
C3p − B2 NDCG@10 = +0.035, 95% CI [+0.015, +0.058], p = 0.0008. Parity rule met.
NFCorpus (323 queries, 3,633 documents), development set
| Arm | NDCG@10 | MRR@10 | MAP@10 | Recall@20 |
|---|---|---|---|---|
| BM25 (reference) | 0.327 | 0.528 | 0.121 | 0.181 |
| Bedrock KB vector, Titan v2 (B1) | 0.325 | 0.539 | 0.117 | 0.182 |
| Bedrock KB vector + Cohere Rerank 3.5 (B2) | 0.341 | 0.558 | 0.126 | 0.190 |
| ctx keyword (C1) | 0.327 | 0.528 | 0.124 | 0.180 |
| ctx hybrid, Titan v2 (C2) | 0.351 | 0.565 | 0.134 | 0.201 |
| ctx hybrid + passage rerank (C3p) | 0.361 | 0.580 | 0.142 | 0.214 |
C3p − B2 NDCG@10 = +0.020, 95% CI [+0.007, +0.033], p = 0.0013. Parity rule met.
MultiHop-RAG (529 queries, 609 documents), development set for ctx lexical; C3p post-hoc
| Arm | NDCG@10 | MRR@10 | MAP@10 | Recall@20 |
|---|---|---|---|---|
| BM25 (reference) | 0.735 | 0.796 | 0.619 | 0.947 |
| Bedrock KB vector, Titan v2 (B1) | 0.685 | 0.760 | 0.569 | 0.935 |
| Bedrock KB vector + Cohere Rerank 3.5 (B2) | 0.795 | 0.874 | 0.700 | 0.954 |
| ctx keyword (C1) | 0.706 | 0.788 | 0.591 | 0.934 |
| ctx hybrid, Titan v2 (C2) | 0.704 | 0.764 | 0.587 | 0.957 |
| ctx hybrid + passage rerank (C3p) | 0.787 | 0.857 | 0.692 | 0.975 |
| ctx hybrid + whole-doc rerank (C3, superseded) | 0.763 | 0.849 | 0.658 | 0.972 |
| Bedrock GraphRAG, 5 results | 0.477 | 0.645 | 0.380 | 0.489 |
| Bedrock GraphRAG, 10 results | 0.529 | 0.618 | 0.414 | 0.626 |
| Bedrock GraphRAG, 20 results | 0.507 | 0.493 | 0.366 | 0.762 |
| Bedrock GraphRAG, 50 results | 0.332 | 0.314 | 0.207 | 0.852 |
| Bedrock GraphRAG, 100 results (pre-registered) | 0.169 | 0.193 | 0.096 | 0.610 |
C3p − B2 NDCG@10 = −0.008, 95% CI [−0.025, +0.009], p = 0.3482. Parity rule not met. On this set plain BM25 (0.735) also beats Bedrock vector without rerank (0.685).
VersionedDocs (541 queries, 180 documents + 180 previous versions), held-out
| Arm | NDCG@10 | MRR@10 | MAP@10 | Recall@20 | Stale@10 |
|---|---|---|---|---|---|
| Bedrock KB vector, old+new copies (B1) | 0.692 | 0.619 | 0.619 | 0.946 | 0.863 |
| Bedrock + status=current filter (B1-filter) | 0.827 | 0.787 | 0.787 | 0.978 | 0.000 |
| Bedrock + Cohere Rerank 3.5 (B2) | 0.792 | 0.738 | 0.738 | 0.969 | 0.900 |
| Nest feeding Bedrock (F1) | 0.838 | 0.799 | 0.799 | 0.983 | 0.000 |
| Bedrock GraphRAG, 5 results | 0.594 | 0.528 | 0.528 | 0.787 | 0.708 |
| Bedrock GraphRAG, 10 results | 0.566 | 0.468 | 0.468 | 0.867 | 0.797 |
| Bedrock GraphRAG, 100 results (pre-registered) | 0.202 | 0.130 | 0.130 | 0.743 | 0.447 |
| ctx nest keyword (C1) | 0.807 | 0.762 | 0.762 | 0.970 | 0.000 |
| ctx nest hybrid (C2) | 0.778 | 0.725 | 0.725 | 0.970 | 0.000 |
| ctx nest hybrid + whole-doc rerank (C3, superseded) | 0.801 | 0.767 | 0.767 | 0.924 | 0.000 |
| ctx nest hybrid + passage rerank (C3p) | 0.902 | 0.883 | 0.883 | 0.963 | 0.000 |
| ctx, old copies as plain files, keyword (flat C1) | 0.678 | 0.602 | 0.602 | 0.946 | 0.861 |
| ctx, old copies as plain files, hybrid (flat C2) | 0.643 | 0.565 | 0.565 | 0.943 | 0.843 |
C3p − B1-filter NDCG@10 = +0.075, 95% CI [+0.047, +0.102], p = 0.0000. Parity rule met; ctx significantly better. Each question has one gold page, so MRR@10 and MAP@10 coincide.
VersionedDocs, paraphrased questions (155 queries)
| Arm | NDCG@10 | MRR@10 | MAP@10 | Recall@20 |
|---|---|---|---|---|
| Bedrock KB vector, old+new copies (B1) | 0.619 | 0.552 | 0.552 | 0.865 |
| Bedrock + status=current filter (B1-filter) | 0.723 | 0.676 | 0.676 | 0.916 |
| Bedrock + Cohere Rerank 3.5 (B2) | 0.679 | 0.619 | 0.619 | 0.897 |
| Nest feeding Bedrock (F1) | 0.735 | 0.688 | 0.688 | 0.935 |
| ctx nest keyword (C1) | 0.456 | 0.398 | 0.398 | 0.748 |
| ctx nest hybrid (C2) | 0.611 | 0.556 | 0.556 | 0.806 |
| ctx nest hybrid + passage rerank (C3p) | 0.770 | 0.737 | 0.737 | 0.890 |
C3p − B1-filter NDCG@10 = +0.048, 95% CI [−0.001, +0.096], p = 0.0620. Parity rule met; not significant. Lexical-only ctx (C1) falls to 0.456 when the question shares little wording with the page: this is where embeddings earn their place. Stale@10 on paraphrases (now measured, A20): Bedrock B1 0.742, B2 0.781; B1-filter, F1 and every nest arm 0.000.
VersionedDocs, validated items only (278 queries, A21)
| Arm | NDCG@10 | MRR@10 | Recall@20 | Stale@10 |
|---|---|---|---|---|
| Bedrock KB vector, old+new copies (B1) | 0.673 | 0.595 | 0.942 | 0.903 |
| Bedrock + status=current filter (B1-filter) | 0.821 | 0.780 | 0.978 | 0.000 |
| Bedrock + Cohere Rerank 3.5 (B2) | 0.768 | 0.705 | 0.968 | 0.946 |
| Nest feeding Bedrock (F1) | 0.828 | 0.787 | 0.982 | 0.000 |
| Bedrock GraphRAG, 5 results | 0.590 | 0.526 | 0.773 | 0.763 |
| Bedrock GraphRAG, 10 results | 0.550 | 0.450 | 0.860 | 0.835 |
| Bedrock GraphRAG, 100 results (pre-registered) | 0.174 | 0.107 | 0.734 | 0.439 |
| ctx nest keyword (C1) | 0.818 | 0.770 | 0.975 | 0.000 |
| ctx nest hybrid (C2) | 0.795 | 0.743 | 0.975 | 0.000 |
| ctx nest hybrid + passage rerank (C3p) | 0.898 | 0.877 | 0.964 | 0.000 |
| ctx, old copies as plain files, keyword (flat C1) | 0.669 | 0.587 | 0.964 | 0.910 |
| ctx, old copies as plain files, hybrid (flat C2) | 0.659 | 0.581 | 0.960 | 0.892 |
C3p − B1-filter NDCG@10 = +0.077, 95% CI [+0.038, +0.117], p = 0.0000. Parity rule met; ctx better.
VersionedDocs, paraphrased, validated items only (91 queries, A21)
| Arm | NDCG@10 | MRR@10 | Recall@20 | Stale@10 |
|---|---|---|---|---|
| Bedrock KB vector, old+new copies (B1) | 0.619 | 0.544 | 0.879 | 0.802 |
| Bedrock + status=current filter (B1-filter) | 0.742 | 0.696 | 0.923 | 0.000 |
| Bedrock + Cohere Rerank 3.5 (B2) | 0.675 | 0.608 | 0.912 | 0.857 |
| Nest feeding Bedrock (F1) | 0.755 | 0.706 | 0.956 | 0.000 |
| ctx nest keyword (C1) | 0.474 | 0.409 | 0.813 | 0.000 |
| ctx nest hybrid (C2) | 0.663 | 0.608 | 0.846 | 0.000 |
| ctx nest hybrid + passage rerank (C3p) | 0.790 | 0.756 | 0.912 | 0.000 |
C3p − B1-filter NDCG@10 = +0.048, 95% CI [−0.022, +0.116], p = 0.1924. Parity rule not met on this subset: 91 questions are too few to bound the difference at −0.02.
Answers, full rows
Claude Sonnet 4.6 answers at temperature 0 from a 6,000-character context; Claude Opus 4.6 labels; gpt-oss-120b check labels. κ is per arm; "mean ctx chars" is the context actually supplied.
| Dataset | Arm | n | Correct | Stale | Hedged | Missing | Wrong | κ | Mean ctx chars |
|---|---|---|---|---|---|---|---|---|---|
| MultiHop-RAG | B2 | 300 | 66.0% | 0.0% | 3.0% | 25.7% | 5.3% | 0.7636 | 6000 |
| GraphRAG best (10 results) | 300 | 39.0% | 0.0% | 2.7% | 54.7% | 3.7% | 0.708 | 6000 | |
| C3pass | 300 | 65.7% | 0.0% | 3.3% | 25.7% | 5.3% | 0.733 | 6000 | |
| VersionedDocs | B2 | 250 | 64.0% | 13.6% | 12.0% | 5.6% | 4.8% | 0.8276 | 6000 |
| GraphRAG best (5 results) | 250 | 54.8% | 12.0% | 6.4% | 20.4% | 6.4% | 0.8866 | 5747 | |
| C3p, whole-document context (harness artifact, A19) | 250 | 46.8% | 0.4% | 0.4% | 48.8% | 3.6% | 0.9206 | 5928 | |
| C3pass | 250 | 73.2% | 2.0% | 1.2% | 17.2% | 6.4% | 0.8576 | 5880 | |
| F1 | 250 | 74.4% | 1.2% | 0.8% | 14.8% | 8.8% | 0.8488 | 6000 |
All-answer agreement: MultiHop-RAG κ 0.7462, raw agreement 0.8522, 133 disagreements; VersionedDocs κ 0.8781, raw agreement 0.9312, 86 disagreements. The MultiHop-RAG sample is stratified by question type from the frozen 600 (seed 13) and includes no-evidence questions (35 of 300, the null_query items in bedrock/out/multihop.sample300.qids). The VersionedDocs sample is stratified by change type (seed 13).
The A19 correction, stated plainly. The first ctx answer run fed the model the opening 6,000 characters of whole documents, while Bedrock arms fed retrieved ~300-token chunks. VersionedDocs pages average 16.6k characters, so the model mostly saw the top of the top page: 46.8% correct and 48.8% missing, despite the best retrieval. The engine returns each hit's best-matching passage in --json, which is what an agent receives. C3pass uses those passages under the same 6,000-character budget. We publish both rows; the amendment was written before the corrected result.
5. GraphRAG analysis
Result-count sensitivity
The protocol pre-registered GraphRAG at 100 results, Bedrock's maximum. At that setting it scored 0.169 NDCG@10 on MultiHop-RAG. As the requested count falls, it improves, peaking at 10 results on MultiHop-RAG and at 5 on VersionedDocs. At high counts, graph-expanded chunks displace the best direct hits (A18). We report every count and use the best one as the headline, which favours Bedrock.
Evidence-Complete@k on MultiHop-RAG (A11)
A graph is meant to help assemble evidence spread across several articles. Evidence-Complete@k is the share of the 529 questions whose top k contains every gold evidence article. The table also shows how many distinct documents each arm returned per question, since GraphRAG's chunks collapse to few documents.
| Arm | Evidence-Complete@5 | Evidence-Complete@10 | Docs returned (mean) |
|---|---|---|---|
| BM25 (reference) | 0.4386 | 0.6635 | 100.0 |
| Bedrock vector (B1) | 0.3743 | 0.5652 | 33.7 |
| Bedrock vector + rerank (B2) | 0.5236 | 0.6994 | 33.7 |
| ctx keyword (C1) | 0.3932 | 0.5917 | 99.8 |
| ctx hybrid (C2) | 0.3856 | 0.6011 | 100.0 |
| ctx hybrid + passage rerank (C3p) | 0.5312 | 0.7032 | 100.0 |
| Bedrock GraphRAG, 5 results | 0.1456 | 0.1456 | 2.7 |
| Bedrock GraphRAG, 10 results | 0.2647 | 0.3025 | 4.4 |
| Bedrock GraphRAG, 20 results | 0.2325 | 0.4518 | 7.4 |
| Bedrock GraphRAG, 50 results | 0.0624 | 0.2495 | 15.1 |
| Bedrock GraphRAG, 100 results | 0.0095 | 0.0454 | 27.2 |
Not in final.md: produced by the harness scorer, bedrock/bx score.py multihop B1 B2 B3 C1 C2 C3p bm25 /lab/runs/multihop_k{5,10,20,50}.B3.json --ks 2 4 5 10 (output in bedrock/out/scores/multihop.json). Its NDCG@10 column reproduces final.md for every arm, which checks the run files. Not significance-tested.
Reading it
- On its intended case, GraphRAG with default configuration did not reach parity with Bedrock's own vector arm, or with ctx. Its best NDCG@10 (0.529) is below plain BM25 (0.735).
- Its answers follow its retrieval: 39.0% correct and 54.7% missing on MultiHop-RAG, against 66.0% and 65.7% correct for B2 and ctx.
- At its best count it returns 4.4 distinct documents per question, which caps how much multi-article evidence reaches the model. Evidence-Complete@10 is 0.3025 against 0.6994 (B2) and 0.7032 (ctx).
- Configuration: AWS defaults for chunking and retrieval,
CHUNK_ENTITY_EXTRACTIONwith Claude Haiku 4.5, Titan v2, 16 m-NCU. We did not tune GraphRAG beyond the result count. A tuned graph may do better; the harness takes any configuration. - Indexing time: about 2 hours per corpus (MultiHop-RAG, 609 documents: 7,535 s and 7,432 s for the two builds; VersionedDocs, 360 documents: 7,011 s), against seconds to minutes for
ctx embed(section 7).
6. Governance results
VersionedDocs asks: when a page changes and the old version is still stored, does the old answer reach the model? Each question's gold fact exists only in the current page, and its stale fact only in the previous one.
- Bedrock leaks without a filter. With old and new copies side by side, Bedrock vector put a superseded version in the top 10 for 86% of questions (Stale@10 0.863). Its reranker made it worse (0.900), because the old page is also on topic. GraphRAG at its best count: 0.708. Answers from B2 committed to the stale fact 13.6% of the time; from GraphRAG, 12.0%.
- A maintained filter fixes it. A
status=currentmetadata attribute on every object, with a filter on every query, takes Bedrock to 0.000. Your ingestion pipeline must set that attribute correctly on every file, every time. - The nest does it by default. Every versioned ctx arm scored 0.000 Stale@10. Superseded versions live in history, reachable through
ctx historyandctx reconstruct, not in search. ctx answers were stale 2.0% of the time (C3pass). - The win is versioning, not search. Store old copies as plain files in ctx and it leaks like Bedrock: Stale@10 0.861 (keyword) and 0.843 (hybrid).
- The two compose. F1 feeds Bedrock only the nest's current published snapshot. It beats Bedrock's own filter on retrieval (0.838 vs 0.827 NDCG@10, not significance-tested) and gave the best answers in the study: 74.4% correct, 1.2% stale.
For a Bedrock team the practical reading: if you already run Bedrock, a nest in front of it removes the filter you would otherwise maintain, and in this test answered best. If you run ctx alone, you get the same governance and, on this corpus, better retrieval (0.902 vs 0.827).
7. Cost
Measured: what this benchmark cost
The harness ledger (bedrock/out/spend.jsonl) records every billable call at AWS list price (us-east-1, fetched 2026-10-13). ./bx plan.py spend reports TOTAL committed $56.59 for the 10-13/14 run (A20 added the first MultiHop graph the harness had missed), of which:
| Activity | $ |
|---|---|
| Answer generation and judging (Converse) | 21.39 |
| Spend before the ledger existed (Cohere Rerank for local C3 runs $7.60, VersionedDocs generation $6.01, Cohere Embed v4 $1.00) | 14.61 |
| GraphRAG ingestion, MultiHop-RAG (two full builds) | 9.09 |
| GraphRAG ingestion, VersionedDocs | 4.22 |
| Rerank (B2) | 3.91 |
| Neptune graph-hours counted by the harness (2.42 h + 2.20 h) | 2.22 |
| Vector ingestion and retrieval calls | < 0.10 |
Correction (A20). The harness first counted only two graph lifetimes. Measured lifetimes from progress.log: MultiHop-RAG graph 03:11 to 05:29 UTC (about 2.3 h), VersionedDocs graph 06:21 to 08:33 (about 2.2 h), MultiHop-RAG rebuild 10:53 to 13:18 (about 2.4 h), at $0.48/h. The first graph is now in the ledger, which brings the total to $56.59. The 2026-10-28 additions were the validity audit ($4.72) and the re-run on the released build (Titan v2 embeddings and Cohere rerank, a few dollars).
Indexing (measured)
| Corpus | System | $ | Wall clock |
|---|---|---|---|
| MultiHop-RAG, 609 docs | Bedrock GraphRAG (Haiku 4.5 extraction + Titan v2) | 4.55 / build | 7,535 s; 7,432 s |
| VersionedDocs, 360 docs | Bedrock GraphRAG | 4.22 | 7,011 s |
| MultiHop-RAG / VersionedDocs / SciFact / NFCorpus | Bedrock vector KB ingestion (S3 Vectors) | < 0.05 | 624 / 561 / 1,785 / 1,257 s |
| SciFact, 5,183 docs | ctx embed, Titan v2 | < 0.05 | 462 s |
| NFCorpus, 3,633 docs | ctx embed, Titan v2 | < 0.05 | 364 s |
| VersionedDocs flat, 360 docs | ctx embed, Titan v2 | < 0.05 | 19 s |
GraphRAG extraction per document = $9.0908 ÷ (2 × 609) = $0.0075 on MultiHop-RAG (~2.6k tokens per document). cost_report.py now divides by both builds (A20). Its ctx rows for MultiHop-RAG and VersionedDocs read "0 s", which reflects an incremental re-run over an existing index, not a cold build, so they are not cited. Bedrock vector ingestion times include Knowledge Base sync polling.
Projected monthly cost
Same formulas and assumptions as cost_report.py, with the extraction rate corrected to $0.0075 per document: 51,000 reads a month (the nest TCO model's mid-market case: 200 people × 5 questions a day × 21 days, plus 20 agents × 10 runs × 5 reads × 30 days), 10% of documents changing per month, 2,600 tokens per document. Reranking ($102/month at 51,000 reads) is included on both sides. Answer-generation tokens are excluded because they are the same whichever retriever feeds them.
| Documents | Bedrock vector, S3 Vectors | Bedrock vector, OpenSearch Serverless | Bedrock GraphRAG | ctx, Community Nest hosting | ctx, CLI only |
|---|---|---|---|---|---|
| 1,000 | $102 | $452 | $453 | $141 | $102 |
| 10,000 | $102 | $452 | $460 | $141 | $102 |
| 100,000 | $103 | $453 | $528 | $142 | $103 |
Fixed floors: Neptune Analytics minimum 16 m-NCU = $0.48/h ≈ $350/month while the graph exists; OpenSearch Serverless 2-OCU floor ≈ $350/month (a NextGen collection can scale to zero, per the TCO node). Community Nest hosting: Lightsail 4 GB ($24) + managed PostgreSQL ($15). Excluded: the Community Nest self-hosted license ($10 per person per month, 30-person minimum, per the TCO node), which applies only to a self-hosted team server; the ctx CLI and engine are free.
The honest cost conclusion.
- Against Bedrock GraphRAG or an OpenSearch-Serverless-backed Knowledge Base, a hosted Community Nest costs about a third as much per month (ratio 0.27 to 0.31 in the table). The gap is the ~$350/month fixed floor, plus LLM extraction on every changed document (measured at ~$0.0075 per document) for GraphRAG.
- Against Bedrock on S3 Vectors, cost is at parity: the CLI costs the same, and a hosted Community Nest adds $39/month of hosting. There the advantage is not price. It is retrieval quality (VersionedDocs 0.902 vs 0.827), zero stale answers without a filter you maintain, and engineering time.
- Engineering time is a model, not a measurement. The nest TCO model (
nodes/research/tco-governed-context-2026-10-06) assumes 0.1 FTE ongoing for ContextNest against 0.5 to 1.5 FTE for Bedrock or Neo4j pipelines, and a one-time build of 0.1 FTE × 0.5 months against 2 FTE × 3 months. That model also sizes Neptune above the 16 m-NCU minimum used here, so the projection above is conservative toward Bedrock. - Indexing time, measured: about 2 hours per corpus for GraphRAG; seconds to minutes for
ctx embed.
Script for the corrected figures
# same constants and formulas as cost_report.py; two corrections
ext = 9.0908 / (2 * 609) # ledger "ingest graph:multihop" covers two builds of 609 docs
hours = {"MultiHop graph 1": 2.3, "VersionedDocs graph": 2.2, "MultiHop rebuild": 2.4}
nep = sum(hours.values()) * 0.48 # 6.9 h, $3.31
missing = 2.3 * 0.48 # first MultiHop graph, not in plan.py TOTAL: $1.10
total = 55.48 + missing # $56.58
READS, CHURN, TOK = 51_000, 0.10, 2_600
TITAN, RR, OSS, NEPM, HOST = 0.02, 2.00, 350.0, 16*0.03*730, 24+15
rerank = READS/1000*RR
for n in (1_000, 10_000, 100_000):
ch = n*CHURN; emb = ch*TOK/1e6*TITAN; qe = READS*30/1e6*TITAN
s3 = emb+qe+rerank; oss = s3+OSS; graph = s3+NEPM+ch*ext; ctx_hosted = HOST+s3
# -> 1k: 102 / 452 / 453 / 141 10k: 102 / 452 / 460 / 141 100k: 103 / 453 / 528 / 142
8. What we fixed in ContextNest because of this
The released ctx 3.0.0 (@promptowl/contextnest-engine@3.0.0, pinned in the lab image) scored 0.604 NDCG@10 on SciFact (results/gate.md), against 0.690 for plain BM25. That was a real gap in our product, and the benchmark found it. These changes shipped in ctx 3.4.0 (PromptOwl/ContextNest#190). They were developed on branch misha/retrieval-modes of PromptOwl/ContextNest, commits through 9e1ab02, the release candidate measured here.
| Change | Commit | Evidence |
|---|---|---|
| Porter stemming at index and query time | 15830c6 | SciFact 0.604 → 0.630 with stemming alone (gate.md) |
| Field boosts from title 3 / tags 2 / description 1.5 to all 1 | 15830c6, c9d72d0 | The large title boost cost about 0.04 NDCG@10 on SciFact; once coordination was removed, any title boost hurt (A7, A8) |
| Removed MiniSearch's coordination factor: plain BM25 sum | c9d72d0 | MultiHop-RAG lexical 0.657 → 0.706 (found on MultiHop-RAG, hence A8) |
| Hybrid lexical + semantic, weighted reciprocal rank fusion 1 : 2 | d3745d8, b81eb2d | Chosen on SciFact and NFCorpus from a 1:1 / 1:1.5 / 1:2 / 1:3 sweep (A7) |
| Passage-level rerank: one call of ≤100 passages per query, each document's best ~300-token window chosen locally | 8499db4, 9e1ab02 | Same budget as Bedrock B2. VersionedDocs (held-out) 0.801 → 0.902; MultiHop-RAG (post-hoc) 0.763 → 0.787 (A17) |
| Transport and throttling retries for embedding and rerank calls | ed318b0, a997f32 | Reliability under Bedrock throttling during the runs |
Search returns each hit's best passage in --json | 9e1ab02 | What an agent receives; the basis of C3pass (A19) |
Shipped-only rule. The protocol counts only shipped ctx code (section 2 of PROTOCOL.md). The first measurements used the release candidate (commit 9e1ab02); every arm is being re-run on the published @promptowl/contextnest-cli@3.4.0. Every arm finished so far is identical to the release candidate; the hybrid + rerank arms for NFCorpus, MultiHop-RAG and VersionedDocs are still running (A20). The search code between the two differs only in hardening (configuration checks, index file format, retry budget) and whitespace folding in exact-title matching.
9. Limitations
- Only one held-out set against Bedrock. SciFact and NFCorpus chose ctx defaults (A7). MultiHop-RAG was used to fix ctx lexical scoring (A8), and the passage rerank was prompted by a MultiHop-RAG result (A17). VersionedDocs and its paraphrases are the only held-out comparison.
- FiQA not run. FiQA-2018 (57,638 documents), pre-registered as held-out and lexical's worst case, was stopped for memory. Its absence leaves the paraphrase-heavy case resting on 155 VersionedDocs paraphrases.
- One run per arm. No re-indexing variance. Bedrock ingestion, especially LLM extraction for GraphRAG, may vary between builds.
- Titan v2, not Cohere Embed v4. Bedrock Knowledge Bases rejected Cohere Embed v4 (A16), so both sides use Titan v2, a weaker embedder. Preliminary local numbers with Cohere v4 exist (A13: e.g. SciFact ctx+Cohere 0.761 vs Cohere dense alone 0.777, whole documents, no chunking) and are not part of the comparison.
- Model-generated questions on VersionedDocs. A model wrote the questions and a validator dropped 56.7%, but a later audit (A21) found only 278 of the remaining 541 describe a real change of fact. Results are reported on both sets; the validated subset is itself chosen by a model (agreement with a 50-item spot-check: 39 of 50), and no human hand-check was done. A stricter generator with a validity gate before any run is the fix for v4.
- GraphRAG configuration. AWS defaults except the result count. Haiku 4.5 extraction (AWS-documented). Other graph configurations were not tested.
- Answer samples. 300 MultiHop-RAG and 250 VersionedDocs questions per arm, only on the arms the claims rest on (A15). Answer rates have no confidence intervals in this paper.
- The A19 correction. The ctx answer figure (C3pass) comes from a corrected harness. The amendment predates the corrected result, and both results are published, but a reader should know the first run looked very different.
- Not reported from the protocol: Holm correction across arms, paired t-tests, p50/p95 latency, the open-source dense anchor (bge-base) in the final tables, and third-pass adjudication of judge disagreements. The headline uses bootstrap CIs and randomization tests only.
- Cost projections use our assumptions for read volume and churn, and the TCO model's engineering estimates, which are assumptions, not measurements.
10. Reproduce it
- Harness:
retrieval-v3-lab/.PROTOCOL.md(frozen protocol and amendments),runs/(raw per-query run files for every arm, Bedrock chunk sidecars),results/make_results.py→results/final.mdandfinal.json,bedrock/score.py(Evidence-Complete, Stale@10),cost_report.py. - Pinned images: everything runs in Docker.
retrieval-v3-bedrock:0.1(the lab image plus boto3/botocore 1.43.100) for the Bedrock arms; the lab image pins@promptowl/contextnest-engine@3.0.0andstemmer@2.0.1; the released ctx 3.4.0 used for the re-runs is installed inengine-release-3.4.0/(run_release.sh,cmp_release.py); the release candidate is snapshotted inengine-snapshot-9e1/. - Bedrock configuration: full Knowledge Base and data-source JSON for every KB in
bedrock/out/manifests/. - Your models: every model is an environment variable (
EMBED_MODEL_ID,EXTRACTION_MODEL_ID,RERANK_MODEL_ID,GEN_MODEL_ID,JUDGE_1,JUDGE_2, …); see "Rerun with your own models" inbedrock/README.md. The default embedder is Titan v2, as used here. - Cost safety:
./dryrun.shruns the whole pipeline against stubs with no AWS calls;./bx estimate.pyprices the plan first; the spend ledger enforces a hard cap;teardown.shdeletes only the resources the harness created.
We would like you to rerun it on your own corpus, with your own embedder, extraction model, answer model and judge. Your data stays in your AWS account and on your machine. If ctx loses on your data, we want to know where.
Annex: the v2 report (2026-10-02), unchanged
Reproduced as delivered. Its conclusions are superseded where v3 measures the same thing, and section 1 maps each objection to v3. Two statements in it are out of date: "semantic seed selection designed, not shipped" (hybrid semantic search shipped in ctx 3.4.0; see section 8) and "Paraphrase recall on large corpora: weaker, lexical" (see the paraphrase table in section 4).
When documents change: a stale-version retrieval test of Amazon Bedrock GraphRAG and ContextNest
Misha Sulpovar, PromptOwl · live run 2026-10-02
Executive summary
We tested one question on a live Amazon Bedrock Knowledge Base with GraphRAG on Neptune Analytics and on ContextNest (ctx): when a document changes, does the superseded answer still reach the model? Both systems got the same text and the same 30 plain-English questions. We scored the text each system actually returned, and separately the answers a model wrote from it.
- Retrieval is at parity. After a clean overwrite and re-sync, Bedrock returned the current document for 29 of 30 questions. ContextNest, cut to the same number of results per question, returned it for 28 of 30. The 95% intervals overlap almost entirely; neither system surfaced stale content.
- Stale leakage depends on how old versions are stored, not on the retriever. With previous versions sitting next to current ones as plain files, Bedrock put superseded content in front of the model on 23 of 30 questions (77%), and ContextNest did the same on 24 of 30 (80%) at Bedrock's depth (29 of 30 at its own three-document depth) when we stored old copies as plain files too.
- Both reach zero with version discipline. Bedrock got to 0 of 30 with a
status=currentmetadata filter, built and maintained in the ingestion pipeline. ContextNest got to 0 of 30 by default, because previous versions are versions of the same document rather than separate files, and they stay queryable through history. - Answers follow retrieval. A model answering from each arm's context committed to the current fact on 27 to 28 of 30 for Bedrock after update, Bedrock with the filter, and ContextNest versioned. With old copies mixed in, it committed to the superseded fact on 13 of 30 (Bedrock) and 10 of 30 (ContextNest plain files).
- Both were deterministic here. Every arm returned identical results across 5 back-to-back repeats of each question. After the update, Bedrock returned fewer results than requested: 1.4 on average at k=3 and 4.6 at k=10.
- Cost differs in kind. Both need hosting. Bedrock adds a Neptune graph billed per hour while it exists, LLM entity extraction on every changed document, and an embedding call per query. ContextNest has none of the three.
What this is not: a GraphRAG benchmark. The questions are single-fact lookups over 11 short documents, so the graph does little work, and n is small (30 questions clustered on 6 documents). It answers the stale-version question and gives you a harness to answer the parity question on your own corpus, with your own judge.
Results
All rates are over 30 questions, with 95% Wilson intervals. "At matched depth" means ContextNest's results were cut, per question, to the number of results and characters Bedrock returned for that question, because Bedrock returns chunks and ContextNest returns whole documents.
Retrieval, k=3 (rates over 30 questions; 95% Wilson interval in brackets)
| Arm | Gold doc retrieved | Current fact in context | Stale fact in context | Results returned (mean) |
|---|---|---|---|---|
| Bedrock, previous versions only (scorer check) | 28/30 | 0/30 [0–11%] | 28/30 [79–98%] | 1.0 |
| Bedrock, after overwrite + re-sync | 29/30 [83–99%] | 29/30 [83–99%] | 0/30 [0–11%] | 1.4 |
| Bedrock, previous copies alongside, no filter | 20/30 [49–81%] | 20/30 [49–81%] | 23/30 [59–88%] | 1.8 |
Bedrock, previous copies alongside, status=current filter | 29/30 [83–99%] | 29/30 [83–99%] | 0/30 [0–11%] | 1.4 |
| ContextNest, versioned (own depth, 3 documents) | 30/30 [89–100%] | 30/30 [89–100%] | 0/30 [0–11%] | 3.0 |
| ContextNest, previous copies as plain files (own depth) | 28/30 [79–98%] | 28/30 [79–98%] | 29/30 [83–99%] | 3.0 |
At k=10 every arm except the scorer check retrieves the current document on 30/30. The unfiltered arms then carry stale content on 30/30 (Bedrock and ContextNest alike), and the filtered and versioned arms on 0/30.
ContextNest at Bedrock's depth, k=3. Per question, ContextNest's results were cut to the number of results Bedrock returned for that question (gold doc), and its text to the number of characters Bedrock returned (facts). This is the like-for-like row.
| Comparison | Gold doc retrieved | Current fact in context | Stale fact in context |
|---|---|---|---|
| Bedrock, after update | 29/30 [83–99%] | 29/30 [83–99%] | 0/30 [0–11%] |
| ContextNest versioned, matched to it | 28/30 [79–98%] | 27/30 [74–97%] | 0/30 [0–11%] |
| Bedrock, previous copies alongside, no filter | 20/30 [49–81%] | 20/30 [49–81%] | 23/30 [59–88%] |
| ContextNest plain-file copies, matched to it | 23/30 [59–88%] | 22/30 [56–86%] | 24/30 [63–90%] |
Answers, k=3. Each arm's retrieved context went to Amazon Nova Pro with the same prompt, and Mistral Large 3 graded every answer by the version it finally commits to (CURRENT, STALE, HEDGED for no commitment, MISSING, WRONG). All 180 answers were graded under that one rubric with no hand edits. Prompts, answers and verdicts ship with the results.
| Arm (k=3) | CURRENT | STALE | HEDGED | MISSING | WRONG |
|---|---|---|---|---|---|
| Bedrock, previous versions only (scorer check) | 0/30 [0%–11%] | 25/30 [66%–93%] | 0/30 | 3/30 | 2/30 |
| Bedrock, after overwrite + re-sync | 27/30 [74%–97%] | 1/30 [1%–17%] | 0/30 | 1/30 | 1/30 |
| Bedrock, previous copies alongside, no filter | 13/30 [27%–61%] | 13/30 [27%–61%] | 1/30 | 2/30 | 1/30 |
Bedrock, previous copies alongside, status filter | 28/30 [79%–98%] | 0/30 [0%–11%] | 0/30 | 1/30 | 1/30 |
| ContextNest, versioned | 27/30 [74%–97%] | 0/30 [0%–11%] | 0/30 | 0/30 | 3/30 |
| ContextNest, previous copies as plain files | 17/30 [39%–73%] | 10/30 [19%–51%] | 2/30 | 0/30 | 1/30 |
- Two weak questions, same effect on both systems. On s18 the gold phrase ("wrapped in an envelope") is too abstract for the judge to match the JSON shape, so correct answers were graded STALE (Bedrock after update) or WRONG (ContextNest versioned). On s13 the runbook says to start a Zoom bridge and post the link in #incidents, and answers of "#incidents" were graded WRONG in every arm. The harness now accepts an optional reference answer per question for cases like s18.
- Reading it. With only current content in reach, the three clean arms committed to the current fact on 27 to 28 of 30. With old copies mixed in, both systems degraded: Bedrock committed to the superseded fact on 13 of 30 and ContextNest's plain-file arm on 10 of 30. Answers used each arm's own retrieval depth (about 1.4 chunks for Bedrock and 3 whole documents for ContextNest), so they are not depth-matched.
- First judge pass. An earlier pass with a looser rubric labelled answers that flag a conflict and then commit to one version inconsistently as HEDGED. Its labels are kept in
answers.firstpass.jsonlfor comparison; this table supersedes it.
- Scorer check. With only previous versions indexed, the current fact appeared on 0/30 and the stale fact on 28/30. That is what a working scorer should show.
- Where the systems differ at matched depth, it is by one to three questions in aggregate, inside the intervals, and the per-question misses differ in both directions. On the 11-document corpus a whole document is one chunk, so depth and character matching line up closely.
- Raw data. Every returned chunk or document, per question and arm, is in the results files, along with the full Bedrock configuration.
What it means
On these questions this test cannot separate the two retrievers. At matched depth they are within two questions of each other, with 4 of 30 questions disagreeing on the gold document in both directions, and answer accuracy is the same within one question (27 to 28 of 30 across the three clean arms). We don't claim either is better at retrieval.
What separates them is what happens to the old answer. Any retriever pointed at a store that holds old and new copies side by side will surface the old one some of the time; both did here, at nearly the same rate. Bedrock fixes that with a metadata attribute and a filter that your ingestion pipeline has to set correctly on every file, forever. ContextNest fixes it in the data model: a change is a new version of the same document, retrieval returns the current one, and the previous one is still there through ctx history and ctx reconstruct when someone needs to know what the answer used to be.
One difference this corpus can't measure: ContextNest returns whole documents, and at its own depth it had the current fact in context on 30 of 30. On long documents, whole-document context can keep together facts that chunking splits, such as a rule and its exception in different sections. Every document here is under about 350 tokens, so each was a single Bedrock chunk and there was nothing for chunking to split; answer accuracy was also the same. Whole documents only help when the set is current. In the plain-file arm they put both versions in front of the model, which then hedged or picked the stale one more often. Testing this properly needs long, multi-section documents and Bedrock's hierarchical chunking as well as the default. The harness reports context size per arm, so any accuracy gain shows up next to its token cost.
So the useful question is less GraphRAG against ContextNest and more whether version discipline is something you build into each client's pipeline, or something the knowledge layer does by default and keeps a record of. The two compose: build the graph from a nest's approved, current snapshot and you get both.
Comparison matrix
| Bedrock Knowledge Bases (GraphRAG) | ContextNest engine (ctx, open source) | Community Nest (server + UI) | |
|---|---|---|---|
| Retrieval on these questions | 29/30 after update (k=3) | 28/30 at Bedrock's depth; 30/30 at its own | same engine |
| Plain-English search | vector + graph | full-text; semantic seed selection designed, not shipped | selector-based context endpoint (selector + hops) |
| Paraphrase recall on large corpora | embeddings help (not tested here) | weaker, lexical | same as engine |
| Superseded content after an update | purged on overwrite + re-sync | not retrievable once a new version is published | same |
| Old copies alongside current ones | leaks without a filter (23/30 here); 0/30 with a status metadata filter you maintain | leaks if stored as plain files (24/30 at matched depth here); excluded when modelled as versions | same |
| Version history | not in the index; S3 versioning is available separately | ctx history, ctx reconstruct <doc> <version> | versions endpoint with approved version per document |
| Integrity evidence | CloudTrail logs API calls; no content hash chain | hash-chained versions, ctx verify; ctx drift scan for edits made outside the tool | same, server-side |
| Approval before content becomes canon | not native; any pipeline can gate S3 uploads | single-user publish | review queue with separation of duties enforced server-side |
| Record of what an agent read | not native | no local read log; --agent/--session attribute writes in ctx history | workflow plane records each read for replay (built, behind a feature flag) |
| Graph edges | extracted by an LLM, rebuilt for changed documents | written as [[links]], versioned with the document, followed with --hops | same |
| Data format | AWS-managed index | Markdown + YAML on disk, Obsidian-compatible; engine AGPL-3.0, spec Apache-2.0 | same, with export and import |
| Running cost beyond hosting | Neptune graph floor, LLM extraction on changed documents, query embedding | none | none (self-hosted Community Edition needs a license key) |
Cost
Both systems need hosting. The difference is what sits on top.
- Bedrock graph floor.
- Neptune Analytics is billed per m-NCU-hour while the graph exists. The minimum is 16 m-NCU.
- At the us-east-1 list price we pulled on 2026-10-02 ($0.03 per m-NCU-hour), that is $0.48 an hour, about $350 a month per always-on graph, with no replicas.
- A stopped graph costs less but can't be queried.
- Several clients can share one graph with metadata partitioning, so this is a per-graph figure, not necessarily per client.
- Bedrock per change and per query.
- Each sync runs LLM entity extraction over changed documents and re-embeds them. Ingestion took 46 to 103 seconds per sync for 6 changed documents, including polling for this corpus.
- Vector retrieval embeds each query with the embedding model.
- We did not measure the dollar cost per query.
- ContextNest.
- The CLI and MCP server are free and run locally.
- A team deployment runs Community Nest: a server and a database, self-hosted (Community Edition, license key) or hosted by us.
- There is no graph store to keep provisioned and no extraction model, because edges are written into the documents.
- A publish is a version write, with no re-index step.
What ContextNest adds beyond retrieval
Engine (shipped)
- Every publish is a hash-chained version.
ctx historylists versions andctx reconstructreturns any document at any version. ctx verifychecks the sealed chain.ctx drift scannames files edited outside the tool and turns the edit into a suggestion to approve or reject.- Retrieval returns current published versions. Superseded versions are reachable only through history and reconstruct;
ctx queryreturns drafts only with--include-drafts. - Typed selectors over type, tag, status, pack and node id, plus
--hops Nover authored[[links]]. - Context packs, an MCP server, a Claude Code plugin, and generated agent config files (
CLAUDE.md,.cursorrules,GEMINI.md,copilot-instructions.md,CONTEXT.md).
Community Nest (shipped)
- Stewards per document, tag or nest. Review workflow with separation of duties enforced server-side; agents and public readers see the approved version only.
- Per-nest MCP endpoint with tool deny-lists, and a REST context endpoint taking a selector and hops.
- Block document editor (save-a-version, with locks), threaded comments anchored to text and versions, notifications to Slack, Teams or a webhook, glossary, folders.
- Public read-only nests, sandboxed hosted artifacts, export, Obsidian import, push from a local vault.
Built, not generally available: a plugin framework for source connectors (registry, OAuth for Google, Microsoft and custom providers, scheduled pull, webhooks; in review), and the workflow plane (behind a feature flag).
Not built: real-time co-editing, self-hosted SSO (hosted SSO exists), semantic seed selection (designed, not shipped).
Method
- Corpus.
- 11 short synthetic engineering documents (about 90 to 240 words each) from our paper's fixture: runbooks, standards, ADRs, an architecture overview and onboarding.
- 6 of them have a previous version that differs from the current one in several places. We test 5 contradicting facts per document, so all 30 questions target those 6.
- Frontmatter is stripped, so both systems see identical text.
- One previous version (ADR 0003) states "Status: superseded" in its body; that is the stale answer to one question.
- Questions.
- Each has gold facts that appear only in the current gold document (not in its previous version, not anywhere else) and stale facts that appear only in the previous version.
- The harness checks this before scoring. All 30 passed.
- Facts match on word boundaries after lowercasing.
- Bedrock configuration.
- Knowledge Base on Neptune Analytics, us-east-1, 16 m-NCU, no replicas.
- Titan Text Embeddings V2 (1024 dimensions).
CHUNK_ENTITY_EXTRACTIONon Amazon Nova Pro, chosen because it needs no use-case form.- S3 data source with deletion policy
DELETE. - Default chunking and retrieval, no reranker,
retrieveat k=3 and k=10. - Every object had a
.metadata.jsonsidecar withstatus. - The first sync shows no documents indexed in the manifest because an interrupted earlier start of this run had already indexed the same previous-version files into this Knowledge Base; probes began after that sync completed.
- The full Knowledge Base and data source JSON is in the run manifest.
- ContextNest configuration.
ctx2.6.0, local.- Each document's title is its own first heading, so the title adds nothing Bedrock lacks.
- Changed documents were created as v1 (previous) and updated to v2 (current); both vaults passed
ctx verify. - Results were scored on the body
ctx readserves.
- Arms.
| Arm | What it models |
|---|---|
| Bedrock, previous only | scorer check: stale should fire, current should not |
| Bedrock, after update | same S3 keys overwritten with current versions and re-synced |
| Bedrock, history | previous versions added under archive/ next to current, no filter |
| Bedrock, history + filter | same index, retrieve filtered on status=current |
| ContextNest, versioned | previous = v1, current = v2 of the same document |
| ContextNest, flat | same, plus previous versions as separate plain documents under archive/ |
- Answers.
- Each arm's k=3 context went to Amazon Nova Pro at temperature 0 with the same prompt.
- Mistral Large 3, from a different model family, labelled each answer CURRENT, STALE, HEDGED, MISSING or WRONG against the gold and stale facts.
- Prompts and every answer and verdict ship with the results.
- Repeatability. Each question ran 5 times per arm. Every arm returned identical results across repeats (mean pairwise Jaccard 1.0). This does not test variance from re-indexing.
Limits
- Size. 11 documents, 30 questions on 6 of them. Intervals are wide and the effective sample is closer to 6 than 30.
- Question type. Single-fact lookups. Multi-hop and global questions, where a graph earns its keep, are not tested.
- Lexical overlap. Questions share wording with the documents, which favours ContextNest's full-text search.
- Bedrock defaults. No tuning, no reranker, Nova Pro for extraction. A tuned configuration may do better on retrieval.
- Synthetic data, our questions. Your corpus and your questions are the real test.
How this version differs from our first draft
An earlier draft of this test had scoring errors that our own red-team found:
- ContextNest was scored on its source files rather than its returned text.
- Bedrock's 1 to 2 chunks were compared with ContextNest's 3 whole documents.
- Some "gold facts" also appeared in the old versions.
- Five of the eleven "previous" documents had not actually changed.
This version fixes all four, adds a filtered Bedrock arm and a ContextNest flat arm so both systems are tested with and without their best practice, and adds answer-level grading.
Run it on your corpus
The harness is attached as stale-retrieval-harness-v2.tar.gz; its README covers setup.
- Inputs: current documents, previous versions of the ones that changed, and questions with gold and stale facts. The harness validates them before scoring.
- Commands:
./preflight.sh,./setup.sh,CORPUS=your-corpus python3 run.py all, optionallyrun.py answerwith your own judge, then./teardown.sh. - Where your data goes: only your AWS account and your machine.
- Cost: a run like ours costs a few dollars, mostly graph-hours.