Paths & files reference

All roots are resolved by hal0.config.paths and are HAL0_HOME-aware — see
Environment variables. Production layout (with
HAL0_HOME unset) follows the FHS:

Root Path Purpose
usr_lib() /usr/lib/hal0/current Code — a symlink to the currently active versioned install dir.
lib() /usr/lib/hal0 Shipped read-only tree: bin/ wrappers, versions/, hooks.
etc() /etc/hal0 User-editable config, preserved across updates.
var_lib() /var/lib/hal0 Mutable runtime state, preserved across updates.
var_log() /var/log/hal0 Optional supplementary log files — journald is the primary log sink.

With HAL0_HOME=$X set (dev installs, tests): usr_lib = $X/usr-lib/hal0/current,
lib = $X/usr-lib/hal0, etc = $X/etc/hal0, var_lib = $X/var-lib/hal0,
var_log = $X/var-log/hal0.

/etc/hal0/ layout

/etc/hal0/
├── hal0.toml                # main config
├── api.env                  # 0600, systemd EnvironmentFile — secrets/keys
├── openwebui.env
├── hardware.json            # probe snapshot
├── manifest.json            # release manifest, image digest pins
├── profiles.toml            # optional — seeds apply if absent
├── stacks.toml               # optional
├── slots/                   # one <name>.toml per slot
├── agents/<name>.toml       # per-agent MCP allowlist/config
└── *.lock                   # advisory RMW lock siblings

api.env is written with mode 0600 — it carries the admin/client bearer keys and
other secrets. See Config schema for every file’s
key layout.

/var/lib/hal0/ layout

/var/lib/hal0/
├── hal0.db                  # primary SQLite — registry, PortAuthority, metrics, runtime state
├── activity.db(+wal/shm)    # audit trail SQLite
├── .first-run.lock          # 0600, single-use OTP for first-run claim
├── .first_run_done          # first-run completion sentinel
├── registry/
│   └── registry.toml(+.lock) # model registry
├── models/                  # default model cache (unless overridden — see below)
├── slots/<name>/             # per-slot working dir
├── agents/
│   ├── secret.bin            # 0600 HMAC secret for the agent chat proxy
│   └── <name>/workspace/     # per-agent sandbox root (chroot/bind-mount target)
├── stacks/state.json         # active-stack pointer + content hash
├── openwebui/                 # OpenWebUI state
├── .hermes/runtime.json      # Hermes runtime info (embed token, etc)
└── .config/flm/models/       # default FLM (NPU) model cache

Model store resolution

The default model cache is /var/lib/hal0/models. Resolution order:

  1. HAL0_MODEL_STORE env var, if set.
  2. [models].store (or the deprecated [models].pull_root) in hal0.toml, via
    effective_store().
  3. /var/lib/hal0/models (the models_dir() default).

[models].roots (default: just the effective store) plus the effective store itself,
deduplicated, form the full scan set (scan_roots()).

DEFAULT_MODEL_STORE = /mnt/ai-models is a historic convention for an external
NFS/fast-disk mount used as a slot container bind target — it’s not a resolver default,
just a documented convention some installs still use.

Other notable paths

Path Purpose
~/.local/share/hal0/models XDG-style per-user model store — a UI display option only, not a resolver default.
$PWD/.hal0ai (or $HAL0_PREFIX) Dev-mode install root, used by hal0 uninstall --dev.
$HERMES_HOME (default ~/.hermes) Hermes agent home directory.

Lock files

hal0 uses a generic sibling-lock pattern (hal0.config.locking.file_lock()):
<target>.lock next to any config file, advisory fcntl.flock.

Pattern Purpose
/etc/hal0/*.lock Advisory read-modify-write locks on config writes.
/var/lib/hal0/*.lock Advisory read-modify-write locks on state writes.
/var/lib/hal0/registry/registry.toml.lock Registry cross-process write lock (sidecar file).
<slots_dir>.lock Slot-config directory-level lock.
<record>.lock Per-record lock for MCP installed.py read-modify-write.

Temp file prefixes (atomic-write pattern)

hal0 writes config and state files via tempfile.mkstemp in the same directory,
fsync, then os.replace — so readers never see a torn write. Recognizable prefixes;
every one of these is transient — created, then either unlinked or renamed away,
never left on disk as a final artifact:

Prefix Used by
.hal0-writeprobe-{pid} Write-permission probe (hal0.agents.hermes_provision.path_is_writable) — touched then immediately unlinked in the same call, to test whether an ancestor directory is actually writable (not just os.access-writable, which lies under SELinux/ACLs/NFS root-squash). Never renamed into place.
.hal0-gpu-arbiter-*.tmp GPU arbiter state.
.hal0-state-*.tmp Slot state / ID-keying migration.
.hal0-stack-state-*.tmp Stack state.
.hal0-env-*.tmp Atomic env-file writer (config/env.py).

Persistent markers

These are not temp files — each is written once and left in place as the
final artifact, checked again on a later run. Don’t confuse them with the
atomic-write temp files above.

Marker Used by
.hal0-managed Stamped into a claimed HERMES_HOME/agent data dir the moment hal0 takes ownership of it (hal0.agents.hermes_provision). It persists for the life of the install: hal0 agent uninstall rmtrees an agent home only when this marker is present, refusing to touch a directory (e.g. a user’s pre-existing ~/.hermes) that lacks it — see _safe_to_remove_data_dir at src/hal0/agents/manager.py:507 (called from manager.py:466).
.hal0-build-stamp Content hash of the ui/ source tree, written under the served UI dist dir (${UI_DIST}/.hal0-build-stamp) right after a successful npm run build (installer/install.sh:1109). Persists across installs — the installer diffs it against the current tree hash on the next run and skips the rebuild only on an exact match.

Portable export envelope formats

Extension Purpose
.hal0profile.json Portable profile export/import envelope.
.hal0stack.json Portable stack export/import envelope.