When a coding agent meets an API it does not know, it guesses, fails, and guesses again — each lap burning the expensive kind of token. Reference-coding fixes that: clone the libraries that already solved your problem, index them with XERJ, and retrieve the real implementation before writing code. Measured across 13 purpose-built libraries in 5 languages against the same Claude Code: on code it has not memorised, plain Claude Code solved 1 of 21 tasks and burned $21.90 doing it — with XERJ retrieval it solved 21 of 21 for $3.38. Same model, same tasks — only the retrieval differs. Paste the one line below to turn it on. Every number behind it is underneath, including where retrieval is worth nothing.
Install XERJ (docs: https://xerj.org/llms.txt), index this project's sources, and set up reference coding: clone and index the open-source repos closest to what we're building, and search how they solved a problem before writing code.
xc-corpus.sh / xc-index.sh / xc.py — at the time, internal wrappers from the maintainers' own agent skill. They now ship at tools/xerj-code/, but nothing requires them: they are thin shells around git clone --depth 1, xerj autoindex, and one multi_match query, and Do it yourself below runs the same method with plain commands.bare = Claude Code with tools off (memory only) · native CC = Claude Code as-is, greps the source · XERJ = the SAME agent, reference retrieved and injected · cost $11.18 / $3.27 / $1.58 · NOT A LATENCY BENCHMARK
git clone --depth 1 the reference repos into one folder per problem domain.xerj autoindex <clone> against a running node; retrieval is O(1) regardless of tree size.multi_match over defs / body / title returns the exact definition with its contract; the model reads a passage instead of re-deriving.git clone --depth 1s them into one folder per domain. Record each repository's
licence alongside the clone; GPL/AGPL entries are approach-only — read the design,
never copy the code.xerj autoindex <clone>:
every file is sniffed by content, and source files go through
tree-sitter grammars (13 languages) that emit the language, every symbol with its kind and line number, a
searchable defs field, and the full body. One index per dataset, incremental on
re-runs — re-index when the references change.multi_match over body,
defs and title — with a measured (not hand-picked)
boost on defs so exact symbol names outrank prose — RRF-fused with a vector arm
where an index supports it. When no index does, it degrades to BM25-only and says so; a silently
lexical-only "hybrid" would be a lie. The default embedder is lexical feature-hashing — vocabulary overlap,
not neural understanding.file:line, adapts it,
cites the source, and checks the licence. If nothing relevant comes back it says so and falls back to normal
work — forcing a bad match in is worse than not retrieving.You cannot measure retrieval on code the model has memorised — it reproduces even a 256-value table from memory. So the unfamiliar corpus is 13 real libraries written for this study (each compiles and passes its own tests), each carrying a runtime contract the compiler cannot warn about. Each task returns the library's own type, so a hand-rolled workaround cannot pass. bare = from memory; XERJ = same model, reference retrieved.
| Library | Lang | Domain | The contract you can't guess | bare | XERJ | tokens bare→XERJ |
|---|---|---|---|---|---|---|
| sift | Rust | streaming sketch | conservative-update count-min; seal before any read | 0/9 | 9/9 | 17,350 → 796 |
| grove | Rust | arena allocator | generational handles — freed handle stays stale after reuse | 0/2 | 2/2 | 15,233 → 1,236 |
| weft | Rust | lexer | builder → weave → scanner; maximal munch | 1/2 | 2/2 | 16,112 → 610 |
| tally | Rust | fixed-point decimal | banker's rounding (round half to even) | 0/2 | 2/2 | 15,599 → 892 |
| spool | Rust | ring buffer | push returns the evicted item; power-of-two capacity via shift | 2/2* | 2/2 | 15,974 → 656 |
| cadence | Rust | rate limiter | token bucket, lazy refill only on advance | 0/2 | 2/2 | 18,162 → 744 |
| quill | Rust | varint codec | zig-zag + LEB128 (a memorised convention) | 2/2 | 2/2 | 1,355 → 999 |
| trellis | Rust | topological sort | smallest-index tie-break; exact cycle-node set on failure | 0/2 | 2/2 | 21,317 → 782 |
| sieve | Rust | bloom filter | Kirsch–Mitzenmacher double hashing; settle before sense | 0/2 | 2/2 | 13,817 → 669 |
| warden | Python | LRU cache | fetch promotes to most-recent; overflow evicts LRU | 3/4* | 4/4 | 15,370 → 240 |
| garner | JavaScript | prefix trie | stored word vs bare prefix (a known structure) | 4/4 | 4/4 | 4,360 → 667 |
| arena | C | generational allocator | opaque handle; stale after slot reuse | 2/4* | 4/4 | 18,730 → 1,489 |
| ledger | Java | append-only log | must seal before replay; checkpoint truncation | 2/4 | 4/4 | 26,767 → 97 |
* bare "passes" some cases by recovering an API name from a compiler/runtime error at 20–150× XERJ's tokens, or because the task spec necessarily stated the one non-obvious rule — never by recalling a genuine runtime contract. On the two memorised cases (quill varint, garner trie) the model already knows the algorithm and retrieval saves nothing — which is the point of including them. Source for every library: docs/case-studies/reference-coding →
Two controls on real, public code the model has trained on — valkey + memcached (a KV server), and tantivy (a search engine) — pin down exactly where retrieval stops paying. The value is gated by memorization.
RETRIEVAL WINS: correct where memory fails, 6.5× cheaper than bare, 1.3× cheaper than native.
RETRIEVAL LOSES: the model knows the protocol, so pure memory is cheapest; searching or injecting a reference is overhead.
Median output tokens to solve, XERJ vs answering from memory. Verdict is the real toolchain in each language (python3, node, cc, javac).
Each pair is drawn against its own bare baseline, because the four libraries differ by two orders of magnitude in absolute cost — the comparison that matters here is within a language, not across them. Absolute medians are printed on every bar.
One Java task: build an append-only ledger that must be sealed before replay, truncated to a checkpoint. bare cannot read the library, so it reinvents the whole thing — 503 lines — and still fails the seal contract. XERJ, handed the reference, writes it in four lines and cites the rule it learned:
// reinvented an entire append-log + checkpoint + seal // state machine from memory, guessing the API surface … // 503 lines … wrong truncation semantics // → hidden test FAILED.
public static Ledger upTo(long[] vals, long seq) {
Ledger l = Ledger.open();
for (long v : vals) l.append(v);
l.checkpoint(seq); // keep 0..seq
l.seal(); // required before replay
return l;
}
CAPTURED VERBATIM · docs/case-studies/reference-coding/generated →
We ran the opener above verbatim on a fresh task — implement a top-3 heavy-hitters function using the unfamiliar sift crate (whose API is not new()/push()/top_k()). Following the prompt, Claude Code indexed the reference, retrieved sift's real API, and wrote code that compiles and passes a hidden test — using furnish / absorb / seal / crest, the seal-before-read contract a from-memory attempt cannot recover. The full transcript and the generated program are in the repo.
REPRODUCE THE VERIFICATION ITSELF · docs/case-studies/reference-coding →
Four commands, no tooling beyond the xerj binary and git. The method works on any repository — including your own private code, which is the real use case.
xerj -d ./.xerj-data --insecure & — the ES-compatible API comes up on :9200. autoindex is a client of a running node; without one it exits 1.git clone --depth 1 https://github.com/tokio-rs/tokio ref/tokio (and the same for each peer) — group by problem domain, not language.xerj autoindex ref/tokio — a 100k-record tree indexes in ~10 s; re-run when the references change and the journal makes it incremental.file:line:
curl -sXPOST 'localhost:9200/ax-*/_search' -H 'content-type: application/json' \
-d '{"size":3,"query":{"multi_match":{
"query":"graceful shutdown with a broadcast channel",
"fields":["defs^3","body","title"]}}}'
defs, symbols, body, title and language are the fields autoindex writes for source files. ax-* matches every dataset on the node, your own project included — check GET /_cat/indices after each index run and narrow the pattern to the reference datasets when you want your own code kept out of the results.xc-corpus.sh / xc-index.sh / xc.py ship in the repo at tools/xerj-code/ and wrap exactly the four steps above. They add a corpus definition: xc-corpus.sh --from hub/xerj-search.json rebuilds a corpus someone already vetted — URLs, pinned commit SHAs and licences; no source, so nothing to redistribute, and the same file gives two machines the same bytes.claude -p --output-format json, no API key. The numbers and every per-run record are in CASE_STUDY.md; the harness itself is not shipped.Set me up for reference-coding with XERJ and then use it automatically for the rest of our work, so I stop burning output tokens re-deriving APIs I could just look up. 1. Make sure a local XERJ instance is running and the `xerj-code` skill is available. If XERJ isn't installed, get it from https://xerj.org/get (curl -fsSL https://xerj.org/get | sh) and start it. 2. Look at this project — its dependencies, the domains its code touches — and decide which well-known open-source libraries already solve those problems. Clone them into a reference corpus with the skill's xc-corpus.sh, grouped by problem domain, not by language. 3. Index that corpus with xc-index.sh (it runs `xerj autoindex`). 4. From now on, for every coding task this session: before writing non-trivial code against an unfamiliar or external API, first run xc.py <corpus> "<what you need>" to retrieve the real implementation, read the returned definition, and adapt it — cite file:line, check the licence. If retrieval returns nothing relevant, say so and fall back to normal work. 5. Re-index whenever the references change. Do this proactively for the rest of the session without me asking again.
Kept for reproducibility, not as current instructions — the one-line prompt at the top of this page is what to use today.