← All guides

Building a POC vertical — from your claims to a deployed adjudicator

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.

What you are building

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".

Roles, organizations, and what stays with us

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.

Step 0 — write the claims first

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:

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.

Step 1 — the corpus

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:

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.

Step 2 — the vocabulary

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:

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.

Step 3 — the curriculum

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.

Step 4 — run it, and read it before you test it

  1. Approve the vocabulary, build the domain KB, approve the curriculum, run school. Read school_summary: every lesson should be mastered; an unmastered lesson lists its unreachable assessments.
  2. Accept the school result and train. Training ingests the corpus onto the schooled brain and reports entities, edges, relations and beliefs.
  3. Check the belief store before you send a single claim: 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.
  4. Now run your step-0 claims as a batch test with expectations: in the "Domain testing" dialog write one claim per line as 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.

Step 5 — freeze and deploy

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.

Step 6 — the policy: exhaustiveness beside the adjudicator, not inside it

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.

Step 7 — keep the evidence

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.

Known defect in engine v0.65.0 — read before relying on a VERIFIED

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:

What is available today, demonstrated separately, and planned

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

Planned, not yet built

Limits that will not change with any of the above

POC readiness checklist

Determinism and data handling