
CORPUS HUB V2: ASK THE QUESTION,
GET THE ONE PACK THAT ANSWERS IT
As of 2026-10-19, 3,663 developers have said yes. Each of them
agreed to let AI agents learn from a piece of their own work: a CUDA kernel fix in llama.cpp, a
Turkish translation for cc-switch, a compiler fix in rust-lang/rust, a streaming fix in Fabric.
Their repositories now wait in licence review as 3,659 corpora, next to the
513 corpora already live on hub.xerj.org.
Hub v2 is how that shared memory reaches an agent. You type
xerj "how does lz4 decompress a block", the CLI picks the right corpus
from a signed catalogue, pulls one pre-indexed 4.4 MB pack and answers on
your own machine: 1.7 to 2.3 seconds the first time, 37 to
57 ms after that. Every number below was fetched or measured on 2026-10-19, and each
one comes with a URL or a command you can run yourself.
The community so far
A corpus is only as good as the people who wrote the code in it. So before the CLI, here is who showed up. These figures come from hub.xerj.org/contributors/, the hub's own APIs and the GitHub API, all read on the evening of 2026-10-19 (Pacific time).
The contributor wall is sorted by the GitHub stars of the repository each person's work landed in, so the top of the list doubles as a map of the projects developers care about most. Here are the first ten, as listed tonight. The stars belong to the repository, not to the person, and every one of these corpora is still in licence review:
| # | contributor | selected work in | corpora | lines | GitHub ★ on repo | hub ★ |
|---|---|---|---|---|---|---|
| 1 | @NianJiuZst | openclaw/openclaw | 1 | 308 | 392,443 | 0 |
| 2 | @abhay-2108 | morluto/rea | 1 | 882 | 330,245 | 0 |
| 3 | @wellkilo | affaan-m/ECC | 1 | 2,822 | 283,635 | 0 |
| 4 | @alkank | farion1231/cc-switch | 1 | 174 | 146,199 | 0 |
| 5 | @edenfunf | ggml-org/llama.cpp | 1 | 399 | 131,685 | 0 |
| 6 | @SrijanSriv | Graphify-Labs/graphify | 1 | 236 | 128,746 | 0 |
| 7 | @fufesou | rustdesk/rustdesk | 1 | 1,357 | 126,303 | 0 |
| 8 | @paradoxicalguy | rust-lang/rust | 1 | 291 | 120,578 | 0 |
| 9 | @bilashcse | axios/axios | 1 | 342 | 109,298 | 0 |
| 10 | @MeAkib | angular/angular | 1 | 265 | 101,110 | 0 |
Hub stars are the ones visitors give on the hub itself, and that count is brand new. Across the whole hub there are 3 as of 2026-10-20: one on @ksylvan's profile, one on @hosseinkarami-dev's, and the first one on a corpus, vuln-fix-commits. If someone's fix saved you an afternoon, their page has a star button. The project repository, xerj-org/xerj, stood at 10,671 GitHub stars and 637 forks on 2026-10-20, with 60 accounts credited as code contributors.
Three different numbers describe "how many corpora", and each one answers a different question:
| what lives where, checked 2026-10-19 (Releases re-counted 2026-10-20) | count | source |
|---|---|---|
| corpora listed as live in the registry | 513 | hub.xerj.org header (11 domains, 550 registry rows, 78.6 GB pinned text) |
| corpora with a signed v2 pack on GitHub Releases | 251 | distinct slugs among published *.hubpack.tar.zst assets; builds are still landing, so this keeps climbing |
corpora in the signed catalogue xerj ask routes over | 14 | catalogue generated 2026-10-18 02:13 UTC, 1.3 MB |
| corpora built from contributors' repositories | 3,659 | hub.xerj.org/contributors/, all in licence review |
The catalogue row is the one that limits xerj ask today. Pack builds
have run ahead of it, so a corpus can have a signed pack on Releases and stay invisible to the
router until the catalogue is regenerated. That gap, 14 against 251, is the one to close.
Ask without naming a corpus
The launch post ended on a promise: "the pre-indexed half — a corpus you mount instead of indexing". This is it. CI builds each pack, indexes it, signs it and attaches it to a GitHub Release. Your machine restores those indices as they are. No clone, no indexer. Here is a fresh install with an empty cache and no node running:
$ xerj "how does lz4 decompress a block"
xerj hub: no node answers at http://localhost:9200; starting a local node on port 9300 (data: ~/.xerj/hub/node, log: ~/.xerj/hub/node.log)
xerj hub: node ready on port 9300 (pid 1626207)
xerj hub: pulling 'lz4' (4.4 MB) — one-time download, later questions answer locally
xerj hub: pulled lz4 0774d055 4623814 → 4 indices, 1702 definitions
lz4 pin 2026-10-02 (18 days old) mixed: BSD-2 (lib) / GPL-2.0+ (rest) lz4/doc/lz4_Block_format.md:0
...
lz4 pin 2026-10-02 (18 days old) mixed: BSD-2 (lib) / GPL-2.0+ (rest) lz4/lib/lz4frame.h:572
LZ4F_decompress_usingDict(LZ4F_dctx* dctxPtr,
...
answered from: local pack
warning: approach-only licence (mixed: BSD-2 (lib) / GPL-2.0+ (rest)): read, do not copy
Every hit names its corpus, pin date, licence and file path. lz4 is GPL-2.0+ outside
lib/, so the CLI marks it approach-only and says "read, do not copy".
For an exact symbol, --def skips ranking and reads the definition
table built with the pack:
$ xerj ask --def LZ4_compress_fast
lz4 pin 2026-10-02 (18 days old) mixed: BSD-2 (lib) / GPL-2.0+ (rest) lz4/lib/lz4.c:1462
int LZ4_compress_fast(const char* src, char* dest, int srcSize, int dstCapacity, int acceleration)
lz4 pin 2026-10-02 (18 days old) mixed: BSD-2 (lib) / GPL-2.0+ (rest) lz4/lib/lz4.h:236
LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
answered from: local pack
One flaw is right there in the first transcript: the top hit is cited at line
0. Prose files in rc.97 lose their line numbers. The fix for the
same bug in xerj code (#1892, fixed by #1908) reached main on
2026-10-19, after rc.97 shipped, and we have not re-run these transcripts against it.
Four more questions, four more corpora
Same rc.97 binary, same fresh scratch HOME, run on 2026-10-19. Each question named no corpus;
the router picked one and the CLI pulled it on first use. Output is real and trimmed with
....
Alertmanager. A how-question about grouping lands on the dispatcher function that does the grouping:
$ xerj "how does alertmanager group alerts before sending notifications"
xerj hub: pulling 'alertmanager' (13.3 MB) — one-time download, later questions answer locally
xerj hub: pulled alertmanager 55b27cfb 13937896 → 3 indices, 4725 definitions
alertmanager pin 2026-10-13 (7 days old) Apache-2.0 alertmanager/docs/alertmanager.md:1
...
It takes care of deduplicating, grouping, and routing
them to the correct receiver integration such as email, PagerDuty, or OpsGenie.
...
alertmanager pin 2026-10-13 (7 days old) Apache-2.0 alertmanager/dispatch/dispatch.go:440
func (d *Dispatcher) groupAlert(ctx context.Context, alrt *alert.Alert, route *Route) {
...
groupLabels := getGroupLabels(alrt, route)
fp := groupLabels.Fingerprint()
...
answered from: local pack
Avro. Schema evolution, answered from two language bindings at once:
$ xerj "how does avro resolve schema evolution between writer and reader"
xerj hub: pulling 'avro' (21.8 MB) — one-time download, later questions answer locally
xerj hub: pulled avro 28cb08c1 22902146 → 21 indices, 22064 definitions
avro pin 2026-10-13 (7 days old) Apache-2.0 avro/lang/c/src/avro/resolver.h:64
avro_resolved_writer_new(avro_schema_t writer_schema,
avro_schema_t reader_schema);
...
avro pin 2026-10-13 (7 days old) Apache-2.0 avro/lang/java/avro/src/main/java/org/apache/avro/Resolver.java:36
public class Resolver {
/**
* Returns a {@link Resolver.Action} tree for resolving the writer schema
* <tt>writer</tt> and the reader schema <tt>reader</tt>.
...
answered from: local pack
Snappy. "How does it find matches" goes straight to the compression loop and the unit test that pins it down:
$ xerj "how does snappy find matches"
xerj hub: pulling 'snappy' (10.8 MB) — one-time download, later questions answer locally
xerj hub: pulled snappy 9c28114a 11338487 → 4 indices, 382 definitions
snappy pin 2026-10-02 (18 days old) BSD-3-Clause snappy/snappy.cc:812
char* CompressFragment(const char* input, size_t input_size, char* op,
uint16_t* table, const int table_size) {
...
snappy pin 2026-10-02 (18 days old) BSD-3-Clause snappy/snappy_unittest.cc:1025
TEST(Snappy, FindMatchLength) {
// Exercise all different code paths through the function.
...
answered from: local pack
zstd, then a symbol. The question finds the level API; a
--def lookup then pins the definition:
$ xerj "how does zstd choose a compression level" xerj hub: pulling 'zstd' (20.1 MB) — one-time download, later questions answer locally xerj hub: pulled zstd 01b7154f 21038204 → 8 indices, 7874 definitions zstd pin 2026-10-02 (18 days old) mixed: BSD-3 (lib) / GPL-2.0 (cli) zstd/lib/zstd.h:264 ZSTDLIB_API int ZSTD_defaultCLevel(void); /*!< default compression level, specified by ZSTD_CLEVEL_DEFAULT, requires v1.5.0+ */ ... $ xerj ask --def ZSTD_defaultCLevel zstd pin 2026-10-02 (18 days old) mixed: BSD-3 (lib) / GPL-2.0 (cli) zstd/lib/compress/zstd_compress.c:8207 int ZSTD_defaultCLevel(void) ... answered from: local pack warning: approach-only licence (mixed: BSD-3 (lib) / GPL-2.0 (cli)): read, do not copy
| step (xerj v1.0.0-rc.97, Linux, isolated HOME, 2026-10-19) | wall clock | runs |
|---|---|---|
| lz4, first question, cold: node start, catalogue, 4.4 MB pull, answer | 1.72–2.27 s | 4 |
| lz4, the same question again, pack cached | 37–57 ms | 11 |
--def LZ4_compress_fast → lz4.c:1462 | 21–25 ms | 4 |
| alertmanager question, pack cached | 54–122 ms | 3 |
| avro question, pack cached | 62–65 ms | 3 |
--def ZSTD_defaultCLevel and --def groupAlert | 27–39 ms | 6 |
Not every answer is this sharp. "How does astro render islands" pulled the astro pack and came back with an error definition, the config types and three test files cited at line 0, not the island renderer itself. Ranking a prose question against framework code still needs work.
Ambiguous questions come back as a choice
The router does not guess between two plausible corpora. Ask about two compressors at once and it hands the decision back:
$ xerj "lz4 or snappy decompression speed" more than one corpus matches; re-run with --corpus <slug> lz4 — an agent needing fast block compression queries this for the block format and decoder loop snappy — an agent needing cheap compression queries this for the format and the reference encoder decisions $ echo $? 4
Exit 4 already means "needs a decision" in xerj autoindex, so a
wrapper can turn it into a question for the user. When nothing clears the bar, the CLI exits 1
and names the nearest corpora:
$ xerj "how do I run a github actions workflow locally"
no hub corpus matches; see xerj.org/llms.txt
closest corpora: act, astro, agentops; re-run with --corpus <slug> to ask one of them
That one is a miss, and we left it in on purpose. act exists for
exactly this question; the router ranked it first but not confidently enough to answer. The
threshold is ours to fix.
Exit 1 used to hide a third case: hub unreachable, an HTML page where the signed catalogue
should be, or --offline with nothing cached. An agent reading exit
codes took that as "the hub has no answer" and fell back to memory. A fresh-user run hit it on
2026-10-19 (#1904), and
PR #1905, merged to main
the same day, gives it its own code. rc.97 still exits 1.
| exit | meaning |
|---|---|
| 0 | answered, at least one hit |
| 1 | no match, or nothing to answer from (closest corpora are named) |
| 2 | usage error or unknown --corpus |
| 3 | a pulled pack failed SHA-256, signature or format verification |
| 4 | ambiguous: candidates printed, re-run with --corpus <slug> |
| 5 | this build has no hub public key, so nothing is downloaded |
| 6 | the hub catalogue cannot be loaded (on main since #1905; rc.97 still exits 1) |
A page for every contributor
Every corpus is somebody's work, so every contributor gets a page that shows exactly what was selected and where it goes. Here is the text of @edenfunf's page as it rendered on 2026-10-19, trimmed:
@edenfunf Corpus Hub contributor since 2026-10-10 1 corpus · 1 selected commit · 3 files · 399 lines ★ 131,685 GitHub stars on repo · ★ 0 hub stars Selected work ggml-org/llama.cpp fda1866613 CUDA: fix norm family kernels when ne[2]/ne[3] exceed grid dim limits… 3 files · 399 lines · CUDA, C++ · ggml, tests selected · in licence review · corpus ggml-org--llama-cpp · MIT · agreed 2026-10-10 Not installable yet ... It becomes installable when this card says live in the hub.
The page is upfront about what you cannot do yet: until licence review passes,
xerj corpus add and xerj ask cannot fetch
that corpus. One commit can feed several corpora.
@ksylvan's single Fabric
commit (aa3e1727b7, 29 files, 1,055 lines of Go) appears in 2, and the
totals count it once. Each profile carries a README badge at
/contributors/<login>/badge.svg and prints how to leave: reply
to the permission email or open an issue.
Human stars and agent scores, kept apart
A corpus page carries two signals and never blends them. Hub stars are people clicking a star.
The agent score comes from structured reviews agents send after using a corpus, through
xerj hub pull, MCP queries or the
xerj_corpus_feedback tool. A review rates usefulness from 1 to 5, says
whether the answer was found (yes, partial or no) and how fresh the corpus felt, and can name a
coverage gap or flag a problem: error, slow, stale or wrong. The rules are printed on every corpus
page:
- The score is a Bayesian mean of usefulness with a prior of 3.0 at weight 5, so a handful of glowing reviews cannot lift a corpus far.
- One voice per installation, per corpus, per week. Reviewers with abnormal volume are dropped.
- A coverage gap is shown only once 3 or more agents report the same one.
Here is the score for zstd tonight, and the hub-wide list:
$ curl -s "https://hub.xerj.org/api/corpus-feedback?corpus=zstd" {"ok":true,"enabled":true,"corpus":"zstd","reviews":0,"score":null,"raw_mean":null, "confidence":"none","prior":{"mean":3,"weight":5},"found":{"yes":0,"partial":0,"no":0,"rate":null}, "freshness_median":null,"families":[],"coverage_gaps":[], "problems":{"error":0,"slow":0,"stale":0,"wrong":0},"agent_reviews":0,"pull_reports":0, "dropped_outliers":0,"k_min":3,"window_weeks":26} $ curl -s "https://hub.xerj.org/api/corpus-feedback" {"ok":true,"enabled":true,"prior":{"mean":3,"weight":5},"scores":{}}
No corpus has an agent score yet. The page shows that as empty instead of inventing a starting rating, and the first agent review will be the first real data point.
Traffic and usage on every corpus page
Each corpus page has a traffic panel modelled on GitHub's, covering the last 14 days in UTC: views and unique views (crawlers excluded, uniques by a hash salted with the day), pulls, hosted-hub searches, requests per agent family, and referring sites once a site has sent 3 views. Pulls are the growth in the GitHub Release download count for that corpus's pack, our own verification downloads included. It is one JSON call:
$ curl -s "https://hub.xerj.org/api/hub-traffic?corpus=act"
{..."totals":{"views":9,"unique_views":8,"pulls":1,"pulls_unique":null,"searches":0},
"pulls_since":"2026-10-20","recording_since":"2026-10-19",
"agents":[{"family":"curl","count":9}],"referrers":[]}
Nine views, all from curl, and most of them were us writing this post. Recording began on 2026-10-19, so every corpus starts near zero.
Below that sits agent usage: requests over 7 and 30 days, agent families, top topics and the
most common query templates. A template swaps identifiers, versions, paths, numbers and quoted
text for placeholders, appears only after 3 sightings in 30 days, and raw queries are never
published. Only requests to the hosted hub API (hub.xerj.org/api/v1/code
and /def) count. A question answered on your own node never leaves
your machine, so xerj ask with a cached pack never shows up here.
Sign in with GitHub or Hugging Face
Everything above is public. Signing in through
/auth/github/login or
/auth/hf/login (standard OAuth,
PKCE on both) opens the detail behind it: a corpus page adds its pack builds, pin history, source
pins, review records and signing; a contributor page adds every selected commit in full, with
commit date, folders, review state and pin. Usage detail needs a session too; without one the
usage API answers "detail_requires_signin":true.
Field reports that turn into fixes
After an agent installs or uses XERJ, the CLI sends a short field report: what the agent
pointed XERJ at, what it used it for, and its verdict. It arrives as a GitHub pull request adding
one Markdown file under
user-feedback/16-agent-field-reports/,
forking first when the account cannot push. Only when GitHub is unavailable does it fall back to
POST https://xerj.org/api/field-report, which files an issue.
--disable-feedback or XERJ_DISABLE_FEEDBACK=true
turns it off. Whether an agent should send one without asking its user first is open in
#1896; the opt-out works
either way.
As of 2026-10-19, main holds 40 reports in that folder, and 12 more came in through the API
fallback as issues titled "Agent field report (API)". One full loop: several reports flagged a
stray backslash in the MCP registration text that xerj init prints.
Issue #1838 was opened
from them on 2026-10-19, and PR #1848
fixed the three format strings and closed it at 23:34 UTC the same day. A small bug, reported by
agents who had no other way to tell us, and fixed before the day was out.
How it works
The catalogue. hub.xerj.org/api/v1/catalogue.json
is one JSON file with a detached ed25519 signature beside it in
catalogue.json.sig. The CLI fetches it with
If-None-Match and verifies the signature before new bytes may replace
its cache; on any network or verification failure it keeps the last good copy. Each entry holds
the slug, domain, topics, a one-sentence ask_for,
dependency_names, the licence (SPDX plus class), the pin SHA and date,
the pack URL, size, SHA-256 and signature URL, and a term_sketch.
Routing. The router reads only that metadata plus two things on your disk:
the dependency names in your Cargo.lock,
package.json or go.mod, and the packs you
already have cached. The term sketch is a Bloom filter over the corpus's terms, built with 7
hashes. A Bloom filter never gives a false negative, so when the sketch rules out a word in your
question, that corpus drops out without a download. A declared dependency is the strongest
evidence a corpus can earn. Corpora whose licence class is excluded
are removed before routing starts: never picked, never offered, never named as closest.
The pack. A confident pick downloads that one pack from GitHub Releases,
checks its SHA-256 and signature (a failure is exit 3) and restores its pre-built indices into a
node. If nothing answers on localhost:9200, the CLI starts its own on
port 9300, and xerj hub stop stops that process and no other. The hub
serves the catalogue, the web pages and a few small JSON APIs; the search runs on your node. A
hosted switch exists (xerj hub config hosted on|off|auto), but no
hosted client ships in the CLI yet (checked in rc.97 and on main, 2026-10-19), so every
xerj ask answer today is computed on your machine.
Getting started
Install the CLI, then ask. The first question starts the node and pulls the pack; later ones answer from the cache:
$ curl -fsSL https://xerj.org/get | sh $ export PATH="$HOME/.local/bin:$PATH" $ xerj "how does lz4 decompress a block" $ xerj "how does alertmanager group alerts before sending notifications" $ xerj ask --def LZ4_compress_fast $ xerj ask --corpus snappy "how does snappy find matches" $ xerj hub status $ xerj hub stop
xerj hub status lists cached packs with age and size, plus the
node, the hosted setting and the catalogue age. Add --offline to stay
on cached packs, --no-fetch to forbid downloads and
--json when an agent parses the answer.
Next steps
- Add your own work next to the 3,663 who already did: hub.xerj.org/contribute.
- Browse the corpora, contributor pages and agent scores at hub.xerj.org, and star the people whose code you learn from.
- Read the Corpus Hub docs and the implementation plan, CORPUS_HUB_V2_IMPLEMENTATION.md.
- Propose a corpus by pull request against the
corpus-hubbranch on GitHub. - Tell us where routing picked wrong on Discord, or follow releases on X.