← All guides

Build a vertical application — your logic, data, memory, reasoning and actions on EARL

Correction (2026-09-23), scoped (2026-09-25). This correction is about EARL modifying its own code. Where this document describes EARL "authoring", "self-modifying" or repairing its own mechanism, read: EARL's governed loop replaces one bounded expression by random search under a grammar the lab supplied, accepted by a lab-frozen gate; nothing in that apparatus learns from its attempts, and self-repair has not been demonstrated. Autonomous code authorship is not an earned claim. Record: earl/genesis/g9/CORRECTION-2026-09-23-CODE-AUTHORSHIP.md; the original text is preserved as written.

It does not describe repair.develop, the code-repair developer preview, which is a different and narrower capability: EARL diagnoses, constructs and verifies supported repairs in the Rust code you designate, from taught operator meanings plus the requirement you supply, and applies them only on your approval. Its scope and limits are its own — see code-repair.md.

What an engineering team needs to build a vertical of their own on EARL: an application that owns its business logic and data, remembers what it learns, reasons over evidence it was given, and acts under a policy it can read — with a signed record of every call. Start with a working application, then adapt its data and workflow to your domain: the package ships three reference applications (procurement review, claims triage, journal-entry review) whose domain files, SDK code and expected outcomes are measured; copy the one nearest your vertical and change its nouns, rules and loop.

This is the guide to read once end to end; the reasoning and planning guide shows the cognitive operations composed, the API walkthrough has every call, authoring a domain has every contract, and deploying applications says what runs where.

What you are building

A vertical application: your service, holding the application's key, that

Evaluating claims is one use of the platform, and the deployable frozen adjudicator is one artifact of it (deploying an adjudicator). The stateful capabilities run on the persistent runtime behind the platform API; see deploying applications for exactly what is hosted where.

EARL is not a language model, a search index, or a rules engine. Treat VERIFIED as "consistent with the corpus, on the asserted pair", never as "true"; the register states per capability what is qualified.

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)
everything a user can do, plus the read-only auditor surfaces (an auditor must be able to exercise what it audits) 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 sit in one clause — the rule since engine v0.66.0-rc6, and the one this package ships. A line break or a table cell ends a clause. authoring-domains.md is the authority on this; read it before designing a corpus.

Historical, no longer in force: engine v0.65.0 used a 25 entity mention window across sentences instead, which paired entities in adjacent sentences and gave them the verb found near them. On that engine a run of short list-style sentences ("The gold policy covers fire. The gold policy covers flood.") could 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. From rc24 an assessment passes only on a fact the brain holds between the two concepts (see authoring-domains), so a gap means the proposition was not learned as written. Rewrite the sentence; don't loosen the assessment.
  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.

Let your users ask the domain

Once the belief store reads right, your application can answer plain-English questions over it: wf.ask("What does a loan require?"). Every answer names the facts it rests on (taught or derived), how the question was read, and a faithfulness check; unfamiliar verbs are clarified, and writing, advice and authorization requests are declined. Try questions on the domain page's Ask tab while you author, and read natural-language-questions before putting a question box in front of users.

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.

What the engine repairs since v0.65.0 mean for you

The 2026-09-22 audit of engine v0.65.0 found false VERIFIED verdicts from correct text (cross-sentence pairing, undirected pair checks). The candidate engine (v0.66.0-rc26, and every release since rc6) repairs those classes and re-measures them: pairs form from the clause that states them, direction and predicate are checked, a denial contests the affirmative claim, and every verdict names its pair, predicate, polarity and source clause. The register (capability limitations) lists every finding with its status; read it before you rely on a verdict, and keep your acceptance list running with expected outcomes so a regression is counted, not noticed.

What is available now, exposed with limits, and not exposed

The register is authoritative. In short: reasoning, memory, planning, observation, plain-English questions over your domain (language.ask), authority checks and records are available through the SDK with stated limits (evidence.evaluate is not qualified for unattended approval decisions; language.ask answers only from the domain's facts and was measured on lab-authored domains, not yet on a customer's; planning is stateless; episodic recall is not offered). Governed language generation (language.realize) and the developmental capabilities (self-modification, self-repair, inherited development) are qualified research results with no developer interface in this intermediate build; installed-release adoption is what an application can invoke. The release gate for the developmental capabilities is in the register's roadmap section.

Readiness checklist

Determinism and data handling