Hologram Live

Hologram Live is a local-first module host for the Hologram ecosystem. This repository produces two independent products:
- Hologram Server — the standalone
hologrambinary, containing the CLI and background service. - Hologram Desktop — a Tauri application that bundles
hologramas a managed sidecar and embeds the shared.holoapplication executor.
The current desktop experience provides a Console dashboard, multi-thread Chat with archiving, content-addressed Files, watched .holo Applications, and module discovery in a responsive dark/light interface. A Cmd/Ctrl+K command palette reaches every action, and text size is adjustable with Cmd/Ctrl +/-/0. Chat routes through a configurable inference engine: the default echo engine repeats your message, while weightc (one-shot CLI over imported .wcpu artifacts) and Ollama-compatible HTTP endpoints serve real model completions.
Quick start
Desktop application
cd apps/desktop
npm ci
npm run dev
The preparation step builds the server sidecar before Tauri opens. The desktop app can:
- start, restart, and stop the local Hologram service;
- create and switch between chat threads with independent, durable histories;
- upload, rename, list, and download local files;
- watch application source directories, compile/import their
.holoarchives, and inspect verified archive metadata; - inspect the enabled module catalogue;
- follow the system appearance or remember a light/dark choice; and
- remain available from the system menu bar after the main window closes.
Build an installable desktop bundle with:
just desktop-build
The desktop UI's colours, type, and marks derive from the Hologram brand kit at a pinned revision; apps/desktop/BRANDING.md explains how to update the brand everywhere in three steps.
Standalone server
cargo build --release --locked --package hologram-live --bin hologram
./target/release/hologram init
./target/release/hologram start
./target/release/hologram status
./target/release/hologram modules list
Run the service in the foreground instead with:
./target/release/hologram serve
Every server advertises http://127.0.0.1:11435 by default and generates a
256-bit membership secret in its state directory at cluster.token. The file
is reused across restarts and is owner-only on Unix. To form a multi-host
cluster, securely give every node the same secret (at least 32 bytes), advertise
the origin other nodes can reach, and seed a new node with any live member:
# first node; generates <state_dir>/cluster.token
hologram serve --advertise https://registry-a.example.com
# joining node; use the contents securely copied from the seed
HOLOGRAM_CLUSTER_TOKEN='<seed cluster.token contents>' \
hologram serve \
--advertise https://registry-b.example.com \
--join https://registry-a.example.com
The joining node heartbeats immediately, learns the live membership set, and
then heartbeats those peers directly. Failed seeds remain eligible for retry;
members disappear from /api/v1/nodes after their TTL. Join messages use a
short-lived keyed proof over the exact payload, so the cluster secret and the
separate user authentication token are never sent over the wire. Non-loopback
origins must use HTTPS.
The default configuration and local endpoint are:
~/.config/hologram/live.toml
http://127.0.0.1:11435
Open the endpoint for the built-in status page. API documentation is available at:
http://127.0.0.1:11435/docs
http://127.0.0.1:11435/openapi.json
/docs is the self-hosted Scalar reference; /openapi.json is the generated OpenAPI document. Native clients use the versioned Protobuf/gRPC service on the same endpoint.
A path no route claims is answered 404 with the daemon's error envelope (LIVE_NOT_FOUND). A caller that sends content-type: application/grpc gets gRPC UNIMPLEMENTED instead, as any gRPC server answers an unknown service.
Machine-readable CLI output
Global --json is supported by every CLI command and may appear before or after the subcommand. A successful command writes one JSON value to stdout, including lifecycle actions, downloads, generated files, accepted mutations, and run --output-format text. Diagnostics remain on stderr, while a runtime failure writes a JSON object with code and message to stdout and exits nonzero. This makes the complete CLI safe to compose with jq:
hologram status --json | jq -r '.status'
hologram files get blake3:... --output ./asset.bin --json | jq '.byte_length'
hologram run ./my-app --input-text 'hello' --output-format text --json | jq -r '.[]'
hologram run application.holo --output-format text --json | jq -r '.[]'
Help and shell-completion text retain Clap's human-readable format.
Demo workflows
Chat and conversation history
Create a thread, copy its returned ID, and send a message:
hologram --json history new "Demo chat"
hologram --json chat send <conversation-id> "Hello, Hologram"
hologram --json history show <conversation-id>
chat send records the user message and the assistant response as one persisted exchange. Threads retain separate histories and can be resumed from the desktop app.
The response comes from the inference engine selected in live.toml:
[inference]
engine = "echo" # echo | weightc | ollama | llamacpp | candle | burn | vllm
default_model = "" # imported id or remote served-model name
weightc_path = "weightc"
ollama_endpoint = "http://127.0.0.1:11434"
vllm_endpoint = "http://127.0.0.1:8000"
vllm_token_env = "VLLM_API_KEY"
model_path = "" # GGUF (llamacpp/candle) or named-MPK checkpoint (burn)
tokenizer_path = "" # tokenizer.json (candle) or tokenizer.model (burn)
model_architecture = "" # candle: llama; burn: a supported Llama 3 variant
n_ctx = 4096
n_gpu_layers = 0
llamacpp_max_concurrent_requests = 1
request_timeout_secs = 300
resident_sessions = false # weightc only: keep one resident enter session per conversation
max_resident_sessions = 4 # LRU cap on resident sessions
The default echo engine repeats the user message; it needs no model and no external process. The weightc engine shells out to weightc ask <artifact-dir> <prompt> --json against an imported .wcpu artifact. ollama proxies /api/generate, while vllm uses the OpenAI-compatible /v1/completions and /v1/models endpoints with native streaming and optional authentication from VLLM_API_KEY.
llamacpp loads a local GGUF model in-process and streams decoded pieces with exact token counts. Set model_path directly, or import a GGUF file and put its returned blake3:... id in default_model. It is off by default because it builds native C++ code and gives model execution the daemon's crash boundary. Build it with cargo build --release --features llamacpp; use llamacpp-metal or llamacpp-cuda for the corresponding GPU backend. These builds require CMake, Clang, and a C++ compiler.
candle is the Rust-native local GGUF alternative. The initial adapter supports Candle's quantized Llama-family implementation, requires a matching Hugging Face tokenizer.json, streams native deltas, and reports exact token counts. Set model_architecture = "llama"; use --features candle for CPU, candle-metal for Apple GPUs, or candle-cuda for NVIDIA GPUs. Candle is not a universal GGUF dispatcher: unsupported model families fail during startup instead of being guessed.
burn uses Tracel's Burn-LM Llama implementation on CPU. It requires a Burn named-MPK checkpoint, the matching Llama 3 tokenizer.model, and one of llama3.2-1b, llama3.2-3b, llama3.1-8b, or llama3-8b in model_architecture. Build with --features burn. Burn generation is currently buffered, so streaming API responses honestly report x-hologram-stream: emulated; GGUF files are not Burn checkpoints.
Run the live acceptance gate against real engines before releasing an inference build. It starts an isolated Hologram server and checks model discovery, buffered and native-streaming OpenAI/Ollama requests, usage, cancellation, and llama.cpp context overflow:
./scripts/check-inference-engine.sh llamacpp /models/tiny.gguf
HOLOGRAM_TOKENIZER_PATH=/models/tokenizer.json HOLOGRAM_MODEL_ARCHITECTURE=llama ./scripts/check-inference-engine.sh candle /models/tiny.gguf
HOLOGRAM_TOKENIZER_PATH=/models/tokenizer.model HOLOGRAM_MODEL_ARCHITECTURE=llama3.2-1b ./scripts/check-inference-engine.sh burn /models/llama3.2-1b.mpk
VLLM_ENDPOINT=http://127.0.0.1:8000 ./scripts/check-inference-engine.sh vllm org/model
VLLM_API_KEY is forwarded when set. Set HOLOGRAM_BIN to test an existing binary, or let the script build the required feature set.
With resident_sessions = true, the weightc engine instead keeps a supervised weightc enter --jsonl process per conversation, so turns reuse the live KV context instead of replaying a transcript, and only the new message crosses the wire each turn. Sessions are LRU-capped by max_resident_sessions; a crashed session is reported as a typed error and lazily respawned (starting fresh context) on the next turn. This mode needs a weightc build with enter --jsonl support. Models are managed with:
hologram models import ./tinyllama.wcpu
hologram models import ./tinyllama.gguf
hologram models list
hologram models remove blake3:...
Threads can be archived instead of deleted. Archived threads keep their messages and their updated_at_millis, but drop out of the default listing:
hologram --json history archive <conversation-id>
hologram --json history list # archived threads are omitted
hologram --json history list --all # archived threads included
hologram --json history unarchive <conversation-id>
In the desktop app, hovering a thread reveals an archive button, and archived threads collapse into an ARCHIVED group at the bottom of the thread list.
Configuration files use schema version 2, and a file written by an earlier build
still starts. Missing sections and fields fall back to their documented defaults,
and an older schema_version is upgraded and written back so the next start reads
a current file; the rewrite records only what the file declares, never a value
injected by an environment override. Unknown fields are still rejected rather than
ignored, and a schema_version newer than the running build supports is refused
rather than silently downgraded.
Removing a field from the schema comes with a schema_version bump and an entry
in the retired-key table in src/config.rs. A file written before the bump keeps
starting: the retired key is dropped during the upgrade, named in a warning, and
gone from the rewritten file. Files already at the current version are not swept,
so an unrecognised key there stays a startup error rather than being discarded as
though it had been retired.
Files
hologram files put ./notes.txt --media-type text/plain
hologram files list
hologram files rename blake3:... meeting-notes.txt
hologram files get blake3:... --output ./meeting-notes.txt
hologram files search --filename-contains notes --limit 20
File bytes are addressed by their BLAKE3 content ID. Renaming changes only persisted filename metadata; the ID and bytes remain unchanged.
Object search
hologram registry search --kind file --limit 50
hologram registry search --media-type text/plain --min-size 1024
hologram --json registry search --limit 2 | jq -r '.next_cursor'
hologram registry search --limit 2 --cursor blake3:...
Search filters on stored metadata: --kind, --media-type, --filename-contains,
--min-size, --max-size. It is not full-text and does not read object content.
Results are ordered ascending by object ID and paginated. --limit defaults to
100 and is clamped to 1000. A page carries next_cursor only while further
matches remain; the cursor is opaque and valid only for the provider that issued
it. truncated reports a page cut short by a provider-side scan bound, so a
capped result is never presented as a complete one.
The same surface is available as GET /api/v1/objects/search and
GET /api/v1/files/search, and as the registry.search and files.search
native operations. hologram files search is the file-kind projection: the
daemon fixes the kind, so --kind is not offered there.
Named artifacts
hologram push ./app.holo demo:v1
hologram serve demo:v1
hologram pull qwen3.5:4b
hologram pull host:5000/models/qwen3.5:4b
hologram run qwen3.5:4b --input-text "hello"
hologram --json pull qwen3.5:4b | jq '{archive_kappa, layers_fetched, bytes_transferred}'
A reference is [host[:port]/]namespace/name[:tag]. A bare name:tag expands
against [registry].endpoint and [registry].namespace; an omitted tag means
latest. Following OCI, the colon separates repository from tag, so
qwen3.5:4b is repository qwen3.5, tag 4b.
An artifact is one OCI manifest whose layers are a thin .holo archive plus the
kappa-addressed payload blobs it references, so two artifacts sharing weights
transfer them once. Pull consults the local store first, fetches only what is
missing, verifies every layer against its kappa on write, and confirms the whole
set is present before reporting success — the registry itself does not check
that a manifest's layers exist. Every step is content-addressed, so a pull is
idempotent and an interrupted one resumes for free.
Tags are mutable, so a pull always re-resolves rather than caching by name, and records the resolved manifest digest — that digest, not the tag, is what makes a pull reproducible.
serve takes an optional reference. hologram serve alone is unchanged;
hologram serve demo:v1 acquires the artifact, imports it, and makes it
resident before the listener binds, so the daemon never reports ready with
the named application not yet invocable. The argument joins whatever
holo.resident already declares rather than replacing it.
That residency lasts for the life of that process. serve <ref> does not write
to your configuration, so a daemon started any other way will not have it — put
it in holo.resident if you want it every time.
run and serve resolve their argument in the same fixed order, so no existing
invocation changes meaning:
- a
blake3:id → the local catalog - an existing filesystem path → a local archive
- anything else → a registry reference, pulled and then executed
Pulling grants no capabilities. A pulled archive receives the same ADR 020 baseline as a local file — no storage roots, no channels, no network scopes — and is executed by exactly the same path. Having come from a configured registry is not evidence about what an archive contains.
push is the inverse. The archive becomes the manifest's archive layer, and
any payload a thin archive references without embedding is published alongside
it — which is what lets a later pull deduplicate against blobs the registry
already holds. A referenced payload that is not in the local store refuses the
push before anything is written, rather than publishing a manifest whose content
is missing. The archive is inspected first, so a file that is not a valid
archive is rejected before the registry is touched.
Tags are mutable upstream, so push refuses to move a tag that already resolves
unless given --force, and the result records whether a tag was moved. Silently
repointing a name someone else is pulling is not a default worth having.
Progress is written to stderr and the result document to stdout, so --json
stays usable in a pipeline; under --json the progress is JSONL events rather
than a rendered bar.
Client library
Applications can talk to a daemon without depending on it. hologram-client is
a standalone crate in this workspace:
use hologram_client::{HologramClient, ObjectQuery};
let client = HologramClient::new("http://127.0.0.1:11435")?;
let stored = client
.put_file(b"hello".to_vec(), Some("notes.txt"), "text/plain")
.await?;
let bytes = client.get_file(&stored.id).await?;
let page = client
.search_files(&ObjectQuery {
filename_contains: Some("notes".to_owned()),
limit: 50,
..ObjectQuery::default()
})
.await?;
It depends on reqwest, rustls, serde, and serde_json only — taking
hologram-live would pull wasmtime, axum, and tonic into every consumer's
build. The cost of mirroring the wire shapes instead of importing them is drift,
so a contract test in the daemon's suite serializes the server types and
deserializes them with the client's; a renamed field fails a build rather than
surfacing at runtime.
search_objects_all follows cursors to the end, stopping at a truncated page
rather than looping, since truncation means the daemon stopped early.
.holo archives
Generate a validated source manifest interactively:
mkdir my-app
cd my-app
hologram app init
The generator prompts for ordered layers, their kind-specific entrypoint or surface information, the primary layer, an optional capability file, and optional child applications with delegated capability documents. It writes hologram.json atomically and prints the commands needed to compile and run it. For scripts and CI, provide the first layer as flags:
hologram app init ./my-app \
--kind wasm \
--path app.wasm \
--entry holo_run
# Compose a previously compiled, self-contained child archive
hologram app init ./parent \
--kind wasm --path parent.wasm --entry holo_run \
--child worker.holo \
--child-capabilities worker-capabilities.json
# A portable View source is a directory containing ui/index.html
hologram app init ./view-app \
--kind view --path ui --surface portable
Use --yes for a minimal app.wasm/holo_run manifest. Existing manifests are preserved unless --force is explicit. Packaging remains a compiler choice: use hologram compile for a fat archive or add --thin for a manifest-only archive.
hologram holo fixture ./fixture.holo
hologram holo import ./fixture.holo
hologram holo list
hologram holo inspect ./application.holo
hologram holo plan ./application.holo
hologram holo verify ./application.holo
hologram holo inspect blake3:...
hologram holo plan blake3:...
hologram holo verify blake3:...
hologram holo remove blake3:...
Desktop watch loop
Open Applications in Hologram Desktop and choose Add directory, then
select a project containing hologram.json. The desktop compiles it immediately,
imports the successful archive into the normal local catalog, and recursively
watches the project for later changes. Builds are debounced and written to the
desktop cache rather than into the source directory.
Choose Run on a ready watched project to inspect its latest successful archive. Non-View applications accept a text input and execute once through the in-process executor. A portable View application instead offers Open application: it opens in its own native window outside the dashboard and keeps its prepared primary available across user messages until that window is closed, Stop application is chosen, or Desktop quits. Choose Add .holo to import an existing archive through the native file picker; catalog archives use the same Run panel. Unsupported providers and denied capabilities remain explicit runtime errors.
examples/wasm-view/ is the complete composed example. Its portable frontend
posts one bounded application.invoke intent to an ASCII-uppercase Wasm
primary:
hologram compile examples/wasm-view/hologram.json \
--output target/wasm-view.holo
Import target/wasm-view.holo in Desktop and choose Open application. Its
separate native window can submit any number of bounded messages to the same
prepared Wasm primary. Closing the window or choosing Stop application
detaches the View and stops the session in reverse layer order. The checked-in
manifest is also compiled by display-independent lifecycle tests, which prove
the persistent attachment, direct and View invocation, transactional window
replacement, idempotent stop, and reverse shutdown without requiring a CI
display. The CLI's hologram run remains deliberately one-shot and headless;
it reports that no portable surface is available instead of changing its
fresh-instance contract.
The Applications list is backed by the same holo list and holo inspect
operations shown above, so its archive κ, application κ, layers, capabilities,
physical sections, and verification state are not reconstructed by the web
frontend. A failed rebuild is shown on the watched project while the last good
immutable archive stays available. Stop watching removes only the persisted
watch registration; it does not delete the last cataloged .holo archive.
The stable build creates and validates v4 .holo archives and rejects every other physical version. The physical file starts with HOLO, a version and section count, then fixed 24-byte section-table entries containing each section's kind, offset, and length. Logical layers do not have separate physical headers: their ordered descriptors live in the canonical AppManifest section and refer to payloads by κ. Every application archive contains exactly one verified application-directory extension.
.holo v4
├─ header + section table
├─ AppManifest primary · requires · ordered layers · children
├─ Extension verified, queryable application directory
├─ ContentBlob × N κ71 · content bytes
├─ other sections plans · weights · ports · certificates · metadata
└─ BLAKE3 footer
The closed layer kinds are wasm, tensor, rootfs, view, and v4's non-exit-bearing inference-model. A layer records its content κ and entrypoint plus an architecture for rootfs, surface for views, or engine identifier for model services. A View source names a directory containing index.html; the compiler emits canonical HOLOVIEW v1 bytes with portable paths and lexically ordered assets, independent of source timestamps, permissions, and creation order. Only the portable surface is accepted. Fat archives embed referenced blobs; thin archives retain the same application identity while resolving content through a store. Live emits fat archives by default, supports thin output with --thin, executes Wasm primary layers through wasmtime, and can directly execute a Python OCI bundle carried by a rootfs layer through the experimental local container provider. The portable View provider validates during prepare, attaches an available host surface during start, and detaches idempotently during reverse shutdown. Hologram Desktop publishes the concrete surface and serves immutable assets from an opaque per-attachment hologram-view origin bound to a separate native application window; it uses neither file://, source paths, nor a localhost server. Dynamic View windows are not included in the main window's Tauri capability assignment, block external navigation and popups, and receive a restrictive CSP. A View can send bounded v1 messages to its own admitted application by posting JSON shaped as {"version":1,"name":"application.invoke","payload":"text"} to the same-origin /_hologram/intent endpoint; the runtime serializes each application's direct and View invocations and returns UTF-8 outputs. Other names, versions, origins, content types, paths, and over-limit payloads fail closed, and no general Tauri or host API is exposed. Explicit application sessions keep providers and surfaces available across messages; one-shot execution still starts, invokes once, and stops. Stop waits for in-flight root and child application messages before reverse shutdown. CLI and server execution intentionally publish no surface, so planning reports an explicit direct/headless unavailable-surface blocker. Desktop window operations run through a display-independent lifecycle seam: replacement is transactional, a failed replacement restores the prior assets and window, detach failure restores the attachment for retry, and repeated detach is safe. The checked-in examples/wasm-view/ archive proves compilation, persistent intent invocation, explicit session ownership, and reverse shutdown.
Applications request authority with an optional capabilities.json. This is a
human-authored compiler input, not the object embedded in the archive:
{
"schema_version": 2,
"storage_roots": [],
"storage_quota_bytes": 0,
"network_fetch_endpoints": [],
"network_announce_endpoints": [],
"publish_channels": [],
"subscribe_channels": [],
"memory_max_bytes": 0,
"cpu_time_per_event_ms": 0,
"priority_weight": 0
}
Endpoint entries use the exact form
https://lowercase-host:explicit-port/path-prefix, must be strictly sorted,
and narrow for children only on exact origin and path-segment boundaries.
Schema-1 documents remain compatible when their legacy network booleans are
absent or false; true is rejected rather than interpreted as ambient access.
Scopes alone link no network interface. Only the explicit
component-network-fetch@1 profile can use an admitted fetch scope; raw sockets
and announce/listen interfaces remain unavailable.
Set "requires": "capabilities.json" in hologram.json. compile --check
validates the version, fields, and canonical sorted κ and endpoint lists;
compile converts
the JSON into the upstream canonical CapabilitySet bytes and stores that
object's κ in AppManifest.requires. Omitting requires produces the canonical
empty request. A request describes what an application needs—it is never itself
a grant. In the upstream capability contract, a scalar budget of 0 means
unbounded; use a nonzero value to request a finite ceiling. Runtime grant
enforcement now decodes this canonical request and authorizes it before any
provider prepares. Schema-v4 source manifests may pair a self-contained child
.holo archive with a delegated capability document; compilation binds both
canonical κ values into the parent and embeds the verified child closure in a
fat build. The planner verifies that recursive child closure—including every
child manifest, delegated capability object, requested capability object, and
layer—under one bounded κ-resolution walk. Before provider preparation, each
delegation must be admitted by its parent's effective grant and must itself
admit the child's request. Admitted children start depth-first in manifest
order under their delegated grants and stop or roll back in exact reverse
order. The current call invokes only the root primary; child primaries are
lifecycle-managed dependencies rather than independent resident applications.
Capability objects always use canonical CapabilitySet bytes. The canonical
empty set represents a deny-all request; zero-byte or otherwise malformed
objects are rejected even when their content address is correct.
Ordinary local execution uses the built-in baseline grant: no storage roots,
publish/subscribe channels, or network endpoint scopes. A non-empty request therefore
fails with LIVE_AUTHORIZATION_DENIED. For an explicit local demo, provide a
separate trusted capability source as the effective grant:
hologram --json run ./application.holo \
--development-grant ./development-grant.json \
--input ./payload.bin | jq
Resident execution reads its development grant only from the service's trusted
configuration, never from a remote request. Set
holo.development_grant = "development-grant.json"; relative paths resolve
from paths.config_dir, and configuration validation rejects this mode on a
non-loopback listener. Both direct and service modes emit a warning and trace
the request κ, effective-grant κ, source, and allow/deny decision without
logging the capability document. They also synchronously append each root
request, child delegation, and child request decision to audit.jsonl under
the configured state directory before provider preparation. Audit rows contain
the authenticated principal, relation, application and parent identities,
request and grant identities, trusted source label, and outcome—never tokens,
source documents, roots, channels, or payload bytes. Successful raw run results
expose the same non-secret decision metadata as requested_capabilities_kappa,
effective_grant_kappa, grant_source, and authorization, so automated
checks can retain the authority evidence:
hologram --json run ./application.holo \
--development-grant ./development-grant.json |
jq '{authorization, grant_source, requested_capabilities_kappa, effective_grant_kappa}'
See the complete .holo format guide for the byte layout, section kinds, manifest schema, identity model, application directory, verification rules, and current runtime support.
Archives whose primary layer is Wasm execute in-process through wasmtime. A self-contained archive can run directly without starting the service:
# Compile a source directory in memory and run it immediately
hologram run ./my-app --input-text 'hello' --output-format text
# Or compile once and run the resulting immutable archive
hologram compile ./my-app/hologram.json -o ./my-app.holo
hologram run ./my-app.holo --input ./payload.bin
Wasm layers use the import-free core-wasm-v1 contract. The module exports
memory, holo_alloc(i32) -> i32, and the function named by the layer's
manifest entry with signature (i32, i32) -> i64. holo_run is a generator
default, not a runtime hard-code; an archive may declare another
export such as transform. The packed i64 identifies one byte output. V1 has
no WASI imports and no numeric process exit status: returning bytes is
successful completion, while a trap is LIVE_PROTOCOL_ERROR. Direct and
resident providers validate the declared entry during preparation and use a
fresh instance for each input.
The callable entry stays separate from guest-contract selection. Source
manifest schema v4 requires contract for Wasm layers; the compiler writes it
to the canonical, identity-bearing aux tag. Empty or omitted contracts are
rejected. In addition to core Wasm, the closed set contains import-free
Component v1 plus the store-read, store-write, channel-publish, and
channel-subscribe profiles described below. An import-free Component manifest
uses hologram:guest/component@1:
{
"schema_version": 4,
"primary": 0,
"layers": [{
"kind": "wasm",
"path": "app.component.wasm",
"entry": "run",
"contract": "hologram:guest/component@1"
}]
}
hologram app init --contract hologram:guest/component@1 generates the same
field. Component archives compile, inspect, plan, and execute directly or as a
resident application; both JSON and gRPC report the normalized contract.
The exact Component v1 entry is run. Its checked-in WIT world accepts and
returns one byte list and imports nothing, so it cannot reach ambient WASI,
filesystem, network, clocks, environment, or host services. Each input gets a
fresh store while the compiled component remains warm. Runtime ceilings apply
even when capability scalars are unspecified: 64 MiB memory, 100 million fuel
units per input, 1 MiB input and output, and a two-second invocation deadline.
Nonzero admitted memory or CPU-time limits can only tighten those ceilings.
Deadline and dropped-future cancellation interrupt the isolated component
engine. Guest errors, traps, and limit failures remain typed operation errors.
There is never a fallback to core Wasm.
Unknown source identifiers are rejected before archive emission, and unknown
canonical manifest tags are invalid archives.
Components that need mediated object reads select the separate
hologram:guest/component-store-read@1 contract and declare at least one exact
object κ in storage_roots. That profile imports only
hologram:host/store@1.0.0; its read function returns bytes for an admitted
root and rejects every other target before accessing the store. The base
component@1 world remains import-free. Direct execution uses the configured
local registry, resident execution uses the catalog store, and child
applications retain only roots admitted through their delegated grant. Missing
profile authority returns LIVE_AUTHORIZATION_DENIED before linker
construction. No filesystem, network, clock, environment, or other WASI
interface is added by this profile.
Typed transitive reads use the distinct
hologram:guest/component-store-graph-read@1 contract, so an existing exact
root grant never widens implicitly. It exposes the same single
hologram:host/store@1.0.0 import, but preparation resolves each admitted root
through registered canonical UOR realization edges. Unknown or untagged bytes
are opaque leaves. Every reachable object must already exist locally and
re-hash to its κ; malformed typed frames, incomplete closures, and host depth,
object, edge, or 64 MiB aggregate-byte limit violations fail before linker
construction. The resolved read set is shared by direct and resident providers,
while child applications may receive only root κ values admitted by their
delegated grant. The write profile remains exact-root-only.
Components that need mediated object writes select
hologram:guest/component-store-write@1. Its fixed world imports only
hologram:host/store-write@1.0.0; it cannot read. The request and trusted grant
must contain the exact destination κ in storage_roots and a nonzero
storage_quota_bytes. Before any write, Live checks the exact root and proves
the bytes hash to the caller-supplied κ. Newly materialized blobs consume the
prepared application's lifetime quota, existing identical blobs consume no
additional quota, and a rejected call leaves no partial blob. Direct, resident,
and delegated-child paths use the same rules and expose no ambient WASI surface.
Components exchange bounded in-process messages through two separate profiles:
hologram:guest/component-channel-publish@1 imports only
hologram:host/channel-publish@1.0.0, while
hologram:guest/component-channel-subscribe@1 imports only
hologram:host/channel-subscribe@1.0.0. Preparation requires a nonempty exact
request admitted by publish_channels or subscribe_channels, and every call
checks the supplied channel κ. The runtime-owned broker provides nonblocking
64 KiB messages and 64-message FIFO mailboxes with explicit full-mailbox
backpressure. One successful receive consumes one message; empty receive
returns immediately. This v1 boundary is an in-memory, at-most-once work queue,
not broadcast, replay, acknowledgement, persistence, or network transport.
Bounded outbound HTTPS GET uses the separate
hologram:guest/component-network-fetch@1 profile. Its world imports only
hologram:host/network-fetch@1.0.0, and preparation requires at least one
admitted network_fetch_endpoints scope. Targets use the same canonical
host/port/path grammar and are rechecked per call. Live resolves DNS under a
public-address-only policy, pins the connection to checked addresses, disables
redirects and environment proxies, and inherits no guest headers, credentials,
cookies, or client identity. The host caps targets at 2 KiB, responses at 1
MiB, transport time at 1.5 seconds, and concurrent fetches at eight. Direct and
resident execution share these rules; raw WASI networking and announce/listen
remain unavailable.
hologram run accepts a project directory, its hologram.json, a local
self-contained .holo file, or a catalog κ. Project references are compiled as
fat archives in memory and are not written or imported. Repeat --input for
binary file inputs or --input-text for UTF-8 values.
For warm, repeated execution, import and load the archive into a resident session:
hologram compile ./my-app/hologram.json -o ./my-app.holo
hologram holo import ./my-app.holo
hologram holo load blake3:...
hologram run blake3:... --input ./payload.bin
hologram holo resident
hologram holo unload blake3:...
The browser API exposes the same resident lifecycle at
GET /api/v1/holo/resident, POST or DELETE
/api/v1/holo/{kappa}/load, and POST /api/v1/holo/{kappa}/run. Run request
inputs are JSON arrays of byte arrays. Native and HTTP resident records include
requested_capabilities_kappa, effective_grant_kappa, grant_source, and
authorization alongside lifecycle counters.
Resident sessions live in the service process: a restart drops them, and the
catalog alone does not bring them back. To run an archive as a persistent
service, declare it in live.toml and the daemon loads it into a resident
session during every startup, before it reports ready:
[[holo.resident]]
kappa = "blake3:ad47a8206fa67ade51af75363b7677f50937c10e207f6a7394148c0db3f227af"
Each κ must name an archive already imported into the local catalog
(blake3: plus 64 hex characters; duplicates are rejected at startup).
Declared loads go through the same verified, audited capability admission as
an explicit holo load, under the service's single effective grant and the
local-runtime principal. A declaration that fails to load — an unknown κ or
an under-granted archive — is logged with its reason and skipped without
stopping the service or the other declarations; the failing archive is simply
absent from GET /api/v1/holo/resident. Changes to [[holo.resident]] take
effect on the next hologram restart.
The compiler emits fat archives by default. --thin emits the same canonical application manifest without its κ-addressed payloads:
hologram compile ./my-app/hologram.json -o ./my-app.holo
hologram compile ./my-app/hologram.json --thin -o ./my-app.thin.holo
With --json, compilation reports the three distinct identities directly:
hologram --json compile ./my-app/hologram.json -o ./my-app.holo |
jq '{archive_kappa, archive_fingerprint, application_kappa, capabilities_kappa}'
archive_kappa is the BLAKE3 address of the complete physical file,
archive_fingerprint is the footer integrity value, and
application_kappa addresses the canonical AppManifest. Fat and thin files
have different archive κ values and footer fingerprints but the same
application κ and capabilities_kappa. The existing kappa returned by holo inspect, catalog,
resident, and run operations continues to mean the physical archive object;
inspection adds application_kappa when an application manifest is present.
Importing the fat archive verifies and caches its content blobs. A subsequently imported thin archive can resolve those payloads from the local κ store and use the same resident load/run commands. Direct file execution requires a fat archive because it deliberately has no external content resolver.
Compiled application archives include exactly one versioned application directory over their canonical manifest. hologram --json holo inspect ./application.holo inspects a local archive without importing it or starting the service; the command also accepts an imported blake3:... object ID. It exposes the physical kappa, canonical application_kappa, footer fingerprint, ordered layers, child applications, required capability set, model engine tags, and embedded κ-addressed blobs. The directory is re-derived and verified against the manifest and blob contents on import; missing, duplicate, malformed, or disagreeing directories are rejected. Structural v4 archives without an application manifest report no application κ and may omit the directory.
Use holo plan to explain whether an application can run before starting any provider. Local paths plan without the service; imported κ values plan against the local content cache. The payload-free report includes all three identities, fat/thin/hybrid packaging, the capability object, ordered layers, resolution sources, provider availability, planning limits, and stable typed blockers:
hologram --json holo plan ./application.holo |
jq '{application_kappa, packaging, runnable, layers, blockers}'
hologram --json holo plan blake3:... |
jq '{execution_target, resolved_object_count, resolved_bytes, runnable}'
An unsupported layer is still a successful plan: runnable is false and blockers explains the unavailable provider with a kind, error_code, and message. Malformed archives, missing catalog objects, and transport failures retain the normal typed JSON error contract.
AI model applications
Hologram Live now reads and writes .holo v4 InferenceModel layers. A complete model archive produced by hologram-ai can be imported and inspected without initializing its engine:
hologram --json ai inspect model.holo
hologram holo import model.holo
The inspection lists each callable service entry, its engine identifier, content κ, and whether the bundle is embedded. Model-source acquisition and R4G1 compilation remain owned by hologram-ai; this binary does not yet expose hologram ai compile or connect a model session to hologram ai infer. Attempting to execute a model-only archive returns LIVE_CAPABILITY_MISSING and identifies the archive as a library archive, because installing the inference provider would not make it runnable; provider availability remains visible in the plan report.
For low-level archive assembly, a source manifest can package an already-built provider bundle:
{
"schema_version": 4,
"library": true,
"layers": [{
"kind": "inference-model",
"path": "model.bundle",
"entry": "ai.default",
"engine": "uor-r4"
}]
}
A manifest without a primary layer, like this one, must declare "library": true; the compiler rejects library: true alongside a declared primary, and rejects an absent primary without the marker, both with LIVE_CONFIG_INVALID before any layer content is built. library is an optional boolean and defaults to false. Library archives fully support compile, import, inspect, and plan, and can be composed as a children entry in another application; only direct or resident execution of the root is refused, with LIVE_CAPABILITY_MISSING. Composing a View-only library as a children entry does not currently work, because a View layer can never be a primary and every application whose layer set contains a View layer is required to declare one; Wasm library children work today and are covered by a test.
weightc remains a chat execution provider over imported .wcpu directories. Those directories are not placed into .holo files until a deterministic single-blob bundle and validation contract is defined. See the AI model application guide and ADR 009.
Before direct execution or holo load starts a provider, Live builds a runtime-owned application plan from the canonical manifest. It recursively resolves and re-hashes root and child manifests, requested and delegated capability objects, and every layer from embedded content or the local κ store. Shared objects are deduplicated while logical applications remain distinct, and aggregate application-depth, application-count, layer, object, and byte limits bound the complete tree. Missing or malformed nested content and cyclic paths therefore fail before provider preparation. Runtime admission first admits the root request, then proves every delegated child grant is a subset of its parent's effective grant and admits the child's request. Each decision is written through the separate audit boundary with the real CLI or service principal before provider preparation; amplification or an under-granted request returns LIVE_AUTHORIZATION_DENIED. A closed LayerKind registry then prepares and starts the complete admitted tree depth-first in manifest order. Every child provider receives only that child's delegated grant. Direct and resident calls invoke only the root primary; child primaries are managed dependencies. Normal stop and failure rollback traverse the exact reverse order, and resident status aggregates root and child layers. Wasm layers use Wasmtime behind this boundary and may have a primary position other than zero; resident status reports state, aggregate resident bytes, queued calls, processed calls, and non-secret authorization evidence. Repeated load and unload are idempotent. Python rootfs archives use the same lifecycle through an explicitly experimental, direct-only OCI adapter. Inference-model services can be inspected but not invoked through Live yet. Tensors, inference models without a provider, resident Python rootfs archives, and unknown rootfs payloads return a typed LIVE_CAPABILITY_MISSING error. The compiler/runtime/executor boundary is recorded in specs/adrs/007-holo-compiler-runtime-execution.md and the planning/provider contract in specs/adrs/010-holo-application-plan-and-provider-lifecycle.md; the Wasm guest contract is documented in src/holo_wasm.rs and demonstrated by features/fixtures/wasm-app/.
Python applications
Python is a compiler input, not a fifth .holo layer kind. Both available
profiles keep the same module:function entrypoint, where the function accepts
and returns bytes:
- schema-v4
wasi-componentpackages Python, CPython, and locked pure-Python wheels into an import-freeWasmCodemoduleselected byhologram:guest/component@1; it runs directly or resident without Docker; - schema-v2
rootfspackages Python, native dependencies, and Linux libraries into an architecture-specific OCI image for the experimental direct provider.
The dependency-free teaching example is examples/python-component-hello/:
$ hologram app init ./my-python-component \
--template python --profile wasi-component \
--project . --entry my_package:main --lock uv.lock
$ hologram compile examples/python-component-hello/hologram.json \
--output python-component-hello.holo
$ hologram run python-component-hello.holo \
--input-text Ada --output-format json
{
"message": "Hello, Ada!",
"name": "Ada",
"python": "3.14.0",
"runtime": "python-component"
}
This compiler invokes Hologram's deterministic componentize-py 0.25.0
distribution through an isolated uvx tool environment, removes the developer
virtual environment and Python search path, and uses --stub-wasi. It selects
one exact wheel URL and SHA-256 from the immutable
componentizer-v0.25.0-hologram.5
release for each of the five server-release hosts, disables package indexes and
source builds, and fails with LIVE_CAPABILITY_MISSING on an unpinned host.
The emitted component therefore imports no WASI and runs under the existing
Component v1 limits. Install uv; the first compile downloads the
hash-verified wheel and later compiles may reuse its cache.
For external packages, the portable profile accepts registry records only when
uv.lock contains an HTTPS, SHA-256-pinned Python 3 *-none-any.whl. It
installs those exact wheels into a private path with indexes, dependency
re-resolution, source builds, and ambient pip/uv configuration disabled.
Native, source-only, Git, and path packages fail during compile --check with
guidance to use rootfs.
Both validation and compilation return a versioned, non-canonical provenance
report when global --json is active:
$ hologram --json compile \
examples/python-component-dependency/hologram.json --check \
| jq '.build_provenance.layers[0].source \
| {runtime, componentizer, inputs, dependencies, reproducibility}'
The schema records normalized SHA-256 source inputs, the complete selected
dependency-wheel inventory, CPython and componentizer pins, the exact
host-specific componentizer wheel URL/hash, immutable release and patch-set
manifest identity, deterministic preinitialization contract, build host, and
target ABI. A completed compile additionally records the observed uvx/uv
versions and the generated layer κ and byte length. canonical: false is
deliberate: the report is not embedded in .holo, so host evidence cannot
silently change the canonical application identity. Save a durable copy with
jq '.build_provenance' > application.provenance.json.
examples/python-component-dependency/ demonstrates six==1.17.0:
$ hologram compile examples/python-component-dependency/hologram.json \
--output python-component-dependency.holo
$ hologram run python-component-dependency.holo \
--input-text Ada --output-format json
{
"dependency": "six-1.17.0",
"message": "Hello, Ada!",
"name": "Ada",
"runtime": "python-component"
}
Stubbed randomness repeats deterministic build-time state inside an emitted
component and is not suitable for security-sensitive runtime randomness. The
pinned Hologram tool controls its private preinitialization randomness, clocks,
filesystem metadata, directory enumeration, hash seed, allocator fills, and
generated ordering.
Completed output reports reproducible: true: workflow run 33227358037
proved matching component, application, archive, footer, and complete-file
identities on two independent clean builders for every supported host. Offline
compile --check remains false because it has not observed output. Real
capability-gated WASI remains a later milestone.
Run the repeatable direct-and-resident proof with:
just python-component-holo-demo
Run two compiler invocations with separate Hologram and uv caches and query the
single JSON result with jq:
just python-component-repro | jq '{status, target_host, equal, identities}'
The component-reproducibility workflow runs that proof on two independent
clean runners for each supported macOS, Linux, and Windows host, then compares
the component layer, capabilities, application, archive, footer, byte-length,
and complete-file SHA-256 identities. Server releases depend on its aggregate
result.
For the dependency-aware rootfs profile, source-manifest schema v4 turns a
locked project into an architecture-specific rootfs layer containing CPython,
the application, dependencies, and required Linux libraries.
For a small second application example, examples/python-hello/ uses only the Python standard library and runs directly from its project directory:
$ hologram run examples/python-hello --input-text Ada --output-format json
{
"message": "Hello, Ada!",
"name": "Ada",
"runtime": "python"
}
The same project can be added as a watched directory in Desktop. After its Docker-backed build reaches Ready, choose Run and enter a name. Desktop verifies and retrieves the immutable catalog archive, then uses the direct executor so experimental Python rootfs applications do not depend on resident rootfs support.
The repository also includes the dependency-heavy NumPy + pandas project in examples/python-numpy-pandas/. A running Docker-compatible engine is required for both examples and this experimental compiler/direct executor:
$ hologram compile --check examples/python-numpy-pandas/hologram.json
$ hologram compile examples/python-numpy-pandas/hologram.json \
--output numpy-pandas.holo
$ hologram run numpy-pandas.holo \
--input examples/python-numpy-pandas/request.json \
--output-format json
{
"columns": [
"label",
"value"
],
"mean": 20.0,
"rows": 3,
"sum": 60.0
}
Both validation and compilation expose non-canonical rootfs build evidence in JSON. Validation hashes the declared project inputs without contacting Docker:
hologram --json compile examples/python-numpy-pandas/hologram.json --check \
| jq '.build_provenance.layers[0].source | {
profile, target_platform, base_image, dependency_installer, inputs,
reproducibility
}'
A real compile resolves a mutable base tag through Docker's registry client,
uses the resulting repository@sha256:digest reference in the actual FROM
instruction, and records both requested and resolved references. It also adds
observed Docker client/server versions and the emitted rootfs layer κ, exact
image ID, and sizes. The report is deliberately canonical: false and is not
embedded in the .holo file; save it separately when needed:
hologram --json compile examples/python-numpy-pandas/hologram.json \
--output numpy-pandas.holo \
| jq '.build_provenance' > numpy-pandas.provenance.json
Pass --no-build-cache to make Docker rebuild every generated rootfs layer.
Completed provenance records builder.cache_disabled: true, so saved evidence
can distinguish an uncached build from the normal developer path. Compare two
uncached builds locally with one machine-readable report:
just python-rootfs-repro | jq .
just python-rootfs-repro | jq '.equal, .identities'
The release gate runs one uncached build on each of two independent clean
Linux runners for both linux/amd64 and linux/arm64, then compares image,
rootfs-layer, application, archive, and footer identities within each target.
The architectures are intentionally not compared with each other because they
are different application artifacts. macOS and Windows server binaries can
drive a configured Docker-compatible Linux engine, but GitHub's hosted runners
do not supply one; they are release hosts, not additional rootfs build
platforms.
The first clean matrix completed all four builds with the same Docker 28.0.4
engine and pinned base but exposed target-local differences from BuildKit's
cross-stage directory serialization. After the canonical runtime-tar change,
workflow run 33035209550 passed with two matching clean replicas for each
Linux target. The amd64 image ID is
sha256:778bc5f5e4c66392b798ff8b6ad6178e42c80efff6a8ff44b18fbfc44573d31f;
the arm64 image ID is
sha256:1f55f44f41af891e3464b056f6b0beefbf6be9d736611de1505d17fb9a8cd754.
compile --check remains offline, so a mutable tag has no
resolved_reference until a real compile. A digest-pinned source.base is
already resolved and bypasses the registry lookup. Completed builds identify
the normalized Docker archive, source epoch zero, bundle schema 3, and exact
output and report reproducible: true. An offline check of a mutable base
reports false with a blocker only until real compilation resolves and binds
that base to an immutable digest; a digest-pinned check can report true
immediately.
Authenticated private registries use Docker's existing credential boundary;
Hologram has no registry username, password, or token option. The repository
gate starts a disposable loopback registry, proves offline validation and
anonymous rejection, logs in through an isolated DOCKER_CONFIG, then compiles
and runs the standard-library Python archive. It rejects credential-bearing
provenance fields and scans both reports and archive bytes for the fixture
credentials:
just python-private-registry | jq .
The rootfs release workflow requires this proof before accepting its clean-builder comparison.
hologram run preserves the binary-safe HoloRunResult envelope by default. Add --output-format text for UTF-8 application output or --output-format json for JSON application output. One decoded result prints directly; results from multiple --input arguments print in order, with JSON results collected into an array. Invalid text or JSON returns a typed protocol error instead of changing the bytes.
The raw envelope keeps output and completion distinct. Core-Wasm v1 returns
"completion":{"kind":"returned"} because its callable returned bytes but has
no process exit code. A provider with a real process status may return
"completion":{"kind":"exited","code":0}. Completion and authorization
evidence are required on every current result. Traps, nonzero processes, and
other failures remain typed errors rather than successful results with invented
status values.
Generate the same schema without hand-writing JSON:
hologram app init ./my-python-app \
--template python --profile rootfs \
--project . --entry my_package:main --lock uv.lock --arch arm64
Compilation stages only pyproject.toml, the declared uv.lock, and src/, then installs the locked third-party dependencies in a disposable Linux builder. It does not read or copy the host .venv, and it does not install the local project as a wheel because uv's local-project cache metadata contains source timestamps. The builder normalizes the application and launcher to epoch zero and writes them as one lexically sorted GNU tar. Hologram copies that artifact from a stopped builder container and constructs the final digest-bound image with local ADD, avoiding engine-selected cross-stage directory ordering without executing a foreign-architecture process. The runtime imports the staged application through /app/src; uv and transient build layers are excluded. Direct execution validates the archive and target architecture, disables networking, mounts the container filesystem read-only, drops Linux capabilities, enables no-new-privileges, and applies CPU, memory, PID, temporary-storage, input/output, and 30-second wall-clock limits.
This is an intentionally explicit demo provider, not the final untrusted-workload boundary: it requires a local Docker-compatible engine with Buildx registry inspection, supports direct fat archives only, and leaves cached OCI images behind for repeat runs. Bundle schema 3 re-addresses the exact image config and ordered layers as SHA-256 blobs, emits a canonical manifest and fixed tar headers, then applies fixed-level Zstandard compression. New archives record the exact image ID, so a warm local run skips decompression and docker image load only when that trusted ID is already present; a cold machine still restores the image from the archive. Compile once with an optimized release binary and reuse the resulting .holo; debug builds spend substantially longer hashing the roughly 100 MiB archive. just python-holo-demo builds and uses the release CLI, with a one-time optimized link on the first invocation. Mutable source.base tags are resolved and bound to a registry manifest digest before the build; an already digest-pinned value skips that lookup. Clean uncached builder equality is proven for both supported Linux target architectures. Capability-gated WASI and hardware-backed microVM rootfs execution remain planned. See the Python application guide, ADR 008, ADR 012, ADR 014, ADR 015, and ADR 017.
The demo writes one JSON document to stdout; progress and failures use stderr:
just python-holo-demo | jq .
just python-holo-demo | jq '.output'
just python-hello-demo | jq .
# Retain the verified archive instead of deleting the temporary artifact
just python-holo-package target/numpy-pandas.holo | jq '{archive, archive_bytes}'
target/release/hologram --json run target/numpy-pandas.holo \
--input examples/python-numpy-pandas/request.json
HoloTrade quote engine
examples/holotrade-quote/ is a worked port of
HoloTrade's deterministic
single-node pricing engine to the core-wasm-v1 guest contract. One
zero-dependency Rust file — built with plain rustc, no Cargo project — takes
a self-contained JSON quote request and returns the full price decomposition
P = P_base × E × G × D × H × Q × L, including the operating floor, margin,
carbon, and the locality multiplier computed from the W(3,3) symplectic
geometry inside the guest. The port is differentially checked against
HoloTrade's own JS PricingEngine on ten scenarios to 1e-9 relative
tolerance.
rustup target add wasm32-unknown-unknown
cd examples/holotrade-quote
rustc --target wasm32-unknown-unknown -O -C panic=abort \
--crate-type cdylib guest.rs -o app.wasm
hologram compile hologram.json -o holotrade-quote.holo
hologram run holotrade-quote.holo --input request.json --output-format text
It also makes a good persistent service: import the archive, declare its κ
under [[holo.resident]] in live.toml, and every hologram serve boot
loads it into a resident session, invocable at
POST /api/v1/holo/{kappa}/run. See the
example README for the request schema,
the ported-source inventory, and the fidelity notes.
Inference compatibility APIs
The daemon exposes OpenAI- and Ollama-compatible HTTP surfaces over the configured inference engine, including streaming:
curl http://127.0.0.1:11435/v1/models
curl -X POST http://127.0.0.1:11435/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"echo","messages":[{"role":"user","content":"Hello"}]}'
curl -X POST http://127.0.0.1:11435/api/generate \
-H 'content-type: application/json' \
-d '{"model":"echo","prompt":"Hello","stream":false}'
curl http://127.0.0.1:11435/api/tags
stream: true is accepted by every engine. Streaming request:
curl -N -X POST http://127.0.0.1:11435/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"echo","messages":[{"role":"user","content":"Hello"}],"stream":true}'
Every response — streaming or not — carries an x-hologram-stream header of
native or emulated. native means the engine produced deltas as it
generated them (currently ollama); emulated means the engine has no
incremental output, so the daemon ran the completion to finish and replayed
the result as a single delta (currently echo and weightc). The tokens are
identical either way — emulation reconstructs only the arrival schedule, never
the content — so on an emulated engine, time-to-first-token equals
time-to-full-completion. See specs/adrs/022-streaming-and-token-usage.md for
the full reasoning, including how token usage is reported (omitted, not
zeroed, when an engine did not measure it).
Both surfaces sit behind the same bearer-token seam as every other module route.
Third-party plugin modules
Beyond the built-in catalogue, the daemon hosts dynamic third-party modules as supervised subprocesses. Plugins are an explicit, sha256-pinned allowlist in live.toml — nothing is scanned or loaded implicitly:
[plugins]
enabled = true
[[plugins.modules]]
id = "com.example.weather"
path = "/usr/local/bin/weather-plugin"
sha256 = "<64 hex chars>"
Each plugin speaks a small gRPC contract (hologram.live.plugin.v1.PluginHost: describe/invoke/ping) over a Unix socket the daemon provides, runs under a supervised actor with a bounded mailbox and bounded restart, and receives a scrubbed environment. Its operations join the capability manifest and are reachable through the native client:
hologram plugins list
hologram plugins call com.example.weather weather.current '{"city":"Berlin"}'
Plugins have no host resource access in v1 — no store, config, or network mediation — and no HTTP routes; they are pure compute over JSON payloads. Wrapping plugin executables in microVMs is the documented hardening path. See specs/adrs/005-subprocess-plugin-boundary.md.
Built-in modules
Trusted modules are statically linked and registered in the builtin_modules! catalogue in src/modules/mod.rs:
| Module | Current responsibility |
|---|---|
dev.hologram.live.system | Health, capabilities, and module discovery |
dev.hologram.live.kappa-registry | Content-addressed registry provider (local or external Kappa Registry), with bounded metadata search |
dev.hologram.live.files | File upload, listing, search, renaming, and download |
dev.hologram.live.holo | .holo import, inspection, verification, cataloguing, and resident Wasm execution |
dev.hologram.live.history | Durable conversations and messages |
dev.hologram.live.chat | Conversation-backed chat over the configured inference engine |
dev.hologram.live.inference | Model import, listing, and removal for the engine boundary |
dev.hologram.live.openai-compat | OpenAI-compatible /v1 inference API, including streaming |
dev.hologram.live.ollama-compat | Ollama-compatible /api inference API, including streaming |
dev.hologram.live.control-plane | Minimal node inventory and heartbeats |
Each module declares its stable ID, dependencies, operation IDs, HTTP routes, and OpenAPI contribution. Operators can enable a subset with modules.enabled in live.toml. Executable module behavior remains compiled Rust rather than configuration-defined code.
Configuration
The current configuration schema is version 2. Generate a complete file with hologram init, inspect it with hologram config show, and validate the installation with