What you write by hand, and the contracts each artefact must satisfy. The API walkthrough covers the calls; this covers the content those calls carry.
A domain is built from three authored things: a corpus (the source of truth), a vocabulary (which words become concepts), and a curriculum (what the engine is taught, and how it is assessed).
The corpus is what EARL learns its facts from. Every sentence you upload is read on its own and can become a fact — including notes, disclaimers, rationale and block quotes. So write the corpus as a list of plain facts, and keep rules, conditions and explanations in your application (or in a README you do not upload).
Upload Markdown, or .txt, .html, .pdf, .docx, .csv (converted to Markdown; the original is
kept). Converted documents follow the same rules — read the converted text before training. One
document per topic is easiest to maintain.
purchase not_requires tender, which contradicts
purchase requires tender.register not_is closed, category not_is ability). Explanations belong in a README.The platform checks rules 4-8 for you at upload. Every document carries corpus_warnings (the
Corpus check column under Documents; GET /api/domains/{id}/documents): each flagged sentence with its
line, the rule (condition, qualified_denial, rationale, value, block_quote, table,
long_sentence) and why. They are advisory - nothing is blocked, and a flagged sentence may be fine.
| Written in corpus v1 | Learned | Corpus v2 |
|---|---|---|
| "A purchase below the tender threshold requires no tender." | purchase not_requires tender and threshold not_requires tender, contradicting purchase requires tender and threshold requires tender |
removed; the threshold is a rule in app.py |
| "Screening certifies a supplier when no sanctions match the supplier." | screening not_certifies supplier |
"Helix is certified by screening." for each supplier |
| "The register is a closed list: the absence …" | register is closed and register not_is closed, plus unrelated denials |
moved to the README |
Corpus v1 trained to 106 facts, 27 of them denials (3 contradicting held facts). Corpus v2 trains to 67 facts with no denials and no contradictions. Every claim verdict and decision in the reference application stayed the same, because they only ever depended on the plain facts.
The domain page's Ask panel opens with a store check (GET /api/domains/{id}/store-check): the
number of facts, every taught denial, every fact held together with its own denial, and facts with a
function word as an endpoint. The training helper in the SDK examples logs the same summary. Then open
Knowledge graph › Beliefs (or GET /api/domains/{id}/kg/beliefs) and look for:
not_… rows you did not mean to teach;not_ form on the same pair (the question box will answer "I hold conflicting facts");category not_used however);related_to rows between concepts your corpus never relates — these come from the vocabulary's bands
and anchor_chain (§2), not from a sentence.Fix the sentence (or the band), retrain, and read the store again. Do not reword the question or the claim to work around a bad fact.
Avoid also: bare bullet fragments and long sentences whose subject and object drift far apart.
Decides which words become first-class concepts. A word that is not
grounded cannot be an endpoint of any verdict — claims about it come back
HALLUCINATED no matter what the corpus says.
CLUSTERS = [
("acquirers", "acquirer", ["acquirer", "atlas", "northstar", "processor"]),
("schemes", "scheme", ["scheme", "visa", "mastercard", "amex"]),
("settlement", "settlement", ["settlement", "currency", "eur", "usd"]),
# One band per NAMED entity your claims are about, anchored on it, listing
# the specific things the corpus says about it. See "Assert the pairs your
# claims are about" below -- without these bands no claim about Atlas and
# EUR can ever verify, however plainly the corpus states it.
("atlas_capabilities", "atlas", ["atlas", "visa", "mastercard", "eur", "gbp"]),
("northstar_capabilities", "northstar", ["northstar", "visa", "mastercard", "usd"]),
]
ALIASES = {
"amex": ["american express"],
"usd": ["united states dollars"],
}
Every member is one lowercase word. The engine grounds a token exactly as
written and resolves a multi-word concept only in its underscore form, at most
four words (journal_entry). A member written with a space (journal entry)
is injected as a token nothing can ever reach: the school scores zero on every
assessment that names it (F31). The validator refuses such members before the
school starts. Prefer the head noun (entry) and, when the phrase matters,
declare it as an alias of the head noun; use the underscore form only when the
corpus uses that exact phrase and the head noun alone would be ambiguous.
# refused (F31): ("entry_types", "journal entry", ["journal entry", "manual entry"])
# grounded: ("entry_types", "entry", ["entry", "manual", "recurring"])
def anchor_chain():
return [
("acquirer", "scheme"),
("scheme", "settlement"),
]
Ground nouns only: the things claims are about. Never put a verb in a
band (settles, supports, covers, holds). A grounded verb is a concept
to the verifier, and a concept can be chosen as a claim's endpoint: with
supplies grounded, "Helix supplies software" is adjudicated on the pair
(helix, supplies) and comes back UNCERTAIN however plainly the corpus states
it, and a reversed claim can even verify on a co-occurrence belief (findings
F13/F14, measured 2026-09-23). The relation is read from the sentence; it does
not need to be a concept. The vocabulary validator rejects members that are
connector or relation words (plus, and, versus, …), which the extractor
consumes as relations, and the curriculum validator warns when a band member
occupies the verb position of a lesson proposition.
The same holds for the curriculum: a proposition like helix passed sanctions
teaches passed as a concept. Use verbs the engine's relation lexicon knows
where you can (requires, enables, certifies, oversees, funds, …).
| Rule | Why |
|---|---|
CLUSTERS is a non-empty list of (cluster_name, anchor, [tokens]) |
Each cluster is a densely connected concept band |
| The anchor must appear in its own token list | It is the band's hub; a hub outside its band connects nothing |
anchor_chain() returns a list of 2-tuples linking anchors |
The prerequisite spine — without it the bands are disconnected islands |
ALIASES is optional: canonical → other surface forms |
See below |
| Every value must be a literal | See below |
The module is parsed as data — the platform reads its syntax tree and never executes it. Comprehensions, variables, imports, and computed returns cannot be resolved and are rejected with a message saying so.
BASE = ["a", "b"]
CLUSTERS = [("band", "a", BASE)] # rejected: not a literal
def anchor_chain(): return [(x, x) for x in BASE] # rejected: not a literal
Inline the lists and tuples instead. This is deliberate: the draft arrives over HTTP, from an operator or a language model, and executing it would be arbitrary code execution on the server.
Declare an alias whenever the corpus spells a concept out but a claim would plausibly use the short form, or the reverse.
Without {"amex": ["american express"]}, a corpus sentence saying "cannot
process an American Express transaction" and a claim saying "supports the
amex scheme" land on different tokens, so the denial cannot constrain the
claim. With it, both resolve to amex and the evidence applies.
Rules: the canonical must be a token in some cluster; a surface is at most 4 words; it may not duplicate a real token (that would shadow it); and it may not be declared for two different canonicals. Violations are reported when the draft is saved, not hours later during a build.
Aliases are declared, never inferred — nothing guesses that two words mean the same thing.
A DAG of lessons. Each teaches propositions and is assessed against what the engine can reconstruct from its own substrate, with no teacher present.
{
"subject": "PaymentsRouting",
"lessons": [
{
"id": "PR.1",
"title": "Acquirers and Schemes",
"grade": "Kindergarten",
"requires": [],
"propositions": [
"an acquirer processes a transaction.",
"a membership admits a scheme."
],
"assessments": [
{"a": "acquirer", "b": "transaction",
"prompt": "Does an acquirer process a transaction?"}
],
"mastery": 0.95
}
]
}
| Field | Notes |
|---|---|
id |
Unique; referenced by other lessons' requires |
requires |
Prerequisite lesson ids. A lesson stays Locked until all are mastered |
propositions |
The facts taught, each a short subject verb object sentence |
assessments |
(a, b) concept pairs the engine must connect unaided |
mastery |
Threshold to pass, typically 0.95 |
Teaching and assessment are deliberately separate: teaching pushes facts in, assessment measures what actually took hold. A proposition the gate rejected, or a link never formed, surfaces as a gap rather than being assumed learned.
What passes an assessment (engine v0.66.0-rc24 on). An (a, b) item passes
only when the brain holds a fact between the two concepts: a three-word
belief a RELATION b or b RELATION a (a taught denial such as
a not_requires b counts; the lesson-order relations taught_after /
taught_before do not).
Before rc24 an item also passed on any belief that merely named both words. That included the verifier's own record that it had found a graph path between them, written for every claim it checks. It also passed on any graph path of up to four hops. Those passes hid sentences the engine had misread.
Measured on the hosted example curricula, the passes that disappeared were all
facts the engine never formed:
- "an acquirer settles a currency." was read as settles related_to currency;
- "a vector lives in a hilbert space." formed nothing, because the concept is two words;
- "a commutator relates position and momentum." formed nothing, because the
sentence has two objects.
A school run on rc24 can therefore master fewer lessons than the same curriculum did before. That is the engine reporting the gap. Read the lesson's gaps and the belief store, and rewrite the proposition: - one subject, one verb, one object; - single-word concepts.
Assess two concepts, never a concept and a verb. An item such as
(compatible, commute) cannot pass on rc24. A verb is not a concept the engine
holds facts about.
Order lessons so each builds on grounded ground. A failed lesson holds its dependents Locked and stalls the grade — which is the intended behaviour, not a bug to route around.
Use tokens your vocabulary grounded. Assessment is inflection-sensitive:
if the vocabulary grounds side, an assessment on sides has no path and
honestly fails.
Generated drafts are grounded by construction and fully recorded. When you
ask the platform to generate a curriculum (POST /tuning-runs/{id}/generate-lessons),
the generator receives your approved vocabulary as a hard constraint and makes
one corrective pass if its first proposal assesses tokens the vocabulary does
not ground (F27) or puts a member in verb position (F14). Whatever remains is
listed in validation_warnings, and the school refuses to start over it unless
you override with a logged reason. Every vocabulary and lesson draft carries a
generation_log: each LLM attempt with the problems found in it, feedback
regenerations, every human edit (which lessons, members or aliases changed) and
the approval, oldest first. That log is the authoring record — read it before
you approve a generated draft, and keep it: it says what the model proposed,
what the platform corrected and what a person changed.
The trainer and the verifier now read a sentence the same way. Knowing the rules lets you write a corpus, a curriculum and claims in ordinary English instead of phrasing them around the engine.
The relation is the clause that states it. A belief is formed from the two
grounded concepts of one clause and the verb between them, in the order the
clause gives them. Two concepts that only ever appear in different sentences are
an association (related_to), never a typed relation, so a claim about them can
be grounded but never VERIFIED. A line break ends a clause; so does a markdown
table cell: a table row is a list of cells, not a sentence, and no relation or
denial is ever read across cells. State facts in prose as well as in tables.
Every stated relation on a pair is kept. "A suite requires a supervisor."
and "A supervisor inspects a suite." in the same document form two beliefs,
one per verb and direction (rc6, F32); before rc6 only the nearest clause
survived. The other side of that rule: every clause that puts a verb-shaped
word between two grounded concepts also forms a belief, so explanatory prose
("sending a manager to every studio would take the manager away from the
reviews the manager owns") yields studio take manager and studio reviews
manager (F36, disclosed). Write explanations in sentences that name at most
one grounded concept, or read the belief store after training and reword the
sentences behind any artefact. A denial contests the affirmative claim. With "A studio does not
require a manager." in the corpus, the claim "A studio requires a manager." is
CONTESTED affirmative_claim_negated_in_substrate (rc6, F33), and the denial
itself verifies as a negated claim; every claim result reports negated.
Voice. "Vega is certified by screening" is read as screening certified Vega on both sides: the corpus sentence trains that belief and a claim in the passive is checked in that direction. Write curriculum propositions in their natural direction ("screening certifies vega"), not in the order you expect the verifier to flank them.
The copula is a verb. "An observable is hermitian" and "spin is quantised"
are typed relations (is); a claim restating them verifies against them, and a
different adjective on the same subject is unasserted.
Negation. One denying clause is recorded beside any affirmation of the same pair; the substrate then carries the contradiction and an affirmative claim on that pair is CONTESTED. A denial marker that is the pair's own verb ("the handler declined the claim") is the relation, not a negation. A negated claim is supported only by the store's own denial of the same pair with the same verb, never by an affirmation.
Named concept over type noun. "A manual entry requires a controller approval" grounds on (manual, controller): inside one noun phrase the concept that names the thing wins over the noun that types it, and if the store asserts a pair among the phrases' tokens, that pair is the claim's. Claims no longer have to be phrased on the bare pair.
Verbs. Connectors ("plus", "versus") are never relation words. A word the sentence marks as a verb by an auxiliary ("do not commute") is read as the verb even when it is not verb-shaped; a bare noun by elimination is not. A coordinated pair as the subject of an intransitive verb ("position and momentum do not commute") takes its verb from the clause tail.
Aliases over foundational n-grams. The foundational KB carries general-English
n-grams ("sanctions_screening"). A declared alias now takes precedence over such
a token in both the trainer and the verifier, so you may map the phrase onto the
concept your bands declare: ALIASES = {"screening": ["sanctions screening"]}.
The KB builder reports the remap; it refuses only a single-word alias that
shadows a real token. That refusal comes from the builder at
POST /tuning-runs/{id}/build-vocab-kb (the foundational KB's tokens are read
then), not from the draft validator — the draft validator checks the module
against its own clusters. The message names the alias
(alias 'housekeeper' shadows an existing token text); remove it or alias a
different surface, save, approve, build again. The domain and its documents
are kept: create the next tuning run on the same domain rather than a new
domain (the reference helper takes domain_id= for exactly this).
Which verbs the engine knows. A claim's verb must be a relation the engine
recognises, or the verdict is UNCERTAIN relation_unrecognized. Recognised:
every verb your own corpus trained as a typed relation (the propositions
beliefs lists — inspects, cleans, contains, performs … whatever your
sentences used), plus the built-in lexicon: requires, enables, defines,
generalizes, specializes, extends, models, contrasts, inverts,
reverses, precedes, underlies, uses, applies, solves, computes,
includes, provides, issues, processes, handles, leads, funds,
disburses, finances, and the copula is — each with its -s/-ed/-ing
forms. A verb outside both sets can appear in a claim only after a corpus
sentence has trained it on some pair.
contradictions is not the denial count. The contradictions field counts
substrate contradiction edges the grounding path crossed. A typed denial in
the store ("A studio does not require a manager.") contests the affirmative
claim through demotion_reason: affirmative_claim_negated_in_substrate and can
leave contradictions: 0; read the reason, negated and asserted_by, not
the counter (clean-room finding R5).
What a verdict reports. Every claim result carries endpoints in semantic
order (subject, object), predicate, passive, asserted_by (the supporting
belief) and asserted_source (the document and clause that formed it), so a
test can check the pair, the relationship, the direction, the polarity and the
source, not only the verdict.
Before the school. The school refuses a curriculum the vocabulary cannot
ground: an assessment on a token that is not a member, or a member used as a
verb, is reported with the lesson and token named. Generated curricula receive
the grounded vocabulary as a hard constraint and are corrected once
automatically; a valid draft needs no manual repair. An explicit override
(accept_ungrounded with a reason) exists for research fixtures and is logged
on the run.
This is the rule the other three sections build towards, and the one most
first domains get wrong. VERIFIED requires that a trained belief asserts
the claim's entity pair (engine ≥ v0.64.0). Grounding — a path between the
two endpoints — is necessary but not sufficient; a claim can be fully grounded
and still come back UNCERTAIN with demotion_reason: relation_unasserted.
A belief about a pair is formed at training time only when both hold:
"Atlas settles the EUR currency." yields atlas settles eur; a line break
or a table cell ends a clause). Engine v0.65.0 used a 25-mention window
across sentences instead; that rule no longer applies. Two things still
produce beliefs the text did not state: a clause that names two grounded
concepts around a verb-shaped word ("the suite is inspected in the same
way as the studio" forms suite inspected studio), and the school's
consolidation step, which writes requires/enables edges between lesson
concepts at confidence 0.9. Read the belief store after training and
remove or reword the sentence behind any artefact you find; andanchor_chain() links the pairs it lists.
Type-level bands (acquirers, settlement) never link atlas to eur.So for every named entity your claims will be about, add a band anchored on
that entity whose members are the specific things the corpus says about it.
Keep it truthful: a band is a declaration of which pairs may be asserted, so
list what the corpus supports and nothing more — northstar_capabilities
omits amex precisely because Northstar does not take Amex. Then check the
belief store after training (GET /domains/{id}/kg/beliefs?query=atlas): the
pairs you need must appear as three-word propositions (atlas settles eur).
If they do not, the sentence or the band is missing, and no curriculum change
will substitute for either.
Design around these rather than discovering them in production:
"Northstar does not support
amex" is CONTESTED, never VERIFIED; and because no belief asserts the
pair, "Northstar supports amex" is UNCERTAIN, never VERIFIED. If
exhaustiveness matters, keep it in a deterministic rule set outside the
adjudicator.VERIFIED as "consistent with the corpus", never as
"true".helix to sanctions
does not link it to that token, so the claim hallucinates with every word
grounded, and an alias over a base token is refused. Reword the claim and
the corpus onto a single-word concept (finding F17).screening certified vega; a claim in the active direction then
fails the direction check. Write the corpus in the active voice, or teach
the pair in the claim's direction in the curriculum (finding F18).software
supplies helix, and a reversed claim then VERIFIES. This cannot be authored
around (finding F16, extraction repair scheduled); treat VERIFIED on a
reversed pair as unreliable until then.accept_unmastered), because locked
lessons never teach their concepts and the trained brain then lacks them.CONTESTED.