--- sidebar_position: 3 title: "Nix & NixOS Setup" description: "Install and deploy Hermes Agent with Nix — from quick `nix run` to fully declarative NixOS module with container mode" --- # Nix & NixOS Setup Hermes Agent ships a Nix flake with three levels of integration: | Level | Who it's for | What you get | |-------|-------------|--------------| | **`nix run` / `nix profile install`** | Any Nix user (macOS, Linux) | Pre-built binary with all deps — then use the standard CLI workflow | | **NixOS module (native)** | NixOS server deployments | Declarative config, hardened systemd service, managed secrets | | **NixOS module (container)** | Agents that need self-modification | Everything above, plus a persistent Ubuntu container where the agent can `apt`/`pip`/`npm install` | :::info What's different from the standard install The `curl | bash` installer manages Python, Node, and dependencies itself. The Nix flake replaces all of that — every Python dependency is a Nix derivation built by [uv2nix](https://github.com/pyproject-nix/uv2nix), and runtime tools (Node.js, git, ripgrep, ffmpeg) are wrapped into the binary's PATH. There is no runtime pip, no venv activation, no `npm install`. **For non-NixOS users**, this only changes the install step. Everything after (`hermes setup`, `hermes gateway install`, config editing) works identically to the standard install. **For NixOS module users**, the entire lifecycle is different: configuration lives in `configuration.nix`, secrets go through sops-nix/agenix, the service is a systemd unit, and CLI config commands are blocked. You manage hermes the same way you manage any other NixOS service. ::: ## Prerequisites - **Nix with flakes enabled** — [Determinate Nix](https://install.determinate.systems) recommended (enables flakes by default) - **API keys** for the services you want to use (at minimum: an OpenRouter or Anthropic key) --- ## Quick Start (Any Nix User) No clone needed. Nix fetches, builds, and runs everything: ```bash # Run directly (builds on first use, cached after) nix run github:NousResearch/hermes-agent -- setup nix run github:NousResearch/hermes-agent -- chat # Or install persistently nix profile install github:NousResearch/hermes-agent hermes setup hermes chat ``` After `nix profile install`, `hermes`, `hermes-agent`, and `hermes-acp` are on your PATH. From here, the workflow is identical to the [standard installation](./installation.md) — `hermes setup` walks you through provider selection, `hermes gateway install` sets up a launchd (macOS) or systemd user service, and config lives in `~/.hermes/`.
Building from a local clone ```bash git clone https://github.com/NousResearch/hermes-agent.git cd hermes-agent nix build ./result/bin/hermes setup ```
--- ## NixOS Module The flake exports `nixosModules.default` — a full NixOS service module that declaratively manages user creation, directories, config generation, secrets, documents, and service lifecycle. :::note This module requires NixOS. For non-NixOS systems (macOS, other Linux distros), use `nix profile install` and the standard CLI workflow above. ::: ### Add the Flake Input ```nix # /etc/nixos/flake.nix (or your system flake) { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; hermes-agent.url = "github:NousResearch/hermes-agent"; }; outputs = { nixpkgs, hermes-agent, ... }: { nixosConfigurations.your-host = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ hermes-agent.nixosModules.default ./configuration.nix ]; }; }; } ``` ### Minimal Configuration ```nix # configuration.nix { config, ... }: { services.hermes-agent = { enable = true; settings.model.default = "anthropic/claude-sonnet-4"; environmentFiles = [ config.sops.secrets."hermes-env".path ]; addToSystemPackages = true; }; } ``` That's it. `nixos-rebuild switch` creates the `hermes` user, generates `config.yaml`, wires up secrets, and starts the gateway — a long-running service that connects the agent to messaging platforms (Telegram, Discord, etc.) and listens for incoming messages. :::warning Secrets are required The `environmentFiles` line above assumes you have [sops-nix](https://github.com/Mic92/sops-nix) or [agenix](https://github.com/ryantm/agenix) configured. The file should contain at least one LLM provider key (e.g., `OPENROUTER_API_KEY=sk-or-...`). See [Secrets Management](#secrets-management) for full setup. If you don't have a secrets manager yet, you can use a plain file as a starting point — just ensure it's not world-readable: ```bash echo "OPENROUTER_API_KEY=sk-or-your-key" | sudo install -m 0600 -o hermes /dev/stdin /var/lib/hermes/env ``` ```nix services.hermes-agent.environmentFiles = [ "/var/lib/hermes/env" ]; ``` ::: :::tip addToSystemPackages Setting `addToSystemPackages = true` does two things: puts the `hermes` CLI on your system PATH **and** sets `HERMES_HOME` system-wide so the interactive CLI shares state (sessions, skills, cron) with the gateway service. Without it, running `hermes` in your shell creates a separate `~/.hermes/` directory. ::: ### Verify It Works After `nixos-rebuild switch`, check that the service is running: ```bash # Check service status systemctl status hermes-agent # Watch logs (Ctrl+C to stop) journalctl -u hermes-agent -f # If addToSystemPackages is true, test the CLI hermes version hermes config # shows the generated config ``` ### Choosing a Deployment Mode The module supports two modes, controlled by `container.enable`: | | **Native** (default) | **Container** | |---|---|---| | How it runs | Hardened systemd service on the host | Persistent Ubuntu container with `/nix/store` bind-mounted | | Security | `NoNewPrivileges`, `ProtectSystem=strict`, `PrivateTmp` | Container isolation, runs as unprivileged user inside | | Agent can self-install packages | No — only tools on the Nix-provided PATH | Yes — `apt`, `pip`, `npm` installs persist across restarts | | Config surface | Same | Same | | When to choose | Standard deployments, maximum security, reproducibility | Agent needs runtime package installation, mutable environment, experimental tools | To enable container mode, add one line: ```nix { services.hermes-agent = { enable = true; container.enable = true; # ... rest of config is identical }; } ``` :::info Container mode auto-enables `virtualisation.docker.enable` via `mkDefault`. If you use Podman instead, set `container.backend = "podman"` and `virtualisation.docker.enable = false`. ::: --- ## Configuration ### Declarative Settings The `settings` option accepts an arbitrary attrset that is rendered as `config.yaml`. It supports deep merging across multiple module definitions (via `lib.recursiveUpdate`), so you can split config across files: ```nix # base.nix services.hermes-agent.settings = { model.default = "anthropic/claude-sonnet-4"; toolsets = [ "all" ]; terminal = { backend = "local"; timeout = 180; }; }; # personality.nix services.hermes-agent.settings = { display = { compact = false; personality = "kawaii"; }; memory = { memory_enabled = true; user_profile_enabled = true; }; }; ``` Both are deep-merged at evaluation time. Nix-declared keys always win over keys in an existing `config.yaml` on disk, but **user-added keys that Nix doesn't touch are preserved**. This means if the agent or a manual edit adds keys like `skills.disabled` or `streaming.enabled`, they survive `nixos-rebuild switch`. :::note Model naming `settings.model.default` uses the model identifier your provider expects. With [OpenRouter](https://openrouter.ai) (the default), these look like `"anthropic/claude-sonnet-4"` or `"google/gemini-3-flash"`. If you're using a provider directly (Anthropic, OpenAI), set `settings.model.base_url` to point at their API and use their native model IDs (e.g., `"claude-sonnet-4-20250514"`). When no `base_url` is set, Hermes defaults to OpenRouter. ::: :::tip Discovering available config keys Run `nix build .#configKeys && cat result` to see every leaf config key extracted from Python's `DEFAULT_CONFIG`. You can paste your existing `config.yaml` into the `settings` attrset — the structure maps 1:1. :::
Full example: all commonly customized settings ```nix { config, ... }: { services.hermes-agent = { enable = true; container.enable = true; # ── Model ────────────────────────────────────────────────────────── settings = { model = { base_url = "https://openrouter.ai/api/v1"; default = "anthropic/claude-opus-4.6"; }; toolsets = [ "all" ]; max_turns = 100; terminal = { backend = "local"; cwd = "."; timeout = 180; }; compression = { enabled = true; threshold = 0.85; summary_model = "google/gemini-3-flash-preview"; }; memory = { memory_enabled = true; user_profile_enabled = true; }; display = { compact = false; personality = "kawaii"; }; agent = { max_turns = 60; verbose = false; }; }; # ── Secrets ──────────────────────────────────────────────────────── environmentFiles = [ config.sops.secrets."hermes-env".path ]; # ── Documents ────────────────────────────────────────────────────── documents = { "SOUL.md" = builtins.readFile /home/user/.hermes/SOUL.md; "USER.md" = ./documents/USER.md; }; # ── MCP Servers ──────────────────────────────────────────────────── mcpServers.filesystem = { command = "npx"; args = [ "-y" "@modelcontextprotocol/server-filesystem" "/data/workspace" ]; }; # ── Container options ────────────────────────────────────────────── container = { image = "ubuntu:24.04"; backend = "docker"; extraVolumes = [ "/home/user/projects:/projects:rw" ]; extraOptions = [ "--gpus" "all" ]; }; # ── Service tuning ───────────────────────────────────────────────── addToSystemPackages = true; extraArgs = [ "--verbose" ]; restart = "always"; restartSec = 5; }; } ```
### Escape Hatch: Bring Your Own Config If you'd rather manage `config.yaml` entirely outside Nix, use `configFile`: ```nix services.hermes-agent.configFile = /etc/hermes/config.yaml; ``` This bypasses `settings` entirely — no merge, no generation. The file is copied as-is to `$HERMES_HOME/config.yaml` on each activation. ### Customization Cheatsheet Quick reference for the most common things Nix users want to customize: | I want to... | Option | Example | |---|---|---| | Change the LLM model | `settings.model.default` | `"anthropic/claude-sonnet-4"` | | Use a different provider endpoint | `settings.model.base_url` | `"https://openrouter.ai/api/v1"` | | Add API keys | `environmentFiles` | `[ config.sops.secrets."hermes-env".path ]` | | Give the agent a personality | `documents."SOUL.md"` | `builtins.readFile ./my-soul.md` | | Add MCP tool servers | `mcpServers.` | See [MCP Servers](#mcp-servers) | | Mount host directories into container | `container.extraVolumes` | `[ "/data:/data:rw" ]` | | Pass GPU access to container | `container.extraOptions` | `[ "--gpus" "all" ]` | | Use Podman instead of Docker | `container.backend` | `"podman"` | | Add tools to the service PATH (native only) | `extraPackages` | `[ pkgs.pandoc pkgs.imagemagick ]` | | Use a custom base image | `container.image` | `"ubuntu:24.04"` | | Override the hermes package | `package` | `inputs.hermes-agent.packages.${system}.default.override { ... }` | | Change state directory | `stateDir` | `"/opt/hermes"` | | Set the agent's working directory | `workingDirectory` | `"/home/user/projects"` | --- ## Secrets Management :::danger Never put API keys in `settings` or `environment` Values in Nix expressions end up in `/nix/store`, which is world-readable. Always use `environmentFiles` with a secrets manager. ::: Both `environment` (non-secret vars) and `environmentFiles` (secret files) are merged into `$HERMES_HOME/.env` at activation time (`nixos-rebuild switch`). Hermes reads this file on every startup, so changes take effect with a `systemctl restart hermes-agent` — no container recreation needed. ### sops-nix ```nix { sops = { defaultSopsFile = ./secrets/hermes.yaml; age.keyFile = "/home/user/.config/sops/age/keys.txt"; secrets."hermes-env" = { format = "yaml"; }; }; services.hermes-agent.environmentFiles = [ config.sops.secrets."hermes-env".path ]; } ``` The secrets file contains key-value pairs: ```yaml # secrets/hermes.yaml (encrypted with sops) hermes-env: | OPENROUTER_API_KEY=sk-or-... TELEGRAM_BOT_TOKEN=123456:ABC... ANTHROPIC_API_KEY=sk-ant-... ``` ### agenix ```nix { age.secrets.hermes-env.file = ./secrets/hermes-env.age; services.hermes-agent.environmentFiles = [ config.age.secrets.hermes-env.path ]; } ``` ### OAuth / Auth Seeding For platforms requiring OAuth (e.g., Discord), use `authFile` to seed credentials on first deploy: ```nix { services.hermes-agent = { authFile = config.sops.secrets."hermes/auth.json".path; # authFileForceOverwrite = true; # overwrite on every activation }; } ``` The file is only copied if `auth.json` doesn't already exist (unless `authFileForceOverwrite = true`). Runtime OAuth token refreshes are written to the state directory and preserved across rebuilds. --- ## Documents The `documents` option installs files into the agent's working directory (the `workingDirectory`, which the agent reads as its workspace). Hermes looks for specific filenames by convention: - **`SOUL.md`** — the agent's system prompt / personality. Hermes reads this on startup and uses it as persistent instructions that shape its behavior across all conversations. - **`USER.md`** — context about the user the agent is interacting with. - Any other files you place here are visible to the agent as workspace files. ```nix { services.hermes-agent.documents = { "SOUL.md" = '' You are a helpful research assistant specializing in NixOS packaging. Always cite sources and prefer reproducible solutions. ''; "USER.md" = ./documents/USER.md; # path reference, copied from Nix store }; } ``` Values can be inline strings or path references. Files are installed on every `nixos-rebuild switch`. --- ## MCP Servers The `mcpServers` option declaratively configures [MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers. Each server uses either **stdio** (local command) or **HTTP** (remote URL) transport. ### Stdio Transport (Local Servers) ```nix { services.hermes-agent.mcpServers = { filesystem = { command = "npx"; args = [ "-y" "@modelcontextprotocol/server-filesystem" "/data/workspace" ]; }; github = { command = "npx"; args = [ "-y" "@modelcontextprotocol/server-github" ]; env.GITHUB_PERSONAL_ACCESS_TOKEN = "\${GITHUB_TOKEN}"; # resolved from .env }; }; } ``` :::tip Environment variables in `env` values are resolved from `$HERMES_HOME/.env` at runtime. Use `environmentFiles` to inject secrets — never put tokens directly in Nix config. ::: ### HTTP Transport (Remote Servers) ```nix { services.hermes-agent.mcpServers.remote-api = { url = "https://mcp.example.com/v1/mcp"; headers.Authorization = "Bearer \${MCP_REMOTE_API_KEY}"; timeout = 180; }; } ``` ### HTTP Transport with OAuth Set `auth = "oauth"` for servers using OAuth 2.1. Hermes implements the full PKCE flow — metadata discovery, dynamic client registration, token exchange, and automatic refresh. ```nix { services.hermes-agent.mcpServers.my-oauth-server = { url = "https://mcp.example.com/mcp"; auth = "oauth"; }; } ``` Tokens are stored in `$HERMES_HOME/mcp-tokens/.json` and persist across restarts and rebuilds.
Initial OAuth authorization on headless servers The first OAuth authorization requires a browser-based consent flow. In a headless deployment, Hermes prints the authorization URL to stdout/logs instead of opening a browser. **Option A: Interactive bootstrap** — run the flow once via `docker exec` (container) or `sudo -u hermes` (native): ```bash # Container mode docker exec -it hermes-agent \ hermes mcp add my-oauth-server --url https://mcp.example.com/mcp --auth oauth # Native mode sudo -u hermes HERMES_HOME=/var/lib/hermes/.hermes \ hermes mcp add my-oauth-server --url https://mcp.example.com/mcp --auth oauth ``` The container uses `--network=host`, so the OAuth callback listener on `127.0.0.1` is reachable from the host browser. **Option B: Pre-seed tokens** — complete the flow on a workstation, then copy tokens: ```bash hermes mcp add my-oauth-server --url https://mcp.example.com/mcp --auth oauth scp ~/.hermes/mcp-tokens/my-oauth-server{,.client}.json \ server:/var/lib/hermes/.hermes/mcp-tokens/ # Ensure: chown hermes:hermes, chmod 0600 ```
### Sampling (Server-Initiated LLM Requests) Some MCP servers can request LLM completions from the agent: ```nix { services.hermes-agent.mcpServers.analysis = { command = "npx"; args = [ "-y" "analysis-server" ]; sampling = { enabled = true; model = "google/gemini-3-flash"; max_tokens_cap = 4096; timeout = 30; max_rpm = 10; }; }; } ``` --- ## Managed Mode When hermes runs via the NixOS module, the following CLI commands are **blocked** with a descriptive error pointing you to `configuration.nix`: | Blocked command | Why | |---|---| | `hermes setup` | Config is declarative — edit `settings` in your Nix config | | `hermes config edit` | Config is generated from `settings` | | `hermes config set ` | Config is generated from `settings` | | `hermes gateway install` | The systemd service is managed by NixOS | | `hermes gateway uninstall` | The systemd service is managed by NixOS | This prevents drift between what Nix declares and what's on disk. Detection uses two signals: 1. **`HERMES_MANAGED=true`** environment variable — set by the systemd service, visible to the gateway process 2. **`.managed` marker file** in `HERMES_HOME` — set by the activation script, visible to interactive shells (e.g., `docker exec -it hermes-agent hermes config set ...` is also blocked) To change configuration, edit your Nix config and run `sudo nixos-rebuild switch`. --- ## Container Architecture :::info This section is only relevant if you're using `container.enable = true`. Skip it for native mode deployments. ::: When container mode is enabled, hermes runs inside a persistent Ubuntu container with the Nix-built binary bind-mounted read-only from the host: ``` Host Container ──── ───────── /nix/store/...-hermes-agent-0.1.0 ──► /nix/store/... (ro) /var/lib/hermes/ ──► /data/ (rw) ├── current-package -> /nix/store/... (symlink, updated each rebuild) ├── .gc-root -> /nix/store/... (prevents nix-collect-garbage) ├── .container-identity (sha256 hash, triggers recreation) ├── .hermes/ (HERMES_HOME) │ ├── .env (merged from environment + environmentFiles) │ ├── config.yaml (Nix-generated, deep-merged by activation) │ ├── .managed (marker file) │ ├── state.db, sessions/, memories/ (runtime state) │ └── mcp-tokens/ (OAuth tokens for MCP servers) ├── home/ ──► /home/hermes (rw) └── workspace/ (MESSAGING_CWD) ├── SOUL.md (from documents option) └── (agent-created files) Container writable layer (apt/pip/npm): /usr, /usr/local, /tmp ``` The Nix-built binary works inside the Ubuntu container because `/nix/store` is bind-mounted — it brings its own interpreter and all dependencies, so there's no reliance on the container's system libraries. The container entrypoint resolves through a `current-package` symlink: `/data/current-package/bin/hermes gateway run --replace`. On `nixos-rebuild switch`, only the symlink is updated — the container keeps running. ### What Persists Across What | Event | Container recreated? | `/data` (state) | `/home/hermes` | Writable layer (`apt`/`pip`/`npm`) | |---|---|---|---|---| | `systemctl restart hermes-agent` | No | Persists | Persists | Persists | | `nixos-rebuild switch` (code change) | No (symlink updated) | Persists | Persists | Persists | | Host reboot | No | Persists | Persists | Persists | | `nix-collect-garbage` | No (GC root) | Persists | Persists | Persists | | Image change (`container.image`) | **Yes** | Persists | Persists | **Lost** | | Volume/options change | **Yes** | Persists | Persists | **Lost** | | `environment`/`environmentFiles` change | No | Persists | Persists | Persists | The container is only recreated when its **identity hash** changes. The hash covers: schema version, image, `extraVolumes`, `extraOptions`, and the entrypoint script. Changes to environment variables, settings, documents, or the hermes package itself do **not** trigger recreation. :::warning Writable layer loss When the identity hash changes (image upgrade, new volumes, new container options), the container is destroyed and recreated from a fresh pull of `container.image`. Any `apt install`, `pip install`, or `npm install` packages in the writable layer are lost. State in `/data` and `/home/hermes` is preserved (these are bind mounts). If the agent relies on specific packages, consider baking them into a custom image (`container.image = "my-registry/hermes-base:latest"`) or scripting their installation in the agent's SOUL.md. ::: ### GC Root Protection The `preStart` script creates a GC root at `${stateDir}/.gc-root` pointing to the current hermes package. This prevents `nix-collect-garbage` from removing the running binary. If the GC root somehow breaks, restarting the service recreates it. --- ## Development ### Dev Shell The flake provides a development shell with Python 3.11, uv, Node.js, and all runtime tools: ```bash cd hermes-agent nix develop # Shell provides: # - Python 3.11 + uv (deps installed into .venv on first entry) # - Node.js 20, ripgrep, git, openssh, ffmpeg on PATH # - Stamp-file optimization: re-entry is near-instant if deps haven't changed hermes setup hermes chat ``` ### direnv (Recommended) The included `.envrc` activates the dev shell automatically: ```bash cd hermes-agent direnv allow # one-time # Subsequent entries are near-instant (stamp file skips dep install) ``` ### Flake Checks The flake includes build-time verification that runs in CI and locally: ```bash # Run all checks nix flake check # Individual checks nix build .#checks.x86_64-linux.package-contents # binaries exist + version nix build .#checks.x86_64-linux.entry-points-sync # pyproject.toml ↔ Nix package sync nix build .#checks.x86_64-linux.cli-commands # gateway/config subcommands nix build .#checks.x86_64-linux.managed-guard # HERMES_MANAGED blocks mutation nix build .#checks.x86_64-linux.bundled-skills # skills present in package nix build .#checks.x86_64-linux.config-roundtrip # merge script preserves user keys ```
What each check verifies | Check | What it tests | |---|---| | `package-contents` | `hermes` and `hermes-agent` binaries exist and `hermes version` runs | | `entry-points-sync` | Every `[project.scripts]` entry in `pyproject.toml` has a wrapped binary in the Nix package | | `cli-commands` | `hermes --help` exposes `gateway` and `config` subcommands | | `managed-guard` | `HERMES_MANAGED=true hermes config set ...` prints the NixOS error | | `bundled-skills` | Skills directory exists, contains SKILL.md files, `HERMES_BUNDLED_SKILLS` is set in wrapper | | `config-roundtrip` | 7 merge scenarios: fresh install, Nix override, user key preservation, mixed merge, MCP additive merge, nested deep merge, idempotency |
--- ## Options Reference ### Core | Option | Type | Default | Description | |---|---|---|---| | `enable` | `bool` | `false` | Enable the hermes-agent service | | `package` | `package` | `hermes-agent` | The hermes-agent package to use | | `user` | `str` | `"hermes"` | System user | | `group` | `str` | `"hermes"` | System group | | `createUser` | `bool` | `true` | Auto-create user/group | | `stateDir` | `str` | `"/var/lib/hermes"` | State directory (`HERMES_HOME` parent) | | `workingDirectory` | `str` | `"${stateDir}/workspace"` | Agent working directory (`MESSAGING_CWD`) | | `addToSystemPackages` | `bool` | `false` | Add `hermes` CLI to system PATH and set `HERMES_HOME` system-wide | ### Configuration | Option | Type | Default | Description | |---|---|---|---| | `settings` | `attrs` (deep-merged) | `{}` | Declarative config rendered as `config.yaml`. Supports arbitrary nesting; multiple definitions are merged via `lib.recursiveUpdate` | | `configFile` | `null` or `path` | `null` | Path to an existing `config.yaml`. Overrides `settings` entirely if set | ### Secrets & Environment | Option | Type | Default | Description | |---|---|---|---| | `environmentFiles` | `listOf str` | `[]` | Paths to env files with secrets. Merged into `$HERMES_HOME/.env` at activation time | | `environment` | `attrsOf str` | `{}` | Non-secret env vars. **Visible in Nix store** — do not put secrets here | | `authFile` | `null` or `path` | `null` | OAuth credentials seed. Only copied on first deploy | | `authFileForceOverwrite` | `bool` | `false` | Always overwrite `auth.json` from `authFile` on activation | ### Documents | Option | Type | Default | Description | |---|---|---|---| | `documents` | `attrsOf (either str path)` | `{}` | Workspace files. Keys are filenames, values are inline strings or paths. Installed into `workingDirectory` on activation | ### MCP Servers | Option | Type | Default | Description | |---|---|---|---| | `mcpServers` | `attrsOf submodule` | `{}` | MCP server definitions, merged into `settings.mcp_servers` | | `mcpServers..command` | `null` or `str` | `null` | Server command (stdio transport) | | `mcpServers..args` | `listOf str` | `[]` | Command arguments | | `mcpServers..env` | `attrsOf str` | `{}` | Environment variables for the server process | | `mcpServers..url` | `null` or `str` | `null` | Server endpoint URL (HTTP/StreamableHTTP transport) | | `mcpServers..headers` | `attrsOf str` | `{}` | HTTP headers, e.g. `Authorization` | | `mcpServers..auth` | `null` or `"oauth"` | `null` | Authentication method. `"oauth"` enables OAuth 2.1 PKCE | | `mcpServers..enabled` | `bool` | `true` | Enable or disable this server | | `mcpServers..timeout` | `null` or `int` | `null` | Tool call timeout in seconds (default: 120) | | `mcpServers..connect_timeout` | `null` or `int` | `null` | Connection timeout in seconds (default: 60) | | `mcpServers..tools` | `null` or `submodule` | `null` | Tool filtering (`include`/`exclude` lists) | | `mcpServers..sampling` | `null` or `submodule` | `null` | Sampling config for server-initiated LLM requests | ### Service Behavior | Option | Type | Default | Description | |---|---|---|---| | `extraArgs` | `listOf str` | `[]` | Extra args for `hermes gateway` | | `extraPackages` | `listOf package` | `[]` | Extra packages on service PATH (native mode only) | | `restart` | `str` | `"always"` | systemd `Restart=` policy | | `restartSec` | `int` | `5` | systemd `RestartSec=` value | ### Container | Option | Type | Default | Description | |---|---|---|---| | `container.enable` | `bool` | `false` | Enable OCI container mode | | `container.backend` | `enum ["docker" "podman"]` | `"docker"` | Container runtime | | `container.image` | `str` | `"ubuntu:24.04"` | Base image (pulled at runtime) | | `container.extraVolumes` | `listOf str` | `[]` | Extra volume mounts (`host:container:mode`) | | `container.extraOptions` | `listOf str` | `[]` | Extra args passed to `docker create` | --- ## Directory Layout ### Native Mode ``` /var/lib/hermes/ # stateDir (owned by hermes:hermes, 0750) ├── .hermes/ # HERMES_HOME │ ├── config.yaml # Nix-generated (deep-merged each rebuild) │ ├── .managed # Marker: CLI config mutation blocked │ ├── .env # Merged from environment + environmentFiles │ ├── auth.json # OAuth credentials (seeded, then self-managed) │ ├── gateway.pid │ ├── state.db │ ├── mcp-tokens/ # OAuth tokens for MCP servers │ ├── sessions/ │ ├── memories/ │ ├── skills/ │ ├── cron/ │ └── logs/ ├── home/ # Agent HOME └── workspace/ # MESSAGING_CWD ├── SOUL.md # From documents option └── (agent-created files) ``` ### Container Mode Same layout, mounted into the container: | Container path | Host path | Mode | Notes | |---|---|---|---| | `/nix/store` | `/nix/store` | `ro` | Hermes binary + all Nix deps | | `/data` | `/var/lib/hermes` | `rw` | All state, config, workspace | | `/home/hermes` | `${stateDir}/home` | `rw` | Persistent agent home — `pip install --user`, tool caches | | `/usr`, `/usr/local`, `/tmp` | (writable layer) | `rw` | `apt`/`pip`/`npm` installs — persists across restarts, lost on recreation | --- ## Updating ```bash # Update the flake input nix flake update hermes-agent --flake /etc/nixos # Rebuild sudo nixos-rebuild switch ``` In container mode, the `current-package` symlink is updated and the agent picks up the new binary on restart. No container recreation, no loss of installed packages. --- ## Troubleshooting :::tip Podman users All `docker` commands below work the same with `podman`. Substitute accordingly if you set `container.backend = "podman"`. ::: ### Service Logs ```bash # Both modes use the same systemd unit journalctl -u hermes-agent -f # Container mode: also available directly docker logs -f hermes-agent ``` ### Container Inspection ```bash systemctl status hermes-agent docker ps -a --filter name=hermes-agent docker inspect hermes-agent --format='{{.State.Status}}' docker exec -it hermes-agent bash docker exec hermes-agent readlink /data/current-package docker exec hermes-agent cat /data/.container-identity ``` ### Force Container Recreation If you need to reset the writable layer (fresh Ubuntu): ```bash sudo systemctl stop hermes-agent docker rm -f hermes-agent sudo rm /var/lib/hermes/.container-identity sudo systemctl start hermes-agent ``` ### Verify Secrets Are Loaded If the agent starts but can't authenticate with the LLM provider, check that the `.env` file was merged correctly: ```bash # Native mode sudo -u hermes cat /var/lib/hermes/.hermes/.env # Container mode docker exec hermes-agent cat /data/.hermes/.env ``` ### GC Root Verification ```bash nix-store --query --roots $(docker exec hermes-agent readlink /data/current-package) ``` ### Common Issues | Symptom | Cause | Fix | |---|---|---| | `Cannot save configuration: managed by NixOS` | CLI guards active | Edit `configuration.nix` and `nixos-rebuild switch` | | Container recreated unexpectedly | `extraVolumes`, `extraOptions`, or `image` changed | Expected — writable layer resets. Reinstall packages or use a custom image | | `hermes version` shows old version | Container not restarted | `systemctl restart hermes-agent` | | Permission denied on `/var/lib/hermes` | State dir is `0750 hermes:hermes` | Use `docker exec` or `sudo -u hermes` | | `nix-collect-garbage` removed hermes | GC root missing | Restart the service (preStart recreates the GC root) |