Skip to content

instance.yaml Reference

One YAML to rule them all — a single file radiating control to every service, the haus itself written down in one place

One wrong indent here and the whole haus forgets its own name.

That is the deal you strike with ~/.sanctum/instance.yaml. Every instance-specific value a Sanctum deployment knows about itself is defined in this one file — services, networking, paths, family members, node topology, and secrets references. It is the single source of truth the whole config system reads from.

One file. Everything your haus knows about itself is in this file. The name, the network layout, which services run, who lives there. If this file is wrong, everything downstream is wrong. If this file is right, everything downstream has a fighting chance.

A JSON cache is auto-regenerated at ~/.sanctum/.instance.json whenever the YAML changes, via lib/yaml2json.py.

The absolute minimum to get Sanctum to acknowledge your existence:

instance:
name: Sanctum Hub
slug: sanctum-hub
timezone: America/Montreal
users:
mac:
username: neo
home: /Users/neo
vm:
username: ubuntu
home: /home/ubuntu
network:
mac_bridge_ip: 10.10.10.1
vm_ip: 10.10.10.10
mac_lan_ip: 192.0.2.10
bridge_interface: bridge100
vm_ssh_alias: openclaw

Top-level identity for this Sanctum deployment. Who you are. Where you are. When you are.

Key Type Required Description
slug string Yes URL-safe identifier used in hostnames, paths, and DNS. Example: sanctum-hub
name string Yes Human-readable display name. Example: Sanctum Hub
timezone string Yes IANA timezone for scheduling and logs. Example: America/Montreal
instance:
slug: sanctum-hub
name: Sanctum Hub
timezone: America/Montreal

OS-level identities on the Mac host and the VM. Unglamorous but load-bearing — every SSH command, every file path, every LaunchAgent plist resolves through one of these. Get them wrong and nothing errors cleanly; things just silently don’t work, which is worse. Each side is a map of a username and a home directory, not a bare string — sanctum_home() and sanctum_expand() read the home path straight off users.mac.home.

Key Type Required Description
mac.username string Yes macOS username on the host machine
mac.home string Yes macOS home directory (absolute)
vm.username string Yes Linux username inside the VM
vm.home string Yes Linux home directory inside the VM
users:
mac:
username: neo
home: /Users/neo
vm:
username: ubuntu
home: /home/ubuntu

The plumbing. Nobody admires plumbing until it breaks, and then it’s the only thing anyone can talk about. This section defines how the Mac, the VM, and the LAN find each other — a tiny private internet inside your haus, inside your actual internet.

Key Type Required Description
mac_bridge_ip string Yes Mac-side IP on the bridge interface
vm_ip string Yes Static IP of the VM on the host-only network
mac_lan_ip string Yes Mac Mini IP on the local network
bridge_interface string Yes macOS bridge interface name (e.g., bridge100)
bridge_subnet string No CIDR for the Mac↔VM bridge network
lan_subnet string No CIDR for the LAN
vm_ssh_alias string Yes SSH config alias for the VM (e.g., openclaw)
network:
mac_bridge_ip: 10.10.10.1
vm_ip: 10.10.10.10
mac_lan_ip: 192.0.2.10
bridge_interface: bridge100
bridge_subnet: 10.10.10.0/24
lan_subnet: 192.0.2.0/24
vm_ssh_alias: openclaw

Where things live on disk. Every backup script, every log rotation, every skill loader reads from this section. Think of it as the haus’s filing cabinet — except the filing cabinet is also load-bearing, so don’t move it.

Key Type Required Description
openclaw_config string Yes Agent config directory. Default: ~/.openclaw
openclaw_skills string Yes Shared skills repo checkout
logs string Yes Centralized log directory
projects string Yes Projects root (Mac side)
backups string Yes Backup destination directory
paths:
openclaw_config: ~/.openclaw
openclaw_skills: ~/Projects/openclaw-skills
logs: ~/.openclaw/logs
projects: ~/Projects
backups: ~/Backups

Here’s where it gets real. Every service your haus runs — the agent gateway, the voice engine, the haus automation hub, the offline library — each one gets an enabled boolean and, if it listens on a port, a port key. Flip the boolean, regenerate plists, and a freshly enabled service gets its LaunchAgent. Configuration as incantation.

The enabled flag controls whether generate-plists.sh renders the corresponding LaunchAgent. Today a disabled service is simply skipped during generation — its plist isn’t rewritten. If it was already loaded, it keeps running until you launchctl bootout it by hand. “Disabled” means “not regenerated,” not yet “torn down.” Honest beats incantation.

Service Key Default Port(s) Description
Agent Gateway dench — OpenClaw/DenchClaw agent gateway (runs in the VM; reached on the host via the Lima-forwarded 127.0.0.1:1977)
Home Assistant home_assistant 8123 Home automation hub (dedicated HA Green box at 10.0.0.3)
Dashboard dashboard 3333, 1111 Command center web (3333) + API (1111); the Holocron UI on 3344 has since become the front door
Firewalla Bridge firewalla_bridge 1984 Firewalla P2P bridge
VM vm — Ubuntu 24.04 guest under Lima
Voice Agent voice_agent 1138 Yoda voice interaction agent
Sanctum TTS qwen3_tts 8008 Text-to-speech dispatcher (Rust sanctum-tts, fronting the cbx-yoda engine on 8011; XTTS-v2 retired 2026-04-19)
Council MLX council_mlx 1337 Council MLX model server (Qwen3.6-35B-A3B, mTLS)
Cloudflare Tunnel cloudflare — Cloudflare Zero Trust tunnel
iCloud Filer icloud_filer — Automatic iCloud filing daemon
Health Center health_center 2222 Health monitoring dashboard
Tailscale tailscale — Mesh VPN
Memory Vault memory_vault 42069 Cross-session message store
Watchdog watchdog — Service health monitoring
Kiwix kiwix 8888 Offline library server
Signal Proxy signal_proxy 5150 Signal messaging bridge (host-forwarded from the VM)
Outline outline 3100 Self-hosted wiki

Seventeen of the services are shown above. The full catalogue is 28 keys under services:. On a Mac Mini. Under a desk. Running a haushold.

services:
dench:
enabled: true
home_assistant:
port: 8123
homekit_port: 21063
dashboard:
port: 3333
backend_port: 1111
firewalla_bridge:
enabled: true
port: 1984
vm:
enabled: true
voice_agent:
enabled: true
port: 1138
qwen3_tts:
enabled: true
port: 8008
council_mlx:
enabled: true
port: 1337
cloudflare:
enabled: true
icloud_filer:
enabled: true
health_center:
enabled: true
port: 2222
tailscale:
enabled: true
memory_vault:
enabled: true
port: 42069
watchdog:
enabled: true
interval: 600
triage:
enabled: true
interval: 30
kiwix:
enabled: true
port: 8888
signal_proxy:
enabled: true
port: 5150
host: vm
outline:
enabled: true
port: 3100

From the shell library:

Terminal window
source ~/.sanctum/lib/config.sh
if sanctum_enabled voice_agent; then
echo "Voice agent is enabled on port $(sanctum_get services.voice_agent.port)"
fi

sanctum_enabled takes the bare service name — it prepends services. and appends .enabled itself. Pass it services.voice_agent and it dutifully looks up services.services.voice_agent.enabled, finds nothing, and reports disabled. sanctum_get, by contrast, wants the full dotted path. The asymmetry is a footgun; the rule of thumb is “name the service for enabled, name the path for get.”

From TypeScript:

import { isEnabled, get } from './lib/config.js';
if (isEnabled('voice_agent')) {
const port = get('services.voice_agent.port');
}

The only section of this file that’s about what isn’t here. Sanctum never stores secrets in instance.yaml directly — the config tells you where secrets live, not what they are. A treasure map that says “the chest is buried under the oak tree” without ever containing the treasure. Three secret stores, three levels of paranoia, all of them justified — the full mechanism is the secrets trifecta.

Key Type Description
keychain_account string macOS Keychain account name for stored tokens
op_vault string 1Password vault name for credentials
op_item_prefix string Prefix for 1Password item names (e.g. Sanctum - Home Assistant)
sops_file string Path to the SOPS-encrypted secrets file
secrets:
keychain_account: sanctum
op_vault: Private
op_item_prefix: Sanctum
sops_file: ~/.openclaw/secrets.enc.yaml

Three stores, three trust models, zero plaintext on this disk. The treasure map can be committed to git. The chests cannot.


Where the smart home meets reality. This section doesn’t configure Home Assistant itself — that’s configuration.yaml’s job. This section tells Sanctum what HA needs to know about the physical world: which speakers exist, which cameras are watching, which thermostat controls which zone. The kind of inventory you never think to write down until the third time you troubleshoot from memory.

Key Type Description
cameras map Camera entity IDs keyed by friendly name
sonos_speakers list HA media_player.* entity IDs for the Sonos fleet
hvac_zones list Thermostat zones (id, name, temp + humidity sensors)
ecobee map Ecobee climate + sensor entity IDs
floor_zones list In-floor heating zones
carrier_sensors map Carrier furnace/airflow sensor entity IDs
home_assistant:
cameras:
front_door: camera.front_door_live_view
sonos_speakers:
- media_player.dining_room
- media_player.living_room
- media_player.master_bedroom
hvac_zones:
- id: climate.main_floor
name: Ground Floor
temp_sensor: sensor.main_floor_temperature
humidity_sensor: sensor.main_floor_humidity
ecobee:
climate: climate.main_floor
occupancy: binary_sensor.main_floor_occupancy

Your haus should know who lives in it. This section is what lets the agents greet a kid by name, route an alert to their bedroom speaker, and know which devices are theirs when curfew lands. A short list with outsized consequences — and the screen-time enforcer reads it directly.

Key Type Description
children list List of family member objects
children[].name string Display name
children[].sonos_speaker string HA media_player.* entity to address this person
children[].devices list Per-device {mac, name, icon} entries (for presence + screen time)
children[].screen_time map Wake/block schedule (wake_hour, block_hour, school_days)
family:
children:
- name: Kid One
sonos_speaker: media_player.vr_room
devices:
- mac: AA:BB:CC:DD:EE:XX
name: Kid One's iPad
icon: phone
screen_time:
wake_hour: 8
block_hour: 21

Hive contract for multi-site Sanctum. Optional but recommended once you have more than one haus. Full story: Hive Topology.

Key Type Description
design string isomorphic-enclave — same local map (.1 FW · .2 Orbi · .3 HA · .10 Mini) at every site
hive string name-only — cross-site address by node name / MagicDNS, never bare 10.0.0.x
identity_pins string Path hint to firewalla-toolkit identity-pins.json (body pins)
topology:
design: isomorphic-enclave
hive: name-only
identity_pins: openclaw-skills/firewalla-toolkit/config/identity-pins.json

Multi-site hive roster. Each key is a stable node id (manoir, chalet, mbp) matching ~/.sanctum/.node_id on that machine. The hub runs everything; satellites run what they can; mobile devices check in when they feel like it. Shape: node topology · hive rules: hive topology.

CLI: sanctum node list | get | whoami | resolve · adopt writes satellites via sanctum node adopt.

Key Type Description
<node_id> map Stable short node identifier (e.g. manoir, chalet)
.type string hub, satellite, mobile, or sensor
.tier string primary (SoT hub), peer (future multi-hub), or edge
.site string Design-map site key (matches identity-pins site)
.display_name string Human-readable label
.location string Logical location tag (e.g. home, chalet, road)
.host string LAN hostname/IP or MagicDNS stem; empty when name-only
.tailscale_ip string Optional cached Tailscale IP (prefer resolve live)
.tailscale_name string MagicDNS stem — preferred for sanctum node resolve
.ssh_user string SSH username (readers also accept legacy .user)
.capabilities list Static offers (vault_authority, inference, ha_site, …)
.sync.hub string Satellite: primary hub node id
.online bool Last-known reachability (optional cache)
.vm map Hub only: nested host / ssh_user / ssh_alias for the guest
.services map Optional per-node service overrides (satellites)
.adopted string ISO date when sanctum node adopt registered this satellite
nodes:
manoir:
type: hub
tier: primary
site: manoir
display_name: Manoir
tailscale_name: manoir
ssh_user: bert
capabilities: [vault_authority, inference, ha_site]
vm:
host: 10.10.10.10
ssh_user: ubuntu
ssh_alias: openclaw
chalet:
type: satellite
tier: edge
site: chalet
display_name: Chalet
host: chalet
tailscale_name: chalet
ssh_user: bert
sync:
hub: manoir
capabilities: [local_inference, satellite]
adopted: '2026-07-18'
Type Description
hub Full infrastructure; exactly one tier: primary; peers allowed later
satellite Secondary site with reduced stack (no cathedral)
mobile Laptop or portable device
sensor Headless monitoring device

The satellite knows it’s not the hub. It doesn’t try to be. It runs what it needs, phones haus over Tailscale, and keeps the lights on when the internet goes out. Self-aware infrastructure is underrated.


An annotated example is available at ~/.sanctum/instance.yaml.example and can be used as a starting point for new instances.


The YAML config is automatically converted to a JSON cache at ~/.sanctum/.instance.json — a nested object tree mirroring the YAML, which the libraries walk with dotted-path lookups (services.voice_agent.port). Nothing is flattened; the dots in the query are traversal, not key names. The shell and TypeScript helpers both read this file for fast lookups. If you edit instance.yaml manually, regenerate the cache:

Terminal window
python3 ~/.sanctum/lib/yaml2json.py

Get the indentation right and the whole constellation knows its own name. Get it wrong and it forgets — quietly, without an error, which is the worst way anything ever breaks. So we treat this file the way it deserves: edit it slowly, regenerate the cache, and read it back once before we walk away.