Cookbook
All recipes
Context graph for a codebaseSearch and retrieval

Agent memory

Search and retrieval

Data quality

Graph analytics

Geospatial

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.

Data model: crates, documents and their sections, and the links between themCrate nodes DEPENDS_ON other crates and CITES ADR documents (Doc:ADR). Each ADR HAS_CHUNK sections; a Chunk MENTIONS crates and CITES ADRs, and an ADR SUPERSEDES an earlier one, sometimes only in part. A Question is linked by dashed, derived ANCHOR relationships to the ADRs and crates it starts from.Questionid · textDoc:ADRpath · titlenumber · statusvendoredtermsCratenamedescriptiontermsChunkid · headingtext · termsANCHORANCHORHAS_CHUNKMENTIONSCITESCITESDEPENDS_ONkindSUPERSEDESpartial
Data model: crates, documents and their sections, and the links between themCrate nodes DEPENDS_ON other crates and CITES ADR documents (Doc:ADR). Each ADR HAS_CHUNK sections; a Chunk MENTIONS crates and CITES ADRs, and an ADR SUPERSEDES an earlier one, sometimes only in part. A Question is linked by dashed, derived ANCHOR relationships to the ADRs and crates it starts from.Questionid · textDoc:ADRpath · titlenumber · statusvendoredtermsCratenamedescriptiontermsChunkid · headingtext · termsANCHORANCHORHAS_CHUNKMENTIONSCITESCITESDEPENDS_ONkindSUPERSEDESpartial
Data model: crates, documents and their sections, and the links between them

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.

Step 1
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]
Recorded result
(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.

Step 2
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)
Loads docs.json (10 KB)
Recorded result
(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.

Step 3
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)
Loads crates.json (8 KB)
Recorded result
(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.

Step 4
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
Loads chunks.json (183 KB)
Recorded result
cratesdocsstubschunksdepends_oncitesmentionssupersedes
24413327772302783

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:

Step 5
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, label
Parameters
Recorded result
scorekindlabelmatched
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:

Step 6
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 $k
Parameters
Recorded result
scorekindlabelmatched
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.

From a question to evidence: seeds, anchors, and bounded hopsA question is normalized and split into content terms and structural words. Each content term runs one SEARCH weighted by IDF, and the seeds within 85% of the best become the anchors, while the structural words choose which hops to follow: supersedes, dependents or cites. A bounded expansion from the anchors, up to three supersession steps and one or two hops, gathers the evidence for the answer.

content terms

structural words

Question

Normalize and split
content terms · structural words

One SEARCH per term
weighted by IDF

Anchors
within 85% of the best seed

Choose the hops
supersedes · dependents · cites

Bounded expansion
supersession {0,3} · 1–2 hops

Evidence for the answer

From a question to evidence: seeds, anchors, and bounded hopsA question is normalized and split into content terms and structural words. Each content term runs one SEARCH weighted by IDF, and the seeds within 85% of the best become the anchors, while the structural words choose which hops to follow: supersedes, dependents or cites. A bounded expansion from the anchors, up to three supersession steps and one or two hops, gathers the evidence for the answer.

content terms

structural words

Question

Normalize and split
content terms · structural words

One SEARCH per term
weighted by IDF

Anchors
within 85% of the best seed

Choose the hops
supersedes · dependents · cites

Bounded expansion
supersession {0,3} · 1–2 hops

Evidence for the answer

From a question to evidence: seeds, anchors, and bounded hops

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.

Step 7
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 anchors
Parameters
Recorded result
questionanchors
"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:

Step 8
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
Recorded result
// 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:

Step 9
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
Recorded result
fromreltovendored
"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.

Step 10
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
Recorded result
fromrelto
"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.

Step 11
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
Recorded result
adrdirectionotherstatusvendored
"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"nulltrue
"0065""cites""ADR-0084 (not vendored)"nullfalse
"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:

Step 12
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
Recorded result
adrsectioncitesexcerpt
"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