A workflow is your application's handle on one EARL runtime. It binds a
domain (your corpus, trained), owns one runtime image, and appends one signed
record per call. Two workflows on the same domain share nothing at run time.
This page says exactly what persists between calls, how to see it, and where
the edges are. Everything here was measured by the P5 battery through the SDK
on engine rc4; unchanged on rc5 (SHIP-1), which adds belief provenance (asserted_source) and the execution path on every record.
create_workflow(domain) -> init: the trained store + substrate graph are copied into the workflow's runtime
call n (tick / submit / prove / plan / observe / beliefs / evaluate ...)
call n+1 reads what call n wrote — each call is its own process; the image on disk is the only carrier
Every stateful call: load the image → act → save to a temporary image → read it
back and verify → swap it in → sign a record that names the image hash before
and after. If the readback fails, the previous image is intact and the call is
an OperationalError (write_verification_failed); nothing half-written is
ever kept.
| state | written by | read by | measured |
|---|---|---|---|
| the belief store (typed propositions with confidence) | init, submit, the cognitive cycle (tick) |
beliefs, query, prove |
P5 I3a/I3b/I3b1/M1 |
| the cognitive cycle counter and the clock domain | tick |
observe (total_cycles), every later call |
I3a, I3c: tick 3 then tick 1 reads cycle 3; census 0 → 5 |
the substrate graph the runtime grounds on (graph.belf) |
init (copied from the trained checkpoint), submit (accreted) |
submit |
rc4 |
| the record chain | every call | records(), record(id), replay |
I6a–I6e |
| the pinned engine release (A1) | adopt_engine / rollback_engine |
every later call | I9a–I9c |
(rc27) text staged for self-training and reported outcomes, until the next tick consumes them |
submit with self_training on, report_outcome |
tick |
engine tests; rc27 gate |
| (rc27) the self-trained vocabulary and the learner's outcome ledger | tick |
learning(), every later tick |
engine tests; rc27 gate |
| (rc27) derived beliefs with their premises, and the deduction snapshots | deduce(confirm=) |
beliefs() (derived_from), rollback_deduction |
engine tests; rc27 gate |
memory.submit was repaired in rc4 to form beliefs
inside the call (F21): before that, a submit was recorded as a success and
lost. From rc27 the two queues a workflow's learning controls feed (text staged
for self-training, reported outcomes) ARE carried by the image to the next
tick; the image carries them only while something waits, so with every switch
off it is exactly what rc26 wrote.goals.plan is stateless: it plans from the goal text and
the current store and keeps nothing.memory.recall). Not offered (earl #313).memory.submit: the input boundary, statedsubmit(text) runs the same extractor training uses over the workflow's copy of
the trained substrate. It forms a typed belief only for a pair of grounded
concepts the domain vocabulary links (a band that contains both) — the
training rule. The record tells you what happened:
| field | meaning |
|---|---|
submitted |
true if the call changed the store: at least one grounded relation was newly asserted (beliefs_formed, propositions) or re-observed (beliefs_reinforced, reinforced). It does not mean a new belief was admitted. A text whose only grounded relations were already held reads submitted: true, beliefs_formed: 0. Read propositions and reinforced for what changed, and unlinked_pairs for what the text stated but could not form |
entities, relations |
what the extractor found |
propositions |
the new typed beliefs (e.g. premier requires flood) |
reinforced |
grounded relations the store already held; re-observed, no duplicate |
reason: no_belief_formed |
nothing grounded; the call is SemanticUncertainty; the image is unchanged |
unlinked_pairs |
(rc25 on) pairs the text stated (a verb between them in one clause) that formed nothing because the domain's vocabulary does not link them, e.g. [{"from": "pump", "to": "approval"}]. Put both in one vocabulary band and rebuild the domain KB to make the pair learnable |
explanation |
(rc25 on, when nothing formed) the reason in words, naming the first unlinked pair and the fix |
A submitted proposition is listed by beliefs() in a later call, survives later
ticks, and feeds query (yesno premier requires flood derives). Persistence
is not truth: what persists is what the extractor read. The open extraction
defects apply to your text exactly as they apply to a corpus — a reversed pair
across consecutive same-verb sentences (F16), a base-KB multiword token (F17),
passive voice (F18), a verb read as a negation (F23). Read propositions back
before relying on them.
Three learning paths are off until you switch them on for the workflow
(set_learning_controls(...); each change is a signed record; switching on
needs an rc27 or later engine):
| switch | what it enables | what the record shows |
|---|---|---|
self_training |
submit also stages its text (code and identifiers removed, from rc30) for EARL's own corpus induction; the next tick inducts over the staged text and the last 32 documents, rejecting identifiers, number-word compounds and WordNet adjectives/adverbs |
submit: staged_for_induction, staged_doc_id, staging_removed; tick: words_minted, words_rejected (from rc44, every candidate left unlearned, each with its reason: corpus glue, too few documents, too few known neighbours, below the threshold), words_associated, associations_made (from rc46: every association the tick created, with the notes in which the pair co-occurs), learning_before / learning_after; learning(): every self-trained word with the submit records it came from, and from rc44 every association self-training holds (associations, each with the tick records and submit records that made it; from rc46 the submit records are those of the notes in which the pair co-occurs, documents_are: "pair", for learned and known words alike, and a tick from an older engine names the notes the word is in, marked "word") and what the last learning tick passed over (passed_over, with reasons) |
outcome_learning |
report_outcome(record_id, succeeded, confidence=, tokens=) tells the learner what happened after an earlier result of this workflow; the next tick attributes it |
outcome: outcome_of (the verified record), the words, pending_outcomes; tick: the learner's outcome ledger before and after |
deduction |
deduce() previews what the closure derives (fact, rule, premises, digest) with each premise's sources and trust; deduce(confirm=digest) writes the trusted conclusions (premises taught or operator-verified) after a snapshot, untrusted ones only if named; rollback_deduction(n) restores the image byte for byte |
preview: derivations, digest; confirm: deduced with premise_ids, snapshot; beliefs(): derived_from |
Switching self_training or outcome_learning off drops anything that path staged and the next tick has not yet consumed (engine rc28; on rc27 the switch record says the staged input is still learned).
The engine decides when it learns: a staged text or an outcome takes effect on
the next tick, never in the call that supplies it. An outcome names no
pattern, outcome type or weight; the learner attributes it. A confirmed
deduction is refused if the closure changed since its preview, and a rollback
is refused if the workflow changed after the deduction. None of the three is
qualified: read what each record says it did.
reason.prove and reason.queryprove(query) is a Result only when the engine reports status: proved
(chain closed, every step justified). incomplete_derivation and
not_derivable are SemanticUncertainty: a withheld derivation, never a
proof.
From rc25, prove proves from the workflow's own beliefs.
- A held fact (taught, trained or submitted) proves in one step that cites its belief.
- Chains prove step by step: transitivity, duality, symmetry, inheritance from a kind.
- steps lists each fact with how it is justified; derivable is the closure's verdict.
- See reasoning-and-planning.
Up to rc24 the prover was given none of the workflow's beliefs, so a held fact returned incomplete_derivation with 0 steps (register row reason.prove).
query(line) derives ontology relations (requires, enables,
special_case_of, part_of, causes, prevents) over the runtime's typed
triples with the explicit grammar (yesno s p o, whatis x, whatneeds x,
related x, ...).
Stateful calls on one workflow are serialized by a per-workflow lock; a second
caller waits, then either runs or gets WorkflowBusy (409) after 30 s. An
idempotency_key (the SDK derives one from the inputs for evaluate) returns
the existing record instead of re-running the engine.
Runtime directories are per workflow; another workflow on the same domain
starts at cycle 0 with the trained store only (I4a). Another organisation gets
NotFound on your workflow, records, packages and replay, and cannot create a
workflow on your domain (I4b/I4c).
adopt_engine(tag) pins an installed release for the workflow; the image is not
touched; the next call runs on the adopted binaries and its record names them.
Adopting a release that lacks a binary makes the next call an
OperationalError engine_binary_missing; rollback_engine() restores the
previous release and the state continues. Adoption of an EARL-authored
successor runtime (A2) is a different contract, gated on mode C, and is not in
this release.
Which engine ran an operation. The authoritative identity is on the operation's own record
(identity.binary_sha256, engine_release_tag). The workflow object's runtime_identity
(GET /v1/workflows/{id}) is the identity recorded at init and does not follow a later
engine_adopt; after an engine adoption or rollback it can name a binary the workflow no longer
runs on (R4 review 2026-10-11, finding F4). Read the records, not the workflow field, when it
matters which engine executed a call. Note also that some reads write: GET …/development/lineage
and GET …/goals/allocation run the engine and append a signed record to the workflow, so a
record count quoted anywhere is a snapshot (finding F6).
| record says | what happened | what to do |
|---|---|---|
operational_error runtime_image_rejected |
the image on disk failed its checks at load | the previous verified image is what is on disk; the call did not save; investigate the storage; a new workflow is the safe recovery |
operational_error engine_binary_missing |
the pinned release lacks the binary | rollback or adopt |
semantic_uncertainty |
the mechanism ran and declined | more evidence or a different question; not a retry |
record fails verify_offline |
the record was altered or is not from this platform's issuer | do not trust it |