Cross-process & hot-load
Coming from Rust / C++? This is typed, versioned state shipped between processes — but without a separate
.protoand a codegen step. Aperspectiveis a serializable parameter bundle; producer and consumer share its schema because they compile from the same source. No protobuf regen, no schema drift, no handshake.
A perspective is a shippable view
Most of a locus’s state is private to its region. A
perspective is the exception: a typed bundle a locus can
publish across a process boundary, with a compile-time guarantee
that the other side agrees on its shape.
perspective Kernel {
params {
scale_row: [Decimal; 8];
sigma_factor: Decimal;
regime_id: Int;
}
stable_when {
return self.num_validated >= 3;
}
serialize_as KernelV1;
}
paramsis the payload — the schema is this type.stable_whenis a predicate the runtime checks before the perspective is allowed to ship — “is this data ready?” lives in the data’s own declaration, not in a publisher flag.serialize_asnames the wire format stably, so you can rename the identifier without breaking serialization.
A perspective is not a locus — no lifecycle, no bus block, no
methods beyond stable_when. It’s a validated, serializable
bundle the substrate knows how to ship.
The fitter / applier pattern
The canonical use: one process computes parameters slowly and
carefully; another applies them at high frequency. Both compile
from the same Kernel declaration, so the type is the protocol.
// fitter — publishes refined Kernels
locus Fitter {
bus { publish KernelUpdates; }
run() {
let mut k = compute_kernel(observations);
while !k.is_stable() { k = refine_kernel(k, more()); }
KernelUpdates <- k;
}
}
// applier — swaps in the latest, atomically
locus Applier {
params { current: Kernel = default_kernel(); }
bus { subscribe KernelUpdates as on_update; }
fn on_update(k: Kernel) { self.current = k; } // atomic swap; no torn read
}
The runtime guarantees the consumer-side swap is atomic — readers see the old perspective or the new one, never a half-written mix. This is also the hot-load mechanism: reconfigure a long-running service by publishing a new perspective, with full type-checking against the locally-compiled schema, no restart.
Capability profiles and substrates
The same locus + bus + perspective triple runs on more than one substrate. The native C-runtime is one; the browser runtime (hale-js) is another. A build target declares the capabilities a substrate offers:
target browser_js {
arenas.epoch_view,
time.monotonic, time.wallclock,
random.csprng,
gfx.canvas2d,
}
A program that reaches for a capability its target doesn’t offer
fails at the translation boundary with a clear CAP-MISSING
diagnostic — at build, not at runtime. Substrate differences are
named and checked, not papered over. The locus you wrote doesn’t
change between substrates; the capability profile and the
transport binding do.
This is the long arc of the whole guide paying off: the same shape you met as a small program in the basics runs across processes, machines, and substrates because nothing in the shape depended on where it ran.
Next, the most specialized tier feature — Modes.