Get started with hal0 — the local AI inference appliance
hal0 turns a Linux box into a polished, OpenAI-compatible inference appliance. Every workload runs in its own podman container supervised by systemd; one control plane on :8080 serves the dashboard, routes /v1/* requests, and stewards the whole box through its built-in brain agent. This guide covers a fresh install for new operators, a clean upgrade from the 0.9.x beta line to v1.0 for existing boxes, and a map of how the pieces fit together.
This guide targets the hal0 1.0 line (current build: v1.0.0-rc.3 on the preview channel). Full reference documentation lives at hal0.dev/docs — this page is the guided tour; links throughout point at the deep-dive pages.
OrientationThe shape of the platform
Four ideas explain almost everything in hal0: slots run things, models describe things, profiles tune things, and one control plane supervises it all.
Slot — a running seat
One podman container + one hal0-slot@<name>.service unit + one port. A slot owns its physical launch facts: device (gpu-vulkan, gpu-rocm, cpu, npu), runner image & binary, threads, NGL, autoload (start at boot?), pinned (exempt from eviction?), and eviction priority. → concepts/slots
Model — a logical asset
A row in the local registry pointing at weights on disk. Models own logical behavior: chat template, context length, tune defaults (extra args, parallel), and the typed capabilities — MTP, Thinking (auto/on/off), vision. Device-agnostic: the same model can serve from any slot. → guides/pull-and-register-models
Profile — a flag bundle
A bench-tuned llama-server flag template per backend (vulkan, rocm, rocm-moe, dedicated embed/rerank lanes, …). Picking one stamps defaults onto the slot — later edits belong to the slot, not the profile. Since 1.0 the profile rides the slot's model choice, per slot. → concepts/capabilities-and-profiles
Control plane — hal0-api
hal0-api on :8080 owns the slot state machines, routes OpenAI-compatible /v1/* calls to the right slot (use the slot name as the model id), serves the dashboard, and exposes the MCP mounts (/mcp/admin, /mcp/memory) that agents use. → concepts/architecture
Who owns which flag
At launch, hal0 layers three tiers into the final runner command. Knowing which tier owns a setting tells you where to edit it — and which migrator moves it (see migrations).
What's around the core
- Seeded capability slots — a fresh install seeds
agent(the capable default/fallback anchor),utility(cheap helper for background jobs),brain,coder,embed,rerank,tts,img, and the NPU-backedflmtrio. Capabilities are on-demand — enable/disable from the dashboard. → guides/manage-slots - hal0-brain — the built-in platform steward behind the dashboard's Agent Chat button (
POST /api/brain/chat). It reaches the whole 180-tool admin MCP catalog: create slots, pull models, run benchmarks, read health — with an operator-approval gate on destructive actions. → concepts/agents - Hermes agents — installable autonomous agents (
hal0 agent install hermes) with personas, driven through the same MCP surface. → guides/run-agents - Hindsight memory — long-term memory service for agents and chat, exposed over the
/mcp/memorymount (26 tools). → concepts/memory, guides/enable-memory - OpenWebUI — prewired chat UI on
:3001, pointed at the local API out of the box. → getting-started/first-chat
PrerequisitesRequirements
hal0 runs on any systemd Linux on x86_64. The installer self-detects your package manager, container runtime, and GPU/NPU backend — there is no minimum-spec gate; backend selection degrades to CPU rather than refusing to run.
Host
- Linux, x86_64 (amd64-only binaries; no ARM/macOS/Windows)
- systemd (
systemctlonPATH; not needed in--devmode) - Python ≥ 3.12 — auto-resolved or auto-installed if the default is older
- A container runtime — podman preferred (auto-installed if absent); docker accepted if already present
- Disk for models — point pulls at a big volume with
--models-dir - Free ports:
8080(API + dashboard),3001(OpenWebUI), + the slot pool
Hardware
- first-class AMD Strix Halo — Ryzen AI Max+ 395 (128 GB) and 385/390 (64 GB): iGPU + XDNA NPU + unified memory
- supported AMD RX 7000-series discrete GPUs
- experimental NVIDIA RTX 30/40/50 via CDI + NVIDIA Container Toolkit
- fallback any x86_64 CPU-only host
Full detail: reference/hardware-matrix · concepts/strix-halo · getting-started/drivers
Inference backends
ROCm seeded, Vulkan faster
LLM slots name their device explicitly and the runner is pinned to it. ROCm is what hal0 seeds on AMD — for validation history and MTP tuning, not speed — and needs /dev/kfd (on Proxmox LXC add dev1: /dev/kfd). Vulkan needs only a render node, so it serves boxes where /dev/kfd was never forwarded, and on Strix Halo it now measures faster: +13.96% prefill, +20.45% decode. Switch with hal0 slot edit <slot> --hardware vulkan. The lane is gated on a Vulkan-validated runner image — the old ade07ba lineage emitted invalid tokens for every model on Vulkan and is refused by name — and every slot must then clear an output-sanity probe before it is marked ready.
ROCm · NPU · CPU
ROCm/HIP for discrete AMD, AMDXDNA NPU via FastFlowLM (STT/embed/small LLMs), and a fully-supported CPU path (incl. Moonshine CPU STT). Devices are picked per-slot; you can pin any of them.
The installer runs as root (re-execs under sudo if piped through plain bash). hal0-api runs as root so it can apply updates + manage units. A dedicated hal0 system user runs the non-root services (agents, hermes-gateway, hindsight-api). Model containers (podman) are the sandbox boundary. Auth & network posture: operate/auth · concepts/security.
Installing under Proxmox or WSL? Dedicated walkthroughs: Proxmox LXC (GPU/NPU passthrough) · bare metal Ubuntu · WSL (experimental).
New usersInstall hal0
The one-liner
# The bootstrap is the trust boundary: it fetches the signed release # manifest, cosign-verifies the tarball, unpacks it, and hands off to # the real installer (installer/install.sh). curl -fsSL https://hal0.dev/install.sh | sudo bash # Pick a release channel (default: stable) HAL0_CHANNEL=preview curl -fsSL https://hal0.dev/install.sh | sudo bash
Common variations
# Point model pulls at a larger disk curl -fsSL https://hal0.dev/install.sh | sudo bash -s -- --models-dir=/mnt/ai-models # Set everything up but don't start the units yet curl -fsSL https://hal0.dev/install.sh | sudo bash -s -- --no-start # From a clone: local-only dev install under $PWD/.hal0ai (editable, no systemd) bash installer/install.sh --dev
Environment overrides
| Variable | Default | Purpose |
|---|---|---|
HAL0_CHANNEL | stable | Release channel: stable · preview · nightly |
HAL0_PREFIX | /usr/lib/hal0 | Install root (versioned dir + current symlink) |
HAL0_PORT | 8080 | API + dashboard port |
HAL0_MODELS_DIR | /var/lib/hal0/models | Where Hugging Face pulls land (same as --models-dir) |
HAL0_PYTHON | python3 | Interpreter to build the venv from |
HAL0_NONINTERACTIVE | — | =1 forces flag/env values instead of prompting |
HAL0_NO_PROBE | — | =1 skips the hardware probe / first-run seeding |
HAL0_SKIP_HINDSIGHT | — | =1 skips installing the Hindsight memory service |
HAL0_PY_AUTOINSTALL | — | =1 lets the installer auto-install Python 3.12 |
What the installer does
Sixteen idempotent steps — safe to re-run after a partial failure, and existing config files are never clobbered. Condensed (full list: getting-started/install):
- Pre-flight — architecture, systemd, Python, container runtime, disk, free ports. Accurate diagnostics on failure (see troubleshooting).
- Layout + venv — code at
/usr/lib/hal0/hal0-<version>behind acurrentsymlink; thehal0service user; config in/etc/hal0/; state in/var/lib/hal0/. Links/usr/local/bin/hal0.--devlands everything under$PWD/.hal0ai/. - Dashboard UI — release tarballs ship a prebuilt
ui/dist; source checkouts build it (Node auto-provisioned). - Config + units — writes
hal0.toml,api.env,upstreams.toml,openwebui.env; installs + enableshal0-apiandhal0-openwebui(unless--no-start). - Slot seeds — copies the curated slot TOMLs (
agent,brain,coder,utility,embed,rerank,tts,img,flm, …) into/etc/hal0/slots/, never overwriting operator edits. - Hardware-anchored provisioning — probes CPU/RAM/GPU/NPU into
/etc/hal0/hardware.json, seeds a hardware-recommended steward slot + profile, installs the FastFlowLM NPU runtime on AMDXDNA/apt hosts (fail-soft elsewhere), sets up the ComfyUI model share, writes the first-run bundle catalog, installs bundled agent skills, and starts the services.
New usersFirst run
There's no separate setup wizard — the install itself probes your hardware and provisions a coherent set of slots, models, and extensions in one pass. Your first run is a verification lap.
- Open the dashboard — browse to
http://<host>:8080. The Overview page shows the unified-memory map, throughput, and the seeded slot roster. A first-run bundle picker offers curated, RAM-sized model collections if you'd rather start from a preset. - Verify health —
hal0 doctor allshould report green across slots, memory, Hermes, services, and (new in 1.0) the MCP mounts;GET :8080/api/healthreturns200. - Look around — the Slots page (sidebar → Slots) is mission control: per-slot cards with model select, live tok/s / TTFT / ctx, device + profile pills, and lifecycle buttons.
# Sanity from the CLI hal0 doctor all --json hal0 slot list hal0 status curl -fsS http://127.0.0.1:8080/api/health
First model & first chat
Models live in a local registry — pull one from Hugging Face, bind it to a slot, talk to it. (Deep dive: first-model · first-chat · choose-models.)
# See what's already registered, pull a starter model hal0 model list hal0 model pull qwen3-4b # Bind it to the agent slot and confirm hal0 slot edit agent --model qwen3-4b hal0 status # Chat — the slot NAME is the model id on the OpenAI surface curl http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"agent","messages":[{"role":"user","content":"hello!"}]}'
Prefer a UI? OpenWebUI is prewired on http://<host>:3001, already pointed at the local API. Or hit the dashboard's Agent Chat button and let the brain steward do the driving:
Models own logical behavior (chat template, tune defaults, MTP/Thinking/vision). Physical launch facts — device, image, binary, threads, NGL — belong to the slot. Profiles stamp starting points. See the ownership diagram.
Existing usersUpgrade from beta — land cleanly on 1.0
Upgrading a 0.9.x beta box is an idempotent, in-place operation — not a reinstall. The swap is a cosign-verified, atomic symlink flip of /usr/lib/hal0/current; your config (/etc/hal0) and state (/var/lib/hal0) are untouched by the swap itself. Full mechanics: guides/update-and-rollback.
Which path is yours?
| You're on | Path to 1.0 | Notes |
|---|---|---|
0.9.8 |
hal0 update (or re-run the installer) |
The full R5 migration set applies — read Migrations first. The old 0.9.8 CLI reports a spurious error at the very end of a successful update; verify with hal0 --version + curl :8080/api/health. |
older than 0.9.8 |
step through 0.9.8 first |
Then follow the 0.9.8 row. |
1.0.0-rc.1 |
hal0 update --target 1.0.0 |
An rc.1 box can't see later releases as available (its version comparison predates the fix, #1663). --target bypasses the availability gate and pulls the release directly. |
1.0.0-rc.2 / rc.3 |
hal0 update |
Plain update on the preview channel; no new migrations. |
# Check what your channel offers, then apply hal0 update --check hal0 update # stages, shows release notes + breaking/migration callouts, confirms, applies # Switch channels first if needed (persists in hal0.toml, also runs a check) hal0 update --channel stable # Pin a specific version / escape the rc.1 visibility bug hal0 update --target 1.0.0
The first hal0 update that crosses the v1.0 schema boundary wipes and reseeds /etc/hal0/profiles.toml (seed profiles now live in code; the file holds only your custom entries). hal0 backs the file up unconditionally to /var/lib/hal0/backups/profiles-<timestamp>.toml and asks for consent if custom profiles are present — but there is no restore command, and rollback does not undo it. If you hand-wrote profiles, copy the file aside first: sudo cp /etc/hal0/profiles.toml ~/profiles.toml.bak.
Fetch manifest → download tarball + cosign signature → verify digest + OIDC identity (a failed verification aborts, nothing swapped) → extract + run forward config migrations → atomic current symlink swap → re-render every slot unit through the new code (running slots are not bounced; a "N slots need restart" banner appears — pass --restart-slots to bounce them) → record the prior tree for rollback.
Migrations — what's automatic, what's operator-run
v1.0 re-drew the ownership lines between slots, models, and profiles (see the diagram). One sweep runs automatically; the rest are deliberate, operator-run, dry-run-by-default one-shots for a maintenance window. Your box keeps working on the old shapes until you run them (see back-compat).
Automatic at first v1.0 boot
| What | Detail |
|---|---|
enabled sweep |
SlotConfig.enabled is gone — a bound model is the activation signal. The boot sweep drops the key from slot TOMLs; a slot that was enabled = false with a bound model gets the model cleared so it stays off. Re-runnable on demand: hal0 slot migrate-enabled-removal (dry-run; --apply to write). |
profiles.toml reseed |
One-time, during the update that crosses the v1.0 boundary — see the warning above. |
Operator-run, at a window
All four share the same shape: bare command = dry-run preview, --apply writes after a timestamped backup under /var/lib/hal0/backups/, --yes skips the prompt, and --stop-services stops hal0-api + active hal0-slot@* units first (without it, --apply refuses while they're active). Full CLI reference: reference/cli.
| Command | Moves what, where |
|---|---|
hal0 slot migrate-flags |
Slot-era tunes → the bound model's defaults: extra_args, parallel, per-slot chat_template overrides (layered over profile flags). Refuses the whole run — writes nothing — if two slots would fold divergent tunes onto one shared model; resolve (pick one tune, or split the model row) and re-run. |
hal0 slot migrate-caps |
Slot-era capability tri-states → the model: mtp, enable_thinking, vision. An existing curator-set model default always wins; conflicts between slots sharing a model are reported, not silently dropped. |
hal0 slot migrate-hw |
Model/profile-era physical facts → the slot's typed hardware grid: NGL, runner binary, deliberate image pins. Clears the folded model hardware columns. |
hal0 slot migrate-id-keying |
Optional. Flips every slot artefact from name-keyed (<name>.toml, hal0-slot@<name>) to stable id-keyed (<id>.toml, hal0-slot@<id>). The runtime reads both layouts — flip when you choose. Since rc.3, id-keyed boxes are first-class across every lane. |
# The pattern, using migrate-flags as the example — same for -caps and -hw sudo hal0 slot migrate-flags # 1. dry-run: read the per-slot plan sudo hal0 slot migrate-flags --apply --yes --stop-services # → "backup written to /var/lib/hal0/backups/<timestamp>.tar.gz" sudo systemctl daemon-reload && sudo systemctl start hal0-api hal0 doctor # 3. verify: expect all-green # Optional: flip to id-keyed slot artefacts sudo hal0 slot migrate-id-keying # dry-run plan (name → id) sudo hal0 slot migrate-id-keying --apply --yes --stop-services hal0 slot list && systemctl list-units 'hal0-slot@*'
Coming from much older boxes
| Situation | What to run |
|---|---|
v0.1.x model store under /mnt/ai-models/ |
hal0 migrate model-layout — builds the canonical <recipe>/<capability>/ symlink tree under /var/lib/hal0/models/. Writes only symlinks; source files never move. Dry-run by default, --apply to write, verify with hal0 doctor migrations + hal0 doctor models. |
| Box that ran the Honcho memory engine | Nothing. Honcho was removed outright; Hindsight is the only memory engine and starts fresh. There is no data carry-over path and no command to run. |
Why your box keeps working before you migrate
Tolerant config
Legacy slot and model keys load without a boot crash — old fields are read (or ignored with a one-time deprecation warning, like the retired lru key) until you run the relevant migrator. Dry-run any of them to see exactly what will change.
Bilingual runtime
Name-keyed and id-keyed slot layouts are both fully supported. Update swaps code; the one-shot migrations stay operator-controlled.
Rollback
hal0 update --rollbackswapscurrentback to the previous tree (recorded in/var/lib/hal0/hal0.previous). It never touches config — theprofiles.tomlreseed and applied slot migrations are not undone by a code rollback.- Release tarballs are immutable and cosign-signed — any prior tag can be re-installed from its GitHub release.
- Rolling back past the v1.0 boundary needs the config backup taken before upgrading: pre-v1.0 code reads a missing
enabledas true, so slots the sweep cleared come back model-less rather than re-enabled. - The slot migrators write timestamped backups under
/var/lib/hal0/backups/before changing anything — restoring one is the undo.
ReleaseWhat's new in 1.0
The 1.0 line is the sum of the R5 rework (rc.1), the feature-complete GA content (previewed by rc.2), and a 38-fix hardening pass (rc.3). The short version, by theme — the full story is in the CHANGELOG.
Ownership model finalized R5
- A slot is
(id, name, model, port, state)plus a typed hardware grid; models carry the materialized tune indefaults; profiles are per-slot templates that stamp, not bind. The migrators in Migrations move existing boxes across. - New slot lifecycle controls:
autoload(binding a model no longer implies boot start) and evictionpriority(0–100, default 50). The inertlru = trueopt-in is retired — every non-pinned resident slot is now an eviction candidate under memory pressure;pinnedis the only exemption. - Stopping a slot is
POST /api/slots/{name}/unload(409slot.pinnedon pinned slots) — config writes no longer have lifecycle side effects. - On-demand capability slots: disabling a capability clears the slot's model (the pick survives in
capabilities.toml); the retired vision lane and curated type-tag chips are gone in favour of typed model fields.
Agents & memory
- Admin MCP catalog: 92 → 180 tools — services, ComfyUI, updater/doctor/health, hardware + request telemetry, slots/models long-tail, bench, activity, approvals, runner images, NPU load/unload. The brain steward reaches all of it through
POST /api/brain/chat. - Memory MCP surface at feature parity with Hindsight 0.8.4: 26 tools (was 5) — reflect, curate/history, mental models, directives, async operations, bank introspection. The
/mcp/memorymount dropped to CLIENT-tier: memory-only agents no longer need the platform-admin key. - Hermes bootstrap MCP wiring fixed end-to-end (auth token injection,
--repairrefresh after key rotation);hal0 doctor allgained live MCP-mount and Hermes-auth preflights.
Voice, image, dashboard
- Moonshine reinstated as the CPU STT engine in its own toolbox image —
voice.sttis a device-keyed switch:cpu→ Moonshine,npu→ whisper-v3:turbo (FLM trio), GPU → no STT engine (the old silent fall-through to a llama chat profile is fixed). - Image Gen pane: state-typed engine pill (stopped/starting/running/generating·%/error) and a real Stop that drives the GPU arbiter back to inference mode.
- Slot drawer: runner image is a catalog dropdown (with a custom-ref escape hatch) that repopulates the binary dropdown; Auto-Load and Pinned pair in the header; profile rides the model choice.
GET /api/slotslatency cut sharply on wide boxes (single-flight snapshot, TTL-cached image inspects, no inspects for stopped slots).
Hardening (rc.3)
- The update path works end-to-end on real hosting: manifest fetch follows redirects, the privileged stage honours
HAL0_RELEASES_URL, wrappers refresh on every activate. - Id-keyed boxes are first-class in every lane (capability toggles, NPU trio, config writes).
- Memory surfaces tell the truth: real operations envelope, accurate bank-delete blast radius, per-agent stats scoping, ACL-aware pagination, extraction-slot changes actually reach the daemon.
- Removed: the experimental standalone browser MCP server (never mounted; Hermes brings its own browser tooling).
[brain_chat] tool_modelis wired for real: rc.1 deleted the key because nothing read it, and it came back with an implementation behind it. Tool rounds run ontool_model(defaulthal0/agent) while chat stays on the brain slot; set it tooff,noneordisabledto route tool turns nowhere.
OperateTroubleshooting
hal0 doctor all --json reports health across slots, memory banks, Hermes, services, and the MCP mounts in one pass. For a permissions-specific audit, hal0 doctor perms; for pending model-layout links, hal0 doctor migrations. Logs live on the dashboard's Logs page and in journalctl -u hal0-api / -u 'hal0-slot@*' — see guides/logs-and-activity.
Known issues on the beta → 1.0 path
- 0.9.8's CLI reports a spurious error at the end of a successful update — it polls the API it's restarting and treats the mid-restart connection refusal as fatal. Verify the real outcome:
hal0 --version+curl http://127.0.0.1:8080/api/health. - The profile-catalog reset doesn't fire during the 0.9.8 → 1.0 update itself (#1585) — the commit phase runs inside the old daemon. Nothing is lost; the reset lands on the next update applied by v1.0 code.
- rc.1 boxes see "nothing to update" (#1663) — use
hal0 update --target <version>(see upgrade paths). - First-boot installs can lose the dpkg lock race to
unattended-upgrades(#1584) — hermes provisioning degrades gracefully; re-runhal0 agent install hermesafterwards.
unprivileged LXC Install refuses at the container-runtime preflight
Symptom: podman run fails with crun: create keyring: Disk quota exceeded. Cause: the host kernel keyring byte-quota (kernel.keys.maxbytes) is exhausted — usually leaked keyrings from repeated failed healthchecks. hal0 correctly refuses (a real slot couldn't start either).
# Clear it, then re-run install # reboot the container — or — keyctl clear @s # or raise the quota on the Proxmox / host kernel sysctl -w kernel.keys.maxbytes=2000000
GPU Slot runs but on CPU / GPU access denied
If /dev/dri/renderD128 is owned by a group that isn't your host's render group, older builds group-added the wrong GID. v1.0 derives the GID from the device node itself. Confirm your hal0 user is in the device's owner group:
stat -c '%G %g' /dev/dri/renderD128 id -nG hal0
Driver/firmware setup (incl. Proxmox passthrough): getting-started/drivers · getting-started/proxmox.
Hardened host: permissions look wrong after install
A CIS/STIG umask (0027/0077) leaking into the install can leave the tree 0700. The installer normalizes to 022 for its own body; if you see PermissionError reading /usr/lib/hal0 or /etc/hal0/slots, run hal0 doctor perms --fix.
ReferencePaths & commands
Filesystem
/usr/lib/hal0/current | active code (atomic symlink) |
/etc/hal0/ | config (hal0.toml, slots/, profiles.toml, api.env) |
/etc/hal0/hardware.json | probe result (CPU/RAM/GPU/NPU) |
/var/lib/hal0/models | model store + canonical symlink tree |
/var/lib/hal0/hal0.db | model registry + slot identity |
/var/lib/hal0/backups | migration + profiles.toml backups |
/var/lib/hal0/slots | per-slot runtime state |
Full map: reference/paths-and-files
Everyday commands
hal0 doctor all | health across the platform |
hal0 slot list / edit | manage inference slots |
hal0 model list / pull | manage the registry |
hal0 status | box summary |
hal0 update [--check] | atomic in-place upgrade |
hal0 update --rollback | swap back to the previous tree |
hal0 agent install hermes | install the Hermes agent |
Full surface: reference/cli · API: reference/api
Environment variables
The install-time variables are in the Install section. These are the runtime ones you'll actually reach for — the full table (60+ vars, grouped by subsystem) is at reference/env-vars.
| Variable | Default | Purpose |
|---|---|---|
HAL0_PORT / HAL0_BIND_HOST | 8080 / 0.0.0.0 | API port and bind host (also read by hal0 serve) |
HAL0_API_URL | http://127.0.0.1:8080 | Base URL the CLI uses to reach hal0-api — set it when driving a remote box |
HAL0_REQUIRE_AUTH | — | Overrides [security].require_auth; keys live in /etc/hal0/api.env (HAL0_ADMIN_KEY, HAL0_CLIENT_KEY — see hal0 auth) |
HF_TOKEN / HUGGING_FACE_HUB_TOKEN | — | Hugging Face auth for gated models (first-set wins; used by install, model routes, pull jobs) |
HAL0_MODEL_STORE | — | Model-store root override (env → [models].store → /var/lib/hal0/models) |
HAL0_FLM_MODELS_DIR | — | NPU/FLM model-store override |
HAL0_CONTAINER_RUNTIME | auto-detect | Override the podman/container runtime binary |
HAL0_HINDSIGHT_URL | http://127.0.0.1:9177 | Hindsight memory engine base URL |
HAL0_RELEASES_URL | official manifest | Point the updater at a self-hosted release manifest (redirects OK since rc.3) |
HAL0_HOME | — (FHS paths) | Dev/test-only: relocate all root paths (etc, var_lib, …) under one directory |
NO_COLOR / HAL0_PLAIN | — | Disable ANSI color / force ASCII-only CLI output |
hal0-api.service serves the control plane + dashboard on :8080; hal0-openwebui.service serves chat on :3001. Slot units are hal0-slot@<name> (or hal0-slot@<id> after id-keying).
Agents, Hermes gateway, and Hindsight run as the non-root hal0 user. Slot lifecycle detail: reference/slot-lifecycle.
This guide is the tour — the complete reference lives at hal0.dev/docs: getting-started walkthroughs (Proxmox, bare metal, WSL), concepts, how-to guides, the CLI/API/config-schema references, and operations runbooks.