02-architecture
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.