Legion

Legion Architecture — the 4-layer API model

Status: DRAFT (Foundation 09, 2026-06-18). This documents the L1–L4 layering and, in particular, makes the L3 (provider) and L4 (consumer) surfaces visible for the first time. The L3/L4 public APIs described here are drafts, not release candidates — expect discussion and changes before any surface is frozen. Feedback welcome. The export boundary is CI-locked; the specific symbol set is pre-release (see abi.md).

Read this first

Legion is a distributed capability mesh for individual data sovereignty: a cohort of nodes (your phone, your machines) that discover and invoke each other's capabilities — speech-to-text, an LLM, storage, audio — without routing your data through a big-cloud dependency.

Its API is a 4-layer stack. As a developer you almost always work at exactly one of the top two layers:

  • Building an app? You work at L4 (consume capabilities).
  • Providing a capability? You write a module at L3.

L1 and L2 are the machinery underneath — they are not application-facing and you should never need to call into them directly.

flowchart TB
    L4["<b>L4 — Consumer / application</b><br/>legion.h · l4_node.h · legion_ffi_*.h<br/><i>apps + bindings — the dynamic ABI (~131 legion_* exports)</i>"]
    L3["<b>L3 — Capability provider</b><br/>sdk.h · legion_identity.h<br/><i>module authors — in-process header contract (not exported)</i>"]
    L2["<b>L2 — Consensus · membership · identity</b><br/>consensus VM + BFT · cert-gated membership · signed beacon<br/><i>internal</i>"]
    L1["<b>L1 — Transport / mesh</b><br/>LNMP · discovery · bridge tunnels · E2E encryption<br/><i>internal</i>"]
    L4 --- L3 --- L2 --- L1

Each layer is built on the one below; an app at the top never reaches past L3.

LayerNameResponsibilityWho uses itExposed as
L4Consumer / appDiscover + consume capabilities; node lifecycle; language bindingsApp developers (native desktop · embedded/MCU); language bindingsDynamic ABI — exported legion_* symbols
L3Capability providerDeclare + serve capabilities (declaring is advertising); identity/cert primitivesModule authorsIn-process header contractsdk.h, legion_identity.h (hidden from the .so)
L2Consensus / membership / identityCohort agreement (consensus VM + BFT), cert-gated membership, capability advertisementInternal— (not app-facing)
L1Transport / meshPeer discovery, LNMP framing, bridge tunnels (WAN), encryptionInternal— (not app-facing)

The layers in detail

L1 — Transport / mesh (foundation req 01)

The mesh that moves bytes between nodes: LAN multicast discovery, the LNMP framing/wire format, WAN bridge tunnels (for class-B devices like phones behind carrier NAT), and end-to-end encryption. Everything above L1 is oblivious to how a peer is reached. Internal headers: legion_wire.h, legion_lnmp.h.

On the LAN, peers discover and reach each other directly (LNMP multicast). A class-B device — e.g. a phone on cellular, behind carrier NAT — can't be reached that way, so it opens an end-to-end-encrypted bridge tunnel to a WAN relay that forwards opaque frames to the cohort. The relay is untrusted: it routes bytes it cannot read, and it takes no part in consensus.

flowchart LR
    subgraph lan["Home LAN — direct peers"]
        D["Desktop"]
        S["Server"]
        K["Speaker"]
        D <-->|"LNMP multicast"| S
        D <--> K
        S <--> K
    end
    P["Phone · 4G<br/><i>class-B — behind carrier NAT,<br/>no multicast</i>"]
    R{{"WAN relay<br/><i>untrusted · consensus-blind<br/>forwards opaque frames</i>"}}
    P <-->|"bridge tunnel · E2E encrypted"| R
    R <-->|"delivers to the cohort"| S

L2 — Consensus, membership & identity (foundation req 02, 03, 07)

The agreement-and-trust layer. A cert-gated membership model (master → auth-node → service-node certificate chains, req 07) decides who is in a cohort; a consensus VM + BFT engine (req 02/03) give the cohort a convergent, ordered view of shared state — including which capabilities each member advertises. Internal header: legion_vm.h. Apps and modules never call this directly; they see only its effects (a peer's capabilities appear; a write converges).

Membership is cert-gated: a master signs an auth-node, which admits members by signing service-node certificates. A node is in the cohort only if it presents a certificate that chains to the cohort master.

flowchart TB
    M["Master<br/><i>cohort root of trust</i>"]
    AN["Auth-node<br/><i>admits members</i>"]
    SN["Service-node<br/><i>a device · capability provider</i>"]
    M -->|signs cert| AN
    AN -->|signs cert| SN
    M -.->|may sign directly| SN

Identity is single-seed at the root: a member's master identity derives from one BIP-39 seed through a SLIP-0010 HD tree (did:legion:user:…) — the whole fleet's root of trust. Each node carries its own Ed25519 keypair and did:legion:node:… DID (not seed-derived); the master signs it a service certificate offline to admit it, so a node proves cohort membership without contacting any online authority. One mnemonic backs up the root that authorises the entire fleet.

flowchart TB
    SEED["<b>One BIP-39 seed</b><br/><i>a single mnemonic</i>"]
    SEED --> HD["SLIP-0010 HD derivation"]
    HD --> MK["<b>Master</b> identity<br/><i>did:legion:user:… — cohort root of trust</i>"]
    P["Phone<br/><i>own Ed25519 key · did:legion:node:…</i>"]
    D["Desktop<br/><i>own Ed25519 key · did:legion:node:…</i>"]
    K["Speaker<br/><i>own Ed25519 key · did:legion:node:…</i>"]
    MK -->|signs service cert| P
    MK -->|signs service cert| D
    MK -->|signs service cert| K

L3 — Capability provider surface (foundation req 04, 05, 08)

What a module author writes against. A module declares the capabilities it provides (provided_caps), implements their method handlers, and — once its node is certified — those capabilities are automatically advertised on consensus + the signed beacon. Declaring a capability is advertising it; there is no separate "publish" step, and apps never advertise — advertising is purely a provider (L3) concern, driven by a certified module's provided_caps. A capability is a (name, semantic) pair (req 08), where the semantic is one of a closed set (replicate, any_one, specific, follow_focus) — e.g. ai.stt.transcribe declared any_one.

L3 lives in sdk.h (module register/dispatch, the L3 KV wrapper, caps & membership queries) and legion_identity.h (cert/identity primitives). These are an in-process header contract, not part of the dynamic ABI — the host runtime/tool links the static liblegion.a with --whole-archive --export-dynamic, so the L3 symbols live in the host executable's dynamic symbol table. A built-in module resolves them directly; a dynamically-loaded (dlopen) module resolves them against that host executable at load time (its init receives a legion_plugin_manager_t * handle to register against). They stay hidden in liblegion.so, which is the L4 surface. (See abi.md.)

L4 — Consumer / application surface (foundation req 06)

What an app developer (or a language binding) works against. An app:

  • manages a node's lifecycle (legion_node_create_from_file, _start, _stop),
  • discovers available capabilities and consumes them via callbacks,
  • and never touches consensus, BFT, or LNMP.

L4 is the dynamic ABI: the ~131 exported legion_* symbols across legion.h (version/logging/config), l4_node.h (node lifecycle), and the legion_ffi_*.h family (audio, bridge, connection, display, focus, identity, js, node, objectstore — plus the legion_ffi.h binding helpers). This is the native C ABI any consumer links against — native desktop applications, embedded / microcontroller targets, and language bindings alike — and it is the sovereignty boundary. (The Android NDK voice-loop app was the first proof-of-concept exercising L1–L4 end to end; Android is one consumer, not a constraint.) Full inventory + stability status in abi.md.

Provider & consumer — not client & server

L3 and L4 are provider and consumer roles, not a client/server split. A single process commonly wears both hats: the on-device voice-loop app provides microphone/playback capabilities (L3) and consumes remote speech-to-text, LLM, and text-to-speech capabilities (L4) — all in one process, no separate service. The mesh makes "local capability" and "a cohort peer's capability" look the same to the consumer.

How a capability flows (the layers in motion)

Provider side (a module on node B):

  1. The module declares provided_caps and implements handlers — L3.
  2. Node B's certificate authorises those caps — L2 (identity/membership).
  3. The caps are advertised on consensus + the signed beacon — L2.

Consumer side (an app on node A):

  1. The app discovers B's capability (via an L4 callback) — L4.
  2. It invokes the capability; the request is routed over the mesh to B — L1.
  3. B's L3 handler runs; the result returns to A's callback — L4.

The app at A sees only steps 1 and 3 (L4). Discovery, ordering, trust, and transport (L1/L2) are handled beneath it.

sequenceDiagram
    autonumber
    participant A as App · node A (L4)
    participant N as Mesh · L1 / consensus L2
    participant B as Module · node B (L3)
    Note over N,B: B declares provided_caps — its cert authorises them,<br/>caps advertised on consensus + the signed beacon
    A->>N: discover B's capability
    N-->>A: available — L4 callback
    A->>N: invoke capability
    N->>B: route request over the mesh (L1)
    B->>B: L3 handler runs
    B-->>A: result → A's callback (L4)
    Note over A: A sees only discover + result (both L4)

Layers vs Tiers — do not confuse them

Legion has two distinct "4-part" models, and they are orthogonal:

The 4 layers (this doc)The 4 tiers (specs/05_tiers.md)
What it isAPI abstraction stackData-placement / privacy policy
ValuesL1 transport · L2 consensus · L3 provider · L4 consumerT0 Sovereign · T1 Pragmatic · T2 Group · T3 Interface
Applies toCode (which API you call)Data & capabilities (where they may live / who may reach them)

The tiers classify how sensitive a piece of data or a capability is and where it may travel — Sovereign (private, air-gapped) → Pragmatic (controlled) → Group (shared on the local cohort) → Interface (public/external). The tier travels with data and capabilities through the API as a tier level (e.g. a method's min_tier, a dispatch's caller_tier, a tiered L3 KV write) and is a per-node, cert-gated policy — enforced within the layers, not as a layer of its own. The detailed cross-tier movement policy is specified in specs/05_tiers.md.

SurfaceHeadersLinkageAudience
L4 (dynamic ABI)legion.h, l4_node.h, legion_ffi*.hExported from liblegion.so (~131 legion_* symbols)App developers, language bindings
L3 (in-process)sdk.h, legion_identity.hHeader contract; static-link or vtable-at-init (not exported)Module authors
Internal (L1/L2)legion_wire.h, legion_vm.h, legion_lnmp.hNot installed

See abi.md for the exact exported-symbol set, the enforcement stack (LEGION_API + C_VISIBILITY_PRESET hidden + -Wl,--exclude-libs,ALL + the header-install split), the CI gate, and stability status.

Status & how this evolves

This is a draft. The L3 and L4 surfaces are now visible precisely so they can be reviewed and revised — names, signatures, and groupings may change before anything is declared a release candidate. What is already locked is the export boundary (liblegion.so exports only legion_*, CI-enforced); the specific set of symbols is pre-release. Companion docs in this series: abi.md (the exported ABI), l3_modules.md (writing a capability-providing module), and l4_apps.md (building an app that discovers + consumes capabilities).

Copyright © 2026 Kultivator Consulting Ltd. All rights reserved.

legios.cloud