What an engineering team needs to take one domain of their own from source documents to a container that answers claims deterministically, with a signed record of every answer, and to know before they start which outcomes the platform can and cannot deliver. This is the guide to read once end to end; the API walkthrough has every call, authoring a domain has every contract, and evidence packages and policy has the formats behind the last two steps.
A domain adjudicator: a frozen container that takes a claim in plain
language and returns a verdict — VERIFIED, CONTESTED, UNCERTAIN or
HALLUCINATED — with the evidence it relied on: the entity pair the verdict is
about, its grounding strength, contradictions crossed, and a demotion_reason
when a verdict was lowered. On top of the verdict, your policy turns it
into a disposition — ALLOW, REVIEW or DENY — by rules you wrote and can
read. Every answer comes with a signed evidence package naming exactly
what produced it. The same claim against the same frozen domain returns the
same verdict, the same disposition, every time.
It is not a language model, a search index, or a rules engine. It
constrains claims against a body of evidence you supply. Treat VERIFIED as
"consistent with the corpus, on an asserted pair", never as "true".
| action | role |
|---|---|
| install EARL engine releases, approve foundational KB packages, run engine example checks, create organizations and accounts | super_admin (Jou Labs during the POC) |
| create domains, upload corpus, author or generate vocab and curriculum, school, train, test, write policy, freeze, deploy, adjudicate | user (your engineers) |
| read everything in your organization, change nothing | auditor |
Your accounts live in your organization. You see your organization's domains, packages, deployments and evidence and nothing else; another tenant's ids answer 404. Engines and foundational KBs are platform-level and shared. An engine and an approved foundational KB are installed before you arrive, so you begin at the domain step.
Before any corpus work, write the claims your vertical must answer, with the verdict each one should get and the disposition your business needs. Ten to twenty is enough for a POC. Include:
VERIFIED → ALLOW)CONTESTED → DENY)UNCERTAIN → REVIEW)UNCERTAIN from the engine and, once you
write the rule in step 6, DENY from the policy)HALLUCINATED → DENY)This list is your acceptance test. Every later step is judged against it, and it is what an engine example check will run for your domain when we bundle it.
Upload .md, .txt, .html, .pdf, .docx or .csv. Anything that is
not markdown is converted to markdown at upload; the original is kept beside
it and the record says which converter ran. Conversion preserves what is
there — headings, paragraphs, lists, tables — and adds nothing. A scanned PDF
has no text layer and is refused.
The extractor forms a belief about two entities only when the vocabulary already links them (step 2) and the two mentions fall within its window. On engine v0.65.0 that window is 25 entity mentions in document order, not a sentence: two entities in adjacent sentences are paired, and the relation they are given is the verb found near them. A run of short list-style sentences ("The gold policy covers fire. The gold policy covers flood.") can therefore produce beliefs the corpus never stated, such as fire covers flood, and those verify. Write for that, whatever the file type:
Atlas and the corpus says
Atlas EU, declare an alias (step 2); nothing guesses.settles used consistently teaches one
relation; four synonyms teach four weak ones.Keep the long-form prose too; the plain-fact sentences are the part the adjudicator grounds on, the prose is what makes the domain legible to a reviewer.
The vocabulary decides which words are concepts and which pairs may ever be asserted. Read authoring a domain § Assert the pairs your claims are about before writing it. In short:
acquirers, schemes, …) with its anchor first;anchor_chain() linking the type bands into one connected region;ALIASES for every long form the corpus uses;Save it with PUT /tuning-runs/{id}/vocab-draft (in the builder: Author
manually opens the editor with a starter template, Import draft takes
your .py file); the response carries the validator's verdict on the
contract. Fix errors there, not hours later in a build. No external model is
involved unless you choose Generate; if generation is not configured on
your platform, Step 1 says so and the two authoring routes are what you use.
A DAG of lessons. Each lesson teaches propositions (short subject-verb-object
sentences over grounded tokens) and is assessed on (a, b) pairs the engine
must connect unaided. Order lessons bottom-up: parts and names first, relations
after. Put the named-entity facts in their own lesson (the example's PR.5) so
their assessments show you directly whether those pairs took hold.
A failed lesson stalls its dependents. That is the intended behaviour: it names the gap. Fix the corpus or vocabulary the gap points at, not the assessment. As with the vocabulary: Author manually or Import curriculum (.json) in Step 2, then edit in Step 3; generation is optional.
school_summary: every lesson should be mastered; an
unmastered lesson lists its unreachable assessments.GET /domains/{id}/kg/beliefs?query=<entity>. Every pair your claims need
must appear as a three-word proposition (atlas settles eur). If a pair is
missing, no claim about it can verify; go back to steps 1–2.claim | EXPECTED_VERDICT | EXPECTED_DISPOSITION (over the API:
expectations on POST /domains/{id}/adjudication-runs). The result
reports expected versus actual per claim and whether the whole list
matched, so the acceptance test is run by the platform, not by eye. Read
the fields, not just the label:| field | what it tells you |
|---|---|
verdict |
the outcome |
endpoints, endpoints_asserted |
the pair the verdict is about, and whether it is the pair the claim asserts rather than an easier pair in the sentence |
grounding |
path strength 0–1 |
contradictions |
contradiction edges crossed: corpus evidence against the claim |
demotion_reason |
why a verdict was lowered: relation_unasserted (grounded, but no belief asserts this pair), relation_unrecognized (the verb is not a relation the brain learned), negated_claim_grounds_only_affirmative_evidence (a denial with no denying evidence) |
policy.disposition, policy.matched_rules |
once you have a policy (step 6): the disposition and the rules that raised it, with their reasons |
Iterate until your list is green. Test runs write "Verifier claim" entries into the working belief store; they never count as asserted pairs, and the frozen package is built from the trained checkpoint. Every test run is recorded as a signed evidence package (step 7); download it from the result modal if you want to keep the run.
POST /tuning-runs/{id}/freeze-domain produces an immutable, portable package
(grounded KB, trained checkpoint, an earl-verify binary for the container
platform, a small FastAPI server, your policy and its evaluator, an identity
record, Dockerfile, Compose file, curl examples, manifest) with a codename, a
hash, and a substrate identity: one sha256 naming exactly what the package
runs — the engine binary, the KB, the checkpoint, the policy. It is on the
package, on every deployment built from it, in the container's GET /identity
and every reply, and in every evidence package. "What you deployed is what you
tested" is a string comparison, not a belief.
POST /deployments builds and starts it under Compose on the platform host;
poll until running. Then call it — through the platform, under your
normal bearer token (the "Test a claim through the platform" card on the
deployment page does exactly this):
POST /api/deployments/{id}/verify {"candidate_text": "Atlas settles eur currency"}
GET /api/deployments/{id}/health
The platform forwards the request to the container and returns the
container's own reply unchanged under result — a pass-through, not a
re-adjudication — together with a signed evidence package for the exchange.
A non-running deployment returns 409; a container that does not answer
returns 502, never a fabricated verdict. If the container's stated identity
differs from the platform's record, substrate_identity_mismatch is true
and the reply says so.
Inside the platform host the container also answers directly on its runtime
port (curl-examples/ in the package), and the frozen archive is downloadable
and runs anywhere Docker runs, with no platform present — which is how a
vertical leaves the POC. Standalone, it answers with its identity but does
not sign; signing is the platform's.
The adjudicator will not invent a denial from a closed-world list, and on
current engines it will not verify a pair the corpus never asserts either. So
a claim like "Northstar supports the amex scheme", against a corpus that says
Northstar takes Visa and Mastercard only, comes back UNCERTAIN with
relation_unasserted — honest, and not a denial.
If your vertical must say no to that, write it as a rule in the domain's
policy (PUT /domains/{id}/policy, or the "Post-verdict policy" card):
{"format": "earl-domain-policy/1", "name": "payments v1",
"rules": [
{"id": "northstar-schemes", "type": "closed_world", "entity": "northstar",
"permitted": ["visa", "mastercard"], "action": "deny",
"reason": "Northstar supports Visa and Mastercard only"},
{"id": "asserted-pair", "type": "require_asserted_pair", "action": "review"},
{"id": "clean", "type": "max_contradictions", "max": 0, "action": "deny"}]}
The policy is deterministic, versioned, and small enough to read in one
sitting. Rules only ever raise a disposition (ALLOW → REVIEW → DENY),
so no rule can make a contested claim pass. It runs after the verdict on the
working checkpoint during your tests and inside the container for a frozen
package — the very same evaluator file — so what you tested is what deploys.
The full rule set is in evidence packages and policy § 3.
Every adjudication, in testing or through a deployment, yields an Ed25519-signed evidence package: the full result with every claim field, bound to the substrate identity, the engine, the KB and checkpoint fingerprints, the policy hash and the claims asked. List them per domain or deployment, fetch any by id, and verify one offline with nothing but the file and the platform's public-key fingerprint:
python verify_evidence_package.py evidence-<id>.json --pin-key <key_id>
Get the script at GET /api/evidence/verifier and the fingerprint at
GET /api/evidence/public-key, once, and keep the fingerprint with your trust
anchors: --pin-key is the check that proves a package came from this
platform and not merely from a consistent signer. Exit status 0 means every
check passed. What each check proves is in
evidence packages and policy § 2.1.
Replay any package (the "Replay and compare" button, or
POST /api/evidence-packages/{id}/replay) to re-adjudicate its claims on the
same source and get a signed comparison: identical, or a named list of what
changed. Together, a package's fingerprints, its verdict evidence with
expected-versus-actual, and its replay comparison are the export an
evaluator asked for.
A diagnostic audit on 2026-09-22 (Jou Labs, on the pinned evaluation build) found that engine v0.65.0 produces false VERIFIED verdicts from correct source text. The mechanism is in evidence construction, not in your authoring: the extractor pairs entities that co-occur within a window of mentions rather than within a stated relationship, so adjacent sentences, coordinated lists ("fire, theft and storm") and table rows generate beliefs the corpus never asserted, and those beliefs verify. Separately, the pair-assertion check does not test the direction of a relation, so a reversed claim ("Fire covers the basic policy") verifies. On the audit corpus 11 of 31 evaluated claims were false VERIFIED.
What this means for your evaluation:
endpoints, endpoints_asserted and the belief store
behind a verdict before acting on it, and run your acceptance list with
expected outcomes so mismatches are counted rather than noticed.ey-eval-2026-09-22) so that
findings against it stay reproducible. A repaired engine will be a new
release, gated by the compatibility check, and announced; the first
candidate repair (sentence-scoped pairing) did not meet its acceptance
and is not released. The remediation record is maintained by Jou Labs.Stated so you can plan around it rather than discover it. This guide describes
the evaluation build you are using (platform commit d570fab, tag
ey-eval-2026-09-22, engine v0.65.0); that build is pinned and will not change
under you.
Available today, on this platform, self-serve
Demonstrated separately, available for integration through Jou Labs
/eval/. It is not yet something an organization
authors and deploys itself on this platform.Planned, not yet built
Limits that will not change with any of the above
running; a claim answered through
the platform with substrate_identity_mismatch: falseall_matched: true--pin-key, exit 0, and
replayed with identical: trueendpoints_asserted, demotion_reason) and the policy
reviewed by someone who did not write the corpusgenerate-vocab and generate-lessons, and both are optional: author
the drafts yourself and no external model is in the path at all.