‹02-architecture

02-architecture

all docs

02 — Architecture

How Zero actually works, grounded in the code. File:line citations throughout. Status labels (built, partial, planned) match [../roadmap/consolidation.md](../roadmap/consolidation.md). When in doubt, the roadmap and the code win over this doc.

The shape in one diagram

zero <object>.<action>

│

┌─────▼─────┐ parse request, set member data,

│ main() │ load ONE seed action, run it

└─────┬─────┘ (kernel/main.cpp — the irreducible floor)

│

┌─────▼─────┐ self-hosted in the DSL: locate object,

│ boot │ load object data + views, walk the class

│ (.action) │ chain, gate access, dispatch the action

└─────┬─────┘

┌───────────┼───────────────┐

▼ ▼ ▼

┌─────────┐ ┌──────────┐ ┌──────────────┐

│ stores │ │ oracle │ │ interpreter │ tree-walker, or compiled

│(folders)│ │ (LLM) │ │ + compiled │ C++ view if the store

│hydrated │ │ provider │ │ views │ shipped one for this class

└─────────┘ └──────────┘ └──────────────┘

Everything below expands one box.

1. The kernel — the irreducible floor

main() does the least possible: parse zero <object>.<action>, populate the request as member data (__object, __action, __home, __dataroot, __query, __version, __schema_min, __schema_max), locate the core directory, load the single boot action, and run it. That's the whole native bootstrap. (kernel/main.cpp:256–438)

The engine is now (mostly) self-hosted: the locating, data loading, and dispatch that main() used to do in C++ now live in the boot *action*. main()'s only job is the irreducible floor. (kernel/main.cpp:176–195)

• Size: ~23 C++ translation units in kernel/, ~2,400 LOC; ~80 native

primitives auto-scanned from functions//*.cpp into a name→pointer registry.

• Identity is resolved before any action runs — __principal, __role,

__machine_owner from environment, the home object, or the OS user. (kernel/identity.cpp:72–107)

• "Main is hello-world" is literally true and verified: pointing the same binary

at a different core (ZERO9_CORE=/path) boots a completely different engine, no recompile. (../roadmap/consolidation.md, track C)

2. The DSL — action-files

An action-file is a .action text file. Filename (sans extension) = the action name; the body is statements, one per line, # for comments. Each file is one function. The grammar (kernel/exec_statement.cpp:22–40):

return <token> # return a value

if <cond> <statement> # conditional

ifnot <cond> <statement> # negated conditional

foreach <item> in <list> <statement> # iterate a newline-separated list

functions/<name> [args...] [-> key] # call a native C++ primitive

<action> [args...] [-> key] # call another action

• Arguments resolve left to right; a "quoted literal" is literal text, an unquoted

token is a data-key lookup. -> key captures the result into member data. (kernel/tokenize.cpp, kernel/resolve.cpp)

• Truthiness: "", "0", "false" are false; everything else true.

Data files are key = value pairs; parent = <class> declares single inheritance; unset keys are inherited, object values win.

Parse-once cache: each .action is tokenized once per process into an immutable shared Action; no per-request re-parse, guarded for concurrent requests. (kernel/load_actions.cpp)

3. Folder = class = composable block

A class is a folder. The class chain is walked child→parent by loadchain:

• Actions: first-definition-wins, so a child (or the object itself) overrides a

parent. (functions/core/loadchain.cpp)

• Data: defaults only — object > child > parent.

• Search path: the served home's working set, then named sibling stores (sorted),

then the default store oneaurica.com (hydrated on miss). (functions/core/loadchain.cpp:55–93)

A folder typically holds: defaults.data, view actions (index.action, _card.action, _scene_*.action), optional .remote (its store base), optional .schema (its DSL version), optional _views-<platform>.{so,dylib} (compiled speed). Each folder is a reusable block; composition is inheritance + explicit action calls + the oracle.

4. Stores — the world, delivered on demand

A store is a directory tree ~/.zero/stores/<storename>/<class>/, a local cache backed by a remote HTTP base (declared in a .remote file). (kernel/remote_store.h:75–85)

• Copy-on-hydrate: fetch_dir() pulls a class's _manifest, downloads each

listed file atomically (.hydrating temp → rename), lock-guarded, and from then on every read is local disk. It pulls only this platform's view lib, never a foreign one. (kernel/remote_store.h:309–360)

• Two modes, one variable:

• Personal (default): only *classes* hydrate from the compiled-in default

store; *objects* are never pulled (your home is not a mirror).

• Node ($ZERO_STORE_BASE set): an explicit node serving a published home —

both classes and objects hydrate from that base. (kernel/remote_store.h:9–18)

• One cache lives outside any single home, so it serves every home, folder, and user

on the machine.

This is how "Zero constructs itself from world definitions": boot points at the object, hydrates what's missing, loads the class chain, and dispatches — keeping only what the request needed.

5. Two engines, one schema — interpreter + compiled views

The same DSL contract is honored by two execution paths:

• Tree-walker: run() → execStatement() walks the cached statements.

(kernel/run.cpp, kernel/exec_statement.cpp)

• Compiled views: tools/transpile.py lowers a class's actions to C++ (one

function per action), compiled into _views-<platform>.{so,dylib} inside the class folder. The lib exports zero_register_views(classdir); run() consults the registry live, so a view loaded during loadchain is honored. A class that didn't change is never recompiled; nothing is compiled *into* the engine binary. (kernel/compiled_rt.cpp, functions/core/loadviews.cpp, ../roadmap/consolidation.md B)

• ZERO_NO_COMPILED=1 forces the interpreter (the A/B switch); zero __bench

<action> <count> reports ns/call and which engine ran. (kernel/main.cpp:285–307)

Scope: dynamic-loading of compiled views is POSIX desktop/server only. Mobile and Windows (static-dispatch, no dlopen for notarization) keep the interpreted path.

6. Versioned runtime — the DSL and engine evolve together

Four pillars, kept separate (../roadmap/versioned-runtime.md):

1. Schema version — what a valid action looks like. Engine declares

kSchemaMin/kSchemaMax (kernel/interp.h:109–110); a class carries an optional .schema integer.

2. Engine version — how a schema is executed. Stamped as __version

(ZERO9_VERSION); two engines already exist (tree-walker + compiled views).

3. Migration — versioned vN→vN+1 transforms, lazy on read like hydration.

*(Phase-1 registry is a designed no-op: a class with no transform is correctly refused.)*

4. Verification — functions/verify <action> <expected> [args] returns "1" on

match, "" on mismatch or error. The deterministic outcome check. (functions/core/verify.cpp)

Load-time gate (functions/core/loadchain.cpp:96–132): schema > max → "update the engine"; schema < min → migrate-or-refuse; in-window or unstamped → run. No flag day.

Run any version at any time (built): every action write is content-addressed to .versions/<sha>.snap with {reason, parent} meta; thing.runversion?sha= executes a chosen version by hash, crash-proof, without disturbing "current"; thing.versions renders the lineage as a native scene. (../roadmap/consolidation.md D)

Status: Phases 1–2 built + verified; run-any-version + scene-native lineage built; Phases 3–6 (real migrations, engine-version-per-schema, data versioning, the full authorship loop) planned. See [03-self-evolution.md](03-self-evolution.md).

7. Intelligence — oracle, a provider not a dependency

The LLM is the oracle primitive, provider-abstracted and resolved per device profile to: local (llama.cpp + GGUF data), bring-your-own (the user's own ChatGPT/Claude key), or peer (another node's brain over the mesh). Every capable device can fall back to fully local, so the product never *requires* someone else's model. Calls are crash-proof: try_call oracle $prompt -> $r sets __call_ok / __call_error instead of dying. (kernel/plugin_try_call.cpp:23–49, ../VISION.md)

This is the broker behind folder cross-connection — e.g. thing.match reads a thing's demands, asks the oracle to pick the best local supplier, and opens a conversation; without a provider it degrades to "no match," it does not error.

8. Distributed access — the mesh

Remote access over iroh, encrypted endpoint-to-endpoint, on a blind-relay model

(../remote-access.md):

• Browser loads a thin shell + a wasm iroh client.

• Blind relay forwards ciphertext only — never plaintext.

• The honest limit: the key the tunnel terminates against is pinned by the name directory,

not authenticated — nothing verifies out of band that the EndpointId returned is the home

node's. So this is confidentiality against the relay, not end-to-end encryption to a verified

peer, and it is not written as the latter anywhere (../remote-access.md §5, "the honest floor,

part 2"; ZMP/1 §12.7's zmp1.reach bundle is the frozen answer and is unbuilt).

• Home node dials the relay and holds its door open; a mesh organ bridges

iroh streams to the loopback web door.

• Identity is node-authoritative; sessions are *proven not asserted*:

zcap = principal:expiry.HMAC(node_secret, …). It travels two ways and no others: explicitly as ?cap=<token> on a capability link, and — once such a link has HMAC-verified on this node — as an HttpOnly; SameSite=Strict session cookie (persisted by _core/_apply_cap, read back at the pair boundary by _core/_cap_from_cookie, cleared by trust.logout). There is no Service Worker in this system. Local doors (CLI/desktop/mobile/tv) are owner-trusted loopback; the web door is anon until a passkey proves a capability.

9. Identity & access — ZeroKey and the three gates

• ZeroKey is a recoverable P-256 wallet: the scalar *is* your Zero-ID, shown as a

recovery key, importable on any device → the same ID everywhere, never two. Native account screens (create/import/show key + sign in) replace any webview wallet. (git history: iOS/Android "ONE recoverable Zero ID")

• Access is live, per-request, fail-closed. Every member read runs

accessAllowed(token, 'r'); grant_<name> rules are read at check time; the owner always wins. (kernel/access.cpp, kernel/resolve.cpp)

• The three independent gates on any device action (../VISION.md, Law 2):

capability installed × OS permission granted × zcap grant authorized. None of them "because the app felt like it."

10. Capability profiles — how one install becomes many devices

A profile (extending manifest.data) declares what a device *is* and *can do*: door (mobile/desktop/tv/web), start page, DSL capabilities (store-driven, instant), native capabilities (compiled into the shell, slow to add), and oracle routing. A weak handheld and a flagship phone run the same app — the profile is the only difference. (../mobile/ARCHITECTURE.md:85–147) *Status: designed; the profile-driven install path is the implementation frontier.*

What's built vs planned (pointer)

This doc describes the engine as it stands plus its designed extensions. For the authoritative line-by-line split, see [08-status.md](08-status.md) and [../roadmap/consolidation.md](../roadmap/consolidation.md). The short version: the floor, the DSL, stores, two-engine execution, the schema gate, verify, run-any-version, the mesh, identity, and native rendering are built; capability- profile install, the full self-evolution loop, lenses convention, and 32-bit ARM CI confirmation are the frontier.