Skip to content

The Seam

Vinxi Kernel — The Governed Seam

Naming (canon): The company is Lepton. The kernel — the shared technical core and base platform — is Vinxi (“the Vinxi kernel”; vinxi-* / @vinxi/… in code). Lepton’s vertical products — NetworkAccess, Trafficure, SmartMarket, SmartBuild — are built on Vinxi: “NetworkAccess is powered by Vinxi.” The phrase reality kernel, lowercase, is the category term for essays and positioning (like “lakehouse” — categories are meant to be generic; the product in the category is ours). Terms of art are semantic, capitalized as vocabulary but never brands (same convention as World plane): the seam, the six verbs, Realm, Entity, Trait, the Reality IR, Reality Packages, the Reality SDK. “Reality” in these compounds is the category concept, not a second brand — no artifact carries two names. These were chosen over Realm-prefixed forms partly for spoken ease (realm is awkward in Indian English; these names are said aloud daily); Realm remains the primitive in code and docs, where precision beats speakability. Brand prefixes never appear in display names of terms of art (no “Vinxi IR”) — platform packages live in vinxi-* / @vinxi/…; product SDKs and userland may use @lepton/…. Products lead with customers; the kernel is mentioned only when relevant (“powered by Vinxi”). “Agent Runtime” may appear in marketing lists but is not an architecture component — agents remain Actors. Retired names for this core are listed in Naming.

The job, in plain words (positioning canon): “Coherent truth about reality plus the right to change it” is the engineering definition of the resource — correct, and it does not communicate. The outward framing names the four planes by what they do for an operator: One living picture (every asset, connection, reading, and crew, fused from every source, honest when sources disagree — the World plane) · One gate for change (every action by staff, systems, or AI agents passes the same checkpoint, permission before, record after — the seam) · One unbroken story (who did what, when, on what evidence; audit as the memory the system runs on — the Truth plane) · One rehearsal space (copy the world, try the change, see consequences, then commit — branches). Picture, gate, story, rehearsal. Use thing-language (towers, roads, crews, orders) in every external articulation; reserve “truth” vocabulary for the architecture documents.

Foundational architecture. Status: converged, with open forks flagged in §XI. This document consolidates what the kernel is, the single resource it manages, the one law that generates every boundary decision, and the mechanisms that fall out of it — the type system, the software model, the six-verb syscall surface, the primitive distinctions the verbs force, the temporal model, the compiled typed SDK, the policy split, and the physical architecture that realizes it on real stores. It is written to be what the kernel is designed to be: small, correct, and meant to outlast the domains built on it.


The resource. The kernel manages one scarce thing: coherent truth about a slice of reality, plus the right to change it.

The law. The kernel owns a small closed set of mechanisms. Userland composes open meaning on top. The seam between them is the governed path.

The corollary. Kernel + your userland is an operating system that is yours and only yours — one universal core, an entire userland assembled for you.


I. The frame

Why this kept circling

The design orbited because it tried to name the primitives before pinning the forces they have to resist. Every new requirement then tempted either a new primitive or a re-litigation of the existing ones. First principles here means the reverse order: nail the forces first, and let the primitives fall out as whatever survives them.

The one resource, and the kernel’s job

An OS kernel manages one scarce thing — access to hardware — and mediates every touch of it. Strip the domain down and the scarce thing here is coherent truth about reality and the right to change it. That is the single resource. The kernel’s only job is to hold a governed, time-and-branch-aware model of reality and mediate every read and write to it. Everything else — analytics, screens, simulations, agents — is a consumer of that mediated access. The test for whether something belongs in the kernel: if a feature doesn’t touch “coherent truth or the right to change it,” it is userland.

The acceptance test: four scenarios

The four target scenarios are not redundant. Each maxes out a different axis, and together they span the space. If the kernel survives all four, it survives the domain — so they stay, verbatim, as the acceptance suite.

ScenarioWhat it isAxis it maxes out
TrafficureMirror a live physical system from external feeds, then fork it to simulate interventionsStreaming flux + counterfactual branching
NetworkAccessThe daily system-of-record and system-of-action for a Jio-scale operator — every screen, query, automationScale + transactional correctness + being the whole OS
Company IT opsSync bidirectionally with systems that are themselves the source of truth, stay consistent, drive them backBidirectional sync + HTAP + consistency under external ownership
SmartMarketInstantiate a new custom reality per customer on the fly, when Varun Beverages and Cashify share almost nothing structurallySchema plasticity

The OS metaphor, kept honest

The metaphor is load-bearing where it gives three things: drivers (a uniform interface over messy external sources — a device driver for Stripe, for Proxmox, for a human), syscalls (one stable action/query surface every app and agent passes through), and protection rings (authorization, policy, audit — nothing touches reality except through the mediated path).

It misleads in exactly one place worth watching. A real kernel is minimal, performance-obsessed, and semantically empty on purpose. Ours must be semantically thin but not empty — it has to know a Trait is a float to index it, without knowing the float is a speed. Those two pressures fight, and the resolution is the whole architecture: the kernel stays thin, and the richness lives in loadable domain models above it. The failure mode to avoid is building a semantic monolith and calling it a kernel.


II. The law, and why it is a law

Thin, not empty: the kernel is meta-semantic

There are three positions, not two. An empty kernel knows bytes and pages. A rich kernel knows “cable.” Ours is neither — it is meta-semantic: it knows the grammar of reality-modeling. It knows that realities are Entities with Traits, that Actions mutate them, that Policy governs, that Events record — the shape of meaning without any specific meaning. “A cable connects to a joint” is not kernel code; it is a customer declaration expressed in the kernel’s primitives, the way a program is data to a CPU. That is what makes “yours and only yours” true without forking the binary: one kernel, N realities loaded into it.

Shape, not meaning. Schema is kernel-visible; semantics is customer-owned. Say it whenever the line wobbles.

Indexing is the sharp test of the line. Traffic, telecom, and IT-topology all lean on spatial and graph relationships at PostGIS-class speed, which is exactly where you are tempted to teach the kernel geometry for performance. Don’t. The kernel provides indexing as mechanism — the customer declares which Traits are spatial, and the kernel indexes them with an R-tree or H3 because their kind says geo. It never learns the point is a manhole.

The same cut, three times

The split is not a one-off line between kernel and apps. The identical figure recurs at three depths, and that recurrence is what makes it a law rather than a slogan:

LayerKernel (closed, mechanism)Userland (open, meaning)The seam
Type systemkindvalue typekernel checks declared constraints
Schemaprimitive + shapeentity type + meaninga schema change is itself an Action
Softwareeffect / verbcode / workflowthe SDK / the syscall interface

The single test for every future decision follows from this: keep the kernel’s vocabulary closed and push the meaning up — it belongs. If something only works by teaching the kernel a new meaning, it is userland in a kernel costume, and it is what pulls the design back into circles.


III. The type system: kinds and value types

The type system splits into two levels, the same cut again.

Level 0 — kinds. Closed, kernel-owned, mechanism. The handful of shapes the kernel must grasp to store and index anything: numeric, geo, temporal, graph-edge, categorical. Sizing this set — small enough to stay thin, expressive enough to index everything — is a genuine kernel decision, and it is mechanism.

Level 1 — value types. Open, user-composed, meaning. Speed is numeric + unit + range; manhole-location is geo + CRS. The kernel sees a value type as its kind (Speed is a float, index it as a float) and stays blind to it as meaning (it never learns float-is-speed). Primitives and composition — int/float sitting under a struct.

The seam runs inside a single value type, and this is your whole software question in miniature. A value type carries constraints. Some are declarative — min/max, enum, regex, cardinality — a closed constraint vocabulary the kernel can check and enforce at write time. Push one step further — “this speed is valid only if the adjacent road is open” — and the constraint stops being a declaration and becomes effectful logic that runs. That transition, within one Trait, from kernel-checkable declaration to userland effect, is the same boundary the entire software model lives on, one scale down.


IV. The software model: govern effects, not code

User-defined software is a first-class goal — customers and their AI coding agents write processes, workflows, drivers, models, dashboards. “Shape, not meaning” governs how userland describes reality, but code acts, and a schema can’t do anything the kernel doesn’t already permit while a workflow can loop, fail, call out, and change the world. So a second discipline is needed for the fact that userland now acts.

Govern effects, not code. The kernel never inspects user code for correctness — it can’t (any nontrivial property of an arbitrary program is undecidable), and it doesn’t want to be proving theorems about customers’ agents’ code. It governs the finite, declared moments where code tries to touch reality, and stays blind to how the code decided to ask.

This is rings and syscalls, exactly. A process computes whatever it likes in its own memory; the instant it wants a file, a socket, another process, it makes a syscall, and the syscall is the checkpoint — capability, not inspection. Translated:

  • No ambient authority. User code runs in a sandbox with no DB handle, no raw socket, no credentials in scope. The only way it can read reality is a Query, change reality an Act, reach outside a Call — each already the mediated path where Policy, authorization, and audit live. User code is born inside the protection rings: it cannot even express an ungoverned effect, because the only vocabulary it has for touching the world is the governed surface. That is what makes a million agent loops safe — an agent is just an Actor, and every loop’s effects hit the same chokepoint regardless of what the agent “wanted.”

  • The SDK is the ABI, not a library. What makes this work is not ergonomics — it is that the SDK is the only door. If user code can reach around it to the database in one line, the entire governance model collapses. “An SDK you could bypass” and “the syscall interface that is the only way through” are different objects; the second is required, and the sandbox has to be real.

  • Effects are requested, not performed. For the kernel to govern an effect it must see it before it happens. So an effect is a declared intent the kernel authorizes and then commits — user code doesn’t mutate, it asks, and the kernel mutates. Putting a mediator between description and execution is what hands you audit, branching, and replay for free on the effect log.

  • Drivers are their own privilege class. A workflow’s effects are all inward, onto your reality — fully governable, just Acts. A driver reaches outward, and its effect escapes your universe; you cannot police what Stripe does after you call it. So a driver is the highest-privilege userland citizen: code handed one specific external capability (these credentials, this endpoint, this rate limit) and nothing more. The kernel can’t govern Stripe, but it governs which driver may hold the Stripe capability and audits every crossing. This is also the containment for the part that should scare you — an agent writing driver code. Let it write freely; the code only becomes dangerous when it is granted a capability, and the grant is explicit, gated, audited.

Writing is free; being handed a capability is governed.

  • Author-blind. From the kernel’s vantage there is no difference between a human and an agent — both are Actors whose requested effects get checked, author-blind. So defining the system is just more governed effect: creating an Entity type is an Act on the schema, shipping a workflow is a governed effect on the software layer. There is no privileged admin channel outside the rings.

The system that defines the system is the system.


V. The syscall table

A workflow is not an eighth primitive — it is the first userland program, and it decomposes into the syscall surface with nothing left over. The surface has two sub-tables: two triggers, four effects.

Triggers — what causes code to run (ignition, not effect):

  • Subscribe — react to an Event.
  • Schedule — a timer fires.

Effects — the only ways code may touch the world:

  • Query — read owned reality. Deterministic.
  • Act — mutate owned reality by decision. Deterministic, replayable; mints an Event; low-volume.
  • Assert — record an authoritative declaration of a trait’s value. Idempotent, last-writer-wins; recorded as bitemporal time-series; not in the decision log, not replayed; high-volume.
  • Call — cross the boundary outward (read or write). Non-deterministic, irreversible; a recorded fact.

Why six, not five — and how the sixth was found. The completeness test earned its keep here. The earlier surface had five verbs and folded the inbound webhook into “a driver emits an Event that fires a Subscribe.” That is correct at low volume and wrong at streaming scale: minting a decision-Event per sensor reading floods the audit/replay spine with observation noise. The case that didn’t fit cleanly is the one legitimate reason to reopen the surface — and it produced Assert. The symmetry is now clean: the outbound boundary crossing is a Call (an irreversible fact you record); the inbound boundary crossing is an Assert (an authoritative declaration you record). Two directions, two verbs, both recorded-not-replayed. The other near-misses still fold in: emit a domain event is an Act appending an Event; transactions are a property of Act; windowed/continuous queries are Subscribe-to-a-view.

The falsifiable test — the only legitimate reason to reopen the kernel. Take a real Trafficure what-if or a NetworkAccess work-order flow and express it as these six verbs over the seven primitives. If it fits, the kernel is done and everything else is provably software. If it doesn’t, you’ve found a missing syscall or a missing primitive — and that is the one reason to touch the core. (This is exactly how Assert was found.)


VI. The distinctions the verbs force

Act vs Call — the reversibility boundary

Linux is the wrong reference and will mislead you. A Linux driver runs in ring 0, fully trusted, with authority decided once at load time (CAP_SYS_MODULE plus a signature) and then unchecked forever — one bug panics the machine. That is the inverse of what we want: “loading is governed, every crossing is unchecked.” The right reference is the capability microkernel (seL4, Minix 3, QNX): drivers run in userspace as confined processes, each holding explicit unforgeable capabilities to specific resources and nothing else, crash-restartable. That is our driver model nearly verbatim, and seL4 formally proved it works.

Linux folds using a device into read/write/ioctl (“everything is a file”) because Linux has no replay and no branching. We have both, and that forces Act and Call apart:

  • An Act is inward. It mutates your reality, the kernel performs it, and it is deterministic and replayable — same state plus same Act yields the same result forever. That property is exactly what lets Acts live on the effect log and power branching, replay, and time-travel.
  • A Call is outward. It crosses into a world you don’t own, the kernel does not perform it — it authorizes confined driver code to — and it is non-deterministic and irreversible: you can’t un-charge a card, replaying charges twice, the same read returns a different answer. The log entry for a Call is a fact, not a re-runnable instruction. Driver reads are Calls too.

The gap is three names for one seam: reversible ⟷ irreversible = owned ⟷ external = kernel-executes ⟷ kernel-authorizes.

It proves itself against simulation: in a branch you replay Acts freely against the branch’s copy of reality, but you must never replay a Call — a sim cannot phone Stripe. So the kernel must read reversibility off the verb’s kind alone, statically. Call is therefore its own verb, not “an Act whose target is a Driver” — collapsing them would bury irreversibility where the kernel can’t see it and make branching either corrupting or impossible. The inbound counterpart is Assert (below): a Call records what an external world did; an Assert records what an authoritative source declares. Both cross the boundary; neither replays.

Event vs Act — two primitives, not one

They look fused because every Act mints an Event, but the fusion is causal, not identity — they sit on opposite sides of the Policy checkpoint.

An Act is a verb: a requested intent to mutate owned reality, evaluated before it happens — the kernel can gate it, reject it, demand approval. An Event is a noun: an immutable fact that something did happen, written after. You can authorize an Act; you cannot authorize an Event, because it is already in the past. Between them stands the governed path. Collapse them and you have deleted the checkpoint — you’d be authorizing history.

Two further breaks confirm it. Event is strictly broader than Act — a Call minting “Stripe returned success,” a Schedule firing, an inbound webhook all produce Events with no Act behind them, and that breadth is required, because it is how Subscribe fires after a Call completes. And they replay differently — you re-execute Acts to reconstruct state; you never re-execute Events, which are the output of replay or facts you consume.

This also disambiguates the primitive from the verb:

Action is the definition (the kernel-visible command type). Act is invoking it (the syscall). Event is the fact it leaves behind.

Which is why there is no “Emit” verb: authoring a domain event (“customer complained”) happens by an Act that appends it. And in the compiled SDK the two don’t even project to the same kind of type — Act becomes a function signature, Event becomes a data record. If they were one primitive they’d compile to one type. They don’t; the type system witnesses the distinction.

Act vs Assert — decision vs declaration

The newest distinction, and the one that keeps the decision log lean. Both write reality; they differ on what kind of statement they are, and therefore on how they are gated, stored, and replayed.

An Act is a decision: an intent, gated on may this actor do this (the full action-policy evaluation), which transitions state, mints a durable Event, and is replayed in branches. Low volume.

An Assert is a declaration: an authorized source stating a trait’s current value, gated on the cheaper question is this source authorized over this trait (an authority lookup, not a conditional action evaluation). It is idempotent / last-writer-wins, recorded as bitemporal time-series, kept out of the decision log, and never replayed. High volume.

By the law, this earns verb status on the same grounds Call did: the kernel must know, statically from the verb, that an Assert skips the decision log, routes to the time-series substrate, and is excluded from replay. Bury that in a provenance field the kernel has to interpret and the bloat returns.

Assert is not external-only. An authorized surveyor declaring a pole’s height is an owned Assert. The axis is declaration-vs-decision, not internal-vs-external; mirrored data is simply the case where a write is always a declaration and always high-volume, which is why the log-bloat pressure surfaced there first. This makes provenance operational rather than decorative: owned decisions are written by Act, declarations and all mirrored truth by Assert, hypothetical truth into a branch — provenance selects the write verb and its storage class.


VII. Temporality: snapshots are the checkpoints, the log is the delta

The analytical store already snapshots at every commit (Iceberg, Delta), which rebalances the temporal spine: the snapshot answers “what was true”; the log answers “what happened and why.” Two temporal questions, two substrates. State as-of a past time is a snapshot read — no log folding required. Change-history and causation are the log.

The scaling payoff is the checkpoint property. A branch never replays from genesis. It clones the nearest snapshot and replays only the Acts committed since, from the log tail — the classic event-sourcing snapshot optimization, obtained for free because the lakehouse checkpoints anyway. As-of queries hit the snapshot; branching hits snapshot-plus-tail; the log is never the read path for state. That is most of what makes the temporal model scale.

Two caveats to model deliberately:

System time vs valid time. A snapshot’s time axis is system time — when the row was committed — not valid time, when the fact was true in the world. The moment decisions mix with ingested observations these diverge (a 3:00 reading lands at 3:02). Honest as-of-the-world queries therefore need bitemporal modeling — valid-time carried as a trait attribute — not just the snapshot axis. This is why Assert records bitemporally.

Retention horizon. Snapshots are expired for cost, so long-range as-of cannot lean on infinite snapshot retention. Past the horizon you are back to log replay or deliberately retained checkpoints. Choose the horizon as an explicit policy, not a storage default.


VIII. Compiling reality into a typed SDK

This is the natural terminus of “the SDK is the ABI.” The thing that compiles is not the kernel — the kernel is universal and stable. What compiles is the reality: the loaded userland schema, projected into a target language’s type system. Everything in that schema is declared, therefore kernel-visible as shape, therefore machine-readable — and a machine-readable schema is one you can project into typed bindings. Same move as protobuf or GraphQL codegen, but richer, because the schema carries Actions, Events, capabilities, and refined value types, not just data.

Schema elementProjects to
Trait kindBase type (numeric → number, geo → Geo, temporal → Instant)
Value type (kind + constraints)Branded type via smart constructor — Speed is not a float, is non-interchangeable with Distance, and enforces the declarative constraints; effectful constraints stay runtime
Entity typeTyped record
ActionTyped function — completeWorkOrder(input): ActResult
EventTyped payload in a subscription handler
QueryTyped builder that only reads traits that exist, at their real types
CapabilityTyped token — the keystone

An Assert projects like an Act — a typed declaration function on an assertable trait — but gated on source authority rather than action policy, and returning an acknowledgement rather than a committed/denied union.

Capabilities-become-types is the payoff. The capability-shaped part of Policy lifts into the type system: an Action that needs a capability takes it as a typed parameter, so you cannot even call the function without holding the token. The sandbox’s “no ambient authority” stops being a runtime rejection and becomes a signature you cannot satisfy. Protection rings, enforced at authoring time.

The SDK obeys the law — two strata. A kernel base SDK (the six verbs, the seven primitive base types, the kind→type map; universal, versioned with the kernel) and a generated reality SDK (this customer’s entity types, actions, events, capabilities; a pure projection of their declared reality). Varun Beverages and Cashify share the base and diverge entirely above it. “Yours and only yours,” made mechanical — your SDK is a different typed surface because your reality is.

Three cautions, each a place this quietly goes wrong:

  1. The kernel does not grow a compiler. It exposes the reality as introspectable shape — reflection is just a Query against the schema itself — and projection to TypeScript/Rust/Python is a platform tool that reads that shape. Kernel provides the mechanism; the platform provides the projection.
  2. It is a live projection, not one-time codegen. The reality is dynamic; a schema change is itself an Act; so the kernel re-projects and emits a new SDK version downstream automatically. Unlike protobuf, schema evolution is a governed runtime Act and regeneration is its consequence.
  3. The types are not the security boundary. An agent can cast a type away or hand-craft a raw request; types are bypassable. So the kernel re-checks authoritatively at the seam, every time. The typed SDK is the runtime governed path’s shadow cast forward onto authoring time.

Three moments of the same governance, earliest to last: can’t-express-it (sandbox) → won’t-type-check (SDK) → rejected-at-request (kernel).

That chain is what makes agent-authored software safe rather than terrifying. Against a stringly-typed API an agent hallucinates action names and params endlessly; against a generated typed SDK of this customer’s specific reality, its own type-checker rejects the hallucination as it writes. The compiler becomes the tight feedback loop the agent runs against — the guardrail that keeps generated code inside the universe with no human on every line.


IX. Splitting Policy

The generating rule, from which every case falls out:

A policy fact lifts into the types if and only if changing it would already force an SDK regeneration. Structure lifts; evaluation never does.

It is read straight off the regeneration semantics. The SDK regenerates when the declared reality changes, so any authority fact that is part of the declared reality rides into the types for free; any authority fact that changes while the schema holds still must stay at the seam, or you bake a stale guarantee into every client and force a redeploy every time someone is promoted.

Write the check as allow(actor, action, resource, context) and ask only when each argument is bound:

ArgumentBound whenSide
action + the capability type it demandsWhen code names the action — compile-time, schemaTypes
actorPer invocation — same code runs for everyoneSeam
resource — its typeCompile-timeTypes
resource — its instance (ReBAC / relationship)RuntimeSeam
context (ABAC predicates, rate limits)World state, never known at authoringSeam

So of the three IAM layers, exactly one structural sliver crosses: the capability vocabulary — that a CompleteWorkOrder capability exists and is required here. Who holds it (RBAC grants), on which instance (ReBAC), under what conditions (ABAC) all stay at the seam — which means the role→capability mapping itself stays at the seam. You grant a role a new permission through an admin Act without redeploying a single client. This is the thin cut, not aggressive lifting.

The mechanism is a capability token — a runtime object wearing a compile-time type. You acquire it, and that call is where the kernel evaluates the full policy — RBAC, ReBAC, ABAC, context — at that moment, against this actor and this instance:

const cap = await ctx.acquire(CompleteWorkOrder, workOrderId); // full policy eval here
completeWorkOrder(cap, input); // won't compile without the token

The token is signed, scoped (this resource, this window), and typed Capability<CompleteWorkOrder>. Ergonomically you sugar it into a scoped handle — ctx.for(workOrderId).completeWorkOrder(...) — so nobody passes raw tokens around, but the guarantee is the same.

The type system doesn’t check policy — it checks that you asked the policy the right question. The token is not proof you’re allowed; it is proof you went through the governed path to find out. The compiler enforces that a check happened; the kernel enforces the check’s outcome.

Two runtime touchpoints, not redundant — this is what defeats TOCTOU. Acquire is the expensive full evaluation that also yields the typed surface. Invoke is a cheap re-validation at the seam: the kernel re-checks the token’s freshness and scope and re-runs the fast context predicates, because the road could have closed in the millisecond between acquiring and invoking. The token is short-scoped precisely so the seam remains the authority and the type is only its shadow.

Both ends of an Act’s signature are structural, so both lift. The input end is the capability token — you must have asked. The output end is the honest outcome type: because policy means an Act is not a guaranteed mutation, its return is a union — Committed | Denied | Pending(approval) — not a bare success. That is not the decision leaking into types; it is the shape of the possibility space, schema-derived, and it lifts as cleanly as a typed Result.

Why hold this line hard. Policy is a primitive, and a primitive is mutated by Acts at runtime — that property is the whole point, the thing that lets you hot-swap authorization, run a break-glass grant, or tighten a rule mid-incident without a deploy. Every role or predicate promoted into a client type is a piece of Policy quietly frozen, because changing it now means regenerating and redistributing SDKs. Aggressive lifting doesn’t merely risk type-explosion and stale guarantees; it demotes Policy from a primitive to a build-time artifact — the one thing this architecture exists to prevent. The capability vocabulary is safe to lift only because it was already schema and never dynamic.

The discipline: coarse capabilities in the types, fine decisions at the seam. Roughly one capability type per Action, structural and stable; push everything fine — instance, relationship, condition, grant — into the runtime acquisition check. The smell to watch for is wanting fine-grained capability types to buy more compile-time safety; that is policy structure crossing the line disguised as vocabulary, and it is where the primitive starts to freeze.

Two authorization modes. Everything above governs the Act path — action-policy, the full allow(actor, action, resource, context) evaluation. Assert is gated differently and more cheaply: is this source authorized over this trait, an authority lookup rather than a conditional action evaluation. Keeping the two modes distinct is what lets the high-volume declaration path stay fast while the decision path stays fully governed — same seam, two checkpoints sized to their traffic.


X. Physical architecture

The conceptual model lands on three physical planes. The stance — the analytical store is the final truth of state, the operational store is a derived cache, the log is the spine — is not a compromise; it is the shape Palantir Foundry and Databricks converged on independently. Foundry’s Ontology is a semantic-plus-kinetic layer over datasets and models, with a fast object store derived from them by an indexing service (Funnel), and an Actions service that applies edits and keeps a historical action log. Databricks’ Lakebase is a Postgres transactional layer whose data lives in open lake storage, synced both ways with the lakehouse via change-data-capture. Two of the most-scaled data platforms built CQRS-over-a-lakehouse. It is the conservative bet, not the risky one.

Three truths

  • The log is the truth of what happened — and because it is ordered, also the commit-ordering authority, the CDC backbone, and the branch-derivation source. Audit falls out for free; it is not the log’s only job. (Only Acts and Calls land here — Asserts do not.)
  • The analytical store is the truth of what is now — canonical, read-optimized, snapshotted, the winner of any conflict about current state.
  • The operational store is the truth of nothing — a low-latency cache, rebuildable at any time from analytical-plus-log-tail. That rebuildability is a feature: it is the DR story and the scaling release valve.

The three planes

PlaneRoleStackFoundry / Databricks analogue
Operationalread-after-write, low-latency OLTP, point serving for agent loopsPostgreSQL/PostGIS (+ hot cache)Foundry Object Storage V2 · Databricks Lakebase
Log / spinecommit ordering, audit, CDC backbone, branch replayNATS JetStream (+ object-store archive)Foundry Actions log + Funnel · Delta _delta_log
Analyticalcanonical current-state truth, aggregates, history, snapshotsIceberg on MinIO + ClickHouse + DuckDBFoundry datasets · Databricks lakehouse

The verb mapping is clean: Act writes through the spine; Assert writes to the time-series analytical substrate; Query reads with a router choosing the plane; Event is the log; Call is the outbound crossing (and writeback); Policy gates at the seam before commit; Entity/Trait is the one logical model materialized across planes.

The write path — the honest, scalable version

The single decision that determines whether this scales or corrupts is avoiding the dual-write: never let an Act write the operational store and the log as two independent operations. The fix is the transactional outbox:

  1. Act arrives → seam validates it (Policy, capability token — the acquire/invoke check).
  2. One operational transaction applies the state mutation and inserts the event into an outbox table, atomically. The write is durable and immediately readable — read-after-write is served straight from the operational plane.
  3. A relay ships the outbox row to JetStream. The log is now the ordered, audited truth of what happened.
  4. A materializer consumes the log and indexes it into Iceberg (canonical state, snapshotted) and ClickHouse (aggregates). Analytical catches up asynchronously and becomes final.

This is Foundry’s Funnel pattern: a materialization service fed by both pipeline truth and live edits, keeping the indexed store current. Reconciliation rule, stated once: analytical wins on state, log wins on history, operational is authoritative for nothing and rebuildable from the other two. Reads flow down (analytical → derived → operational serving); writes flow up (Act → operational + log → analytical). The log is the spine both flows hang from.

Asserts take a lighter path: high-volume declarations stream directly to the time-series substrate (ClickHouse), bypassing both the outbox and the decision log. Only the current hot value need be cached operationally for point reads.

The read path — routing is the honest HTAP

The OLTP-vs-OLAP tension resolves without a magic unified engine: keep two stores and put a router at the Query verb. Point reads / read-your-own-write / agent point-lookups → operational Postgres; aggregates / windows / cross-entity / historical / as-of → ClickHouse and Iceberg. Userland sees one Entity/Trait model; the seam hides which plane answered.

The split is mandatory, not an optimization, because of the load profile: the lakehouse unified storage and analytics but never unified serving, and agent loops are point-lookup traffic that scan-optimized engines handle terribly. The operational plane is not optional cache — it is what makes agent-scale reads viable.

Branching — where the model pays rent

A branch of reality = a copy-on-write clone of the analytical snapshot plus a branch-local log. Replay Acts into the branch (deterministic); never replay a Call (stub it or serve its recorded fact); take Asserts as-of from the time-series (they do not replay either). The operational store is not branched — it is a cache, and branches live in the analytical plane where cheap copy-on-write exists. This is a shipping primitive: Lakebase advertises instant copy-on-write branching for humans or agents; Iceberg branches and Delta shallow clones give the self-hosted equivalent. Trafficure’s “make this road one-way” becomes: clone current-state, replay the intervention Act, run the sim engine against the branch, diff against canon. The reversibility boundary makes the branch safe; the lakehouse’s cheap clone makes it fast.

Provenance → verb → storage class

ProvenanceVerbStorage classReplayed?
Owned decisionActdecision log + Iceberg stateyes
Declaration / mirroredAssertbitemporal time-series (ClickHouse)no
HypotheticalAct into a branchbranch snapshot + branch login-branch only
External effectCallrecorded fact in the logno

Provenance stops being a tag and becomes the selector of write verb and storage class.

Two scaling lessons to steal

  • Foundry V1→V2: the original object store (Phonograph) did indexing and querying in one system and was rebuilt to separate them for horizontal scale. Split your materializer (write/index path) from your Query router (read path) from day one.
  • Operational as a rebuildable index: treat the operational plane as a derived index you can always wipe and rebuild, never a store you fear losing. That property is what lets you shard, scale, or recover it without ceremony.

One place to diverge: portability

Foundry is a closed bundle; Lakebase is cloud-managed; neither runs air-gapped, which half the deployment modes require. Adopt the shape, self-assemble the components: Postgres/PostGIS + Iceberg-on-MinIO + ClickHouse + JetStream for on-prem/air-gapped; a managed lakehouse or Lakebase only in SaaS mode. Because the seam closes over the stores, store choice becomes a deployment-mode decision behind the Act/Assert/Query verbs — userland never knows which engine answered. The physical architecture is swappable precisely because the logical one is closed.


XI. Open decisions

These are the live forks — flagged, not resolved.

  • The Assert→Act promotion boundary. When a mirrored/asserted value becomes something a decision is made on and must be audited, it has to be promoted from declaration to decision. Where that boundary sits — and whether promotion is explicit or inferred — is unresolved.
  • The atomicity boundary. The outbox gives atomic state-plus-event within one aggregate; cross-aggregate consistency is a saga over the log, not a distributed transaction. Which invariants genuinely need atomicity versus eventual consistency is where most real design effort will go.
  • Freshness SLA per provenance. “Analytical as final truth” implies a freshness gradient — operational fresh, analytical canonical-but-behind. Each read path owes an explicit SLA, and different provenance classes may deserve different ones.
  • Snapshot retention horizon and bitemporal depth. How long snapshots are retained, and how deeply valid-time is modeled, together bound the honest range of as-of-the-world queries.
  • The trait-kinds closed set. Whether graph-edge alone carries telecom topology, road networks, and IT dependency graphs, or whether those want distinct kinds, is asserted-closed but not yet stress-tested against all four scenarios.

Appendix — reference

The seven primitives.

  • Realm — a bounded slice of reality; the bounded context that scopes a world.
  • Actor — anything that can request effects; human and agent, treated identically.
  • Action — the definition of a command type that mutates owned reality (kernel-visible; invoked via the Act verb).
  • Entity — an instance of reality that carries Traits.
  • Trait — a typed property of an Entity; carries a kernel-known kind, composed into a user-defined value type.
  • Event — an immutable fact that a decision happened; the output of replay and the trigger for Subscribe.
  • Policy — the governance rules evaluated at the seam; a primitive, mutated by Acts at runtime.

The six verbs. Triggers: Subscribe, Schedule. Effects: Query, Act, Assert, Call.

The trait kinds (closed set). numeric, geo, temporal, graph-edge, categorical.

The two temporal axes. System time (the snapshot axis — when committed) and valid time (bitemporal — when true in the world; carried on Assert records).

The load-bearing lines, in one place.

  • Shape, not meaning.
  • Govern effects, not code.
  • The SDK is the ABI — the only door.
  • Writing is free; being handed a capability is governed.
  • The system that defines the system is the system.
  • The outbound crossing is a Call; the inbound crossing is an Assert.
  • Decision vs declaration selects the verb.
  • Snapshots are the checkpoints; the log is the delta.
  • Can’t-express-it → won’t-type-check → rejected-at-request.
  • Structure lifts; evaluation never does.
  • The type system doesn’t check policy — it checks that you asked the policy the right question.
  • Coarse capabilities in the types, fine decisions at the seam.
  • Analytical wins on state, log wins on history, operational is authoritative for nothing.