
XERJ QUERY-1: A NEW NEUROSYMBOLIC MODEL.
IT COMPILES PROMPTS TO VERIFIABLE SEARCH QUERIES, 400-1,500× CHEAPER THAN RAG AND LLM.
A new way to answer questions over your data: the model writes a small program, the engine runs it over your own facts. No embeddings to build, no GPU to rent, no answer you cannot check. Four CPU cores, offline, and hundreds of times cheaper than RAG or an LLM reading the data.
Retrieval finds documents. XERJ Query-1 turns a prompt into an answer you can check.
On a node started with xerj --facts-model-dir <DIR>, it does six things:
TRY IT IN ONE PROMPT.
Paste this into your AI agent (Claude Code, Codex, Cursor or any agent with a shell):
Install XERJ with the XERJ Query-1 model from https://xerj.org/llms.txt, start it with --facts-model-dir, write three facts about my project with subject_id, and ask one question about them; show me the program and the evidence it returns.
Or run it yourself:
$ curl -fsSL https://xerj.org/get | sh -s -- --with-model
Check that the model is loaded: curl -s localhost:9200/_facts | jq .model.state should print "loaded". The model is off by default; the installer prints the xerj --facts-model-dir <DIR> line that turns it on.
WHY DOES SEARCH NEED A NEW AI MODEL?
WHAT IS WRONG WITH LLMS?
Most questions people ask of their own data have one right answer that is already sitting in it. What is this customer's start date. Which of these two rivers carries more water. How many of these works are over 19 centimetres tall. A search engine can find the documents that mention the words. It cannot give you the answer. For the last few years the fix has been to hand the documents to a large language model, an LLM, and let it write the answer.
It works often enough to be tempting. It also has two problems that a better model does not fix. The first is that the answer is generated text. The model writes what seems likely given what it read, and a wrong answer reads exactly like a right one. Nothing in the pipeline checks the sentence against the data. In the head-to-head we report in §07, every pipeline built on Opus 5.5, one of the strongest models available, answered between 5 and 15 of 160 questions wrong. XERJ Query-1 answered one: nine times fewer wrong answers than RAG on Opus 5.5 (1 vs 9 to 15 of 160), five times fewer than Opus reading everything (1 vs 5 to 6).
The second problem is cost. A language model is paid per piece of text it reads and writes, on every question, for every user. The cheapest Opus 5.5 pipeline we measured cost $13 per 1,000 questions at list price; the one that reads the whole data set for each question cost $694. XERJ Query-1 cost $0.042 for the same 1,000 questions on four CPU cores, with no GPU and no network: 300 to 16,000 times cheaper per question. The rest of this post covers how AI search works today and what it costs, what XERJ Query-1 does differently, how we built it, and what we measured.
Today we are introducing XERJ Query-1, a new AI model built into the XERJ search engine. A model that turns a question into a short, checkable program over a search index, inside the engine, that refuses when the data cannot support an answer. The research ideas behind it are decades old and we cite them in §04. What we know of no other search engine shipping is the combination: the model lives in the engine, its programs run over facts written to its store, each with its source, it says UNKNOWN rather than guessing, and it serves offline on four CPU cores in under a gigabyte.
On our standard evaluation, 1,600 questions scored against Opus 5.5 reading the same records under the same frozen prompt, XERJ Query-1 scores 90.29 where the reference defines 100, with 9 wrong answers against the reference's 22. That evaluation is the one the model was developed against, not a fresh set; the head-to-head numbers above are a separate test, and every caveat is in §07.
HOW AI SEARCH WORKS TODAY.
EMBEDDINGS, RAG, GRAPHRAG, AND A MODEL THAT READS EVERYTHING.
Start with the baseline. A classic search engine keeps an index: a table from every word to the documents that contain it, like the index at the back of a book but built for every word at once. When you type a query the engine looks up each word, collects the documents, and ranks them; the usual rule, BM25, scores a document higher when it contains your words often and when those words are rare elsewhere. It is fast and it never answers. Ask "which of these two rivers carries more water" and you get the two reports back, and you do the reading and the comparing yourself.
Embeddings. The first AI step was to turn text into numbers. An embedding model reads a passage and produces a list of a few hundred numbers meant to capture its meaning, so that two passages that say the same thing in different words land close together. Searching embeddings finds "similar meaning" instead of matching words. Think of it as giving every page a coordinate on a map, where nearby pages are about the same thing. The map has to be drawn for every page before you can use it, and redrawn when the model that draws it changes.
RAG. Retrieval-augmented generation is the standard recipe for answering questions with an LLM: search first, then let the model read only the top hits. The data is cut into chunks, each chunk is indexed by words or by embedding, the question pulls the five or ten best chunks, and the model reads those and writes an answer. The analogy is a research assistant who is handed the five most relevant pages and asked to answer from them. The assistant is only as good as the five pages, and still writes the answer from memory of what they read.
GraphRAG. A newer recipe has the model read the whole corpus first and build a knowledge graph: the people, places and things it finds, the relationships between them, and a written summary of each cluster. Questions are then answered from the graph and its summaries. It is like asking the assistant to write an encyclopedia of your data before you ask the first question. The encyclopedia is expensive to write and has to be rewritten when the data changes.
Long context: the model reads everything. The simplest recipe is to put all the data in the prompt and ask. The model reads every token, a token being roughly a word piece, on every question. While it reads it keeps a working memory called the KV cache: a record of every token seen so far, held in GPU memory, one copy per user. It is the model's short-term memory, and it grows with the length of what it reads. This is the brilliant reader with no memory of your files, so you hand over the whole filing cabinet with every question.
Diagram 1 · Search with an LLM today. Retrieval narrows what the model reads; the model still writes the answer from what it read, and the pipeline has no step that checks the sentence against the records.
WHAT GENERIC LLM SEARCH COSTS.
RAG, GRAPHRAG, LONG CONTEXT AND THE KV CACHE, IN DOLLARS.
Here is the bill, on one store of 5,000 facts and the same 160 questions, at list prices: Opus 5.5 at $4 per million tokens read and $20 per million written, CPU at $0.03625 per vCPU-hour. Everything in this chapter is either measured in the head-to-head of §07 or arithmetic from a measured rate, and we say which.
Per 1,000 questions. Classic RAG on Opus 5.5, with five or ten chunks shown per question, cost $13.41 to $20.95. GraphRAG cost $65.39, plus $14.27 to build the graph for those 5,000 facts: 267 model calls, 72 minutes end to end. Opus 5.5 reading the whole store cost $19.41 when 40 questions share one reading, and $694.30 if you ask one question per call, which is what an interactive user does. Opus 5.5 with perfect retrieval, shown exactly the right collection every time, cost $28.84. XERJ Query-1 cost $0.042 of CPU time, measured on a live run.
| Method | Cost / 1,000 questions | Cost / 1,000,000 questions |
|---|---|---|
| XERJ Query-1, live CPU run | $0.042 | $42 |
| RAG, embeddings top-5 + Opus 5.5 | $13.41 | $13,400 |
| RAG, BM25 top-5 + Opus 5.5 | $13.83 | $13,800 |
| Opus 5.5, whole store, 40 q per call | $19.41 | $19,400 |
| RAG, embeddings top-10 + Opus 5.5 | $19.85 | $19,900 |
| RAG, BM25 top-10 + Opus 5.5 | $20.95 | $21,000 |
| Opus 5.5, perfect retrieval (oracle) | $28.84 | $28,800 |
| GraphRAG + Opus 5.5 | $65.39 | $65,400 |
| Opus 5.5, whole store, 1 q per call | $694.30 | $694,000 |
Sorted by cost per 1,000 questions, cheapest first. Accent marks the best cell in the key column, red the worst. List prices on the same 160 questions; per-million figures are the per-1,000 figures times 1,000, arithmetic. The chart below shows the same column on a log axis.
Chart 3 · Cost per 1,000 questions, list prices, log axis. Opus 5.5 at $4 per million input tokens and $20 per million output tokens, output including thinking, no caching; CPU at $0.03625 per vCPU-hour, the on-demand rate of an AWS c7g.xlarge (4 vCPU, $0.145 per hour) in US East, read from the Vantage mirror of the EC2 price list on 2026-11-05 because the AWS on-demand page did not render its table to our fetch. The dimmed XERJ Query-1 bar is a projection from the int8-guard serving configuration measured on another host (518 CPU-ms per question), not a run on this store. GraphRAG's bar excludes its $14.27 index build. Measured on the same 160 questions; list prices.
Before the first question: embedding the corpus. We measured it at scale. On 10 GB of English Wikipedia, 3.3 million articles from the Hugging Face wikimedia/wikipedia English dump of November 2023, XERJ built its full-text index in 17 minutes and 0.7 CPU-hours on a shared 16-core aarch64 box, and answered keyword queries at 9.3 ms at the median. Embedding the same text with bge-small, at the rates we measured on a 32-thread x86 laptop and on that 16-core box, takes 62 to 97 hours of CPU and leaves 8.5 GB of vectors. Building a GraphRAG graph over it, at our measured cost per text unit, would take about 4.7 million model calls and roughly $251,000 before the first question (arithmetic). The smaller rates behind the same story: We measured the encoder on real 378-token passages with 8 CPU threads: 10.4 passages per second with a small model (bge-small) and 4.5 per second with a base one (bge-base). From those rates, labelled as arithmetic: a million passages takes 27 hours or 62 hours on that box, $7.75 or $17.90 of CPU; a hundred million takes 111 or 257 days, $775 or $1,790. The vectors then have to live in memory: 1.54 or 3.07 GB per million passages, 154 or 307 GB per hundred million, plus about 13 GB of search links. All of it is paid again whenever the embedding model changes.
Per user, while the model reads: the KV cache. From the published configuration of Llama 3.1 70B, with keys and values held in 16-bit, the cache is about 320 KiB per token: around 43 GB of GPU memory for a 128k-token context and about 57 GB to hold this store's 172,870 tokens, per concurrent user. Arithmetic from the configuration, not a measurement; the internals of closed models are unknown. Hosted APIs sell the same thing as prompt caching: writing this store into the cache costs about $0.86 per five-minute window it is kept warm, and each question then pays about $0.035 in cache reads before the answer is written. Our single-question runs did not hit the cache, so the $694.30 above is the uncached case.
A month of running it. We priced a 10 GB example workload, not a size limit: 10 GB of raw text, about 6.6 million passages of 378 tokens, and 1 million questions a month, at list prices fetched on 2026-11-05. On the XERJ side that is one AWS c7g.4xlarge (16 vCPU, 32 GB), sized from our measured 10 GB run: the index was 11.35 GB on disk and indexing peaked at 18.1 GB of RAM. Add the model in RAM and a 30 GB disk: $468 a month. The managed stack holds 6.6 million 384-dimension vectors, about 10 GB plus search links, in Pinecone, a graph of about 2 million entities in Neo4j AuraDB, and a Redis cache, and pays Opus 5.5 for every answer: $65,747 a month with GraphRAG, $21,175 with vector RAG alone. That is 141 times and 45 times. We want to be plain about where the gap comes from. The infrastructure alone is close, and on this sizing the stack's is a little cheaper: $357 a month against $468. The difference is the LLM bill on every question.
| Setup, 10 GB example workload, 1M questions a month | What it runs | Monthly total | vs XERJ |
|---|---|---|---|
| XERJ, one CPU server | EC2 c7g.4xlarge $423.40 + measured compute for 1M questions $42.00 + 30 GB gp3 $2.40 | $467.80 | 1× |
| Vector-RAG stack | Pinecone Standard $220.30 + Redis Cloud $5.00 + Opus 5.5 answers $20,950.00 | $21,175.30 | 45× |
| GraphRAG stack | Pinecone Standard $220.30 + Neo4j AuraDB 2 GB $131.40 + Redis Cloud $5.00 + Opus 5.5 answers $65,390.00 | $65,746.70 | 141× |
Sorted by monthly total, cheapest first. Accent marks the cheapest, red the most expensive. List prices fetched 2026-11-05 from each vendor's pricing page and the AWS Price List; 730 hours a month; arithmetic from the stated workload and our measured per-question ledger. One-time builds are not in the monthly totals: on the stack side, embedding the 6.6 million passages costs about $50 (managed API or our own CPU), the first Pinecone upsert $53.96, and building the GraphRAG graph with Opus 5.5 $18,836.40; XERJ needs no graph build and no upsert. Caveats: smallest paid tiers that fit, no replicas or backups on either side, no committed-use, batch or prompt-cache discounts; a different top-k or answer length moves the LLM line linearly.
The $42 is the measured compute for 1 million questions, and it does not grow with the size of the data. More data on XERJ mostly means more disk, at $0.08 per GB-month, because segments are read from disk. On the managed stack the vector and graph tiers grow with the data.
Put together: 300 to 16,000 times cheaper per question ($42 against $13,400 to $694,000 per million). Averaged over the methods you could deploy, XERJ Query-1 is about 410 times cheaper per question than classic RAG on Opus 5.5, 580 times cheaper than Opus 5.5 reading the data, and 1,570 times cheaper than GraphRAG. Hundreds of times, on the same questions, with no graph to build, no corpus to embed, and no cache to keep warm. The accuracy side of the comparison is in §07.
MEET THE NEUROSYMBOLIC APPROACH.
PROMPTS BECOME VERIFIABLE, EXECUTABLE PROGRAMS, FOR CHEAP.
Neural networks are very good at reading language and bad at being exact. Symbolic systems, meaning programs, logic and databases, are exact and cannot read language. Neurosymbolic AI puts them together: the network writes something a symbolic system can run and check. That is the one-line definition. The research family goes back decades, and the version XERJ Query-1 belongs to was set out by NS-VQA (Yi et al., 2018), where a network reads a question about a picture, writes a small program, and a symbolic executor runs it to get the answer.
The approach is already in production, in places where a wrong answer is expensive. Amazon's Automated Reasoning checks in Bedrock Guardrails turn a policy document into logic rules and use formal verification to test a model's answer against them, returning findings such as valid, invalid or impossible, rather than asking a second model whether the first one sounded right. Google DeepMind's AlphaProof and AlphaGeometry 2 reached silver-medal standard at the 2024 International Mathematical Olympiad by having a language model propose steps and a symbolic engine or the Lean proof system prove or reject them. Wolfram's tools for chat models have the model write a Wolfram Language query and let a symbolic computation engine produce the number, so the answer does not change when you ask again. These systems verify, prove, or compute. None of them compiles a user's search prompt into a program over a search index, which is what XERJ Query-1 does.
The lineage for that last step is also old, and we want the novelty claim to survive a hostile reader. Mapping language to executable meaning is called semantic parsing. Text-to-SQL does it for databases. Neural Symbolic Machines trained a model to write programs over a knowledge base; program-aided language models have a large model write Python and let an interpreter do the arithmetic; search products offer natural-language querying by sending your question to a hosted model that writes a query string back. What nobody had done, stated as narrowly as we can: put the learned compiler inside the search engine itself, so its programs are pointers into typed facts in the engine's store, each written with its source; make refusal part of the contract, so it says UNKNOWN rather than guessing when a pointer cannot be grounded; and serve it offline on four CPU cores in under a gigabyte. We know of no other search engine that ships that combination, and we looked. If one exists we will add it here and link it.
Diagram 2 · XERJ Query-1. The model never writes the answer. It writes a program over the engine's typed records; the engine runs it, logs each step, and a verifier reads the trace. A pointer that finds nothing gives UNKNOWN; two records that disagree give CONFLICT. The mechanism in detail is in §06.
What is different is inside the network, and it is small. XERJ Query-1 is the same kind of reader network as a language model: it reads the question and a numbered listing of records, token by token. The difference is what it is allowed to say back. A language model's output vocabulary is words, so its output is the answer, and the answer is whatever it wrote. XERJ Query-1's output vocabulary is program steps and record numbers: look up record 10, look up record 7, compare, return. That makes the output checkable, because every token names an operation or a record you can open. It makes it cheap, because the program is a handful of tokens written in one pass, with no corpus to re-read and no paragraph to compose. And it makes refusal honest: when the data does not contain what the program points at, the program fails to ground and the engine says UNKNOWN, instead of the model improvising a sentence to fill the gap.
Diagram 3 · What is different inside the network. Same reader, different output vocabulary. On the left the tokens are the answer; on the right the tokens are steps and record numbers, and the answer is whatever running them produces.
Prior art checked 2026-11-04: Liang et al. 2017 (ACL P17-1097); Wang et al. 2018, execution-guided neural program decoding; Chen et al. 2020, Neural Symbolic Reader; Gao et al. 2022, PAL; Chen et al. 2022, Program of Thoughts; Cheng et al. 2022, Binder; Khattab et al. 2023, DSPy; the 2025 Text-to-ES benchmark; vendor documentation for engine-side natural-language querying, which routes through an external model connector. Production examples checked 2026-11-05 against the linked AWS, DeepMind and Wolfram pages.
HOW WE MADE AND TRAINED IT.
FP16 ON RENTED GPUS, INT8 ON FOUR CPU CORES, PAID FOR BY DONATIONS.
The goal was fixed before the first training run: a model that answers on an ordinary CPU, with no GPU, and with nothing leaving the machine. That rules out most of what a model is usually allowed to be. It has to be small enough to sit beside the index in memory, fast enough to answer in a fraction of a second on four cores, and exact enough that its output can be run rather than read. Everything below follows from that target.
Trained in FP16 on rented GPUs. FP16 means half-precision numbers: each weight takes half the memory of the usual 32-bit kind, and the whole recipe, training through shipping, stays in half precision end to end. We never used FP32. The GPUs were rented by the hour on Clore.ai, chosen by measured throughput per dollar on our own job rather than by name, alongside a few machines of our own. It was paid for by donations to the project. The total is in the thousands of US dollars, not millions; we will not print the figure and we will not round it up. We are grateful to everyone who gave: this model exists because they did.
Served in int8 on a CPU, with a guard. For serving, the weights are stored as 8-bit integers, a quarter of the 32-bit size, which is what makes four cores and under a gigabyte possible. Smaller numbers can change answers, so the int8 path has a calibrated guard: when a computation drifts past a threshold it falls back to the exact path. The int8 configuration was accepted only when its answers agreed with the FP16 model's own answers on 400 of 400 checks, not on a benchmark score. In that configuration a question takes about 120 ms at the median on four cores, on a namespace of up to 8,000 facts.
The rules were written first. The scoring rules, the Net formula (right answers minus twice the wrong ones), the family weights, the bootstrap seed and what counts as a decline, were written down on 31 October, before a single answer under them was scored. The pre-registration also records that its author had seen the earlier results, because that is true and it matters. Every candidate recipe trains on three seeds (11, 29, 47), and the bar for promoting one was fixed before the screens ran: a mean gain of at least 3 points, an interval above zero, no family losing more than 1 point, no rise in confident-wrong answers. Several candidates scored 91 to 94 on a looser rubric variant and were never published, because that variant was known to be inflated.
Chart 8 · The score trajectory, with the point we withdrew. The first XERJ Score, reported on 31 October as "100 capped, 130.7 unclipped", rewarded the model for declining count questions the reference was penalised for answering. It was an artefact of the rubric, and the report said so in its own text. We withdrew it on 1 November, pre-registered v2, and started again from 49.9. Intervals are 95% paired world-cluster bootstraps; the two early points carry no interval in the record. The scoring rules were written down before the second version was scored.
Calibration is tested on held-out wording, not tuned on the development set. A diagnostic on 4 November sorted every non-right item on the standard evaluation: 96 recoverable by recalibrating the decline policy alone, 126 by training, 169 beyond the executor's current op-set. A recalibration that uses no gold answers raised the development mean from 90.3 to 93.6 and passed every safety gate. We did not ship it, because on a separate 270-item set of fresh wordings it moved Net only from 0.333 to 0.348 while a looser variant made things worse. A gain that does not transfer is not a gain.
Bit-exact parity between the engine and the research code. The executor inside the engine is a port of the research implementation. Before landing, the engine served all 2,387 evaluation items from the bundle and the rows were compared with the research rows: program, raw output and evidence identical on every item, score reproduced to the hundredth. The one divergence found on the way, eight items that answered differently, was traced to the test driver asking historical questions at the wrong revision; the engine was right. The ES-YAML conformance suite, the gate that outranks everything else in this repository, read 1,457 passed and 0 failed on the landed branch. Every number in this post traces to a run; our provenance record maps each figure, example and chart to the file or commit it came from.
If you have read our other posts, this is the same method applied to a model instead of a file format: gates written before the work, losing rows kept, numbers published with the run that produced them, a decisions study that found our own defects. The model is the newest piece and it will get the same treatment as it changes.
HOW XERJ QUERY-1 WORKS.
IN DETAIL: RECORDS, PROGRAM, TRACE, VERIFIER, AND THE COST OF A QUESTION.
Five parts, in the order a question meets them. Records. You, or your agent, write typed records into the engine's store: a subject, a relation, a value with its type and unit, and the exact span of text the value came from. Every record can be traced back to its sentence and its document version. The engine does not yet turn arbitrary documents into records on its own; in the evaluation worlds below, the records were extracted by the evaluation pipeline. The listing. When a question arrives, the engine gathers the candidate records and numbers them, and hands the question and the numbered listing to the model. The program. The model writes a few steps that refer to the listing by number: look up record 10, look up record 7, test whether the first is less than the second, return. The executor. Engine code runs the steps over the records, checking types and units as it goes, and logs each step with the record it touched. The verifier. A frozen rule set reads the trace and its decline codes, never the gold answer, and decides whether to return the value. A count is returned only when a second, independently trained model agrees on the program. The outcome is one of three statuses: ANSWER with its evidence, UNKNOWN when a pointer finds nothing or a set cannot be closed, CONFLICT when two records disagree.
Here is the whole mechanism on one real evaluation item. The evaluation world holds seven short reports about invented rivers; the evaluation pipeline extracted 20 records from them and wrote them to the store; four are shown. The question asks whether one river's discharge, the water it carries per second, is exceeded by another's.
# world-dev-90014, as stored (names are synthetic; the numbers are the data) doc1 v1 | Dunzelfen River flows for 3636 km. It carries 2446 m3/s of water. It runs through Hallend. doc2 v1 | Liosolzel River flows for 3079 km. It carries 1439 m3/s of water. It runs through Fallow. doc3 v1 | Arhalga River flows for 499 km. It carries 6956 m3/s of water. It runs through Dunmere. doc4 v1 | Iswenquel River flows for 3284 km. It carries 8692 m3/s of water. It runs through Esterra. doc5 v1 | Tersolsol River flows for 1132 km. It carries 4884 m3/s of water. doc6 v1 | Gacunas River flows for 202 km. It carries 7567 m3/s of water. It runs through Gorvland. doc7 v1 | Pratav River flows for 478 km. It carries 3775 m3/s of water. It runs through Fallow. (added by an edit) # extracted records, 20 in all; four of them: doc3@v1/length | Arhalga River | length | number 499 km doc3@v1/discharge | Arhalga River | discharge | number 6956 m3/s doc3@v1/country | Arhalga River | country | text Dunmere doc6@v1/discharge | Gacunas River | discharge | number 7567 m3/s
XERJ Query-1 does not say "yes". It writes four steps. The engine runs them and logs this:
program LOOKUP #10 ; LOOKUP #7 ; LT ; RETURN trace step 0 LOOKUP #10 VALUE number 6956 m3/s doc3@v1/discharge, Arhalga River step 1 LOOKUP #7 VALUE number 7567 m3/s doc6@v1/discharge, Gacunas River step 2 LT TRUE 6956 < 7567, units checked by the executor step 3 RETURN answer yes gold: yes · Opus 5.5 on the same listing: yes
#10 and #7 are not words from your prompt. For each question the engine lists the facts in the namespace as a short numbered listing (#0, #1, #2 …). LOOKUP #10 means: read record 10 of that listing. Here record 10 is the Arhalga River's discharge, 6,956 m3/s, and record 7 is the Gacunas River's, 7,567 m3/s. Every step of the trace names the record it used, with its source document and span.
# the numbered listing the model reads, two of its 20 lines #7 Gacunas River | discharge | number 7567 m3/s doc6@v1 #10 Arhalga River | discharge | number 6956 m3/s doc3@v1
The numbers never touch the model's output. The model chose two pointers and an operator. The engine did the arithmetic.
The same shape covers the questions that trip up a reader. A count is a loop the engine runs: find the subject's records, keep the ones that pass each filter, with an explicit rule for records that lack the field, and count what is left. A comparison is two values and an operator. A question that needs two hops follows a link from one record to the next. Every answer carries the ids of the records it was computed from, because the program is made of them. Run the same program over the same data tomorrow and you get the same trace. And when the data cannot support an answer, the program fails to ground instead of the model improvising: a pointer that resolves to nothing returns UNKNOWN, two records that disagree return CONFLICT, and that carries through any comparison built on them. Everything the verifier does not accept is returned as a decline, which our scoring counts as neither right nor wrong. Nine wrong answers in 1,600 is what that policy buys, and §07 says what it costs.
A store you can change. The model answers from a store you control, every change to it is versioned, and a question can be asked as of an earlier revision. We tried the whole cycle on a fresh node. We wrote three short HR notes and six facts about them. "How tall is Ana Bel?" answered 170 cm and pointed at characters 11 to 14 of document d1. We corrected the record to 172 cm with one edit that names the fact it replaces; the same question now answers 172 cm, and asked at revision 9 it still answers 170 cm. A second, disagreeing height for Bo Chen made the answer CONFLICT; withdrawing it brought back 182 cm. Nothing was guessed in between, and each answer took about 44 ms on four x86 cores, on this small namespace.
ask How tall is Ana Bel? ANSWER 170 cm evidence: d1 v1, characters 11-14, "170" edit correct d1 → v2, new fact supersedes the old one committed, revision 13 → 14 ask How tall is Ana Bel? ANSWER 172 cm ask same question, revision: 9 ANSWER 170 cm the old answer is still readable write a second height for Bo Chen, 175, no supersede ask How tall is Bo Chen? CONFLICT 175 and 182 both stand delete the 175 record revision 12 ask How tall is Bo Chen? ANSWER 182 cm
Every symbol the model can write. Two models write programs, and both run through the same executor. The facts model writes pointer programs, two to six steps. The counting model writes set programs, or refuses. The counts below come from the served rows of the 2,387-item standard evaluation: the lookups, chains and comparisons are from the facts model's 2,387 programs, and the counting model writes a second, separate program set over the same items, 836 set programs of which 529 were served after verification.
Fourteen symbols, usage counts on the standard evaluation. The big number on each card is how often the served model wrote it. One more symbol, ABSTAIN, ends a program with UNKNOWN on purpose; it is trained and legal, and the served model used it 0 times, declining through UNKNOWN pointers instead.
| Status the user sees | Meaning | Facts rows of 2,387 |
|---|---|---|
| UNKNOWN | a pointer resolved to nothing, or a set could not be closed | 1,484 |
| ANSWER | the program grounded and returned a value, yes/no, a subject or a count | 805 |
| CONFLICT | two stored facts disagree and the program touched both | 98 |
Sorted by rows, most first. Facts path only; counting answers are served separately when the counting model's program verifies.
What the op-set cannot express. No sums, totals, averages, minimum or maximum, lists or grouped counts: the engine compiles only COUNT. No differences or ratios, which are routed away. No arithmetic on values, only comparisons. No joins beyond one hop. No programs longer than six steps or deeper than four stack slots; those stop and answer UNKNOWN. No free-text answers: every ANSWER is a stored value, a subject name, yes/no or an integer. And a count does not yet bind the noun of the question, the defect disclosed in §07.
LOOKUP #n, CHAIN, EQ / LT / GT / NOT, PICK_LT / PICK_GT, RETURN; across the four evaluation families the served model wrote 2,972 lookups, 378 chains, 583 comparisons or picks and 190 negations in 2,387 programs. Set programs: SET, FIND, FILTER with an explicit rule for missing values, AGG COUNT over members or distinct values, RETURN. Three statuses, shared with the reference reader: ANSWER, CONFLICT, UNKNOWN. It is still a neural network: it reads the question and the listing and emits program tokens. What is different is the type of its output, and where it runs.
What a question costs. XERJ Query-1 needs no GPU. The engine, the index and one model bundle sit in ordinary memory: 674 MiB resident with the model loaded, 159.6 ms to load it with its checksums verified. Answering is one pass of the model plus an executor that runs in microseconds. Two latency numbers appear in this post and they are not the same measurement. The 120 ms median is the int8-guard serving mode on a quiet x86 box with four cores, charted below. The 1,039 ms median in §07 is the live head-to-head: this bundle's own serving path on four cores of a shared aarch64 box that was also running training, 4.1 CPU-seconds per question. The $0.042 per 1,000 questions is priced from that slower run; the int8-guard mode would price at $0.0052, and that figure stays a projection until it is run on the same store.
Chart 4 · Serving latency and footprint. Left: median and 95th-percentile latency per question for the two serving modes, 4 cores, 8,000 records, sequential; int8-guard also measured 7.4 questions per second sequential and 16.8 at concurrency 8. The tail barely moves because it is the guard's fallbacks, by design. Right: resident memory and load time of the released build serving the FP16 bundle on aarch64, model state loaded, checkpoint hashes verified. Measured on two machines, a quiet x86 box and an aarch64 build host, shown side by side, not combined.
What the index costs. Because the model's world is the engine's own index, there is no encoder pass over the corpus, no vector field and no context to cache. On 10 GB of English Wikipedia, 3.3 million articles, XERJ built its index in 17 minutes and 0.7 CPU-hours, 11.35 GB on disk, searchable at 9.3 ms at the median; embedding the same text takes 62 to 97 hours of CPU. Facts are fast to write too: 95,000 facts went into one namespace in 23 s, about 4,100 a second. On the smaller 1.1-million-document corpus below, the engine's whole index, which answers word queries, aggregations and XERJ Query-1 programs from the same files, is 712.9 MiB in the default layout and 303.7 MiB with both opt-in layouts on:
Chart 1 · Index size by on-disk layout, 1 GB JSONL slice, 1,117,217 docs. Default layout and three opt-in layouts, plus the stored-text family alone before and after source_ref. All four return the identical top-16 result set on 200/200 recorded queries (overlap@16 = 1.0000). Opt-in because each gives something up: source_ref needs the source files present and unchanged (an edited file yields an explicit error, never stale text, at about +7 ms p50), and no-positions refuses phrase queries with a 400. Measured on the same 1 GB slice.
Common questions
- Do I have to train the model on my data?
- No. XERJ Query-1 is trained once, by us, on synthetic practice data. On your machine it is never retrained or fine-tuned. It reads your facts when you ask, so new data works immediately.
- What do #10 and #7 mean?
- They are record numbers. For each question the engine lists the facts in the namespace as a numbered listing, and
LOOKUP #10reads record 10 of it. The trace names every record it used. - Does it read my documents?
- It reads the facts you, or your agent, write into a facts namespace through
/_facts/{ns}/_bulkor the MCP tool. It does not yet extract facts from arbitrary documents on its own. Full-text search over your documents is the ordinary search, and it works alongside. - What happens when it does not know?
- It says so. If no stored fact backs the answer it returns UNKNOWN; if two facts disagree it returns CONFLICT. It never guesses.
- Does anything leave my machine?
- No. The model and the engine run on your CPU. No question, fact or answer is sent anywhere.
How to run it today. Install the engine with the model, start it with the flag, write a few records, ask a question with a definite answer, and read the trace:
$ curl -fsSL https://xerj.org/get | sh -s -- --with-model $ xerj --facts-model-dir ~/.local/share/xerj/model --insecure --data-dir ~/xerj-data
You write the facts yourself, through _bulk or the MCP tool, with a stable subject_id on each subject; the engine does not yet turn arbitrary documents into facts automatically. The same binary is the whole engine. xerj autoindex <folder> turns a folder into searchable data with no configuration: five files became two datasets, seven records and a seven-edge graph in 0.7 s on our test. Agents get memory and brains over MCP: store a note, recall by meaning, link records into a graph, read a node's neighbourhood with its evidence; 24 tools with the model on, 23 without it. And it is an Elasticsearch-compatible search engine: bulk indexing, full-text search, aggregations and kNN over your own vectors, on the same port.
xerj --facts-model-dir <DIR>; without the flag the engine behaves exactly as rc.100 did. The model bundle is a separate release asset. Counting works on records that carry a stable subject id (subject_id), and in this release covers "how many" set counts, not sums. The step-by-step guide is the quickstart; the engine is one binary.
- Smaller postings by default: the word index shrinks from 325.1 to 288.3 MB and reader memory after load from 573 to 480 MB, for about 2% at the 95th percentile of query latency.
- Stored rows in compressed blocks: reading a page of 25 rows drops from 4,968 ms to 50 ms at the median; the cost is merge-time encode CPU, 48.6 s against 18.5 s.
- The read fast paths are on by default, each proven identical to the old path on pages, scores, sources, totals and facets before it shipped.
- Ingest keeps several bulks in flight and honours the node's
Retry-Afterhint. - Background merges stay inside the memory budget: a 1M-passage index that reached 36.9 GB peak resident under a 6 GiB budget no longer does.
Chart 6 · rc.101 engine changes, wins and the cost, 1M-document corpus. Left: on-disk size before and after for the postings codec (.post) and the stored-row section (after merge). Middle: reader memory after load. Right, on a log axis: reading a page of 25 rows, and the merge-time encode CPU, which went the wrong way and is drawn in the loss colour. Measured on a 1 million document corpus.
TESTS AND BENCHMARKS.
NINE TIMES FEWER WRONG ANSWERS THAN RAG ON OPUS 5.5. HUNDREDS OF TIMES CHEAPER.
The head-to-head. We took 5,000 records (subject, relation, typed value, source sentence) from 95 collections of our standard evaluation and merged them into one store: 357,085 characters, 172,870 Opus tokens. We asked 160 questions, forty from each of four families, drawn by a deterministic hash. Every method got the same rules as the reference prompt: answer, say UNKNOWN, or say CONFLICT, and a wrong answer costs two right ones. Every output was scored by the same frozen scorer against the same gold. The sample score is 100 times a method's family-weighted Net over the Net of Opus 5.5 given perfect retrieval, so that reader defines 100. Intervals are paired bootstraps over collections, 4,000 draws.
- Classic RAG. Records chunked eight to a passage (660 chunks); BM25 word retrieval, or bge-small embeddings with cosine search; the top 5 or top 10 chunks handed to Opus 5.5 to answer.
- GraphRAG, a minimal faithful implementation of Edge et al. (2024): Opus 5.5 extracts entities and relationships from 197 text units (1,556 entities, 2,225 relationships), groups them into 97 communities, writes 70 community reports, and answers each question from the matching entities, relationships, reports and text. Our shortcuts (no second extraction pass, no description summaries, one community level) all make its index cheaper than the real thing.
- Opus 5.5 reading the data directly, two ways: the oracle, which is shown only the right collection's listing (perfect retrieval assumed, the upper bound for a reader), and the whole store in the prompt, 40 questions per call, with a 20-question probe at one question per call to price the single-question case.
- XERJ Query-1, live: the rc.101 engine build, the promoted bundle, four CPU cores on a shared aarch64 box, the records written into the engine through
/_facts, no language model and no embedding anywhere in the path.
| Method | Right | Wrong | Declined | Cost / 1,000 questions |
|---|---|---|---|---|
| XERJ Query-1, live | 124 | 1 | 35 | $0.042 |
| Opus 5.5, whole store, 1 q per call (20-q probe) | 15 of 20 | 1 of 20 | 4 | $694.30 |
| Opus 5.5, perfect retrieval (oracle) | 137 | 5 | 18 | $28.84 |
| Opus 5.5, whole store, 40 q per call | 130 | 6 | 24 | $19.41 |
| GraphRAG + Opus 5.5 | 127 | 9 | 24 | $65.39 |
| RAG, BM25 top-10 + Opus 5.5 | 124 | 9 | 27 | $20.95 |
| RAG, BM25 top-5 + Opus 5.5 | 117 | 12 | 31 | $13.83 |
| RAG, embeddings top-10 + Opus 5.5 | 109 | 14 | 37 | $19.85 |
| RAG, embeddings top-5 + Opus 5.5 | 106 | 15 | 39 | $13.41 |
Sorted by wrong answers, fewest first; out of 160 questions, list prices. Accent marks the best cell in the wrong and cost columns, red the worst; the 20-question probe is listed by its own count. Building the index, per method: XERJ Query-1 8.8 s wall, 5.5 CPU-s, 15.4 MB, no LLM calls. Opus 5.5 oracle and whole store: none. GraphRAG: 267 LLM calls, $14.27, 72 min serial (12 min at 6 in parallel). RAG, BM25: 0.02 s. RAG, embeddings: 19.6 s encode at 8 threads, 1.28 GB peak memory. The cost chart is in §03.
Chart 2 · Score with intervals, and wrong answers, per method. Dots are sample scores against the Opus 5.5 oracle at 100; bars are 95% paired bootstrap intervals over collections. The right-hand columns are the wrong-answer count out of 160 and the 95% interval of the method's wrong count minus XERJ Query-1's. Measured, same 160 questions.
What the numbers say, in plain words. The finding that holds up is the error rate. Of 160 questions XERJ Query-1 answered one wrong: nine times fewer wrong answers than RAG on Opus 5.5 (1 vs 9 to 15), five times fewer than Opus reading everything (1 vs 5 to 6). Every pipeline built on a language model answered between 5 and 15 wrong, and the paired intervals of the difference exclude zero against GraphRAG and every RAG variant; against whole-store Opus the interval touches zero, and against the oracle it does not separate. On overall score the picture is narrower. XERJ Query-1 at 96.1 is significantly ahead of embedding RAG and BM25 top-5, and within noise of GraphRAG, BM25 top-10, whole-store Opus and the oracle at this sample size. It declined more than any method, 35 of 160, and that is where its score goes: a decline earns nothing. That is the price of refusing to guess, and §08 is about lowering it.
Three worked questions, as "show your work". In the standard evaluation the reference reader is Opus 5.5, given the identical listing of records under a frozen prompt: "Answer each question from the listing given with it and from nothing else: no outside knowledge, no tools, no code, no calculators. Work it out yourself." Every item below is a real row; the program and trace are from the served rows, the reference answer from the reference run.
A count with two conditions. The reader has to keep two filters and a running tally in its head; the program makes the filters explicit and the engine does the tally.
6
gold: 6 · rightSET FIND artist = Pierre-Auguste Renoir FILTER height | number cm > 19 cm missing: exclude FILTER medium | text contains "lithographs" missing: exclude AGG COUNT each member RETURN 6 verifier rc4: accepted
10
wrongTwo filters and a tally held in a reader's head at once, and the tally slipped. In the program the filters are explicit and the tally is a loop; there is nothing to hold.
A comparison where the data disagrees with itself. The store holds two different heights for one artwork.
CONFLICT
gold: CONFLICT · rightLOOKUP #12 CONFLICT two heights stored, neither supersedes LOOKUP #0 VALUE number 210 mm PICK_LT CONFLICT RETURN CONFLICT
Head of Christ
wrongThe store held two disagreeing heights for one work. The reference picked one and answered. The executor's lookup returned CONFLICT and the comparison could not proceed, which is correct under the rubric and, we would argue, in production.
And one of the nine wrong answers. We show it because the error is visible in the trace: the pointer and the record it resolved to are named, so the mistake can be found, counted and trained against.
Elquel Vobelri Works
gold: surveyor · wrongLOOKUP #5 VALUE text Elquel Vobelri Works the employer record, not the occupation RETURN
surveyor
rightThe model pointed at the wrong record for the right subject. We show it because the failure is legible: the trace names the pointer and the record it resolved to, so the error can be found, counted and trained against. A wrong paragraph carries no such handle.
The standard evaluation. This is a different test from the head-to-head: 1,600 questions in their own collections, each scored against Opus 5.5 reading that collection's listing. There are four families of 400 questions: teacher_dev (small worlds with edits over time and planted conflicts: rivers, artworks, code), bench_select (a museum catalogue, a retail ledger and similar real-shaped data, including questions the records cannot answer), s1b_dev and d1_select (facts about code structure, employer rosters, and counting over an artist catalogue). Per family, Net is right answers minus twice the wrong ones, out of 400. The score is 100 times XERJ Query-1's weighted Net over the reference's, so the reference defines 100, under the rules pre-registered on 31 October and the withdrawn first score described in §05.
| Family | XERJ Query-1 right / wrong / declined | Opus 5.5 right / wrong / declined |
|---|---|---|
| d1_select | 319 / 0 / 81 | 381 / 2 / 17 |
| bench_select | 222 / 2 / 176 | 245 / 3 / 152 |
| s1b_dev | 337 / 3 / 60 | 358 / 5 / 37 |
| teacher_dev | 330 / 4 / 66 | 378 / 12 / 10 |
| Total, 1,600 | 1,208 / 9 / 383 | 1,362 / 22 / 216 |
Sorted by XERJ Query-1 wrong answers, fewest first. Accent marks the family with the fewest wrong answers, red the most, for each system. 400 questions per family.
Chart 5 · Net per family, XERJ Query-1 and the reference. The reference leads on every family, by least on bench_select, where both decline most, and by most on d1_select, where XERJ Query-1 declines 81 counts the reference answered. The arithmetic line under the bars reproduces 90.29 exactly.
bench_select and d1_select families were also the design data for the decline and routing rules, so in-distribution numbers flatter. In the head-to-head, too, XERJ Query-1 is on home ground and the language-model methods were not trained for it.
(2) Robustness to new wordings and training overlap were not re-measured for this bundle. On a separate 270-item fresh-wording set, which has no reference run and no 0-to-100 score, the previous candidate answered 112 right, 11 wrong and declined 147. Counting on that set is unmeasurable there: its worlds carry no subject ids, so every count is declined (0 of 60 count items, 1 of 30 list items), a property of the test set, not evidence either way about counting.
(3) n=160 over 95 collections in the head-to-head; the intervals are wide, and most score gaps against the strong baselines are not significant. The wrong-answer gap is the finding that survives.
(4) All costs are list prices from the logged token counts; Opus 5.5 ran at its default effort through a subscription, whose own billed figures, which include cache writes, are higher and are in the results file. Language-model latencies include the network; XERJ Query-1's is local CPU on a shared box under load.
(5) Every head-to-head method started from the same pre-extracted records. Extraction from raw documents is unmeasured for all of them; GraphRAG's index re-extracts entities from the records, and it is a faithful minimal implementation (Louvain communities, no second extraction pass, one community level), each shortcut making its index cheaper than the reference package.
(6) The promoted bundle is the best of three seeds of its training family. The family's three-seed mean did not pass our retention rule (the bench family slipped 1.67 points on average); the single seed did, with bench at −0.25. We promoted it on that basis and record it here rather than quietly.
(7) XERJ Query-1 over-declines: 383 of 1,600 items where the reference declines 216, and 35 of 160 where no other method declined more than 24. Of the teacher family's 66 declines, most suppressed an answer the executor had already computed correctly.
(8) The 96.1 and the 90.29 are different evaluations: 160 questions over one merged store against an oracle reader, versus 1,600 questions in their own collections. Do not add them up.
What it does not do yet. Counting covers set counts only, "how many X with Y", and only over facts that carry a subject_id; sums and thresholds are outside the op-set and are declined. One defect we found in our own end-to-end test ships with this release: a count tallies the members of the matched set and does not check the noun in your question. Ask for the total of a quantity the store does not hold, such as devices across the Berlin offices when the store only knows who sits in Berlin, and you can get a member count back as a verified answer. The response names the program it ran; read it before trusting a count. A proper sum operation, and refusing when the noun is not in the store, is the next model. It over-declines, 383 of 1,600, and its robustness to fresh wordings is unmeasured for this bundle. Large fact sets are slow and decline more: on a 95,000-fact namespace an ask took 1.2 to 2.4 seconds and returned UNKNOWN in our test. Keep namespaces small (one per document set, project or customer) until the next model. We also ran an honest end-to-end test of the whole experience. Eight fresh agents, Haiku 4.5 and 5.5 and Opus 4.6 and 5.5, each got one prompt, a records file and twelve questions, and were told to install the product from its own instructions and decide which capability to use. Seven of eight runs answered all twelve questions right, by full-text search and their own arithmetic. Only Opus 5.5 got XERJ Query-1 to answer at all: the write contract for facts was not in the instructions, and the other agents never got a fact into the store. We fixed the docs.
How we measured. Every method answered the same 160 questions over the same 5,000 stored records, and every answer was scored with the same rules against the same gold answers. The standard evaluation is 1,600 questions in their own collections, scored against Opus 5.5 reading the same records. Costs use list prices we fetched on 2026-11-05: Anthropic's Opus 5.5 token prices for the language-model methods, and the on-demand rate of an AWS c7g-class CPU server for XERJ Query-1. Indexing and embedding at scale ran on 10 GB of English Wikipedia. The scripts and the engine are open at github.com/xerj-org/xerj, so every number here can be re-run.
TRY IT IN ONE PROMPT.
Paste this into your AI agent (Claude Code, Codex, Cursor or any agent with a shell):
Install XERJ with the XERJ Query-1 model from https://xerj.org/llms.txt, start it with --facts-model-dir, write three facts about my project with subject_id, and ask one question about them; show me the program and the evidence it returns.
Or run it yourself:
$ curl -fsSL https://xerj.org/get | sh -s -- --with-model
Check that the model is loaded: curl -s localhost:9200/_facts | jq .model.state should print "loaded". The model is off by default; the installer prints the xerj --facts-model-dir <DIR> line that turns it on.
DISCLOSURE, AND WHAT WE NEED NEXT.
OPEN METHOD, OPEN GAPS, AND 100,000 H100 GPU-HOURS.
What is open, and what is not. The XERJ engine is open source under Apache-2.0. Everything that made this post is open too: the method, the scoring rules, the numbers, and every run behind them, mapped in our provenance record. The model itself is not open yet. XERJ Query-1 ships as a compiled part of the engine binary plus a separate model bundle, and the crates that train and serve it are proprietary for now. We would rather say that plainly than let the word "open" do work it has not earned.
The known gaps, each with its number. XERJ Query-1 over-declines: 383 of 1,600 items, against 216 for the reference, most of them answers the executor had already computed correctly. Counting works only on records that carry a stable subject id, and covers set counts, not sums or thresholds. Robustness to fresh wordings is unmeasured for this bundle, and the fresh set we built cannot judge counting at all. Large fact sets are slow and decline more: on a 95,000-fact namespace an ask took 1.2 to 2.4 seconds and returned UNKNOWN in our test. Keep namespaces small (one per document set, project or customer) until the next model. The op-set is limited: 168 catalogue items need joins, grouping and sums the programs cannot yet express, and no amount of training moves them.
The ask. XERJ Query-1 was trained on thousands of donated dollars of rented GPU time. To reach the next quality level we estimate we need about 100,000 H100 GPU-hours for the next training program. That is our estimate, not a measured number. It buys larger and more varied training worlds, robustness to fresh wordings measured on sealed sets with subject ids, sums and thresholds in the op-set with the verifier to match, and a re-scoring of everything in this post on data the model has never seen. If you can fund part of that, the project takes donations in USDT on the TRON network; a donation page at xerj.org/donate will carry the same address. We are grateful to the people who have already given: this model exists because they did.
# XERJ model donations wallet · USDT (TRC20)
TF4Nzy4KWum9gtxXQddfTop7B52nSJsr7S
How to help without money. Try it, with the two lines in §06, on records you know the answers to. File what breaks or surprises you as GitHub issues, including answers that should have been given and were declined. Send pull requests to the engine. And if you have evaluation data with entity ids, questions with definite answers over records whose subjects are identified, share it: that is the single thing that would most improve the next round of measurement.
Research in the open. XERJ Query-1 is the first model of a series, and the series is being built in public: the rules before the runs, the losing rows kept, the provenance beside every number. The next models are trained the same way, with a larger op-set that includes sums and thresholds, robustness to fresh wordings measured on sealed sets, and training worlds that carry entity ids so counting can be judged. Donations fund those next models, not only this one.
Ask it something with a definite answer, and read the trace. The trace is the product.