XERJ QUERY-1· NEUROSYMBOLIC MODEL· SHIPS IN RC.101 · OPT-IN

PROMPTS IN.
PROGRAMS OUT, CHECKED AGAINST YOUR FACTS.

A new way to answer questions over your data: the model writes a small program, the engine runs it over facts you have written. No embeddings to build and no GPU to rent. Lookups and comparisons come with the facts they used; counts come with the program's hash and a second model's agreement, not the records. Four CPU cores, offline, and in our 160-question test hundreds of times cheaper per question than RAG or an LLM reading the data (ARITHMETIC at list prices).

It is off by default. A node becomes a XERJ Query-1 node when you start it with xerj --facts-model-dir <DIR>. Without the flag, rc.101 serves no /_facts route and the same MCP tools as rc.100, but its storage format changed: the first write moves a data folder to format 4, which rc.100 cannot open.

≈220 msvs1.5–2.3 s
question to answer on the same 160 questions: XERJ Query-1 on 4 laptop CPU cores vs Opus 5.5 writing a RAG answer (search itself took under 1 ms to 106 ms)
Ask. It writes the program.
On the same 160 questions, Opus 5.5 took 1.5 to 2.3 s to write each RAG answer (API time per call; our command-line client's start-up is not counted), after a search that took under 1 ms to 106 ms. Writing the answer is where RAG's answer quality comes from; XERJ Query-1 writes a program instead and runs it on the CPU.
$42vs$13,400–$694,000
per million questions on our 160-question test, at list prices: XERJ Query-1's CPU time vs RAG, GraphRAG and Opus 5.5 reading the data
Runs on your records.
No API model is billed per question: XERJ Query-1 runs a small model on your CPU, and that CPU time is the $42. The same million questions cost $13,400 to $21,000 on RAG with Opus 5.5, $65,400 on GraphRAG and up to $694,000 with Opus re-reading the whole store for every question (ARITHMETIC from measured tokens).
1vs9–15
wrong of 160 questions from our development set, vs RAG on Opus 5.5. XERJ Query-1 was given each question's own records and declined 35; Opus 5.5 given the same records: 5 wrong
Checked or refused.
A missing fact gives UNKNOWN; facts that disagree give CONFLICT. Most RAG errors came from search missing the evidence. Answers are stored values or computed counts, never free text, but they can still be wrong: 1 of 160 here, and some counts in known cases.
17 minvs62–97 h
a keyword (BM25) index of 10 GB of English Wikipedia, 3.3 million articles, on CPU (measured) vs embedding it with bge-small (estimated)
No embeddings.
17 minutes and 0.7 CPU-hours; 100 article-title queries then took 9.3 ms at the median. Embedding the same text would take an estimated 62 to 97 hours and 8.5 GB of vectors. The two do different jobs: on BEIR, vector search beat BM25 by 0.04 to 0.17 nDCG@10. XERJ Query-1 answers over facts you write, not over this index.
≈2 msvs≈30 ms
to apply one correction: a XERJ facts write (measured) vs re-embedding the one changed chunk (ARITHMETIC)
A store you can correct.
Fix a fact and the next ask uses it (10 of 10 in our test); earlier answers replay by revision. An embedding index re-encodes the changed text, and the whole store (19.6 s for our 5,000-record test store) when its model changes.
1 binaryvs3 services
facts, memory and brains over 24 MCP tools vs vector DB + graph DB + cache
Memory and brains beside it.
Agent memory, linked knowledge and facts together; no separate vector DB, graph DB or cache service.
01 · CAPABILITY

Ask. It writes the program.

≈220 msvs1.5–2.3 s
question to answer on the same 160 questions: XERJ Query-1 on 4 laptop CPU cores vs Opus 5.5 writing a RAG answer (search itself took under 1 ms to 106 ms)

You write a plain question: "What is Bo Chen's height?", "Which river is longer, X or Y?", "How many people have office Berlin?" (counting needs a stable subject_id on each person; without one the answer is UNKNOWN). The model reads the question plus a numbered listing of the facts in the namespace and emits a few program steps: LOOKUP #12, CHAIN, LT / GT / EQ / NOT, PICK_LT, or for counts SET → FIND → FILTER → AGG COUNT. It never writes the answer itself.

Compared with an LLM, whose output is the answer as text. Here the output is a program, and the answer comes from running it. For lookups and comparisons the program and its evidence come back in the response; for counts you get the program's hash, not its steps.

Request · an evaluation item from our teacher_dev set (the world and its names are invented)

POST /_facts/world-dev-90014/_ask
{"question": "Is the Arhalga River's discharge exceeded by that of the Gacunas River?"}

Response · the program the model wrote, then the trace and the answer

program   LOOKUP #10 ; LOOKUP #7 ; LT ; RETURN
step 0    LOOKUP #10  → 6956 m3/s   doc3@v1/discharge, Arhalga River
step 1    LOOKUP #7   → 7567 m3/s   doc6@v1/discharge, Gacunas River
step 2    LT          → TRUE        6956 < 7567
answer    yes

What #10 and #7 mean

#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.

02 · CAPABILITY

Runs on your records.

$42vs$13,400–$694,000
per million questions on our 160-question test, at list prices: XERJ Query-1's CPU time vs RAG, GraphRAG and Opus 5.5 reading the data

Each step points at a stored fact: subject, relation, typed value, source document and the character span the value came from. The executor runs the program in microseconds and returns the answer plus the trace: which record, which version, which span. Nothing in the answer is generated; it is read off the store.

Compared with RAG, where the model reads retrieved chunks and writes the answer as text; citations can point at sources, but the text itself is not checked against your data. Here the evidence is a pointer you can follow to the exact characters.

Request

POST /_facts/hr/_ask
{"question": "How tall is Ana Bel?"}

Response · answer with the trace

{
  "final_status": "ANSWER",
  "final_answer": "170 cm",
  "program": [["LOOKUP", 0], ["RETURN"]],
  "evidence": [{
    "fact_id": "f1",
    "doc_id": "d1", "version": 1,
    "char_start": 11, "char_end": 14, "quote": "170",
    "content_sha256": "…"
  }],
  "published_rev": 9
}
03 · CAPABILITY

Verified or refused.

1vs9–15
wrong of 160 questions from our development set, vs RAG on Opus 5.5. XERJ Query-1 was given each question's own records and declined 35; Opus 5.5 given the same records: 5 wrong

A verifier checks the program against the store before anything is served. A pointer that resolves to nothing returns UNKNOWN. Two stored facts that disagree return CONFLICT, and that propagates through any comparison built on them. A count is served only when the executor can close the set and a second, independently seeded model writes the same program.

Measured on the same 160 questions: XERJ Query-1 answered 1 wrong; the language-model pipelines answered 5 to 15 wrong. XERJ Query-1 and the "right records" Opus row read only each question's own records; RAG, GraphRAG and the whole-store row read the merged store. It declined 35. Embedding RAG declined more (37 and 39) because its search missed records; Opus with the right records declined 18. Part of our declines are the price of not guessing; part is a decline rule that is still too strict.

A fact that was never written

{"question": "What is Ana Bel's salary?"}
→ {"final_status": "UNKNOWN"}

Two heights stored for the same person, neither supersedes the other

{"question": "How tall is Bo Chen?"}
→ {"final_status": "CONFLICT", "evidence": []}

A count, served only with partner agreement

{"question": "How many people have office Berlin?"}
→ {"final_status": "ANSWER", "final_answer": "3",
   "path": "counting", "partner_agreement": true}
04 · CAPABILITY

No embeddings.

17 minvs62–97 h
a keyword (BM25) index of 10 GB of English Wikipedia, 3.3 million articles, on CPU (measured) vs embedding it with bge-small (estimated); this is the search engine, not XERJ Query-1

There is no vector step on this path. The "index" the model uses is the facts store the engine already keeps: typed records, an inverted index and doc-values. Writing the 9,496 facts of our 160-question test (each question's records in its own namespace) took 8.8 seconds and 15.4 MB on disk on a shared ARM server (1.8 to 3.0 s on a laptop); no encoder ran, no vector memory was allocated, and there is nothing to redo when a model changes. The same binary still offers BM25 full-text search, aggregations and kNN for documents.

Compared with an embedding index: the 5,000-record store, cut into 660 chunks of 8 records, took 19.6 seconds and 1.28 GB of memory to encode with bge-small on 8 threads of a laptop CPU, and must be re-encoded whenever that model changes.

Index build, measured, per arm (same 5,000 records)

XERJ Query-1: 8.8 s wall, 5.5 CPU-s, 15.4 MB, no LLM call (writing the 9,496 facts of the 160 test questions into 160 small namespaces on a shared ARM server; 1.8 to 3.0 s on a laptop)
BM25 RAG       0.02 s · no LLM call
Embedding RAG  19.6 s · 1.28 GB peak RSS (bge-small, 8 threads)
GraphRAG       267 LLM calls · $14.27 · 72 min serial

Still a full search engine on the same port

POST /hr/_search  {"query": {"match": {"bio": "designer Berlin"}}}
POST /hr/_search  {"aggs": {"by_office": {"terms": {"field": "office.keyword"}}}}
POST /vecs/_search {"knn": {"field": "v", "query_vector": [...], "k": 2}}
05 · CAPABILITY

A store you can correct.

≈2 msvs≈30 ms
to apply one correction: a XERJ facts write (measured) vs re-embedding the one changed chunk (ARITHMETIC)

Facts go in through /_facts/{ns}/_bulk: text, number, date or boolean values, a stable subject_id per entity, and the source document with the quote span. A correction supersedes the old fact and the answer changes, while the old answer stays readable at its revision. A second, disagreeing fact turns the answer into CONFLICT until one is withdrawn. Every ask can be pinned to a revision. About 35 to 45 ms per ask on a tiny namespace (3 documents, 6 facts) on a laptop with 4 model threads; about 220 ms at the median on our 160-question test (namespaces of 6 to 148 facts). The 120 ms we published earlier came from an unreleased int8 setting on one 40,000-fact namespace with a single repeated lookup question.

Compared with a prompt cache or a vector index, where a changed fact means re-embedding or re-reading. Here a change is one edit, and history is kept.

Write facts with their source

POST /_facts/hr/_bulk
{
  "documents": [{"doc_id": "d1", "version": 1,
                 "text": "Ana Bel is 170 cm tall and works in Berlin."}],
  "facts": [{"fact_id": "f1", "subject": "Ana Bel", "subject_id": "emp:1",
             "relation": "height", "kind": "number", "value": "170 cm",
             "doc_id": "d1", "version": 1, "char_start": 11, "char_end": 14}]
}
→ {"committed": {"from_rev": 1, "to_rev": 9}}

Correct it, then ask again, then ask as of before

POST /_facts/hr/_bulk
{"edits": [{"op": "correct",
            "doc":  {"doc_id": "d1", "version": 2, "text": "Ana Bel is 172 cm tall…"},
            "fact": {"fact_id": "f7", "subject_id": "emp:1", "relation": "height",
                     "kind": "number", "value": "172 cm", "doc_id": "d1", "version": 2},
            "supersedes": "f1"}]}
→ {"committed": {"from_rev": 13, "to_rev": 14}}

{"question": "How tall is Ana Bel?"}                 → "172 cm"  (evidence f7)
{"question": "How tall is Ana Bel?", "revision": 9}  → "170 cm"
DELETE /_facts/hr/f9                                   → a withdrawn fact; CONFLICT clears
06 · CAPABILITY

Memory and brains beside it.

1 binaryvs3 services
facts, memory and brains over 24 MCP tools vs vector DB + graph DB + cache

Agent memory (store, recall, forget; lexical ranking today) and brains (link, unlink, ego with evidence, overview) are separate surfaces, available over HTTP and as MCP tools. Brain names ending in -facts are reserved for facts namespaces. The model reads facts namespaces only; memory is for notes an agent wants back later, brains are for the links between them.

Compared with a chat model's context window, which forgets at the end of the session and is paid for on every turn. Memory here is a store on disk, recalled by query, with a graph on top.

Store a note, recall it, link it

POST /_memory/agent1  {"text": "Ana Bel is the Falcon tech lead", "tags": ["falcon"]}
POST /_memory/agent1/_recall  {"query": "who leads Falcon", "k": 2}
→ two notes with scores (lexical ranking)

POST /_graph/agent1/link
{"src": "mem:falcon", "dst": "person:ana-bel", "type": "led_by",
 "evidence": {"quote": "…", "source": "…", "offset": 0}}
→ 201 {"edge_id": "…"}
GET /_graph/agent1/ego?node=person:ana-bel&hops=1  → the edges, with evidence

The same surfaces as MCP tools

xerj_memory_store · xerj_memory_recall
xerj_brain_link · xerj_brain_unlink · xerj_brain_ego · xerj_brain_overview
xerj_facts_ask (listed only when the node runs the model: 24 tools on, 23 off in rc.101; main adds xerj_sources, so rc.102 will list 25 and 24)
COMPARED · SAME 160 QUESTIONS · LIST PRICES · XERJ QUERY-1 AND THE ORACLE READ EACH QUESTION'S OWN RECORDS

Seven ways to answer a question

160 questions with known answers from our own development evaluation, scored by the same frozen rubric. The language-model arms read one merged store of 5,000 records (4,305 distinct: 13 collections were stored twice); 3,732 of those records come from public data (MoMA and Tate collection metadata, OurAirports, USGS earthquakes, UCI Online Retail, the NASA Exoplanet Archive, Python package metadata), the rest from invented worlds. XERJ Query-1 and the oracle read only each question's own records. Costs are per million questions, ARITHMETIC: the measured per-1,000-question list price times 1,000. Opus 5.5 at $4 per million input tokens and $20 per million output tokens; CPU at the AWS c7g.xlarge on-demand rate of $0.145 per hour ($0.03625 per vCPU-hour) at 4.1 CPU-seconds per question on a shared ARM server (0.9 on a quiet x86 laptop).

ApproachReturnsCheckableIndex timeCost / million qWrong of 160 (declined)Offline on CPU
Keyword search (BM25)recordsno answer to check0.02 sCPU onlyn/ayes
Embeddings + RAG on Opus 5.5 (top-10)a short written answerno record pointer (our prompt asked for none)19.6 s, 1.28 GB$19,85014 (37)no
BM25 + RAG on Opus 5.5 (top-10)a short written answerno record pointer0.02 s$20,9509 (27)no
GraphRAG on Opus 5.5a short written answerno record pointer267 LLM calls, $14.27; 72 min one call at a time, 12 min at 6 in parallel$65,3909 (24)no
Opus 5.5 reading the whole store (40 q per call; 1 q per call: $694,300)a short written answerno record pointernone$19,4106 (24)no
Opus 5.5 given each question's own records (oracle; our 2026-10-31 reference run, re-scored)a short written answerno record pointernone (needs perfect retrieval)$28,8405 (18)no
XERJ Query-1 (each question's own records)program + tracelookups: each step cites a fact; counts: program hash only8.8 s, 15.4 MB (160 small namespaces, 9,496 facts)$421 (35)yes

Source: the head-to-head measured on 2026-11-05 (n = 160, 95% paired bootstrap intervals). The Score gap (our credit scale, the Opus 5.5 reference reader = 100) is statistically clear against embedding RAG and BM25 RAG top-5, and not clear against BM25 top-10, GraphRAG, Opus reading the whole store or the oracle. The wrong-answer gap is clear against GraphRAG and every RAG setup, borderline against Opus reading the whole store (0 to 11) and not clear against the oracle (−1 to 10). Records were pre-extracted for every arm. All 160 questions come from the model's development set. Retrieval quality of the search arms (R@k, nDCG), the latency split and the data sets: benchmarks and methodology.

WHAT IT DOES NOT DO · RC.101
No fact extraction from arbitrary documents. Users or agents write facts (xerj autoindex yields records and a graph, not facts namespaces). The river example in the announcement uses records that came from the evaluation pipeline.
A count does not check the noun of the question. "How many devices in Berlin in total?" over a store with no devices relation returns the member count (ANSWER 3) as verified. Disclosed here and in the docs; the real fix is in the next model.
Some counts come back wrong as verified. "How many people are in Berlin or Munich?" returned 1 (the answer is 3) with both counting models agreeing. Counts return a hash of their program, not the counted records.
It over-declines. It declined 383 of 1,600 standard-evaluation items (the reference reader, Opus 5.5 reading each question's whole world, declined 216) and 35 of the 160 head-to-head questions (Opus given the same records declined 18). Part of that is the price of not guessing and part is a decline rule that is still too strict: in one evaluation family, 54 of 66 declines would have been right answers. Fixing that is the first target of the next training program.
Large fact sets are slow and decline more. On a namespace of 95,000 facts (the first sentences of 95,000 English Wikipedia articles), 20 test questions took 0.5 to 1.5 s each (median 0.8 s) on a laptop, and all came back UNKNOWN or UNSUPPORTED. Keep namespaces small until the next model.
Off by default. Without --facts-model-dir there is no /_facts route, no model load and the same MCP tool list as rc.100. Storage still changed: rc.101's first write moves a data folder to format 4, which rc.100 cannot open.
COUNTING · IN DETAIL

Counting, in detail

set counts, partner-verified · needs subject_id · no sums in rc.101 (a sum question can come back as a member count)

Set counts over facts that carry a subject_id: "how many X have Y", "distinct values of a field". The model writes a set program, the executor runs it over the store, and the count is served only when a second, independently seeded model writes the same program. Not sums, totals or thresholds in this release.

Compared with asking a language model to count from retrieved chunks, where the number is generated and nothing checks it. Here the count is a loop the engine ran; the response gives the program's hash and whether a second model agreed, not the program's steps.

Facts with subject ids, then a count

facts: [{"subject": "Ana Bel", "subject_id": "emp:1", "relation": "office", "value": "Berlin"},
        {"subject": "Bo Chen", "subject_id": "emp:2", "relation": "office", "value": "Berlin"},
        {"subject": "Cy Dao",  "subject_id": "emp:3", "relation": "office", "value": "Berlin"},
        {"subject": "Di Eck",  "subject_id": "emp:4", "relation": "office", "value": "Munich"}]

{"question": "How many people have office Berlin?"}
→ {"final_status": "ANSWER", "final_answer": "3",
   "path": "counting", "partner_agreement": true, "program_sha256": "…"}

{"question": "How many people have role engineer?"}   → "2"

Without subject ids the set cannot be closed

→ {"final_status": "UNKNOWN", "path": "facts", "counting": {"status": "INCOMPLETE"}}

Common questions

Do I have to train the model on my data?
No. XERJ Query-1 is trained once, by us, on generated practice questions over public datasets (such as Tate collection and Python package metadata) and invented worlds, questions written by Claude Opus 5.5, and items from public question-answering sets (MQuAKE, HotpotQA, 2WikiMultihopQA). 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 #10 reads 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}/_bulk (over MCP you can ask with xerj_facts_ask, but no MCP tool writes facts). 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 is built to say so: if no stored fact backs the answer it returns UNKNOWN; if two facts disagree it returns CONFLICT. It is not perfect: in our tests a 'how many devices in total' question over office facts and 'How many people are in Berlin or Munich?' came back with wrong counts.
Does anything leave my machine?
The model and the engine send no question, fact or answer anywhere; they run on your CPU. The installer asks AI agents to send a short usage report after they use XERJ; turn that off with XERJ_DISABLE_FEEDBACK=true or --disable-feedback.
TRY IT · ONE BINARY PLUS A 389 MB MODEL BUNDLE · FOUR CPU CORES · ABOUT 0.7 GB AFTER LOADING, 1 TO 1.5 GB IN USE

Run it today

Install the engine with the model bundle, start the node with the flag, write a few facts, ask a question. The trace is the product.

curl -fsSL https://xerj.org/get | sh -s -- --with-model
xerj --facts-model-dir ~/.local/share/xerj/model --insecure --data-dir ~/xerj-data
curl -s localhost:9200/_facts | jq '.model.state'      # "loaded"

The write contract, the two ask routes and the counting rules are in the model quickstart and the facts API reference. The announcement, with the measurements and how the model was trained, is at /blog/xerj-query-ai-model.

XERJ Query-1 was trained mostly on rented GPU time paid for with donations, plus a few machines of our own. If it is useful to you, fund the next model.