How your software runs against EARL once it works on your desk: what you deploy, what the platform keeps, what to do on every outcome, how to watch state and records, and how to change the engine underneath you. This is the primary deployment guide. Deploying a frozen adjudicator container is a specific case with its own guide: deploying an adjudicator.
In this release the persistent cognitive runtime is not a container you run. It runs inside the platform:
one runtime image per workflow, loaded, acted on and saved per call, behind the application API
(/api/v1) with the application's key. So "deploying an application" means:
POST /verify with the same claim results and a signed evidence package. It runs anywhere
Docker runs, with no platform present, and it is the only self-hosted artifact in this release.That is the implementation boundary, stated plainly: the stateful capabilities (memory.*, reason.*,
goals.plan, self.observe) are reachable only through the platform; the standalone verifier is the only
part you can take off-platform. A self-hosted persistent runtime is future work, not a documented path.
Your service authenticates with the application's key (earl_ak_…), never with a person's session. Every
capability call is recorded under the application's service identity (app-<slug>), which is how an auditor
later tells your software's calls from a developer's. Keep one key per deployed environment and revoke
keys from the application page when an environment is retired.
from earl_sdk import EarlClient, Result, SemanticUncertainty, OperationalError, NotFound
client = EarlClient(PLATFORM_URL, API_KEY) # from your secret store
app = client.application(APPLICATION_ID)
wf = app.workflow() # the workflow bound to the application's domain
The workflow id is stable for the life of the application. Store it with your configuration; do not create a workflow per request.
Every SDK call returns one of three disjoint outcomes. Your service must branch on all three; nothing is raised for a semantic answer.
| outcome | meaning | what your service does |
|---|---|---|
Result |
the capability did what was asked; result carries the payload |
act on it, with the policy disposition where one applies |
SemanticUncertainty |
the engine declined with a reason (relation_unasserted, incomplete_derivation, no_belief_formed, a non-ALLOW policy decision …); result still carries the claim results |
treat as a non-answer: route to review, ask for more evidence, or record and continue — never as a denial by itself |
OperationalError |
the environment failed (engine missing, store unavailable, lock timeout); nothing semantic happened | retry or alert; the record says what failed |
NotFound (404) means the object is not visible to this key — a wrong id or another tenant's object; both
look identical on purpose. WorkflowBusy (409) means another call holds the workflow's lock; wait and
retry.
outcome = wf.evaluate(claim_text)
if isinstance(outcome, Result):
disposition = outcome.result["policy"]["disposition"] if outcome.result.get("policy") else None
elif isinstance(outcome, SemanticUncertainty):
reasons = outcome.reasons # e.g. ["relation_unasserted"]
else: # OperationalError
raise RetryLater(outcome.error)
The workflow's runtime image is the state your service builds up. Rules of operation:
submit, tick, prove, query, plan) are serialized by a
per-workflow lock. Design your service so a workflow is driven by one logical actor; fan out reads, not
writes.idempotency_key on calls you may repeat (the SDK derives one for evaluate);
a repeated key returns the existing record instead of acting twice.submit stored is what the extractor read; check it with beliefs
when the text mattered, and keep the source text in your own system of record.wf.observe() and the image hash from the last record; after it, compare.Every call is an append-only, Ed25519-signed record. Operate from them:
wf.records() — contiguous sequence numbers; a gap means something was deleted, which never happens
on the platform side.client.verify_offline(record) — verify any record with the issuer key pinned in your deployment; do
this on a sample in your monitoring, not only in tests.wf.replay(record_id) — re-run an evaluation against the same frozen substrate and compare
(identical: true). A non-identical replay is an incident.verify_evidence_package.py tool (see the adjudicator guide).Alert on: OperationalError rates, substrate_identity_mismatch: true from a deployed container, a
non-identical replay, and any record whose execution_path is not the one the manifest declares for that
capability.
runtime.version lets an application pin or adopt an installed engine release for its workflow
(wf.adopt_engine(tag), wf.rollback_engine()); the next call runs on the adopted binaries and every
record names them. The contained successor interface goes one step further for candidates: evaluate
a candidate release over a copy of the workflow image (evaluate_successor), then adopt it as an explicit
authority action bound to that evaluation with preservation checks (adopt_successor), and roll back
byte-identically (rollback_successor). Both are installed-release adoption under your control; neither
is EARL producing its own successors (see the register's developmental roadmap).
Procedure for a change: evaluate the candidate → run your own acceptance tests against the evaluation's copy → adopt → verify records still contiguous and offline-valid → keep the rollback handle until your next release.
SemanticUncertainty never treated as a denial