Tarski/ docsJoin the waitlist

The Tarski language

A strict Datalog subset for declaring ontologies — relations, rules, decisions, invariants, and the reserved namespaces that make containment structural.

Tarski is Tarski’s ontology language: a strict Datalog subset with Souffle-like syntax and reserved platform namespaces. It is deliberately not Turing-complete — by staying inside well-behaved logic fragments, the platform can verify properties of your system that general-purpose code cannot offer: exact replay, load-time containment checks, cost and monotonicity analysis.

.ta files serve two roles: ontology files (ontology/*.ta) declare relations, rules, decisions, invariants, and intents; query files (queries/*.ta) declare parameterized worldview reads (covered in Declared queries).

What V1 supports and rejects

Supported: positive derivation; bounded recursive positive derivation; stratified negation; finite non-recursive aggregates; explicit assertions and retractions; pure helper calls (helper.std.* and app helpers); invariants; intent. derivation.

Rejected: recursive aggregates; unstratified negation; ambient I/O; dynamic rule loading; and unsafe relay paths from fallible evidence into trusted facts or executable intents.

One syntax rule worth knowing early: an aggregate clause terminates a rule body. Put comparisons before the aggregate, or split the rule through a local relation — the parser diagnostic will tell you exactly this.

Rules and decisions

Derivation is classic Datalog: a head relation derives from a body of conditions.

-- fallible parse lands as candidate evidence (relay namespace)
-- promotion is an explicit decision the loader can check

decision.accepted.orders.line(item, qty) :-
  candidate.order(item, qty),
  menu.bounds(item, max),
  qty <= max.

order.line(item, qty) :-
  decision.accepted.orders.line(item, qty).

-- out of bounds: route for confirmation instead
decision.requires_review.orders.line(item, qty, "reviewer", "order_desk") :-
  candidate.order(item, qty),
  menu.bounds(item, max),
  qty > max.

The decision relations here are not ceremony — they are the load-checked cut between fallible evidence and committed truth. Every positive path from candidate.* to a trusted fact must cross decision.accepted.*, and every path from proposal.* to a world-facing intent must cross decision.approved.*. The full model, including the payload-projection rule and common mistakes, is in Facts, decisions & intents.

Invariants

Invariants are named, fail-closed statements about states the completed model must never contain — checked over the fully materialized model at every fixpoint:

-- the wall: an effect receipt without canonical approval is impossible
invariant pos_only_accepted_lines {
  never pos.ticket(item, qty), not order.line(item, qty)
}

Use invariants for impossible committed states and effect-boundary backstops — not for ordinary proposal denial, which should commit as decision.rejected.* evidence. Common idioms include exclusivity invariants (a count bounded by one) and budget backstops that make over-approval structurally impossible.

Reserved namespaces

Namespace Owner Role
source.* mappers Base evidence extracted from observations
candidate.* mappers or rules Provisional fact-domain evidence
proposal.* mappers or rules Provisional action-domain evidence
decision.* rules only Deterministic adjudication (closed outcome set)
intent.* rules only Committed action requests
effect.* runtime only Effect lifecycle: queued, in_flight, succeeded, failed, blocked, reconcile_required, conflict
helper.std.* platform Version-pinned standard helper library
queryresult.*, proposal.query_requested runtime only Agent read relay families — app declaration is a load error
access.* rules The account/authorization plane

Entity sugar

entity blocks are the language’s one sugar construct — a single declaration that expands deterministically into the full record kit (observation kinds, mapper, current-state rules, and read queries). See Entities.

entity customer {
  key customer_id: text
  field name: text
  field plan: text index
}

Diagnostics as a contract

Every .ta load failure carries a stable diagnostic code, the source file, line, and column, the offending construct, and a correct form or concrete remediation. This is a deliberate product surface: coding agents iterate against diagnostics, so diagnostics are written to teach. Load-time families you will meet include parse errors (E1040E1047 for query files), query validation (E2601E2611), plugin surface violations (E2701E2707), and composition admission (E2810, E2811).

Monotonicity, classified

Every rule, relation, and stratum in a loaded ontology is labeled monotone or non-monotone (the CALM boundary), with explicit witnesses naming why — a negation, an aggregate, a retraction, an ungrounded comparison. Monotone logic can distribute and react coordination-free; non-monotone logic needs the stratified fixed point’s ordering.

You can declare the classification you designed around, and the load fails when the computed classification silently disagrees:

[analysis.monotonicity]
expect_monotone = ["workspace.open_task", "task.activity"]
expect_non_monotone = ["task.record", "task.current_version"]

Expectations gate the load only — they never change evaluation semantics. The classification also feeds the composition architecture report; see Workviews, Collectors & modules.