Cookbook / Search and retrieval · Advanced
Context graph for a codebase
An agent working on a codebase asks what replaced a design decision, or which crates build on the parser. The paragraph that matches the question rarely holds the answer: the links between documents do. Build a context graph over a snapshot of Relate's own crates and architecture decision records (ADRs), let IDF-weighted full-text search pick where to start, and let short, bounded graph hops bring in the rest.
The dataset is a frozen snapshot of commit 14ab418, so its crate list and
document sections are the tree at that commit — including the language-harness
crates (relate-tck, relate-cip-eval) that have since moved to the spec
harness (ADR-0520).
The graph has three kinds of source node and one kind of text node. The
Question nodes (dashed) come later, when each question records where its
search started.
Every ADR, and the README, is a Doc split into one Chunk per section
(a long section becomes several). A chunk MENTIONS a crate whose name
appears in its text and CITES every ADR-0072-style reference. SUPERSEDES comes from an ADR's status line
("Superseded in part by ADR-0014"), and DEPENDS_ON from each crate's
Cargo.toml. An ADR that is cited but not in the snapshot becomes a stub,
{vendored: false}, so the graph can still say the storage crate cites
ADR-0077 without having its text.
terms is the searchable text. The program that builds the graph
normalizes titles and section text before indexing: lowercase, stopwords
out, crude suffix stemming ("checkpoints" becomes checkpoint, "storage"
becomes storag). The same function normalizes each question, so both
sides of the search agree. The data blocks hold that output: the distinct
terms of each section and a 160-character excerpt of its text, not the
whole section.
Load the snapshot
Constraints keep crate names, document paths, and chunk ids unique. ADR
numbers get a plain index instead: two ADRs in this snapshot share the
number 0079. One full-text index covers the terms of every chunk, crate,
and document.
Show the setup step
CREATE CONSTRAINT crate_name IF NOT EXISTS FOR (c:Crate) REQUIRE c.name IS UNIQUE;
CREATE CONSTRAINT doc_path IF NOT EXISTS FOR (d:Doc) REQUIRE d.path IS UNIQUE;
CREATE CONSTRAINT chunk_id IF NOT EXISTS FOR (c:Chunk) REQUIRE c.id IS UNIQUE;
CREATE INDEX adr_number IF NOT EXISTS FOR (a:ADR) ON (a.number);
CREATE FULLTEXT INDEX terms IF NOT EXISTS FOR (n:Chunk|Crate|Doc) ON EACH [n.terms](no rows) · indexes added: 2, constraints added: 3
Documents first, so the crates and chunks loaded next can link to them. An
ADR gets a second label, and the three status lines that name a successor
become SUPERSEDES relationships.
Show the setup step
UNWIND $docs AS r
CREATE (d:Doc {path: r.path, title: r.title, terms: r.terms})
SET d.number = r.number, d.status = r.status, d.date = r.date
WITH d, r WHERE r.adr
SET d:ADR, d.vendored = true;
UNWIND $supersedes AS r
MATCH (newer:ADR {number: r.new}), (older:ADR {number: r.old})
MERGE (newer)-[:SUPERSEDES {partial: r.partial}]->(older)(no rows) · nodes created: 41, relationships created: 3, properties set: 284, labels added: 81
Crates and their dependencies. A crate's Cargo.toml comments cite ADRs
too; MERGE … ON CREATE SET finds the vendored ADR or creates its stub.
Show the setup step
UNWIND $crates AS r
CREATE (:Crate {name: r.name, description: r.description, terms: r.terms});
UNWIND $deps AS r
MATCH (a:Crate {name: r.src}), (b:Crate {name: r.dst})
MERGE (a)-[:DEPENDS_ON {kind: r.kind}]->(b);
UNWIND $crate_cites AS r
MATCH (k:Crate {name: r.src})
MERGE (a:ADR {number: r.adr})
ON CREATE SET a.title = 'ADR-' + r.adr + ' (not vendored)', a.vendored = false
MERGE (k)-[:CITES]->(a)(no rows) · nodes created: 27, relationships created: 75, properties set: 153, labels added: 27
Then the 277 sections. Each row carries the crate names and ADR numbers its
text mentions; two CALL subqueries turn them into relationships, creating
stubs as needed.
Show the setup step
UNWIND $chunks AS r
MATCH (d:Doc {path: r.doc})
CREATE (d)-[:HAS_CHUNK]->(c:Chunk {id: r.id, ord: r.ord, heading: r.heading,
text: r.text, terms: r.terms})
WITH c, r
CALL (c, r) {
UNWIND r.crates AS name
MATCH (k:Crate {name: name})
MERGE (c)-[:MENTIONS]->(k)
}
CALL (c, r) {
UNWIND r.cites AS n
MERGE (a:ADR {number: n})
ON CREATE SET a.title = 'ADR-' + n + ' (not vendored)', a.vendored = false
MERGE (c)-[:CITES]->(a)
};
RETURN COUNT { (:Crate) } AS crates, COUNT { (:Doc) } AS docs,
COUNT { (a:ADR) WHERE NOT a.vendored } AS stubs, COUNT { (:Chunk) } AS chunks,
COUNT { ()-[:DEPENDS_ON]->() } AS depends_on, COUNT { ()-[:CITES]->() } AS cites,
COUNT { ()-[:MENTIONS]->() } AS mentions,
COUNT { ()-[:SUPERSEDES]->() } AS supersedes| crates | docs | stubs | chunks | depends_on | cites | mentions | supersedes |
|---|---|---|---|---|---|---|---|
| 24 | 41 | 33 | 277 | 72 | 302 | 78 | 3 |
1 row · nodes created: 307, relationships created: 654, properties set: 1475, labels added: 307
| crates | docs | stubs | chunks | depends_on | cites | mentions | supersedes | |--------|------|-------|--------|------------|-------|----------|------------| | 24 | 41 | 33 | 277 | 72 | 302 | 78 | 3 | 1 row · nodes created: 307, relationships created: 654, properties set: 1475, labels added: 307
Where to start: full-text seeds
Take the question "What did the WAL-first checkpoint decision supersede,
and was it itself superseded later?" Normalized, it is wal first checkpoint decision supersed lat. The structural words (decision,
supersed, lat) name relationships, not text, so they choose which graph
hop runs later and stay out of the search. Three terms remain:
A plain SEARCH for all three ranks every node that contains any of them by
BM25, the score Neo4j's full-text index gives: a term few nodes contain
counts for more, and a hit in a short text counts for more than one in a
long text. The top eight, with the terms each one contains:
WITH reduce(q = head($terms), t IN tail($terms) | q + ' ' + t) AS query
MATCH (n) SEARCH n IN (FULLTEXT INDEX terms FOR query LIMIT 8) SCORE AS score
OPTIONAL MATCH (doc:Doc)-[:HAS_CHUNK]->(n)
RETURN round(score, 2) AS score,
CASE WHEN n:Crate THEN 'Crate' WHEN n:Chunk THEN 'Chunk' ELSE 'Doc' END AS kind,
coalesce(n.name, doc.title + ' § ' + n.heading, n.title) AS label,
[t IN $terms WHERE t IN split(n.terms, ' ')] AS matched
ORDER BY score DESC, labelParameters
| score | kind | label | matched |
|---|---|---|---|
| 3.65 | "Doc" | "0012. WAL-first durability with Parquet checkpoints" | ["wal", "first", "checkpoint"] |
| 3.25 | "Chunk" | "0012. WAL-first durability with Parquet checkpoints § Preamble" | ["wal", "first", "checkpoint"] |
| 2.37 | "Chunk" | "0044. Withdraw engine-level S3 § Context" | ["wal", "first", "checkpoint"] |
| 2.06 | "Chunk" | "0072. Synchronous virtual filesystem for WAL, snapshots, and CDC § Alternatives considered" | ["wal", "first", "checkpoint"] |
| 2.05 | "Doc" | "0020. Checkpoint at a frozen LSN; writers append a new WAL segment" | ["wal", "checkpoint"] |
| 1.9 | "Chunk" | "0022. Checkpoint shards are byte-budgeted id windows (copy-on-write) § Consequences" | ["first", "checkpoint"] |
| 1.86 | "Chunk" | "0020. Checkpoint at a frozen LSN; writers append a new WAL segment § Preamble" | ["wal", "checkpoint"] |
| 1.83 | "Chunk" | "0018. Snapshot reads do not exclude apply_redo § Decision" | ["wal", "first", "checkpoint"] |
8 rows
| score | kind | label | matched | |-------|---------|----------------------------------------------------------------------------------------------|--------------------------------| | 3.65 | "Doc" | "0012. WAL-first durability with Parquet checkpoints" | ["wal", "first", "checkpoint"] | | 3.25 | "Chunk" | "0012. WAL-first durability with Parquet checkpoints § Preamble" | ["wal", "first", "checkpoint"] | | 2.37 | "Chunk" | "0044. Withdraw engine-level S3 § Context" | ["wal", "first", "checkpoint"] | | 2.06 | "Chunk" | "0072. Synchronous virtual filesystem for WAL, snapshots, and CDC § Alternatives considered" | ["wal", "first", "checkpoint"] | | 2.05 | "Doc" | "0020. Checkpoint at a frozen LSN; writers append a new WAL segment" | ["wal", "checkpoint"] | | 1.9 | "Chunk" | "0022. Checkpoint shards are byte-budgeted id windows (copy-on-write) § Consequences" | ["first", "checkpoint"] | | 1.86 | "Chunk" | "0020. Checkpoint at a frozen LSN; writers append a new WAL segment § Preamble" | ["wal", "checkpoint"] | | 1.83 | "Chunk" | "0018. Snapshot reads do not exclude apply_redo § Decision" | ["wal", "first", "checkpoint"] | 8 rows
ADR-0012's title leads, and its preamble follows. Below them, text length
decides as much as coverage: the ADR-0020 title and two sections hold two of
the three terms and still outrank the ADR-0018 section, which holds all
three. The procedure db.index.fulltext.queryNodes('terms', 'wal first checkpoint') returns the same ranking. A seed for this question should
cover its words, with the rare ones counting for more, and two refinements
give that. Each term is weighted by its inverse document frequency,
log(N / df), where df is the number of nodes the term hits, and a node
scores the sum of the weights of the terms it contains, whatever its
length. A hit on a title or a crate name counts 1.5 times a hit in body
text.
One SEARCH per term gives each term's hits, so its df is the size of
that list. The search LIMIT must cover every hit, because SEARCH cuts
before anything else runs:
MATCH (x) WHERE x:Chunk OR x:Crate OR x:Doc
WITH count(x) AS total
UNWIND $terms AS t
MATCH (n) SEARCH n IN (FULLTEXT INDEX terms FOR t LIMIT 1000) SCORE AS s
WITH total, t, collect(n) AS hits
WITH t, log(toFloat(total) / size(hits)) AS idf, hits
UNWIND hits AS n
WITH n, sum(idf) * CASE WHEN n:Chunk THEN 1.0 ELSE 1.5 END AS score,
collect(t) AS matched
OPTIONAL MATCH (doc:Doc)-[:HAS_CHUNK]->(n)
RETURN round(score, 2) AS score,
CASE WHEN n:Crate THEN 'Crate' WHEN n:Chunk THEN 'Chunk' ELSE 'Doc' END AS kind,
coalesce(n.name, doc.title + ' § ' + n.heading, n.title) AS label,
matched
ORDER BY score DESC, coalesce(n.name, n.id, n.path)
LIMIT $kParameters
| score | kind | label | matched |
|---|---|---|---|
| 7.75 | "Doc" | "0012. WAL-first durability with Parquet checkpoints" | ["wal", "first", "checkpoint"] |
| 5.16 | "Chunk" | "0005. Labeled property graph stored as columnar node and relationship Parquet tables § Later references" | ["wal", "first", "checkpoint"] |
| 5.16 | "Chunk" | "0012. WAL-first durability with Parquet checkpoints § Preamble" | ["wal", "first", "checkpoint"] |
| 5.16 | "Chunk" | "0012. WAL-first durability with Parquet checkpoints § Decision" | ["wal", "first", "checkpoint"] |
| 5.16 | "Chunk" | "0018. Snapshot reads do not exclude apply_redo § Decision" | ["wal", "first", "checkpoint"] |
| 5.16 | "Chunk" | "0020. Checkpoint at a frozen LSN; writers append a new WAL segment § Decision" | ["wal", "first", "checkpoint"] |
6 rows
| score | kind | label | matched | |-------|---------|-----------------------------------------------------------------------------------------------------------|--------------------------------| | 7.75 | "Doc" | "0012. WAL-first durability with Parquet checkpoints" | ["wal", "first", "checkpoint"] | | 5.16 | "Chunk" | "0005. Labeled property graph stored as columnar node and relationship Parquet tables § Later references" | ["wal", "first", "checkpoint"] | | 5.16 | "Chunk" | "0012. WAL-first durability with Parquet checkpoints § Preamble" | ["wal", "first", "checkpoint"] | | 5.16 | "Chunk" | "0012. WAL-first durability with Parquet checkpoints § Decision" | ["wal", "first", "checkpoint"] | | 5.16 | "Chunk" | "0018. Snapshot reads do not exclude apply_redo § Decision" | ["wal", "first", "checkpoint"] | | 5.16 | "Chunk" | "0020. Checkpoint at a frozen LSN; writers append a new WAL segment § Decision" | ["wal", "first", "checkpoint"] | 6 rows
Across the 342 indexed nodes, wal hits 85, checkpoint 69, and first
39, so first weighs the most (2.17, against 1.60 and 1.39). ADR-0012's
title leads at 7.75, and every section below it contains all three terms;
which five of them make the cut is decided by their ids, so treat the tail
as a list, not a ranking.
Anchor each question
Each question takes the same path: its content words find seeds, the best seeds become anchors, and its structural words choose which bounded hops run from those anchors.
A seed is only a starting point. A seed within 85% of the best score becomes an anchor: a document or crate that a bounded hop starts from. A section anchors the document it belongs to, and the README, which touches everything, never anchors. Four questions, normalized the same way:
The seed ranking from the step above runs once per question in a CALL
subquery, and each question is written down with an ANCHOR relationship to
each anchor. Logging where a retrieval started lets the answer cite it and
lets a reviewer replay it; it also keeps the hops below short. The first
statement clears earlier questions, so the step can be run again.
MATCH (old:Question) DETACH DELETE old;
MATCH (x) WHERE x:Chunk OR x:Crate OR x:Doc
WITH count(x) AS total
UNWIND $questions AS q
CALL (q, total) {
UNWIND q.terms AS t
MATCH (n) SEARCH n IN (FULLTEXT INDEX terms FOR t LIMIT 1000) SCORE AS s
WITH t, collect(n) AS hits
WITH log(toFloat(total) / size(hits)) AS idf, hits
UNWIND hits AS n
WITH n, sum(idf) * CASE WHEN n:Chunk THEN 1.0 ELSE 1.5 END AS score
RETURN n, score
ORDER BY score DESC, coalesce(n.name, n.id, n.path)
LIMIT $k
}
WITH q, collect({n: n, score: score}) AS seeds, max(score) AS best
UNWIND seeds AS s
WITH q, s.n AS n WHERE s.score >= $ratio * best
OPTIONAL MATCH (doc:Doc)-[:HAS_CHUNK]->(n)
WITH q, CASE WHEN n:Chunk THEN doc ELSE n END AS anchor
WHERE anchor.path IS NULL OR anchor.path <> 'README.md'
WITH q, collect(DISTINCT anchor) AS anchors
CREATE (question:Question {id: q.id, text: q.text})
FOREACH (a IN anchors | CREATE (question)-[:ANCHOR]->(a))
RETURN q.id AS question, [a IN anchors | coalesce(a.name, a.title)] AS anchorsParameters
| question | anchors |
|---|---|
| "wal-first" | ["0012. WAL-first durability with Parquet checkpoints"] |
| "storage" | ["relate-storage"] |
| "parser" | ["0007. Cypher is parsed with a hand-written recursive-descent parser", "0046. Relate parser in front of the MemoryDB runtime", "relate-front", "relate-parser"] |
| "wasm" | ["0065. Withdraw the WASM and Swift surfaces"] |
4 rows · nodes created: 4, relationships created: 7, properties set: 8, labels added: 4
| question | anchors | |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | "wal-first" | ["0012. WAL-first durability with Parquet checkpoints"] | | "storage" | ["relate-storage"] | | "parser" | ["0007. Cypher is parsed with a hand-written recursive-descent parser", "0046. Relate parser in front of the MemoryDB runtime", "relate-front", "relate-parser"] | | "wasm" | ["0065. Withdraw the WASM and Swift surfaces"] | 4 rows · nodes created: 4, relationships created: 7, properties set: 8, labels added: 4
Each question gets one ADR or one crate, except the parser question: two ADRs and two crates tie for its single term.
Was the WAL-first decision superseded?
The anchor is ADR-0012. Its supersession history is a chain, and the
question asks about both ends of it: what 0012 replaced, and what replaced
0012. Two quantified path patterns grow the chain from the anchor in both
directions, at most three hops each, and the EXISTS checks keep only the
whole chain, not its pieces. Open the graph view:
MATCH (:Question {id: 'wal-first'})-[:ANCHOR]->(d:ADR)
MATCH p = (newer:ADR)-[:SUPERSEDES]->{0,3}(d)-[:SUPERSEDES]->{0,3}(older:ADR)
WHERE length(p) > 0
AND NOT EXISTS { (:ADR)-[:SUPERSEDES]->(newer) }
AND NOT EXISTS { (older)-[:SUPERSEDES]->(:ADR) }
RETURN p// 1 row
(n8:Doc:ADR {date: "2026-08-28", number: "0014", path: "adr/0014-single-wal-write-path.md", status: "Accepted", terms: "singl writ path wal snapshot graph materializ", title: "0014. Single write path: WAL then snapshot; graph is a materialization", vendored: true})-[r2:SUPERSEDES {partial: true}]->(n6:Doc:ADR {date: "2026-08-28", number: "0012", path: "adr/0012-wal-and-parquet-checkpoints.md", status: "Superseded in part by ADR-0014", terms: "wal first durability parquet checkpoint", title: "0012. WAL-first durability with Parquet checkpoints", vendored: true})-[r0:SUPERSEDES {partial: true}]->(n1:Doc:ADR {date: "2026-08-28", number: "0004", path: "adr/0004-memory-first-parquet-object-store.md", status: "Superseded in part by ADR-0012", terms: "memory first graph parquet snapshot object stor", title: "0004. Memory-first graph with Parquet snapshots via object_store", vendored: true})ADR-0012 replaced part of ADR-0004 (snapshot-only durability), and ADR-0014 later replaced part of 0012. The text search alone found 0012 and some sections about checkpoints; neither 0004 nor 0014 was among the seeds.
What depends on the storage crate?
The single term storag anchors the crate itself. Its dependents follow
DEPENDS_ON backwards, counting only normal (not dev or build)
dependencies, and its citations follow CITES forwards. UNION puts both
in one table:
MATCH (:Question {id: 'storage'})-[:ANCHOR]->(k:Crate)
CALL (k) {
MATCH (dep:Crate)-[:DEPENDS_ON {kind: 'normal'}]->(k)
RETURN dep.name AS from, 'DEPENDS_ON' AS rel, k.name AS to, null AS vendored
UNION
MATCH (k)-[:CITES]->(a:ADR)
RETURN k.name AS from, 'CITES' AS rel, a.title AS to, a.vendored AS vendored
}
RETURN from, rel, to, vendored
ORDER BY rel DESC, from, to| from | rel | to | vendored |
|---|---|---|---|
| "relate" | "DEPENDS_ON" | "relate-storage" | null |
| "relate-datasets" | "DEPENDS_ON" | "relate-storage" | null |
| "relate-storage" | "CITES" | "ADR-0077 (not vendored)" | false |
| "relate-storage" | "CITES" | "ADR-0085 (not vendored)" | false |
4 rows
| from | rel | to | vendored | |-------------------|--------------|---------------------------|----------| | "relate" | "DEPENDS_ON" | "relate-storage" | null | | "relate-datasets" | "DEPENDS_ON" | "relate-storage" | null | | "relate-storage" | "CITES" | "ADR-0077 (not vendored)" | false | | "relate-storage" | "CITES" | "ADR-0085 (not vendored)" | false | 4 rows
Both ADRs the crate cites are stubs: the snapshot does not include them, but the graph still records that the crate depends on those decisions.
What builds on the parser?
This question has four anchors: two crates (relate-parser,
relate-front) and two ADRs (0007, the parser design, and 0046). The
same hop shape works for both kinds of anchor: crates bring their
dependents, and ADRs bring what their sections cite.
MATCH (:Question {id: 'parser'})-[:ANCHOR]->(a)
CALL (a) {
MATCH (dep:Crate)-[:DEPENDS_ON {kind: 'normal'}]->(a)
RETURN dep.name AS from, 'DEPENDS_ON' AS rel, a.name AS to
UNION
MATCH (a)-[:HAS_CHUNK]->(:Chunk)-[:CITES]->(c:ADR)
WHERE c <> a
RETURN a.title AS from, 'CITES' AS rel, c.title AS to
}
RETURN from, rel, to
ORDER BY rel DESC, from, to| from | rel | to |
|---|---|---|
| "relate" | "DEPENDS_ON" | "relate-front" |
| "relate" | "DEPENDS_ON" | "relate-parser" |
| "relate-error" | "DEPENDS_ON" | "relate-parser" |
| "relate-executor" | "DEPENDS_ON" | "relate-front" |
| "relate-executor" | "DEPENDS_ON" | "relate-parser" |
| "relate-front" | "DEPENDS_ON" | "relate-parser" |
| "relate-ir" | "DEPENDS_ON" | "relate-parser" |
| "relate-load" | "DEPENDS_ON" | "relate-parser" |
| "relate-planner" | "DEPENDS_ON" | "relate-front" |
| "relate-planner" | "DEPENDS_ON" | "relate-parser" |
| "0007. Cypher is parsed with a hand-written recursive-descent parser" | "CITES" | "0056. OpenCypher TCK is the Cypher language contract" |
| "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "0061. CI-host loadtest baselines (two host classes)" |
| "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "0070. Docker baseline loadtest environment (2 vCPU / 2 GB)" |
| "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "ADR-0066 (not vendored)" |
| "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "ADR-0071 (not vendored)" |
15 rows
| from | rel | to | |-----------------------------------------------------------------------|--------------|--------------------------------------------------------------| | "relate" | "DEPENDS_ON" | "relate-front" | | "relate" | "DEPENDS_ON" | "relate-parser" | | "relate-error" | "DEPENDS_ON" | "relate-parser" | | "relate-executor" | "DEPENDS_ON" | "relate-front" | | "relate-executor" | "DEPENDS_ON" | "relate-parser" | | "relate-front" | "DEPENDS_ON" | "relate-parser" | | "relate-ir" | "DEPENDS_ON" | "relate-parser" | | "relate-load" | "DEPENDS_ON" | "relate-parser" | | "relate-planner" | "DEPENDS_ON" | "relate-front" | | "relate-planner" | "DEPENDS_ON" | "relate-parser" | | "0007. Cypher is parsed with a hand-written recursive-descent parser" | "CITES" | "0056. OpenCypher TCK is the Cypher language contract" | | "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "0061. CI-host loadtest baselines (two host classes)" | | "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "0070. Docker baseline loadtest environment (2 vCPU / 2 GB)" | | "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "ADR-0066 (not vendored)" | | "0046. Relate parser in front of the MemoryDB runtime" | "CITES" | "ADR-0071 (not vendored)" | 15 rows
What did the WASM withdrawal cite?
The anchor is ADR-0065, "Withdraw the WASM and Swift surfaces". Citations run through sections, two hops each way: what 0065's sections cite, and which ADRs cite 0065.
MATCH (:Question {id: 'wasm'})-[:ANCHOR]->(d:ADR)
CALL (d) {
MATCH (d)-[:HAS_CHUNK]->(:Chunk)-[:CITES]->(a:ADR)
WHERE a <> d
RETURN 'cites' AS direction, a AS other
UNION
MATCH (d)<-[:CITES]-(:Chunk)<-[:HAS_CHUNK]-(b:ADR)
WHERE b <> d
RETURN 'cited by' AS direction, b AS other
}
RETURN d.number AS adr, direction, other.title AS other, other.status AS status,
other.vendored AS vendored
ORDER BY direction DESC, other| adr | direction | other | status | vendored |
|---|---|---|---|---|
| "0065" | "cites" | "0051. WASM product path is Database `:memory:`" | "Accepted" | true |
| "0065" | "cites" | "0057. Embedded C ABI is the client boundary; Bolt is optional remote transport" | "Accepted" | true |
| "0065" | "cites" | "ADR 0052: GraphStore withdrawn" | null | true |
| "0065" | "cites" | "ADR-0084 (not vendored)" | null | false |
| "0065" | "cited by" | "0051. WASM product path is Database `:memory:`" | "Accepted" | true |
5 rows
| adr | direction | other | status | vendored | |--------|------------|----------------------------------------------------------------------------------|------------|----------| | "0065" | "cites" | "0051. WASM product path is Database `:memory:`" | "Accepted" | true | | "0065" | "cites" | "0057. Embedded C ABI is the client boundary; Bolt is optional remote transport" | "Accepted" | true | | "0065" | "cites" | "ADR 0052: GraphStore withdrawn" | null | true | | "0065" | "cites" | "ADR-0084 (not vendored)" | null | false | | "0065" | "cited by" | "0051. WASM product path is Database `:memory:`" | "Accepted" | true | 5 rows
ADR-0051, "WASM product path is Database :memory:", is the decision that
was withdrawn. Its status line still says Accepted; only the citation in
both directions shows the withdrawal. ADR-0084 is a stub here, cited but
not in the snapshot. In the live repository it reinstates the WASM build,
which is the engine running the steps on this page.
Hand the evidence to a model
With an LLM in the loop, the companion program sends the seed sections and the edges above to the model and asks for an answer that cites them. The most useful text is often the sections that sit on the path the graph found. For the WAL-first chain, those are the sections of each ADR that cite another ADR on the chain:
MATCH (:Question {id: 'wal-first'})-[:ANCHOR]->(d:ADR)
MATCH p = (newer:ADR)-[:SUPERSEDES]->{0,3}(d)-[:SUPERSEDES]->{0,3}(older:ADR)
WHERE length(p) > 0
AND NOT EXISTS { (:ADR)-[:SUPERSEDES]->(newer) }
AND NOT EXISTS { (older)-[:SUPERSEDES]->(:ADR) }
WITH nodes(p) AS chain
UNWIND chain AS a
MATCH (a)-[:HAS_CHUNK]->(c:Chunk)-[:CITES]->(b:ADR)
WHERE b IN chain
RETURN a.number AS adr, c.heading AS section, b.number AS cites, c.text AS excerpt
ORDER BY adr, c.ord, cites| adr | section | cites | excerpt |
|---|---|---|---|
| "0004" | "Preamble" | "0012" | "# 0004. Memory-first graph with Parquet snapshots via object_store - **Status**: Superseded in part by ADR-0012 - **Date**: 2026-08-28 - **Deciders**: MemoryDB …" |
| "0004" | "Later references" | "0012" | "ADR-0012 replaces snapshot-only durability. Execution is still an in-memory LPG; `object_store` still addresses `file://` and `s3://`; out-of-core Parquet …" |
| "0004" | "Later references" | "0014" | "ADR-0012 replaces snapshot-only durability. Execution is still an in-memory LPG; `object_store` still addresses `file://` and `s3://`; out-of-core Parquet …" |
| "0012" | "Preamble" | "0014" | "# 0012. WAL-first durability with Parquet checkpoints - **Status**: Superseded in part by ADR-0014 - **Date**: 2026-08-28 - **Deciders**: MemoryDB maintainers" |
| "0012" | "Context" | "0004" | "ADR-0004 made the in-memory graph the source of execution and Parquet snapshots the only durable form. A crash or process exit without an explicit …" |
| "0012" | "Alternatives considered" | "0004" | "- **Keep snapshot-only durability (ADR-0004)** — simple, loses uncheckpointed writes. Rejected; persistent URIs need commit durability. - **WAL as S3 objects** …" |
| "0012" | "Consequences" | "0004" | "- Open of a persistent URI is snapshot load plus WAL replay. - `s3://` without a writable local WAL directory cannot offer fsync-per-commit. - Manifest …" |
| "0012" | "Later references" | "0014" | "ADR-0014: `:memory:` uses the same WAL + snapshot path (in-process store). The live graph is materialized from Parquet + WAL replay; commits are WAL append …" |
| "0014" | "Context" | "0012" | "ADR-0012 added a WAL for persistent URIs but left several write paths: `:memory:` skipped the WAL; Cypher mutated the live graph then optionally fsynced; …" |
| "0014" | "Consequences" | "0012" | "- Crash recovery and “open an existing URI” are the same function. - Loadtests with `--uri :memory:` still hit the WAL (volatile). - ADR-0012’s “`:memory:` has …" |
10 rows
| adr | section | cites | excerpt | |--------|---------------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | "0004" | "Preamble" | "0012" | "# 0004. Memory-first graph with Parquet snapshots via object_store - **Status**: Superseded in part by ADR-0012 - **Date**: 2026-08-28 - **Deciders**: MemoryDB …" | | "0004" | "Later references" | "0012" | "ADR-0012 replaces snapshot-only durability. Execution is still an in-memory LPG; `object_store` still addresses `file://` and `s3://`; out-of-core Parquet …" | | "0004" | "Later references" | "0014" | "ADR-0012 replaces snapshot-only durability. Execution is still an in-memory LPG; `object_store` still addresses `file://` and `s3://`; out-of-core Parquet …" | | "0012" | "Preamble" | "0014" | "# 0012. WAL-first durability with Parquet checkpoints - **Status**: Superseded in part by ADR-0014 - **Date**: 2026-08-28 - **Deciders**: MemoryDB maintainers" | | "0012" | "Context" | "0004" | "ADR-0004 made the in-memory graph the source of execution and Parquet snapshots the only durable form. A crash or process exit without an explicit …" | | "0012" | "Alternatives considered" | "0004" | "- **Keep snapshot-only durability (ADR-0004)** — simple, loses uncheckpointed writes. Rejected; persistent URIs need commit durability. - **WAL as S3 objects** …" | | "0012" | "Consequences" | "0004" | "- Open of a persistent URI is snapshot load plus WAL replay. - `s3://` without a writable local WAL directory cannot offer fsync-per-commit. - Manifest …" | | "0012" | "Later references" | "0014" | "ADR-0014: `:memory:` uses the same WAL + snapshot path (in-process store). The live graph is materialized from Parquet + WAL replay; commits are WAL append …" | | "0014" | "Context" | "0012" | "ADR-0012 added a WAL for persistent URIs but left several write paths: `:memory:` skipped the WAL; Cypher mutated the live graph then optionally fsynced; …" | | "0014" | "Consequences" | "0012" | "- Crash recovery and “open an existing URI” are the same function. - Loadtests with `--uri :memory:` still hit the WAL (volatile). - ADR-0012’s “`:memory:` has …" | 10 rows
The "Later references" section of ADR-0012 names its successor in the first words; that sentence, and the chain it sits on, is what a cited answer needs.
About the data
- Dataset
- Relate at commit 14ab418: 26 crates and their 76 internal dependencies, the README and 40 ADRs split into 277 sections (normalized terms and a 160-character excerpt each), and 3 supersessions (203 KB)
- Source
- Relate repository at commit 14ab418 (README.md, every crate's Cargo.toml, 40 of its ADRs), vendored by the examples project's 02-context-graph/vendor.sh and flattened to JSON with that example's build.py chunking and text.py normalizer, then name-updated by ADR-0517's rename
- License
- Apache-2.0
Every data block is published beside this page as data/<name>.json, and the whole recipe as markdown.
More in Search and retrieval
- Hybrid recall: vector + full-text + RRFFuse vector and full-text SEARCH by reciprocal rank in one Cypher statement, then hop from each recalled note to its topic's latest decision.
- Query the relate docsFind entry points in the relate docs graph with vector SEARCH, then let the graph expand them into context, scope the search by a traversal, and reach the capability and its worked example.