moca-reader
The MOCA Reader for Python: open a package from a directory, a .moca archive
or a host-supplied source; check it; compute its digest; verify attestations
and evidence; resolve members; answer structure operations; produce citation
records; and search with one entry point over lexical, dense-sidecar, store or
hybrid backends.
It implements the Reader class of the Reader contract, follows the Reader interface with Python names, passes the whole conformance corpus, and returns the same ranked results as the reference Reader, moca-reader-typescript.
Install
Section titled “Install”pip install moca-readerPython 3.11 or later. Getting started takes you from install to a cited search result and a conformance run in five steps.
from moca_reader import Library, read_package
pkg = read_package("support-kb.moca", trust_root="trust-root.json")print(pkg.valid, pkg.digest, pkg.capabilities)
library = Library().add(pkg)for record in library.search("refund window", audiences=["public"]): print(record["node"]["title"], record["node"]["ref"], record["score"])A malformed package never raises: pkg.valid is False and pkg.diagnostics
says why. The Reader never reaches the network, or any file outside the
package, because a package names it. Citation records are plain dicts, the
same JSON every MOCA Reader produces (citation-record.schema.json).
The API
Section titled “The API”| Name | Purpose |
|---|---|
read_package(target, *, trust_root, resolve_member, strict, limits, known_profiles, now) |
Read and check a package: a path, a .moca/.zip path, or a host source with list() and read(path). |
Library(clock=, overlays=, limits=) |
Holds verified packages, with bound sidecar chunks: add, citations, get, search, structure. |
Search(source, backend, audiences=, hooks=) |
One entry point over a backend: search(query, limit=, include_all=, locale=, concepts=, scope=). The gate always runs last. source is a Library, or a StoreScope(package_states, clock) for a store backend: package_state(result) for each loaded package, so every library- and trust-dependent field is recomputed (contract §9.4). |
LexicalBackend, DenseBackend, MemoryStoreBackend, HybridBackend |
BM25 over package text or sidecar chunks; sidecar vectors with a host embedder (S006, S007); a reference in-memory store; Reciprocal Rank Fusion of a lexical and a dense backend. |
Embedder |
The protocol a host embedder follows: name, version, dimensions, query_prefix, document_prefix, embed(texts, role). |
moca_reader.embedders |
MOCA’s four standard embedding models run locally (standard_embedder("bge-small-en-v1.5")), with the onnx extra. |
SearchHooks, ontology_guided(), diversify(per_node=) |
Retrieval hooks: host code, never package content. |
bind_sidecar(target, pkg) |
Check a sidecar against a package and return usable chunks. |
directory_resolver(dirs) |
A simple member resolver over folders. |
TrustRoot |
A host trust root, from a path or a dict. |
flat_metadata(record) |
The consuming guide’s flat moca_* fields for a citation record, for vector stores. |
library.structure |
concept, requires, required_by, parts, narrower, broader, related, sequence, descendants, nodes. |
The API is synchronous.
Framework bindings
Section titled “Framework bindings”Separate packages, each a thin adapter over the Reader:
| Package | What it gives you |
|---|---|
moca-reader-langchain |
MocaRetriever: a LangChain retriever returning Documents |
moca-reader-llamaindex |
MocaRetriever: a LlamaIndex retriever returning scored nodes |
Both wrap a Search you build, so the gate and your audience filter always
apply, and carry the whole citation record and the consuming guide’s flat
moca_* fields in each result’s metadata. flat_metadata(record) gives you
the same fields for your own vector store.
Search by meaning with a standard model
Section titled “Search by meaning with a standard model”With the onnx extra, moca_reader.embedders runs MOCA’s four standard
embedding models locally, the way every MOCA tool does, so a query embedded
here matches an index built with moca-index --model:
pip install "moca-reader[onnx]"npx @openmoca/moca-embed models add bge-small-en-v1.5 # once: download and check the modelfrom moca_reader import DenseBackend, HybridBackend, LexicalBackendfrom moca_reader.embedders import standard_embedder
embedder = standard_embedder("bge-small-en-v1.5") # files checked against their pinned SHA-256hybrid = HybridBackend(LexicalBackend(library), DenseBackend(library, embedder))Models are read from ~/.moca/models (or MOCA_MODELS). The Reader never
downloads them.
Lexical search
Section titled “Lexical search”The built-in lexical backend is BM25 (k1 1.2, b 0.75) over node text, or over sidecar chunks when a sidecar is bound. Every MOCA Reader tokenises the same way, so they return the same results:
- NFKC-normalise, then lower-case each code point (simple case mapping).
- Letters, digits and combining marks make words; anything else separates them. Words in scripts with vowel signs, such as Devanagari, stay whole.
- Chinese, Japanese, Thai, Lao, Khmer and Myanmar text is split into each character (with its marks) and each pair of neighbouring characters.
- No stemming. A host that needs it supplies its own lexical backend.
- Without a locale, every language of a node is searched and its best match returned; with one, the representation package spec §5 selects.
Characters added in very recent Unicode versions can tokenise differently across Readers, because each language runtime ships its own Unicode data.
Conformance
Section titled “Conformance”As Reader contract §14 asks:
| Class | Reader |
| Contract version | 0.6.0 |
| Implemented | Archives, host sources, DSSE attestations, sidecars (lexical and dense), the ontology profile |
| Sigstore | Offline only, with the sigstore extra (pip install moca-reader[sigstore]), against the host trust root’s sigstore.trustedRoot. Without the extra, or without a trusted root, a *.sigstore.json attestation is unverifiable (A003). There is no online mode: refresh the trusted root yourself (moca-spec attestations §6) |
The conformance adapter ships with the package. From a moca-spec checkout:
node scripts/run-conformance.mjs --reader "python -m moca_reader.conformance"Develop
Section titled “Develop”You need uv. The tests read moca-spec’s examples
and run its conformance corpus. Check out
moca-spec beside this repository, or
set MOCA_SPEC_DIR. CI uses the tags pinned at the top of
.github/workflows/test.yml (MOCA_SPEC_REF and the like); check out the same
tags to match it.
uv syncuv run pytestuv run ruff check && uv run ruff format --checkuv run mypytests/parity/parity.mjs runs 18 search scenarios (87 queries) over
moca-spec’s examples through this Reader and the reference Reader, and fails
on any difference in records, order, spans, text or scores. Dense sidecars use
a deterministic hashing embedder, so no model is needed. With
moca-reader-typescript and moca-tools (after npm ci) beside moca-spec:
node tests/parity/parity.mjsRelease
Section titled “Release”- Add a dated section to CHANGELOG.md and update
versioninpyproject.tomland__version__insrc/moca_reader/__init__.py. - Point the
MOCA_*_REFpins in.github/workflows/test.ymlat the release tags (moca-spec docs/releasing.md). - The tests, corpus and parity pass.
- Tag
vX.Y.Zand publish a GitHub release whose notes are the changelog section. uv build --all-packagesanduv publishfrom the tagged commit:moca-readerfirst, then its bindings, which release at the same version.
Licence
Section titled “Licence”Apache-2.0. See LICENSE and NOTICE.
OpenMOCA™ is a trade mark of PI & Mash Solutions Ltd. The licence covers the code, not the name: see the trade mark policy.