Tarski/ docsJoin the waitlist

Entities

One declaration expands into the whole record kit — versioned latest-wins state, fail-closed conflicts, soft delete with revival, and generated read queries.

Record-shaped data — the CRUD-like backbone of most apps — normally costs a hand-written kit: observation kinds, a mapper, current-state rules, and read queries. An entity block replaces that kit with one declaration. The expansion is deterministic at load time into ordinary V1 artifacts; nothing downstream has entity-specific semantics.

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

What one block generates

  • Observation kindscustomer.created, customer.updated, customer.deleted. Payloads carry the key, an integer version, and the full field set on writes. Appends go through your ordinary write policies — there is no second write path.
  • A generated mapper that owns those kinds exclusively (your app mappers never receive them), emitting namespace-contained source.customer.* atoms.
  • Current-state rules — event union → activity → max-aggregate current version → customer.record (latest version wins), plus customer.tombstoned (soft delete; a higher-version write revives) and fail-closed customer.conflict.
  • Spectator read queriescustomer-get, customer-list, and customer-by-plan (one per index marker), exposed to studio, api. Agent exposure stays hand-authored with decision coverage, deliberately.

Inspect the generated artifacts anytime:

tarski entity expand

Materialized expansions live under generated/entities/<entity>/, written only when changed.

Conflicts fail closed

Entities use optimistic concurrency: writers own version monotonicity. If two divergent writes arrive at the same version, the runtime does not pick a winner — customer.record derives nothing for that key until a higher version resolves the conflict, and customer.conflict makes the situation visible. Clients render the conflict and offer a next-version write. The Team Tasks example is the reference implementation of this pattern, UI included.

Overrides

Generated artifacts can be ceded to hand-written code explicitly:

entity customer {
  key customer_id: text
  field name: text
  omit query customer-list
}

Collisions between generated and hand-written names are load errors naming both provenances — nothing is silently shadowed. Typed E_ENTITY_* diagnostics come from a block scanner that runs before the contract parser.

V1 boundaries

  • Field types are text, int, float, and bool.
  • Updates carry the full field set; field-level patch is a compatible future extension.
  • *.deleted is domain evidence (soft delete); byte erasure remains the job of privacy-retention tombstones.
  • Modules and plugin packs do not carry entity blocks in V1.