The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Hologram Model Hub listing page.

Hologram Live is a local-first module host for the Hologram ecosystem. This repository produces two independent products:
hologram binary, containing the CLI and background service.hologram as a managed sidecar and embeds the shared .holo application 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.
The preparation step builds the server sidecar before Tauri opens. The desktop app can:
.holo archives,
and inspect verified archive metadata;Build an installable desktop bundle with:
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.
Run the service in the foreground instead with:
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:
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:
Open the endpoint for the built-in status page. API documentation is available at:
/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.
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:
Help and shell-completion text retain Clap's human-readable format.
Create a thread, copy its returned ID, and send a message:
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:
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:
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:
Threads can be archived instead of deleted. Archived threads keep their messages and their updated_at_millis, but drop out of the default listing:
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.
File bytes are addressed by their BLAKE3 content ID. Renaming changes only persisted filename metadata; the ID and bytes remain unchanged.
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.
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:
blake3: id → the local catalogPulling 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.
Applications can talk to a daemon without depending on it. hologram-client is
a standalone crate in this workspace:
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 archivesGenerate a validated source manifest interactively:
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:
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.
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:
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.
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:
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:
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:
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:
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:
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:
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:
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:
With --json, compilation reports the three distinct identities directly:
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:
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.
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:
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:
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 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:
wasi-component packages Python, CPython, and locked pure-Python
wheels into an import-free WasmCodemodule selected by
hologram:guest/component@1; it runs directly or resident without Docker;rootfs packages 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/:
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:
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:
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:
Run two compiler invocations with separate Hologram and uv caches and query the
single JSON result with jq:
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:
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:
Both validation and compilation expose non-canonical rootfs build evidence in JSON. Validation hashes the declared project inputs without contacting Docker:
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:
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:
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:
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:
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:
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.
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.
The daemon exposes OpenAI- and Ollama-compatible HTTP surfaces over the configured inference engine, including streaming:
stream: true is accepted by every engine. Streaming request:
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.
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:
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:
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.
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.
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 hologram doctor.
Local structured tracing is always available. OTLP export is enabled only when a collector endpoint is configured:
OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME override those values. No telemetry network connection is made when the endpoint is unset.
Object storage selects a provider. The default keeps a stock install self-contained, needing no external service:
local keeps every object on this machine. kappa serves them from an external
Kappa Registry instance over
its OCI blob and manifest surface. Both systems address objects as
blake3:<64 hex> — already a valid kappa-label upstream — so identity crosses
the boundary unchanged.
An unknown provider name, or kappa without an endpoint or namespace, fails
validation at startup rather than falling back to local storage: an operator who
asks for a remote registry is told it cannot be built, instead of discovering
later that their objects never left the machine.
max_scan_pages bounds how many upstream tag pages one selective query walks,
since the remote registry cannot filter on kind or filename. Reaching that bound
sets truncated on the result rather than silently returning a short page.
Both providers are held to one behavioural contract by a conformance suite. Run
it against a real registry with just kappa-registry, which builds a pinned
kappa-registry, starts it on an ephemeral port, and executes the suite.
Adding this section does not require a configuration rewrite: every section is
defaulted, so a file written before [registry] existed keeps loading unchanged.
The client can route to local or remote authorities after a capability handshake. Non-loopback remote endpoints require HTTPS, and authentication, authorization, TLS, and integrity errors never trigger fallback to another authority.
Cluster membership defaults to the local server origin and needs no setup for a
single node. Service installations set reachable origins and seeds in the same
file used by hologram start:
When the environment variable named by token_env is absent, Hologram creates
and reuses <state_dir>/cluster.token. A joining host must receive the seed's
token through a secure channel, either in that environment variable or by
copying it to the joining host's token file before startup. The token is never
logged or exchanged by the join protocol.
Hologram membership and Kappa data distribution are intentionally separate layers. Joining discovers Hologram frontends and their capabilities; it never replays a Docker upload or another mutation against a different server. With the OCI feature enabled, each frontend owns a Kappa-backed registry volume; Kappa's data-plane federation remains responsible for content convergence. Keeping the layers separate preserves Docker upload-session ownership.
The kernel owns configuration, lifecycle, capability-aware routing, authenticated dispatch, tracing, audit, update coordination, and actor supervision primitives. Product behavior belongs to modules. Kameo actors are used only for long-lived mutable state or bounded background work; ordinary request handlers remain ordinary Rust code.
The source tree enforces the product boundary:
The desktop never owns a second server implementation. Its preparation script
builds the root hologram-live package explicitly and copies the resulting
hologram executable into Tauri's ignored sidecar staging directory. The
application-watch crate owns reusable persistence and debounce behavior outside
src-tauri; the desktop adapter supplies user-approved paths, fixed
compile/import commands, and UI events. A cloud server build therefore needs
only Rust and the root package—no Tauri, WebKit, Node.js, or desktop source.
The product-boundary verification gate inspects the resolved Cargo graph and
fails if Tauri or the application-watch crate enters the standalone server.
Server and desktop versions advance independently and create separate GitHub Releases:
server-v<version> publishes standalone Linux, macOS, and Windows server archives plus SHA256SUMS.desktop-v<version> publishes Linux, macOS, and Windows Tauri installers.docs-v<version> deploys the versioned Astro documentation site to GitHub Pages.The desktop installer includes a server sidecar for local operation, but it remains a desktop release. See RELEASING.md for version checks, tags, and the artifact matrix.
Build either product locally with:
This builds the release binary and installs hologram into ~/.local/bin. Set HOLOGRAM_INSTALL_PREFIX to choose a different prefix.
The server crate declares Rust 1.94 as its minimum supported version, matching Wasmtime 46's compiler floor. rust-toolchain.toml currently pins Rust 1.97.1 for repository development and release CI. The desktop and documentation projects also require Node.js and npm; just is used for the common project recipes.
Run the complete server verification path with:
That includes formatting, source-size policy, checks, unit tests, Clippy, public-boundary BDD scenarios, a release build, and an isolated end-to-end smoke test. The smoke test covers strict configuration validation, module discovery, file storage and renaming, .holo operations including resident Wasm execution, chat persistence through the default echo engine, model listing, the OpenAI/Ollama compatibility endpoints, OpenAPI generation, and shutdown.
Useful development commands:
To use just dev, install cargo-watch once:
The default build does not yet provide:
chat by reference; orChat runs against the configured inference engine (echo remains the default), Wasm-layer .holo archives—including Python Components with locked pure-Python wheels and the capability-gated exact-object and typed-graph read profiles—execute resident, direct Python OCI rootfs archives execute through the experimental local container provider, and the weightc engine can keep resident per-conversation sessions. Tensor execution, inference-model provider invocation, resident rootfs execution, native Component dependencies, remaining Hologram host profiles, capability-gated WASI, transitive graph writes, deterministic Python Component output, and the production microVM provider remain future work. Missing runtime capabilities return a typed LIVE_CAPABILITY_MISSING error rather than simulating success.