Skip to content
JacqOS
Get started

Atoms, Facts, and Intents

JacqOS’s runtime containment works because evidence, derived truth, and proposed actions are kept strictly separate. When a runtime agent proposes an intent, the platform can check it against invariants precisely because facts are derived from observations through explicit, traceable rules — not mixed together in mutable state. When a development-time AI generates those rules, you can verify behavior through fixtures and provenance precisely because each plane is independently inspectable.

Everything flows through one cycle:

Observation → Atoms → Facts → Intents → Effects → (new Observations)

Each stage is a separate durable plane — a distinct storage layer with its own semantics. Keeping them separate is what makes both runtime safety and authoring verification possible.

PlaneWhat it holds
ObservationAppend-only evidence that something happened
BlobRefContent-addressed handle for large raw payloads
AtomBatchDeterministic flattening of one observation into semantic atoms
FactDerived truth record with provenance (assertions and retractions)
IntentDerived request to perform an external action
EffectExecution lifecycle of an intent plus resulting observations

Observations are the ground truth. A booking webhook, an API response, a timer tick, an LLM completion — each arrives as an immutable observation appended to the lineage log.

Every observation carries:

  • A unique observation reference
  • A kind classifier (e.g. "booking.request", "slot.status")
  • A raw payload (JSON, text, or a BlobRef for large data)
  • Ingestion metadata (timestamp, source)

Observations are never modified or deleted. They are the evidence layer — they record what the outside world said, not what the system believes about it.

Rhai mappers turn each observation into a batch of atoms — typed key-value pairs that form the structural boundary between messy external data and the semantic logic layer.

fn map_observation(obs) {
let atoms = [];
if obs.kind == "booking.request" {
let body = parse_json(obs.payload);
atoms.push(atom("booking.email", body.email));
atoms.push(atom("booking.slot_id", body.slot_id));
atoms.push(atom("booking.name", body.patient_name));
}
atoms
}

Atom extraction is deterministic. The same observation always produces the same atoms. A mapper cannot read facts, derive intents, or access external resources — it receives one observation and returns atoms, nothing more.

The canonical mapper export is the portable contract for this boundary. Two different mapper implementations that produce the same canonical export converge on the same mapper_output_digest. You can refactor mapper code freely without changing semantics.

Atoms enter the ontology through the built-in atom() relation:

rule booking.request_received(req, email, slot) :-
atom(req, "booking.email", email),
atom(req, "booking.slot_id", slot).

Naming Canonical Relations and Activity Labels

Section titled “Naming Canonical Relations and Activity Labels”

JacqOS has canonical names first. A .dh relation name such as sales.decision.offer_blocked or intent.offer_decision_requested is the stable semantic identifier used by rules, fixtures, exports, provenance, and namespace analysis.

Activity labels in Studio and docs should be presentation derived from those canonical names, not a second semantic identity. Readable relation tails make the UI clearer at a glance:

Canonical nameActivity label
sales.offer_request_receivedOffer Request Received
sales.decision.offer_blockedOffer Blocked
proposal.offer_price_suggestedOffer Price Suggested
intent.offer_decision_requestedOffer Decision Requested
sales.manager_review_openedManager Review Opened

Use lowercase snake_case relation segments and dot namespaces. Prefer lifecycle suffixes that say what happened or what decision was made:

  • Observation-derived facts: *_received, *_recorded, *_opened, *_completed, *_failed
  • Candidate evidence: candidate.*_parsed, candidate.*_reported
  • Proposal evidence: proposal.*_suggested
  • Domain decisions: domain.decision.*_authorized, *_blocked, *_required
  • Intents: intent.*_requested
  • Receipt facts: *_sent, *_executed, *_resolved

Observation kinds and atom predicates use the same lowercase, dotted style but serve different jobs. Observation kinds name external evidence events such as booking.request, sales.manager_review_opened, or effect.receipt. Atom predicates name deterministic fields extracted from one observation, such as booking.email, proposal.amount, or effect.result. They are provenance anchors and mapper contracts, not activity labels.

Avoid generic relation names like proposal.action, authorized_action, blocked_action, or intent.execute_action in public examples. They are mechanically valid, but they produce vague activity labels and hide the semantic lifecycle you need to inspect.

Facts are what the system currently believes, derived from atoms and other facts through .dh ontology rules. Every fact carries full provenance — which observations, atoms, and rules contributed to its existence.

Facts support two operations:

  • Assertions — adding new derived truth
  • Retractions — removing previously derived truth when evidence changes
rule assert booking.confirmed(req, slot) :-
atom(obs, "reserve.succeeded", "true"),
atom(obs, "reserve.request_id", req),
atom(obs, "reserve.slot_id", slot).
rule retract booking.slot_available(slot) :-
booking.confirmed(_, slot).

When new observations arrive, the evaluator reaches a new fixed point and materializes fact deltas — which facts were asserted, which were retracted, and why. The provenance chain for every fact traces all the way back to the raw observations that produced it.

This is where invariant review operates. Humans declare constraints over facts, and the evaluator proves they hold across every scenario. You read the invariant, not the rules that derive the facts.

Intents are facts with a special prefix — intent. — that the shell intercepts as requests for external action. They are derived exactly like any other fact, through ontology rules.

rule intent.slot_reservation_requested(req, slot) :-
booking.request_received(req, _, slot),
not booking.slot_reserved(slot).

Intents go through a strict lifecycle:

  1. Derived — the evaluator produces the intent from current facts
  2. Admitted — the shell durably records the intent before any external execution
  3. Executing — the effect runner begins the external action
  4. Completed — the effect result is captured as a new observation

Every intent is durably admitted before execution begins. No external action fires from a transient derivation.

Effects are the execution lifecycle of admitted intents. They use declared effect capabilities:

CapabilityPurpose
http.fetchDeclared outbound HTTP
llm.completeExplicit model call for LLM-assisted agents
blob.put / blob.getLarge raw body storage
timer.scheduleRequest a future timer observation
log.devDeveloper diagnostics (never canonical state)

Guest code never mutates facts directly and never appends observations directly. Every external action goes through a declared capability. Undeclared capability use is a hard load error.

Effect results — success or failure — are captured as new observations, which closes the derivation loop and starts the cycle again.

Retry policy is explicit. Idempotent effects auto-retry on failure. Ambiguous mutations (non-idempotent requests that failed mid-execution) enter reconcile_required status for explicit human resolution. No silent auto-retry of mutations whose outcome is uncertain.

The six durable planes describe the evidence and execution loop. JacqOS also keeps separate audit surfaces so current truth never gets confused with historical explanation.

SurfaceWhat it answers
Committed semantic snapshotWhat does this evaluator currently believe at the committed head?
Attempt reportWhy did one evaluated head commit or reject?
Fact planeWhich fact memberships were added or removed when a head committed?
Intent planeWhich intents entered or exited, and which effect records are attached to them?

These surfaces stay distinct on purpose:

  • The committed semantic snapshot is the current truth surface.
  • Every evaluated head writes one attempt report, whether it commits or rejects.
  • Attempt reports explain one evaluation attempt. They are not ontology facts.
  • The Fact plane appends committed add and remove deltas for facts and contradictions.
  • The Intent plane appends committed enter and exit deltas plus the latest effect linkage for each intent contract.
  • Fact and Intent planes are append-only audit history, not alternate worldviews.
  • Studio, verification, and crash-recovery tooling inspect these audit surfaces without turning them into hidden state.

The derivation pipeline is not a one-shot sequence — it is a continuous loop:

┌─────────────┐
│ Observation │ ← new evidence arrives (webhook, API response, effect result)
└──────┬──────┘
│ Rhai mappers
┌─────────────┐
│ Atoms │ ← deterministic semantic extraction
└──────┬───────┘
│ .dh ontology rules
┌─────────────┐
│ Facts │ ← derived truth with full provenance
└──────┬───────┘
│ intent. rules
┌─────────────┐
│ Intents │ ← derived action requests
└──────┬───────┘
│ effect runner + declared capabilities
┌─────────────┐
│ Effects │ ← execution lifecycle
└──────┬───────┘
│ results captured as observations
└──────────→ back to Observation

Each pass through the cycle:

  1. New observations arrive — either from external sources or from effect results
  2. Mappers extract atoms deterministically
  3. The evaluator reaches a new fixed point, materializing fact deltas
  4. New intents may be derived from the updated fact set
  5. The shell admits and executes intents through declared capabilities
  6. Effect results become new observations, and the cycle continues

The loop terminates naturally when a fixed point produces no new intents. Until then, each effect result feeds back as evidence for the next evaluation pass.


JacqOS uses a small set of explicit, non-interchangeable identities to track what changed and why:

IdentityPurpose
lineage_idNames one observation history
evaluator_digestSemantic identity for fact derivation — hash of ontology IR, mapper semantics, and helper digests
package_digestFrozen runtime handoff identity
mapper_output_digestCross-shell evidence equivalence

evaluator_digest is the primary semantic identity. When ontology rules, mapper logic, or helper code changes, a new digest is produced. Prompt-only changes do not affect the evaluator digest — semantic identity tracks logic, not presentation.