‹consolidation

consolidation

all docs

The consolidation — one substrate, per-platform release, compiled where it pays

A seed + status document. Written for the next agent (human or model). It is the settled map of the substrate's architecture across every track, written in one go so the whole shape is legible at once. Each track says exactly what is built, what is already shipping, and what is still a plan — and where the code is. Status: mixed, and honest about it. This session (2026-06) landed real, verified engine code on several tracks; others are confirmed already-built or remain captured plans. The split is spelled out per track below — nothing here is relabelled "done" that is not.

The one idea (unchanged from VISION)

Zero is a thin C++ tree-walking interpreter of a filesystem-tree DSL that *becomes what the moment demands*. The whole leverage is: the engine is the only universal floor; everything else is a thing in the store. This document is about making that floor (a) slim to compile and load, (b) the same on every device and CPU, (c) fast to release, and (d) able to reuse compiled work without giving up the "behavior ships from the store, not a release" law.

Five truths the engine already embodies, that every track here rides on:

• A class is a folder; .action = methods, .data = state, parent = =

inheritance. Behavior lives in the store, refreshed by hydration — not a release.

• main() is the irreducible bootstrap. It sets the request as member data,

loads ONE action (boot), and runs it; boot + the store do everything else (kernel/main.cpp). The engine *discovers what it is* from the object's class.

• Two engines, one schema: the tree-walker (run→execStatement) and compiled

C++ views (compiled_rt.cpp, tools/transpile.py) honour the same contract.

• Static registry + POSIX dlsym/dlopen fallback (registry.cpp,

loadplugins.cpp): primitives are a name→pointer table; user-space growth is a dropped-in shared library.

• Copy-on-hydrate store cache (remote_store.h, loadchain.cpp): a class the

machine lacks is pulled once and is local from then on.

The track map

#

Track

Status

Where

A

Per-platform submodules + full release matrix (incl. ARM / Raspberry Pi)

built this session (cross-build + CI job written to the multiarch pattern, not locally compiled — see caveat)

Makefile, make/*.mk, .github/workflows/release.yml, install.sh

B

Compiled-view libraries: release, download, load on demand, reuse

built + verified this session

tools/transpile.py, kernel/compiled_rt.cpp, functions/core/loadviews.cpp, functions/core/loadchain.cpp, kernel/run.cpp, make views

C

Main = hello-world; the engine executes any kind of engine

already realized; verified this session

kernel/main.cpp ($ZERO9_CORE)

D

Versioned action language (schema + execution, evolving on its own)

Phases 1–2 built + verified; run-any-version-by-hash + scene-native lineage built this session; 3–6 planned

kernel/interp.h, functions/core/loadchain.cpp, functions/core/verify.cpp, thing/runversion, thing/versions, [versioned-runtime.md](./versioned-runtime.md)

E

Fully autonomous LLM loop (runs as long as it takes, evolves)

dependency shipped (verify); per-thing evolution-loop page + demand↔supply matchmaking built this session (on the chat fabric); the oracle author step needs a provider

builds on D + oracle ([VISION.md](../VISION.md))

F

Viewers — separation of concerns, everything is actions

built (native rendering) + proven zero-engine-change (lenses)

[native-rendering.md](./native-rendering.md), [lenses.md](./lenses.md)

G

Real-time, filtered, no-PII telemetry to pinpoint problems

deliberately deferred ("once this is perfect") — designed below

—

H

Fast, simple, tag-triggered release; client auto-update peace of mind

built (already) + extended this session

release.yml, mobile.yml, install.sh, make release-check, desktop selfUpdate()

─────────────────────────────────────────────────────────────────────────────

A — Per-platform submodules + the full release matrix

The build is now modular by platform. The root Makefile holds the cross-platform engine core (sources, $(ENGINE), libzero, views, install, clean); each native shell / platform binary is its own dedicated module the root includes:

• make/windows.mk — the Windows engine (MinGW-w64 cross → zero.exe).

• make/desktop.mk — the desktop app shells (macOS AppKit .app/.dmg, Linux

GTK/WebKitGTK tarball), sharing one app-identity block.

• The mobile shells are their own subproject (mobile/, Gradle/Xcode).

The split was verified to change no command for any target (make -n diff, before vs after).

Cross-compile knob. make CROSS=<prefix> PLATFORM=<plat> sets the toolchain prefix; the Linux link flags already apply because the build host is Linux.

Canonical platform keys — one dist/zero-<platform> per CPU family, applied *consistently* in three places so install, zero update, and the release matrix agree:

• enginePlatform() (kernel/main.cpp) — runtime, what the binary self-reports.

• install.sh — what the installer fetches.

• Makefile (ZARCH) — what a local build stamps.

Mapping: amd64→x86_64, arm64/aarch64→aarch64, arm/armhf/armv6l/armv7l/armv8l →armv7. So a 32-bit Pi (uname armv7l) and a 64-bit Pi (aarch64) each fetch a key that actually exists.

The matrix (tag v* → release.yml):

Platform key

Built how

Covers

linux-x86_64

native (ubuntu)

servers, desktops

linux-aarch64

native (ubuntu-24.04-arm)

64-bit Raspberry Pi, ARM servers

linux-armv7

new — armhf cross on the arm64 runner

32-bit Raspberry Pi (Pi Zero/1/2, 32-bit OS)

darwin-x86_64 + darwin-arm64

one UNIVERSAL macOS binary

Intel + Apple Silicon

windows-x86_64

MinGW cross

Windows

CAVEAT (honesty): the linux-armv7 cross-compile and its CI job are written to the standard armhf-multiarch pattern but were not locally compiled — the dev box has no ARM cross toolchain. The Makefile expansion (make -n) and the canonicalization logic are verified; the first tag build is what confirms the armhf link. (aarch64 / x86_64 / darwin / windows paths are unchanged.)

B — Compiled-view libraries: release, download, load, reuse

This is the answer to *"release compiled libraries, let zero download them as they update and load them when needed, reusing what didn't change."* The realization is dead in the grain: a compiled view is just another per-platform file in a class folder, distributed by the *same* hydration as .action/.data.

• Emit. make views CLASS=<c> runs tools/transpile.py to lower the class's

actions to native C++ and compiles _views-<platform>.{so,dylib} into the class folder. The lib exports zero_register_views(const char* classdir), which registers each action's compiled fn at <classdir>/<name>.action — exactly the key run() looks up (so the class-chain override still decides which body runs).

• Distribute. Because the lib is a file in the class dir, store.push +

fetch_dir carry it to every node like any other class file. Compiled once; reused from cache wherever it lands; a class that didn't change is never recompiled.

• Load on demand. loadchain, after loading a class's actions, loads

<classdir>/_views-<platform>.{so,dylib} if present (load_view_lib, kernel/compiled_rt.cpp) — dlopen(RTLD_NOW|RTLD_LOCAL) resolving the engine's verbs from the -rdynamic main, never dlclosed. functions/loadviews exposes the same step to the DSL.

• Prefer at runtime. run()'s compiled lookup is now live (not a one-shot

have_compiled() probe that fired before any class loaded), so a library loaded during loadchain is honoured. compiled_lookup short-circuits on an empty table, so the interpreter path still pays ≈one map check. ZERO_NO_COMPILED=1 forces the interpreter (the A/B switch).

• Slim. No views are compiled into the engine binary; the binary stays the

tiny floor and pulls compiled speed per-class, per-platform, on demand.

Verified end to end on darwin: a faithful lib runs correctly; a deliberately *divergent* compiled lib emits a different string than its source, proving the compiled view actually executed; ZERO_NO_COMPILED=1 falls back to the interpreter.

Scope: POSIX desktop/server (macOS, Linux). The static-dispatch builds (mobile, Windows) keep the in-binary / interpreted path — exactly where the engine already forbids dlsym/dlopen for notarization. macOS emits an advisory -undefined dynamic_lookup linker warning; it is benign (the load works), and is the seam to harden (an export-list stub) if a future linker drops the flag.

C — Main is hello-world; the engine becomes any engine

Already true, now verified: main() loads only boot and runs it; the engine's identity is the object's class, resolved from the store. The same binary boots a completely different engine when pointed at a different core (ZERO9_CORE=/path zero x.y ran an alternate boot that emitted its own output and the request object). No recompile, no special case — "execute any kind of engine by itself, at any point" is a property of the bootstrap + the store, not new machinery.

We deliberately did not convert the per-request thread_local engine state into an instance object. That state is per-request on purpose — the HTTP driver runs many requests concurrently on pooled worker threads (dispatch.cpp, interp.h). "The engine is a class" is realized as *the store class the bootstrap instantiates*, which is the in-grain meaning, not a C++ reentrancy rewrite that would fight the concurrency model for no behavioral gain.

D — Versioned action language (see versioned-runtime.md)

Phases 1–2 built + verified this session. A class may carry a .schema integer (mirror of .remote); loadchain gates it against the engine's [kSchemaMin, kSchemaMax] window: refuse-above-max ("update the engine"), migrate-below-min (via a registry that is the designed no-op, so a class with no transform is correctly refused), else run; unstamped classes run as the current grammar. The window is stamped to the DSL as __schema_min/__schema_max. functions/verify <action> <expected> [args] is the deterministic outcome check — the pillar that earns the right to let the language drift toward LLM-optimal. Phases 3–6 (real migrations, engine-version-per-schema, data versioning, the authorship loop) remain planned.

Run any version at any time (built this session). Every action write is already content-addressed (_ev_version: an immutable .versions/<sha>.snap + a per-path log plus {reason, parent} meta). thing.runversion (?sha=) now *executes* a chosen version by hash — functions/eval on its snapshot, crash-proof — WITHOUT disturbing "current", so any version of any action is runnable at will, the versions kept internal to the system. thing.versions (?action=) renders the lineage as a native SCENE (every version newest- first, its reason, what it changed FROM, a "run this version" button) — so we always know how an action went "from this to that". A deterministic outcome check through the verify pillar (thing.selftest → verify-evolve.sh) proves a saved version reproduces its own era's output, the live version untouched. The self-evolving actions (evolve, live/ work) ship baseline-versioned.

E — The autonomous loop

The loop *author → version → run → verify → migrate* is store/DSL orchestration over engine primitives, not engine code — keeping the engine thin. Its load-bearing dependency, functions/verify, now ships (D). The remaining pieces: an oracle-driven author step (provider-abstracted per VISION.md), the migration transforms (D, Phase 3), and a _evolve-style driver action that loops until verify accepts — all DSL, all in the store, no release. This is the point the language is *allowed* to go human-illegible, because verify, not a human, guards behavior.

The loop has a face now (built this session). thing.loop is the per-thing EVOLUTION-LOOP page on the SAME native chat fabric as /<thing>/chat: each autonomous cycle (the thing's journal) is a message card, with controls to run the self-evolution loop (evolve), the autonomous loop (live), or open the VERSION lineage of either — so any version of either is runnable at any time. thing.match is demand↔supply MATCHMAKING through conversation: a thing's demands (a universal drive on thing) is paired with the best local supplier and a conversation is opened (a notice + a seeded thread, the chat fabric); the pick is crash-proof (try_call oracle), so it degrades to "no match" without a provider instead of erroring. The chat launcher lists every thing's conversation AND its loop. All native scenes (HTML in a browser by the same definition), all store content, no engine change. The remaining gap is unchanged: an oracle-driven author step + a self- driving cycle runner, which need a provider — the substrate they run on is now built.

F — Viewers: separation of concerns, everything is actions

Native rendering is built and proven (native-rendering.md): an action declares *what* is on screen as a scene; each viewer (HTML/webview, AppKit, GTK, Win32, Android, UIKit) draws *how*. Lenses (lenses.md) — a scoped projection of a thing to a viewer — need zero engine change (access is already live, per-request, fail-closed: access.cpp, resolve.cpp); they are pure store content. Both keep the engine thin and push meaning into the store, which is the whole spine.

G — Telemetry (deliberately deferred)

The user gated this "once this is perfect," and we honour that ordering. Designed shape, for when it is time: the engine already has print (console/logs) distinct from emit (response). A telemetry track adds an opt-in, filtered, no-PII execution-event sink (action path + error class + engine/schema/platform version, never request bodies, identities, or store data) funnelled to a collector so real-world failures pinpoint themselves. It rides the same store/release machinery; it is intentionally not built until the substrate above is settled.

H — Fast, simple release; client peace of mind

• Tag-driven, single source of truth. git tag vX.Y.Z && git push → release.yml

builds every platform (now including linux-armv7) and publishes to the store's dist/. Versions derive from git describe — never hand-edited.

• make release-check is the local pre-tag gate: build the engine + embed-test,

run verify-scene.sh (CLI + HTTP scene negotiation + form round-trip), and print the version a tag would publish. Green here → tag with confidence.

• Client auto-update is peace of mind by design: desktop zero update

(selfUpdate()) swaps the binary in place; the DSL core + classes refresh from the store silently (no release); install.sh + every shell fetch the engine on first launch and keep it fresh. Mobile build-config was fixed so versionName matches the published marker and versionCode is monotonic (build.gradle, mobile.yml) — the prerequisites for the (parked) APK self-updater and Play.

Release model — component tags + runtime auto-assembly

Three components, three tag namespaces, one repo. The engine and the native shells have independent release cadences but share one contract (the manifest + the store). Rather than split into multiple repos (which would fragment that contract and need cross-repo tokens in CI), each component is a tag namespace with its own workflow — the "other way to clearly distinguish each platform" that keeps the shared contract in one place:

Tag

Component

Workflow

Targets

When to tag

v* (e.g. v0.0.32)

engine / CLI

release.yml

linux x86_64 / aarch64 / armv7, darwin universal, windows + mesh + docker

engine source changed

desktop-v* (e.g. desktop-v0.0.2)

desktop apps

desktop.yml

macOS .dmg (universal), Linux .tar.gz (x86_64 + aarch64), Windows installer

a desktop SHELL changed

app-v* (e.g. app-v0.0.25)

mobile apps

mobile.yml

Android (AAB+APK), iOS

a mobile shell changed

"No update where none is needed." A component rebuilds/republishes ONLY when you tag it. An engine release no longer rebuilds the desktop apps (the desktop jobs moved out of release.yml), and a desktop release never touches the engine binary or dist/VERSION. Each app's version is its OWN (desktop/<app>.manifest, build.gradle), not the engine's — the tag is just the trigger; the published artifact carries the manifest version. So tagging is surgical: change a shell → desktop-v*; change the engine → v*; change nothing → release nothing.

Runtime auto-assembly (apps stay slim). This is how an app "assembles itself at runtime" instead of shipping fat. The engine binary is the tiny floor; behavior (store classes) and *speed* (compiled-view libs) arrive on demand:

1. A class the machine lacks is hydrated (fetch_dir) — its .action/.data

and, if the store published one, the compiled-view lib for THIS platform.

2. fetch_dir pulls only this platform's _views-<platform>.{so,dylib}

(foreign_view_lib skips every other platform's binary) — so a node never downloads a Mac dylib onto a Pi. Slim by construction.

3. loadchain dlopens the lib; run() prefers the compiled view live. The

class now runs as native code, assembled at runtime from a part compiled once and reused from cache everywhere it lands.

4. The engine itself auto-updates in place (zero update / shell first-launch

fetch); store content refreshes silently. So an app gets new behavior (store), new speed (view libs), and new engine (binary) — each on its own channel, none forcing a full reinstall.

Mobile caveat: the dynamic-loading half is POSIX desktop/server only. The mobile/Windows-static builds keep the in-binary/interpreted path (no dlopen — notarization / no -rdynamic), and the offline-seeded mobile build does not fetch at all. Mobile still auto-updates its engine + hydrates store content; it just does not dynamic-load compiled libs. The foreign_view_lib filter is therefore a no-op there (no fetch, nothing to skip).

To release everything from a clean state: git tag v<n> && git tag desktop-v<n> && git tag app-v<n> then push the tags — each fires its workflow independently and in parallel; a failure in one never blocks the others.

What remains a plan (do not mistake for done)

• A: the linux-armv7 binary must be confirmed by a real CI tag build (not

locally compilable here). Mesh + desktop for 32-bit ARM are not in scope yet.

• D/E: Phases 3–6 of the versioned runtime + the autonomous loop driver.

• F: the lens *convention* + a proof example (pure store; lenses.md Phase 1–5).

• G: the whole telemetry track (deferred by request).

Where to go next

• [VISION.md](../VISION.md) — why the substrate exists.

• [versioned-runtime.md](./versioned-runtime.md) — D + E in depth (Phases 1–2 built).

• [native-rendering.md](./native-rendering.md), [lenses.md](./lenses.md) — F.

• [app-self-update.md](./app-self-update.md) — H, the parked native APK updater.

• Makefile + make/*.mk — A. make views + tools/transpile.py — B.