A Forward Pass on the Graph
A forward pass, not a walk — and what had to be deleted before the retrieval design could be explained in one direction.

The words “forward pass” are borrowed, and the borrowing is the point.
In a transformer, a forward pass is the whole of the work. Input enters at the bottom, passes up through the layers once, and an output leaves at the top. Nothing loops back. Each layer’s contribution is separable, so you can measure what each one did — and delete the ones that did nothing.
We took that shape and pointed it at a knowledge store instead of a tensor.
Ask a question and it is embedded, routed to a region of the store, matched against what is actually held there, checked against a floor, and either answered or refused. Once. In one direction. Nothing in that sequence writes back into the store.
It is a pass, not a walk. That distinction is the design, and this is the argument for it.
Why not a walk
Nearly every serious retrieval system built on a knowledge graph is a walk. GraphRAG has a model extract entities and relations, then traverses them. Agentic retrieval expands, reflects, re-queries. Even ordinary RAG does a version of it: retrieve, re-rank, then retrieve again with whatever the first result suggested.
Walks are intuitive. They also compound. Each step reads the output of the last, so an error does not stay local, and the more stages you stack the harder it is to say which one produced the answer you got.
We built the walk. For about a year this system had one — phase-coupled nodes, a path walk across a thirteen-node graph, cascade weighting, additive bonuses, and a re-ranking pass in the synthesizer. It was the interesting part of the architecture, and it is the part we measured hardest.
The measurements went one way:
- Every additive term — affinity, type, recency, length, a confidence bump — lowered hit@10, the share of queries whose known right entry lands in the top ten. Zeroing all of them scored 0.995–1.000, against 0.750 for the live configuration.
- The cross-node multiplier of 0.3 was a hard partition, not a soft prior: 0.3, 0.5 and 0.7 produced byte-identical orderings. Removing it lifted known-entry hit@10 from 0.750 to 1.000 on the live dictionary.
- The synthesizer’s re-rank was the only piece of the walk a chat answer ever actually saw. Turning it off lifted hit@10 from 0.579 to 1.000, and blind-judge useful retrieval from 30/43 to 37/43 — seven gains, no losses, and 96 entries rescued from displacement.
- The phase engine underneath could not hold a configuration at all: 0 of 33 edges lockable, its state erased within roughly five passes, and a frozen rotor retrieving identically. It was computing something no reader consumed.
So the walk came out. What replaced it was not a better walk. It was the observation that the right answer was already near the front of the raw ordering, and everything we had stacked on top of it was noise with good manners.
What “the graph” is here
Worth being precise, because the phrase usually means something else.
This is not an entity–relation graph. No model reads your documents and extracts triples. That approach makes the model the author of your knowledge, and it puts a generation between the text you gave it and the answer it gives back.
The graph here is the store’s own shape. Documents sit at the top. Their chunks are parents. Entries — phrases with a vector and a node — are the leaves, and each one carries provenance edges back to the chunk and document it came from. Above them sits a routing layer of thirteen anchor directions. Alongside them, computed offline, are bridge edges linking chunks in different domains that sit close together in vector space.
The forward pass moves through that structure. It reads the routing layer, then the entry layer, and every entry it touches can name its parents and its source. The bridge edges are injected as an optional enrichment surface — a small reader’s route to a cross-domain link — rather than as a routing decision, because routing on those edges was falsified: the engine grounds, the reader abstracts.
So the graph is where answers come from and where the footnote lives. The pass is how you get in and out of it.
The pass, in order
1. The question becomes a point. Encoded with nomic-embed-text-v1.5 into 768 dimensions and normalised onto the unit sphere.
2. It is routed, not searched. Resonance is measured against each of the thirteen anchors as geodesic distance, 1 − arccos(v·a)/π, not cosine. This matters: arccos is nonlinear, and two vectors at cosine 0.95 and 0.90 are 42% apart in arc length. The routing takes the argmax and calls it winnerNode.
The important word is label. The winning node says where in the store the question is standing. It is not a score, and it never multiplies one — that was the multiplier’s job, and the multiplier was the single largest source of loss.
3. The candidate set comes from an index, not a scan. One HNSW search over LanceDB, scoped to the cube the question landed in — fifty candidates by default. At a million vectors that index returns about 99.8% of the true top ten. A scan of the full dictionary would return all of it, and take seconds doing so. A second, flat search across every ready cube lets specialist stores contribute candidates on the same score, with no home-field advantage; the two streams merge on similarity alone.
4. Everything is scored the same way. Raw geodesic similarity between the question and the entry’s own document vector. One number, sorted descending. finalScore is querySim; the field survives because downstream code reads it, but it is not a second ranking axis. Entries below an adaptive floor, clamped to 0.55–0.65 and calibrated from the embedder’s own distribution, are dropped at the moment of comparison rather than allocated and thrown away later.
5. Precision is spent only where it is needed. A cross-encoder re-ranker can reorder candidates, but only in the band where the bi-encoder genuinely cannot discriminate. On the hosted instance that band is top similarity between 0.72 and 0.80, and it scores the top eight. Above 0.80 the ordering is already authoritative; below 0.72 there is nothing worth reordering. The re-ranker reads rendered text, never geometry, so it cannot perturb the store — and on a timeout or failure it returns its input unchanged.
6. Ground, or refuse. The gate is a single number: the best similarity in the window must clear 0.750. Below it, the engine declines.
7. Then, and only then, the model speaks. The engine hands the reader the retrieved passages. The reader’s job is phrasing.
Two rules that keep the stages honest
Once the pass has a fixed shape, each stage gets exactly one job. Two rules keep it that way — the second is what stops a stage from quietly doing someone else’s.
A precision stage can only reorder what recall surfaced. The re-ranker cannot find anything. So widening the candidate pool is a recall decision, and pooling and reordering are tuned separately rather than as one knob. It also means the re-ranker is only ever allowed to touch the order, never the admission — which is why it is gated to a similarity band instead of running on every query.
An admission stage may only add. Ingest can write a second key per entry — the embedding of its opening 200 characters — whose only power is to put an entry whose whole-document vector was too diluted back into the candidate list. The parent is then scored on its own document vector, exactly like any other candidate, so nothing downstream can tell a key-admitted entry from a document-admitted one. It is a union, not a re-rank: taking the better of the two scores would let a key evict a document match, and a test that lets one stage cancel another’s work cannot measure either. The flag ships off by default and stays off until it clears its own pre-registered bar.
What this buys
Attribution that belongs to the engine, not the model. Every entry in the cascade carries its own identity — content hash, source document, cube, node — through the pass. The model never decides where an answer came from, so it cannot invent a citation. That is the footnote the last article said was missing, and this is the mechanism that keeps it.
Honesty, measured. The hosted instance audits itself against its own store. On 2026-10-04, over forty probes generated from the documents it holds, hit@10 was 0.875 and 80% of windows were grounded. Over fifty probes built to be outside the corpus — questions it should not be able to answer — the decline rate was 1.000. That has held at 1.000 on every audit since 28 September, across a dictionary that changed underneath it the whole time.
A retrieval cost that does not grow with the store. The pass is index-bounded, not dictionary-bounded. On the production box, pure retrieval — the forward pass, routing and blend together — measures in the 75–160 ms range, and essentially all of the remaining wait is the reader generating tokens. Add a hundred thousand entries and the pass does not get slower.
Reproducibility. The same question returns the same top ten — not “usually the same.” The walk could not promise this: its state moved every turn, and an identical query returned a different window five turns later. A feed-forward path over a frozen store is deterministic, which is what makes every measurement above possible in the first place.
A store you can keep feeding. Because nothing in the query path learns, ingestion has no interference problem. New documents are validated, embedded and added, and the pass over them is the same pass. This is continuous ingestion rather than continuous learning, and the difference is the honest one.
What it does not do
It does not learn at query time. That was the claim we had to withdraw. The store is liquid; the retrieval is not adaptive. If you want the system to answer better, there are exactly two levers that have ever moved the number — ingest material it does not have, or sharpen the retrieval on top of what it does. Everything else tried (re-training adapters, re-weighting the geometry, federating stores, widening the window) measured flat or negative.
The decline gate is also blunt. One global floor is the wrong instrument for a store holding both a large generalist and a two-hundred-entry specialist, which is why per-cube floors exist. It remains a calibration problem, not a solved one.
And retrieval precision has a ceiling. Below roughly 0.74 similarity, a bi-encoder’s relevance signal is noise-dominated. That is exactly why the re-ranker exists, and why the next gain will come from a better joint scorer or a better embedder — not from another term in the score.
One direction, no loop. That is the whole of the design. A forward pass on the graph is not a weaker version of a walk. It is what remains after you remove every stage that could not prove it was carrying its weight — and then refuse to add another one without the same proof.
Every number above is the engine’s own receipt — the retrieval audit it runs against its own store, and the ablation record that justified each deletion. The code is the authority; these are the figures it produced.
Receipts — every primary source used
Every load-bearing number in the piece maps to one of these. Files in backticks are in this pack; where the source is a run that is not shipped, it says so.
| Claim | Primary source |
|---|---|
| Live weighted hit@10 0.750 vs raw 0.995–1.000 | ablation-record.md; the removed-stage table in architecture.md |
| Cross-node ×0.3 produced identical orderings at 0.3/0.5/0.7; ×1.0 lifted hit@10 +0.15 to +0.25 | architecture.md |
| Synth re-rank off: hit@10 0.579 → 1.000, useful retrieval 30/43 → 37/43, 96 entries rescued | ablation-record.md, from the engine’s own forward-pass report |
| Phase engine: 0 of 33 edges lockable, state erased in ~5 passes, frozen rotor identical | architecture.md §Phase Engine — REMOVED |
| The seven-step pass (embed, route by geodesic distance, HNSW candidates, one score, banded re-rank, decline gate, then the reader) | forward-pass.ts, retrieve.ts, reranker.ts, grounding-ladder.ts |
| The two rules: a precision stage may only reorder; an admission stage may only add | forward-pass.ts, retrieve.ts, reranker.ts |
| Retrieval audit, 2026-10-04: hit@10 0.875, 80% grounded, 40+ probes | the engine’s read-only retrieval-audit run; the method is in architecture.md §Audit is due. The raw output is not in this pack. |
| Out-of-corpus decline rate 1.000, every audit since 2026-09-28, 50+ probes | the same read-only audit. Re-run it to reproduce. |
| Pure retrieval 75–160 ms on the production box | a production-box measurement; not in this pack |
| HNSW ~99.8% recall@10 at a million vectors | the engine’s HNSW benchmark (whitepaper); not in this pack |
| The figure | make-figure.py redraws it; the published PNG is img/forward-pass-on-the-graph.png in the post folder |
Measurements were taken on the engine’s own store in September–October 2026. The store changed while the audits ran; the decline rate held across a changing dictionary, which is the strongest claim in the piece. Everything else is a snapshot of one tree at one commit — re-run before relying on a delta.