Skip to content
hornbook-knowledgePublic

About

Verified-corpus organ: human-checked Q&A with citations, plus a standalone MCP server.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

jeles

The verified-corpus organ, extracted from the Ask Jeles app into its own installable package — plus the canonical Jeles persona.

A nugget is a human-verified question/answer pair with citations: {question, answer, sources, verified_by, verified_at, tags}. This package is the settled layer of answers that sits in front of a host's live search — a confident nugget match answers instantly; everything else falls through to the host's own search path unchanged. Misses are logged as gaps ("I don't know yet") so the corpus knows what it's been asked and couldn't answer.

What's in the organ

Module Role
jeles.corpus Pure storage + ranked lookup of verified nuggets and gap logging. Stdlib-only, no MCP, no network at import. Reuses willow-mcp's SOIL Store SQLite schema at $WILLOW_STORE_ROOT/<collection>/store.db — writes are upserts that touch only jeles' own columns, in WAL mode with BEGIN IMMEDIATE, so the store really is shared rather than shared-until-one-side-writes.
jeles.reactions Pure (event) -> [proposed actions] handlers. conflict_scan searches for what supersedes or refutes a design claim rather than what resembles it, and proposes a nugget only when two independent, relevant, non-excluded domains corroborate it — otherwise a contested gap. search_adapter is its web edge.
jeles.corpus_server Standalone MCPServer (MCP SDK 2.x) over the corpus (python -m jeles.corpus_server). Mirrors willow-mcp's shape (app_id on every tool) without depending on willow-mcp. Writes through it are assertions, not verifications — see below.
jeles.sources The institutional collections themselves — 66 registered source functions, 62 of them in the default fan-out (arXiv, PubMed, Crossref, OpenAlex, Library of Congress, Europeana, CourtListener, the Smithsonian, the Almanac's dataset catalogs), plus the concurrent fan-out across them. Stdlib-only at import; optional jeles[connectors] prefers maintained clients for a first slice and keeps urllib fallbacks.
jeles.connectors Optional façades over pyalex / habanero / arxiv / Bio.Entrez / internetarchive / musicbrainzngs / pygbif / pyinaturalist. Not imported until a search_* call asks.
jeles.institutional The third hop: fans a query across jeles.sources in-process, and shapes results like every other hit. Optionally delegates to a hosted jeles-remote instead.
jeles.willow_mcp_client Best-effort, fire-and-forget forwarding of gaps into willow-mcp's fleet-wide backlog. Never blocks, never raises; 30s retry cooldown so a single failed connect doesn't permanently disable forwarding.
jeles.load_persona() Loads the canonical Jeles persona JSON (this package is its canonical home).

Design principles

  1. The corpus sits in front of live search, it doesn't replace it. A confident nugget match answers instantly — no search, no LLM call.
  2. corpus.py stays pure. Storage and ranking have no MCP, no network, no side effects beyond SQLite. Everything MCP-shaped wraps it; it never depends on anything MCP-shaped itself. This is what keeps its tests fast and network-free.
  3. The corpus is its own standalone MCP server, on purpose. corpus_server.py is a small MCPServer any stdio client can run directly, mirroring willow-mcp's shape without depending on it.
  4. Two kinds of "ask," two gap-logging rules. search_nuggets() (passive/background) checks the corpus but never logs a gap on a miss. ask_corpus() (deliberate) treats a miss — or a match below MIN_ASK_SCORE — as a real gap worth tracking, and logs it.
  5. Ranking and answering are different decisions. search_nuggets() ranks loosely and will happily surface a near-miss. ask_corpus() answers only when the nugget's question contains every content word the asker used, and the two questions overlap symmetrically — so a nugget about staging cannot answer a question about production, and one word cannot pull an answer out of a nugget it barely resembles. Saying "I don't know yet" and logging the gap is the correct output far more often than it looks.
  6. The collections live here, not behind a service. sources.py is the same relationship corpus.py has with corpus_server.py: a pure core that something thin wraps. A hosted deployment is a convenience — never a prerequisite, never a secret you must hold, never a second repository in the test loop. Stated precisely, because the short version was wrong: jeles-remote's main.py is a 74-line FastAPI shim, but it wraps its own vendored copy of sources.py (import sources, a file in that repo), not this module. That copy was forked from the same origin and has drifted — 833 differing lines, ~200 fewer, six fewer source functions, and zero references to _egress, so the SSRF/key-leak fix that landed here is simply absent from it. Treat jeles-remote as a downstream fork due a re-vendor, not as a thin wrapper that inherits this package's fixes.
  7. Local is the source of truth; the fleet backlog is additive. corpus.log_gap() (synchronous, local SQLite) always runs first and makes the host fully functional offline. willow_mcp_client.forward_gap() is a best-effort copy into willow-mcp's shared backlog.

Quick start: on your own, no fleet

Jeles was built as an organ inside a willow-mcp fleet, where the fleet signs each app's manifest and that manifest bounds what the corpus may touch. It refuses to open its store without one, signed. On your own machine, make one and sign it with your own GPG key:

pip install jeles
jeles-seed --init-local     # writes ~/.jeles, signs the manifest with your default GPG key
# then export the three variables it prints, e.g.:
export WILLOW_MCP_APPS_ROOT=~/.jeles/mcp_apps
export WILLOW_STORE_ROOT=~/.jeles/store
export JELES_CORPUS_APP_ID=jeles-corpus
jeles-seed                  # load the bundled seed
python -c "from jeles import corpus; print(corpus.ask_corpus('What is a black hole?'))"

--init-local keeps to its own ~/.jeles tree (or --home DIR), never willow-mcp's ~/.willow/mcp_apps. The manifest it writes grants only the corpus's own two collections, and it never overwrites an existing one. If gpg isn't installed or you have no key yet (gpg --quick-gen-key "Your Name <[email protected]>" makes one), the manifest is left unsigned and the command prints the one gpg --detach-sign line to run. Jeles itself never writes or imitates a signature.

Then set the two things only you can supply: JELES_SOURCES_CONTACT_EMAIL (your address for the scholarly APIs' polite pools) and, if you'll review intake material yourself, JELES_VERIFIER (your name). Both are in Configuration.

Install

Base jeles has zero runtime dependencies. The corpus, the persona, and the reactions are stdlib-only, so a host can depend on this package without inheriting a single version constraint from it. Only the standalone MCP server needs the SDK, and it lives behind an extra.

pip install jeles           # corpus + persona + reactions. No dependencies.
pip install "jeles[mcp]"    # adds the MCP SDK, for the standalone server
pip install "jeles[nestor]" # adds Nestor, to check human-rung seals (below)
pip install "jeles[connectors]"  # maintained clients for institutional sources (optional)
pip install -e ".[dev]"     # editable, with pytest and the SDK

jeles.sources stays stdlib-only at import. With [connectors], eight scholarly/biodiversity façades (OpenAlex, Crossref, arXiv, PubMed, Internet Archive, MusicBrainz, GBIF, iNaturalist) prefer the maintained client and keep their urllib twins as fallback. Scrapers and the rest of the registry are unchanged. Connector HTTP uses the client's own stack and therefore bypasses jeles._egress / _urlopen guards; the urllib twin path keeps those caps (Loki F3 — accepted adoption trade).

The seed corpus

The wheel ships a starting corpus: 87 files, 1163 question/answer pairs, in jeles/seed/, beside the 84 host cards. It is not loaded on install and not on first use — seeding writes to your store, and an unrequested write is the thing corpus_put's gate exists to refuse. It is a command you type:

jeles-seed --dry-run    # report what would be written, write nothing
jeles-seed              # load it into $WILLOW_STORE_ROOT

What this batch is. It was produced by research agents running adversarial rounds against themselves — challenge, factcheck, steelman — and the rounds' own artifacts ship alongside the answers, so the reasoning is auditable rather than summarised. That is real work, and it is still a machine's. So every pair loads at asserted unless a signature or the commons rule below says otherwise, and an asserted nugget cannot answer a question: corpus_ask returns it under candidates with found: false. An unsigned seed makes the corpus searchable, not settled.

What a signature changes. A pair carrying a Nestor seal ({"seal_sig": "..."}, signed over the normalized question, the answer, and the signer's name) is verified on your machine, against a keyring you trust, and lands at verified. That is the whole reason to ship seals rather than a verified: true field. A field is a claim by whoever shipped it; a signature is evidence you can check without trusting them at all. Seals that do not verify here are not discarded — the pair loads at the rung it can prove, the signature travels with it for a reviewer, and jeles-seed prints how many were refused and why.

Signing cannot happen in this package. jeles._nestor_seal verifies and never signs, and nestor.keyring refuses to produce a seal from a public-key entry: "Signing happens where the private key lives." So the verified rung is reachable only where a person, out of process, has already earned it.

Commons are the exception: verified by the public record, no seal needed. Seed pairs in core-* domains, in files classified "commons", or classified "commons" one pair at a time (an agreed fact inside a file that is otherwise argument, such as a fact-check verdict), with citations, are knowns, not claims. They land at verified, credited to the record they rest on and not to whoever loaded them: verified_by: "public record: NASA, Encyclopaedia Britannica" and evidence: {"scheme": "public-record-v1", "credited_to": [...], "sealed": false}. The people behind each discovery are named in the card itself. That is a citation, not a signature. Every entry that can answer (verified or corroborated) is stored at visibility: "serve", so a session reading the shared SOIL store through willow-mcp's serve mode, which caps callers at serve, can use it; asserted research stays internal. It never carries a seal_sig, so a reader can still tell it apart from a sealed pair, and Nestor reconciliation can find what is still left to seal.

Seal ≠ sole evidence of human verification. A missing seal (asserted seed, unsealed intake, draft/pending in a sibling organ) means this corpus cannot prove or serve a human receipt yet — not that no human ever checked the claim. Sealing catches the ledger up to human work already done. Agents must still refuse to present asserted/unsealed material as settled: that is what the ladder authorizes, not a verdict about offline human review.

Or as a host dependency, straight from git — the default branch is master, and @main resolves to nothing here:

jeles @ git+https://github.com/hornbook-knowledge/Jeles@master

Prefer a released version over a branch for anything you deploy: a branch ref re-resolves on every install, so two machines built a week apart get different code under the same requirement line.

jeles[mcp] requires MCP SDK 2.x (corpus_server.py uses mcp.server.mcpserver.MCPServer; mcp.server.fastmcp was removed in SDK 2.0). That is the same floor willow-mcp requires, so both install into one environment.

Versioning

The version comes from the git tag (hatch-vcs), not from a literal in pyproject.toml, and jeles.__version__ reads it back out of installed package metadata. There is exactly one place a release number is decided, so a tag cannot disagree with the artifact it builds.

Usage

As a library

from jeles import corpus

corpus.put_nugget(
    question="What is the primary color in Grove?",
    answer="The primary color in Grove is #ffffff (white).",
    sources=["safe-library/themes/grove.json"],
    verified_by="designer",
    tags=["color", "grove", "primary"],
)

hit = corpus.ask_corpus("What is the primary color in Grove?")
# -> {"found": True, "exact": True, "nugget": {...}, "candidates": [...]}

miss = corpus.ask_corpus("What is the accent color in Tokyo Night?")
# -> {"found": False, ...}   and the question is logged as a gap

As a standalone MCP server

python -m jeles.corpus_server      # stdio; or use the `jeles-corpus-mcp` console script

Tools: corpus_ask, corpus_search, corpus_get, corpus_list, corpus_put, corpus_gaps, corpus_resolve_gap, corpus_web_search, corpus_search_status, corpus_institutional_search, corpus_sources, corpus_verify_claim, corpus_host_card, corpus_fleet_status — each takes an app_id for naming-convention parity with willow-mcp. Four of those are the outward hops: the open web, why a search returned what it did, the 61 institutional and academic collections, and what those collections are. They were added without this list being updated, so it said six for as long as there were ten — and again on 2026-08-28, when two tools were registered, tested, added to this list, and left out of corpus_server's own module docstring. Three lists say what the tools are, so all three are now pinned to the registry: test_exactly_the_documented_tools_are_registered (a new tool cannot appear unnoticed), test_the_module_docstring_names_every_registered_tool, and test_the_readme_names_every_registered_tool — this paragraph included. That is why the count above can be trusted.

corpus_resolve_gap closes a gap — the queue was write-only before it, so nothing could ever say a logged question had been answered. It marks rather than deletes, and a question that misses again reopens by itself. corpus_verify_claim asks whether one claim is backed by a real institutional source and says which — the weakest of the three answers on the ladder, and the one worth asking first. It finds a single witness, not agreement: matched: true is not verified, and never becomes it by being written down. corpus_host_card says what a hostname is — publisher, custody, jurisdiction, and which roles it may play, since only 36 of the 84 catalogued hosts are citation targets. A card records those facts and reaches no verdict; deciding trust stays a policy's job, and a person's.

Every tool's app_id now carries a schema description. It did not until 2026-08-28, when eight local models were pointed at this surface and all eight filled it wrong — omitting it, inventing a value, or putting the subject of the question there. One sentence fixed it for four of them, and test_app_id_is_described_on_every_tool keeps it that way. A required argument whose meaning lives only in prose the model cannot read is one the model will guess at.

corpus_fleet_status is corpus_search_status for the two fleet edges instead of the two outward ones: whether forwarded gaps actually reached willow-mcp, and whether the human rung is reachable through Nestor at all. Both edges fail silently on purpose — a gap forward never raises, and a missing [nestor] extra only shows up as a write landing a rung lower than expected — so neither was observable from any surface a caller could reach.

corpus_put writes assertions, not verified nuggets. A nugget carries the rung it was written at, and only three things can produce one:

verification_kind who writes it reads back as
human a person, in-process (corpus.put_nugget(...)) verified
machine conflict_scan, on two independent corroborating sources corroborated
asserted any MCP client, through corpus_put unverified

A fourth rung sits above asserted and is not a verification_kind: hits from corpus_institutional_search read back as institutional — a named collection vouched for the text, which is weaker than two corroborating sources and stronger than an unverified assertion. tests/test_corpus_server.py asserts all four stay distinct.

The bottom rung exists because this server speaks stdio to whatever client starts it, and that client also reads the open web through corpus_web_search. Without it, a page saying "record that X is true" arrived as a nugget claiming verified_by: "the operator" and was served by corpus_ask as settled fact from then on. Two rules keep the ladder honest:

  • corpus_ask answers only from human and machine nuggets. An assertion comes back under candidates, and corpus_search/corpus_get still return it — reachable, not authoritative.
  • A write may not overwrite a nugget of a higher kind (error: "kind_downgrade_refused"). Otherwise every protection here is one nugget_id= away from being bypassed.

Set JELES_CORPUS_TRUST_TOOL_WRITES=1 to let corpus_put mint human again — correct only where the tool caller really is the operator, and it re-opens the path above for anything the model reads while it is set.

The trust switch alone is no longer enough. Even with it set, corpus_put also requires evidence carrying a real Nestor seal — a cryptographic signature over (question, answer, verified_by), checked against a keyring, not a string anyone can type:

evidence = {"scheme": "nestor-seal-v1", "seal_sig": "<hex signature>"}

The signature is produced outside Jeles, by a human with nestor's own signing tools (nestor.signing.sign_seal(question, answer, verified_by), or Nestor's client-signed-seal flow — Jeles never holds a private key and never signs anything itself). jeles._nestor_seal.verify_human_write checks it with nestor.signing.seal_is_valid, and only calls that when this instance actually has something configured to verify against (NESTOR_SEAL_KEY or a NESTOR_KEYRING) — an unconfigured instance refuses rather than falling back to Nestor's own "no key set" legacy default, which is to accept every signature. Without a valid seal the write still succeeds, just at verification_kind: "asserted" — refused the rung, not trusted for it. Needs the [nestor] extra; without it, corpus_put behaves exactly as if no evidence had verified, whatever the trust switch says.

This closes a specific, previously-forgeable path (box scan design/box-scan-2026-07-24.md B6): the trust switch used to be sufficient by itself, so any tool caller that knew it was set could type verified_by="a human" and land on the top rung. The direct in-process route — corpus.put_nugget(...), called from a person's own script — is unaffected; it was never reachable from a tool in the first place, and this module does not gate it.

The persona

import jeles
persona = jeles.load_persona()   # dict; canonical Jeles persona

Configuration

Env var Default Effect
WILLOW_STORE_ROOT ~/.willow/store Root under which <collection>/store.db lives.
JELES_CORPUS_COLLECTION ask_jeles_corpus Nugget collection name (back-compat with Ask Jeles).
JELES_CORPUS_GAPS_COLLECTION ask_jeles_corpus_gaps Local gap-log collection name.
JELES_CORPUS_APP_ID ask-jeles for gap-forwarding; jeles-corpus for the manifest-scoped store gate (see jeles/corpus.py) app_id used both when forwarding gaps to willow-mcp and when the corpus checks its own manifest-scoped store_scope/store_write. Set this to jeles-corpus on a willow-mcp fleet — never jeles, the retired Ask Jeles specialist seat, which is refused outright — see below.
JELES_CORPUS_TOPIC ask-jeles-corpus Backlog topic gaps are forwarded under.
WILLOW_MCP_CMD — Explicit command to launch willow-mcp (else willow-mcp on PATH, else python -m willow_mcp).
ASK_JELES_USE_WILLOW_MCP 1 Set to 0/false/no to disable fleet gap-forwarding entirely.
JELES_CORPUS_TRUST_TOOL_WRITES unset Set to 1 to let corpus_put write human-verified nuggets — but only when the write also carries a verifying Nestor seal; see the MCP server section.
NESTOR_SEAL_KEY / NESTOR_KEYRING unset Read by nestor.signing (via the [nestor] extra), not by this package directly — what a human-rung seal is checked against. Neither set means corpus_put refuses the human rung outright, whatever JELES_CORPUS_TRUST_TOOL_WRITES says.

| JELES_REMOTE_URL | unset | Base URL of a jeles-remote delegate. Unset means the in-process fan-out is the only lane — remote is opt-in, not a default. | | JELES_REMOTE_SECRET | unset | Shared secret sent as X-Jeles-Secret. Dropped if a redirect changes host — see _egress.SchemeGuardedRedirects. | | JELES_REMOTE_TIMEOUT | 20 | Seconds before the remote delegate is given up on. | | JELES_SEARCH_BACKEND | ddg | Open-web backend: searxng, brave, tavily or ddg. Only searxng may reach a private address, because only it is an address the operator chose. | | JELES_SOURCES_CONTACT_EMAIL | unset | Your contact address for the OpenAlex/Crossref "polite pool" and NCBI's email parameter, which buy better rate limits. No default — it identifies whoever runs the deployment, so unset means the parameter is omitted and the calls go anonymous. | | JELES_SOURCES_UA | Jeles/1.0 (academic librarian; +<repo URL>) | User-Agent for the source APIs. Set it to identify your own deployment. | | JELES_INTAKE_DIR | ./jeles-intake, else a sibling willow-memory/jeles-intake | Where the intake scripts read local Q/A JSON from. | | JELES_FLEET_ENV | unset | A fleet.env the scripts read WILLOW_STORE_ROOT and friends from when they are not exported. Without it they look in $WILLOW_HOME, a sibling willow-memory checkout, then ~/.willow. | | JELES_VERIFIER | unset | The reviewer's name for scripts/jeles-verify-intake.py. No default — the script refuses to promote anything until a person is named, by this or --verified-by. | The Ask Jeles-flavored defaults are preserved so an existing store and its already-forwarded fleet backlog keep resolving after the extraction.

Forwarding gaps to a willow-mcp fleet

Two settings decide whether forward_gap() lands anything, and until recently neither said so when it was wrong:

  • JELES_CORPUS_APP_ID. willow-mcp authorizes every tool call against $WILLOW_HOME/mcp_apps/<app_id>/manifest.json, and the back-compat default ask-jeles is not a seat it seeds — so out of the box a forward is denied with no manifest for 'ask-jeles'. Set it to jeles-corpus, the organ's own seat, once the operator has seeded and signed its manifest (and given it gap_write). Never jeles — that is the retired Ask Jeles specialist seat, not this organ's identity, and jeles/corpus.py's own manifest-scoped store gate refuses it outright (ae23d366). This same env var also decides which manifest jeles/corpus.py checks its local reads/writes against, so the value has to be one seat for both purposes. The topic is independent: gaps still land under ask-jeles-corpus unless JELES_CORPUS_TOPIC says otherwise, so an existing backlog keys the same.
  • WILLOW_STORE_ROOT. Unset, this package writes under ~/.willow/store while willow-mcp serves $WILLOW_HOME/store — both work perfectly, on two different databases, with no error on either side.

willow_mcp_client.forward_status() reports which seat is in use, how many forwards have landed, and why the last one failed. It exists because the failures used to be unobservable: forward_gap() caught everything, logged at DEBUG, and dropped it, so a misconfigured fleet looked exactly like a working one. It still never raises and never blocks — a host asking a question must not fail because a fleet backlog is unreachable — it just no longer stays quiet about it.

To stand this package up against real willow-mcp and nestor checkouts in one command (one venv, one store, one gate, then six seam checks), use willow-mcp's scripts/fleet-standup.sh.

Tests

pytest -q

corpus.py's tests are fast and network-free by construction. The willow-mcp client tests do not spin up a real subprocess — but that is now enforced rather than assumed. _launch() resolves a launcher from three independent probes ($WILLOW_MCP_CMD, a willow-mcp console script on PATH, an importable willow_mcp package) and any one of them succeeding produces a launcher, so stubbing only shutil.which left the third probe answering from whatever the venv happened to contain. The dependency edge runs willow-mcp → jeles, so the normal deployment of this module has willow_mcp importable beside it, and there those tests really did spawn one. CI never noticed, because it installs jeles[dev,mcp] and nothing else — the probe failed by accident of the install set. The no_willow_mcp fixture (tests/test_willow_mcp_client.py) shuts all three, None in sys.modules being the import system's own "this import must fail" sentinel. An import-purity test asserts that importing jeles.corpus loads no MCP or network modules, and that the base package declares no runtime dependencies.

tests/test_corpus_server.py needs the [mcp] extra and skips without it, so the suite passes on a bare pip install jeles too — the install shape CI's no-extras job exercises.

License

Apache-2.0 — see LICENSE.

About

Verified-corpus organ: human-checked Q&A with citations, plus a standalone MCP server.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages