Runtime architecture
Layers
Clients (CLI, `@kallamo/umwelt`, `Kallamo.Umwelt` over the C ABI, any stdio client)
│ JSON protocol v1 envelopes
▼
umwelt-protocol ── Dispatcher: handshake, parsing, error codes
│
umwelt-api ─────── Engine: commands and queries, clock, id generator
│
umwelt-core ────── contracts, validation, materialization (pure)
│ Store trait
umwelt-sqlite ──── SQLite storage: atomic writes, indexed reads, search
│
file.umwNo client-specific logic lives in any of these layers. Applications, games and mods are separate projects that speak the protocol.
Storage safety
Every write is one immediate SQLite transaction: the event, its records,
transitions, claim and side indexes, full-text entries, attempt and change-feed
item are written together or not at all. A replayed attemptId with the same
fingerprint returns the stored outcome; a different payload is a CONFLICT.
Files carry an application id and a storage version. A file of a newer
storage version than the engine knows is refused; see spec/versioning.md.
Every record is one row rebuilt from columns (see spec/storage.md).
A lock file gives one process exclusive access. WAL is used with
synchronous=NORMAL by default or FULL on request (see spec/protocol.md).
Epistemic invariants
The invariants in spec/invariants.md are enforced
by tests in crates/umwelt-api/tests and by the language-neutral cases in
conformance/cases:
- Nobody knows anything without a record whose holder they are. Being present at an event grants nothing; witnessing is recorded per holder.
- Canon (Facts) never enters a subjective projection.
- A belief that contradicts canon stays held; contradictions are reported.
- Group knowledge is the group’s own records, never the union of its members’.
- Reads at
(axis, tick, asOfVersion)never see anything later, and reads of one timeline never see another’s rows except its ancestors’ up to each branch point. - Confirmed history is never deleted; state changes are transitions.
- The engine applies no policy and runs no model: decay, trust, secrecy rules and embedding vectors come from the client; the engine records and reports.
Timelines
Main is the confirmed history. Drafts branch from main or from another draft
(at most four deep), see each ancestor up to the version their chain was opened
from, and are promoted into their parent (atomically, only if the parent did
not move), rebased (replayed on the parent’s new head) or discarded and purged.
See spec/timelines.md.
Time axes and search
Positions are on the main axis (tick) unless a read names an additional axis
declared with axis.define; a read on an axis sees only records whose event
has a position on it. Search is full-text, semantic over vectors the client’s
model made, or both, and applies the same visibility as every other read. See
spec/queries.md and spec/search.md.
Context admission
context.admit checks candidates in a fixed order that is part of the
contract: candidate shape, project and timeline, time, status, perspective,
accessibility, classification, duplication, then budget. A candidate reports
the first check it fails, as a machine-readable reason. Admitting withheld
content requires an authorized narrator; asking for it without one is a
validation failure. See spec/knowledge-operations.md.