A cart (cartridge) is Aporia's domain coprocessor: a self-contained bundle of parsed structure and embeddings over ONE body of source code, plus the checkers that verify claims against it. A host model (any LLM) talks to a cart only through a narrow contract, so the cart is portable across hosts. This document explains what a cart is, the end-to-end pipeline that builds one, how to point the builder at your own source tree, and why no cart DATA ships in this repo.
Cart data is absent from this repo by policy. You build carts from your OWN sources. See Why cart data is not in the repo below.
A cart has three parts, all derived from one source tree:
- A parsed API index —
class -> {methods: {name: (return_type, specifiers, is_const)}, delegates: {name: arity}}. Built by parsing headers (build_indexinchecker.py,parse_headeringenerate_claims.py). Pure structure, no model, zero variance. Since 2026-07-19 this also includes a call-graph index (who calls whom, who Broadcasts what, from brace-matched.cppbodies), and all parse products are served through a content-keyed cache (index_cache.py) — rebuilds cost the diff, not the corpus. - An embedding index — every source chunk embedded to a 768-d vector via a local embedding model, cached on disk. This is the semantic-retrieval half, used to pull relevant code as evidence for prose claims.
- The checker pipeline over both — a fast deterministic checker (index lookups) with an
LLM-judge fallback for prose/behaviour claims (
checker.py,runtime_verify.py). Each claim resolves toflag(source contradicts it),pass(source confirms it), orabstain(cannot be decided from source — holding aporia).
The cart implements the Slot Contract (cartridge_contract.py): the host passes raw
domain text in and gets cartridge-native scalars and vectors out. No host hidden-states,
tokenizer, or embeddings ever cross the boundary — that single rule is what makes one cart
usable by any host model.
The pipeline turns a source tree into a working, verifiable cart. Each stage has code that owns it and a done-criterion you can check before spending time on the next stage.
The active source roots come from domain_roots() in corpus.py, selected by environment
variable:
# default domain
.venv\Scripts\python.exe corpus.py
# switch to the secondary (large) domain sample
$env:CARTRIDGE_DOMAIN = "ue"Two environment knobs shape what gets ingested (both read in corpus.py):
CARTRIDGE_FILTER— keep only headers whose text contains this token (e.g. an API-export macro). This is how a huge tree is narrowed to API-rich headers instead of everything.CARTRIDGE_MAXFILES— an optional global cap on file count, for quick smoke runs.
The ingester skips generated and build-output directories and auto-generated header suffixes
(_SKIP_DIRS, _SKIP_SUFFIXES in corpus.py), so build artifacts never pollute the cart.
Done-criterion: corpus.py prints file and chunk counts for your roots. Non-zero, and
the numbers look plausible for your tree.
chunk_text() splits each file into fixed line-windows (default 12 lines, minimum 40
characters). Fixed windows are deliberate — no language parser, so the same chunker works on
any text. file_chunk_lists() returns per-file chunk lists; a sliding window of consecutive
chunks within one file is the predictive unit ("reconstruct a masked chunk from its
neighbours in the same file").
Done-criterion: chunk counts are stable and reproducible across runs on the same tree.
build_index() (checker.py) walks the headers and calls parse_header()
(generate_claims.py) to extract, per class:
- methods — return type, blueprint/authority specifiers, and const-ness, read off each exported-function declaration;
- delegates — multicast-delegate declarations and their parameter arity.
This index is the ground truth the deterministic checker looks claims up against — no model, no cost, no variance.
Done-criterion: the index reports a plausible class and method count for your tree, and
the deterministic eval (below) holds ~100% on structural claim types before you spend time
embedding. warm_ue_cache.py runs this as an explicit gate: if the structural real-pass or
fake-catch rate drops below 99%, it stops and tells you to investigate the parser on your
headers rather than proceeding to the expensive embed stage.
embed.py is the cart's input encoder: raw text in, 768-d vector out, via a local
embedding model served by Ollama. Vectors are cached on disk (a keys file plus a binary
vectors array) so the cart never cold-embeds at startup:
- batched requests with periodic incremental saves (a crash loses at most one save window);
- an oversized/poison chunk is isolated by bisecting the batch and given a zero vector rather than killing an hours-long run;
- a one-time migration path folds any legacy single-file cache into the binary format.
For a large tree, pre-warm the cache with a dedicated one-shot run (see warm_ue_cache.py,
which counts the corpus, runs the deterministic gate, then embeds the full filtered corpus in
large batches). After warming, the registered server starts fast because every vector is
already on disk.
Done-criterion: the embed cache exists and its vector count matches the chunk count from stage 2. A second run reports everything cached (no new embeddings).
The cart exposes verification through two checkers run in order (checker.py); the first
non-abstain verdict wins:
- Deterministic checker — resolves structural claims (return type, method existence, authority/purity specifiers, delegate arity) as pure index lookups. Abstains on anything it cannot reduce to a parsed fact — which is exactly the hand-off point to the judge.
- Judge checker — the fallback for prose/behaviour claims. It retrieves the relevant real code (symbol-guided plus embedding-nearest), attaches the exact doc-comment and implementation body as evidence, and asks the local judge model for one word: contradicted / consistent / unsure. Unsure maps to abstain — the judge never bluffs.
The runtime loop (runtime_verify.py) is the actual-use path: it first asks the extractor
model to convert a prose statement into typed facts (EXISTS, RETURN_TYPE, AUTHORITY,
PURITY, DELEGATE_ARITY, CALLS, and BEHAVIOR for everything else), each carrying a
Class::Member anchor. Structural facts are then checked as index lookups; CALLS facts
("X calls Y" / "X fires OnZ") resolve against the call-graph index (direct = pass, one hop =
pass at reduced confidence, absent = flag); BEHAVIOR facts
route to the judge carrying their anchor, so symbol-precise evidence always attaches. Every
anchor also gets an implicit existence check, so an invented method is caught no matter which
fact type the extractor chose.
Every verdict carries confidence + basis (discrete rubric in checker.py):
extracted 1.0 for a parsed-index fact, inferred 0.55–0.9 graded by evidence tier, and
ambiguous 0.0 for every abstain — the labeled null, never a fake answer.
Done-criterion: a round-trip smoke test passes — a true statement about a real indexed
method returns pass, a wrong-return-type variant returns flag, and an invented-method
statement returns flag. build_ue_cart.py runs exactly this smoke round-trip after building
the index and embeddings.
verify_server.py exposes the runtime loop as a local MCP tool (verify_statement) so any
host can call it inline. The heavy state (parsed index, embeddings, judge corpus) loads once at
server start; individual calls are then fast. See REPRODUCE.md for registration shape and
environment.
If the indexed sources are git clones, run install_hooks.py once: it drops post-commit /
post-checkout hooks that touch a dirty marker and spawn a detached prewarm_cache.py. The
server stats the marker on every call and hot-refreshes when it's newer than the index — a
commit lands in the verifier without a restart, and the content-keyed cache makes the refresh
cost the diff. For non-git sources (e.g. an installed engine tree), both servers expose a
refresh_index MCP tool; describe reports index_built so staleness is always visible.
Done-criterion: freshness_calls_test.py passes (cache serves unchanged files without
re-parse; an edit lands after refresh without restart), and a commit in a hooked clone is
verifiable on the next verify_statement call.
The whole pipeline is domain-agnostic. To build a cart over your own code:
-
Define a source-roots list. In
corpus.py, add a list of directoryPaths for your tree and return it fromdomain_roots()(mirror how the existing roots are selected by theCARTRIDGE_DOMAINenvironment variable). Point it at the directories that hold the source you want the cart to know. -
Set the file extensions. The C/C++ builders use
(".h", ".cpp"); a Python cart uses(".py",). Pass the extension tuple your language uses toiter_files/file_chunk_lists. -
Tune the header parser to your API surface (only if you want structural checks). The deterministic checker's power comes from parsing your declaration syntax.
parse_header()is written for one C++ export/annotation style — the class-declaration regex, the function-declaration regex, the specifier keywords, and the delegate macro. If your codebase annotates its API differently, adapt those regexes to match. If you skip this, the deterministic checker simply abstains more and the judge does more of the work — the cart still functions, just with less zero-cost coverage. -
Set the content filter for large trees. If your tree is big, choose an
CARTRIDGE_FILTERtoken that marks the API-rich files worth indexing (an export macro, an annotation), so you index the interface rather than every file. -
Warm the cache and run the gate, then register the server (stages 4–6 above).
Nothing else in the pipeline is domain-specific: chunking, embedding, retrieval, the judge prompt shape, and the trinary contract are all generic.
These are synthetic illustrations of the claim/verdict shapes the cart handles. The class, method, type, and delegate names are invented for documentation — they are not from any real codebase. Substitute your own symbols.
Structural — return type (deterministic checker):
CLAIM: UWidgetCatalog::GetEntryCount returns int32.
-> PASS (deterministic: index says GetEntryCount returns int32)
CLAIM: UWidgetCatalog::GetEntryCount returns bool.
-> FLAG (deterministic: claims bool, actually int32)
Structural — existence:
CLAIM: UWidgetCatalog exposes a RebuildEntries() method.
-> PASS (deterministic: RebuildEntries exists in source)
CLAIM: UWidgetCatalog exposes a TotallyInventedMethodXYZ() method.
-> FLAG (deterministic: no method named TotallyInventedMethodXYZ() anywhere in source)
Structural — delegate arity:
CLAIM: UWidgetCatalog's OnEntriesRebuilt is a two-parameter delegate.
-> PASS (deterministic: OnEntriesRebuilt takes two params)
CLAIM: UWidgetCatalog's OnEntriesRebuilt is a three-parameter delegate.
-> FLAG (deterministic: claims three, actually two params)
Behavioural — routed to the judge (typed as BEHAVIOR):
CLAIM: UWidgetCatalog::RebuildEntries fires the OnEntriesRebuilt event.
-> PASS (judge: implementation broadcasts OnEntriesRebuilt — consistent)
CLAIM: UWidgetCatalog::RebuildEntries fires the OnCatalogCleared event.
-> FLAG (judge: implementation broadcasts OnEntriesRebuilt, not OnCatalogCleared)
Inherently null — held as aporia (never flagged):
CLAIM: UWidgetCatalog lives on the player pawn.
-> ABSTAIN (ownership/location is a design decision — a spawnable component can live on
any actor; not verifiable from source)
A prose statement typically decomposes into several typed facts, each verified independently; the runtime loop reports per-fact verdicts and a summary count (flagged / unverifiable / confirmed).
The cart BUILDER code is tracked in this repo — it is the author's own code, and it is what you reproduce from. But no cart DATA output ships, by policy:
- The embedding caches ARE the cart data. They are embeddings of the source that was indexed. Shipping them would ship a derivative of that source.
- The secondary-domain cart derives from a commercial engine's licensed source, whose license does not permit redistributing derivatives — so its embeddings and any file carrying its symbols are excluded.
- The primary cart derives from the author's unreleased code, which is not published.
- Downstream artifacts carry those symbols too (the field log, the learned-fact store), so they are excluded on the same grounds.
The exclusion is enforced in .gitignore (the embed caches, *.npy, the field logs, and the
learned-fact store are all listed). This is intentional and load-bearing: the repo is a
reproducible blueprint, not a data drop. You build carts from your own sources using the
builder code here, and the sanitized examples above show the shapes to expect.