Legion

Legion L4 — Application / Consumer SDK (building apps)

Status: DRAFT (Foundation 09). The first visible cut of the L4 application API. A draft, not a release candidate — names and signatures may change. Start with architecture.md (the layer model) and abi.md (the exported ABI). The L3 side — providing a capability — is in l3_modules.md.

What L4 is

L4 is the consumer surface: you build an app that runs a Legion node, discovers capabilities the cohort advertises, and consumes them — calling a remote ai.stt.transcribe, a peer's display.gui, a storage cap, etc. Apps never advertise capabilities (that's L3); they consume.

L4 is the dynamic ABI — the ~131 exported legion_* symbols. It is the native-C surface any consumer links against: native desktop apps, embedded / microcontroller targets, and language bindings alike (the Android NDK voice-loop app was the first proof-of-concept, not a constraint). See abi.md.

#include <legion/legion.h>   /* umbrella — pulls the whole L4 surface */

legion.h transitively includes the library base, l4_node.h, the legion_ffi_*.h family, and (for convenience) sdk.h.

Two flavors of the L4 surface

L4 exposes the same node two ways; pick by how you're building:

Native C API (l4_node.h)Binding / FFI API (legion_ffi_*.h)
Handletyped legion_node_t *opaque intptr_t
Createlegion_node_create_from_file(<JSON config>)legion_ffi_node_start(node_id, cohort, master_pk, seed, cert, cert_len, multicast)
Consume a capvia the plugin manager + L3 dispatch (legion_node_get_pluginslegion_module_dispatch_to_cap_async, see l3_modules.md)self-contained legion_ffi_call_cap(...)
Best fornative apps that want typed pointers + config fileslanguage bindings, embedding, simple consumers

Both are L4 (both exported). Don't mix handles between the two. The worked example below uses the FFI flavor because it's the most self-contained and is exactly what the voice-loop app does.

Library base (legion.h)

FunctionPurpose
legion_version / _version_major / _minor / _patchVersion string + components.
legion_build_idGit hash + build metadata.
legion_free(ptr)Free a buffer the library allocated and handed back.
legion_log_set_callback(fn, ud) / legion_log_set_level(lvl)Logging sink (level, component, msg, ud) + minimum level.
legion_config_find(cli_path, …)Resolve a config file: CLI path → $LEGION_CONFIG./ → user dir → system dir.

Node lifecycle

Native (l4_node.h): legion_node_create_from_file (read a JSON config → legion_node_t *) → legion_node_start → … → legion_node_stoplegion_node_destroy. Plus legion_node_reload and read-only getters: get_node_id, get_pk_hex, get_epoch, get_cohort_name, get_peers, get_plugins, format_status (a human-readable status report).

FFI (legion_ffi_node.h): legion_ffi_node_start(node_id, cohort_name, master_pk_hex, seed_hex, service_cert, service_cert_len, multicast_enabled) returns an intptr_t handle (0 on failure) with discovery / heartbeat / transport threads already running; legion_ffi_node_stop(handle); legion_ffi_node_is_running(handle). All FFI calls are thread-safe. The member-enrolment params anchor the cohort for a member device: master_pk_hex + seed_hex identify the cohort, and service_cert / _len is an optional master-issued member certificate (pass NULL / 0 for a self-master node).

Worked example — discover + consume a remote capability

This is the pattern the voice-loop app uses to drive a remote ai.stt.transcribe provider, using the real, verified legion_ffi_node.h surface.

#include <legion/legion.h>
#include <string.h>
#include <stdio.h>

/* 1. Start an in-process node (discovery + transport threads run in the bg).
 *    master_pk_hex + seed_hex anchor the cohort for a member device; the cert
 *    is an optional master-issued member cert (NULL/0 for a self-master node). */
intptr_t node = legion_ffi_node_start("my-app", "personal",
            /*master_pk_hex=*/ NULL, /*seed_hex=*/ NULL,
            /*service_cert=*/  NULL, /*service_cert_len=*/ 0,
            /*multicast=*/     1);
if (!node) return 1;

/* 2. DISCOVER — what peers are visible, and what caps do they advertise?
 *    JSON: [{"node_id":"...","did":"...","caps":["ai.stt.transcribe"],"trust":0.9}, ...] */
char peers[8192];
if (legion_ffi_node_get_peers_json(node, peers, sizeof(peers)) > 0)
    printf("peers: %s\n", peers);

/* 3. (LEGACY FALLBACK — usually NOT needed.) A full cohort member catches up
 *    via consensus sync (cert-validated discovery) and resolves any_one caps on
 *    its own, so skip this. Only a device that can't converge on consensus needs
 *    to pin a module's delegation to a provider discovered in step 2. */
/* legion_ffi_node_delegate_pin(node, "stt", "balrog-ai-node"); */

/* 4. CONSUME — call `transcribe` on ANY live provider of `ai.stt.transcribe`
 *    (fungible any_one delegation). The single binary param carries the audio. */
uint8_t  out[64 * 1024];
size_t   out_len = 0;
int rc = legion_ffi_call_cap(node,
            /*cap=*/    "ai.stt.transcribe",
            /*module=*/ "stt",
            /*method=*/ "transcribe",
            /*param_key=*/  "audio",
            /*param_data=*/ pcm_bytes, pcm_len,
            /*timeout_ms=*/ 15000,
            out, sizeof(out), &out_len);

if (rc == LEGION_OK) {
    fwrite(out, 1, out_len, stdout);                 /* the transcript */
} else if (rc == LEGION_ERR_NOMEM && out_len > sizeof(out)) {
    /* out_len is the FULL result size — grow the buffer and retry. */
} else if (rc == LEGION_ERR_NO_DELEGATE) {
    /* No live provider of the cap (loud, never silently swallowed). */
}

/* 5. Done. */
legion_ffi_node_stop(node);

Notes:

  • legion_ffi_call_cap targets any provider of the cap (any_one semantic); *out_len is always set to the full result size, so LEGION_ERR_NOMEM with *out_len > out_cap means "grow and retry," and LEGION_ERR_NO_DELEGATE means no provider is live.
  • legion_ffi_node_delegate_pin is a legacy fallback, not the current class-B path: a class-B device now catches up via consensus sync (cert-validated discovery), so a full member resolves any_one caps without pinning. It still exists for a device that can't converge on consensus — pin the call to a peer discovered via the beacon — but a full cohort member should skip it.

Reference

Node + consume (legion_ffi_node.h, 24 fns)

GroupFunctions
Lifecyclelegion_ffi_node_start, _stop, _is_running
Discoverylegion_ffi_node_peer_count, _get_peers_json
Caps introspectionlegion_ffi_node_provided_caps (enumerate the caps THIS build would provide, without starting a node — so a joining device can declare its own caps)
Consume a caplegion_ffi_call_cap (any provider), legion_ffi_node_delegate / _delegate_async (a specific peer), legion_ffi_node_delegate_pin (legacy fallback — pin to a discovered peer; superseded by consensus-sync catch-up)
Livenesslegion_ffi_node_ping_peer_start / _poll / _cancel (non-blocking)
Byte streamslegion_ffi_node_stream_open / _status / _write / _close
Eventslegion_ffi_node_poll_events (drain focus/content events — poll, not callback)
OS processlegion_ffi_osp_activate (ask a peer to launch a signed app)
Transportlegion_ffi_node_attach_mobile_bridge (class-B WAN tunnel), legion_ffi_node_network_changed (signal an OS network change to drive reconnect — Spec 34), legion_ffi_node_transport_status (poll mesh link-state — Spec 34)
Routinglegion_ffi_node_route_query (resolve a multi-hop route to a target node before delegating — Spec 33.04)
Storagelegion_ffi_node_enable_persistent_store (turn the objectstore file-backed)

FFI subsystem families

Each FFI header bridges one subsystem to apps/bindings (opaque handles, explicit memory ownership). Consume what your app needs:

HeaderFnsWhat it's for
legion_ffi_audio.h12Mic capture + playback: audio_in_start/_stop/_get_utterance/_poll_events, audio_out_play/_poll_events.
legion_ffi_focus.h4Follow-focus targeting: focus_get/_set/_set_for/_clear (which node a follow_focus cap routes to).
legion_ffi_display.h11GUI surface: display_create/_destroy/_get_html/_open_shell/_pop_event/_pop_handoff.
legion_ffi_identity.h17Keys + certs: generate_seed, derive_did, derive_master_pk, did_to_pk_hex, issue_auth_cert, issue_service_cert; plus device enrolment — enroll_request_build / _open (a joining device self-signs an enrolment request with proof-of-possession; the master verifies it before issuing a cert — channel-agnostic, QR or file).
legion_ffi_js.h11JS app runtime: js_create/_destroy/_eval/_eval_string/_deliver_event.
legion_ffi_objectstore.h13Content store: objectstore_create/_get/_get_bytes/_fetch_bytes/_list/_delete/_gc_cid.
legion_ffi_bridge.h8Class-B WAN transport: mobile_bridge_connect/_connect_with_seed/_send/_recv/_add_route/_close/_is_connected.
legion_ffi_connection.h1Connection hints: connection_manager_resume_hint.

FFI helpers (legion_ffi.h)

legion_result_t + legion_result_free; the params builder legion_params_create/_destroy/_set_string/_set_int/_set_bytes (for languages without native maps); legion_string_len.

Key rules & gotchas

  • Apps consume, never advertise. Advertising is purely a provider (L3) concern — a certified module declaring provided_caps. The L4 surface has no advertise call.
  • Three ways to invoke: legion_ffi_call_capany live provider (any_one); legion_ffi_node_delegate → a named peer; legion_ffi_node_delegate_pin → pin a module to a discovered peer (legacy fallback — superseded by consensus-sync catch-up; a full member needn't pin). LEGION_ERR_NO_DELEGATE is the loud "no provider" signal.
  • Events are polled, not pushed. legion_ffi_node_poll_events, legion_ffi_audio_*_poll_events, etc. return pending events on your cadence — there is no C callback fired into your runtime (a deliberate choice; a blocking callback once stalled a cellular isolate).
  • Result buffers: *out_len is the full size; LEGION_ERR_NOMEM means grow and retry. Free library-allocated buffers with legion_free / legion_pal->free, never libc free.
  • Don't mix flavors: a legion_node_t * (native) and an intptr_t (FFI) handle are not interchangeable.
  • Native apps invoke caps via the plugin manager (legion_node_get_plugins

Status

DRAFT — surfaced for review; expect changes. With this, the API-doc series is architecture.mdabi.mdl3_modules.md → this. Next phase: build real Legion apps on this surface.

Note: the examples/*.c tree predates this L4 surface and needs a refresh to match it; treat the docs above (not those examples) as canonical for now.

Copyright © 2026 Kultivator Consulting Ltd. All rights reserved.

legios.cloud