Academy → Getting StartedOfficial documentation · Arabic guidance

Nix & NixOS Setup

التثبيت على Nix وNixOS

Beginner28 min readLesson 55 questions✓ 2026-08-18
Before you read

What this page is, and what it holds.

This page covers Nix & NixOS Setup. It carries a source warning and takes about 28 minutes to read. Read what the installer does before running it, then run hermes doctor to see what is missing.

15sections
42code examples
17tables
18commands
4,748source words
The official one-line description

Install and deploy Hermes Agent with Nix — from quick nix run to fully declarative NixOS module with container mode

What you will be able to do

Outcomes taken from this page, not a template.

  • Understand what التثبيت is and when you need it.
  • Run hermes cat and hermes mcp add my-oauth-server and understand what happens next.
  • Read the table and take only the row that applies to you.
  • Set OPENROUTER_API_KEY in the right place.
Identifiers you will meet

Exactly as they appear in Hermes.

Commands
  • hermes cat
  • hermes mcp add my-oauth-server
  • hermes setup
  • hermes gateway install
  • hermes chat
  • hermes sudo rm
  • hermes version
  • hermes readlink
Environment variables
  • OPENROUTER_API_KEY
  • HERMES_HOME
  • DEFAULT_CONFIG
  • TELEGRAM_BOT_TOKEN
  • ANTHROPIC_API_KEY
  • GITHUB_PERSONAL_ACCESS_TOKEN
  • GITHUB_TOKEN
  • MCP_REMOTE_API_KEY
Page map

Jump to the part you need.

  1. 01Prerequisites
  2. 02Quick Start (Any Nix User)
  3. 03NixOS Module
  4. 04Configuration
  5. 05Secrets Management
  6. 06Documents
  7. 07MCP Servers
  8. 08Managed Mode
  9. 09Container Architecture
  10. 10Plugins
  11. 11Development
  12. 12Options Reference
  13. 13Directory Layout
  14. 14Updating
  15. 15Troubleshooting
The full official page

Nothing summarised away.

The documentation body below is reproduced from the official source so commands and identifiers stay exact. Each section carries a short note describing what it contains.

Hermes Agent ships a Nix flake & a NixOS module.

LevelWho it's forWhat you get
nix run / nix profile installAny Nix user (macOS, Linux)Pre-built binary with all deps — then use the standard CLI workflow
NixOS module (native)NixOS server deploymentsDeclarative config, hardened systemd service, managed secrets
NixOS module (container)Agents that need self-modificationEverything above, plus a persistent Ubuntu container where the agent can apt/pip/npm install

Prerequisites

Explains the idea itself. Read it slowly; the later sections build on it.

  • Nix with flakes enabled — Determinate Nix ↗ 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)

Carries a warning. Read it before running anything here. Commands here: hermes setup, hermes gateway install. The upstream warning appears below.

No clone needed. Nix fetches, builds, and runs everything:

Shell14 lines
# Run the desktop app
nix run github:NousResearch/hermes-agent#desktop

# Or install persistently
nix profile install github:NousResearch/hermes-agent#desktop

# run the tui
nix run github:NousResearch/hermes-agent -- setup
nix run github:NousResearch/hermes-agent -- --tui

# or install it in your profile
nix profile install github:NousResearch/hermes-agent
hermes setup
hermes --tui

After nix profile install, hermes, hermes-agent, and hermes-acp are on your PATH. From here, the workflow is identical to the standard installation — hermes setup walks you through provider selection, hermes gateway install sets up a launchd (macOS) or systemd user service, and config lives in ~/.hermes/.

<details> <summary><strong>Running from a local clone</strong></summary>

Shell4 lines
git clone https://github.com/NousResearch/hermes-agent.git
cd hermes-agent
nix develop
hermes setup

</details>

---

NixOS Module

Carries a warning. Read it before running anything here. Commands here: hermes config. The upstream warning appears below.

The flake exports nixosModules.default — a full NixOS service module that declaratively manages user creation, directories, config generation, secrets, documents, and service lifecycle.

Add the Flake Input

NIX17 lines
# /etc/nixos/flake.nix (or your system flake)
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    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

NIX9 lines
# 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.

Container-aware CLI

Verify It Works

After nixos-rebuild switch, check that the service is running:

Shell9 lines
# 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 runsHardened systemd service on the hostPersistent Ubuntu container with /nix/store bind-mounted
SecurityNoNewPrivileges, ProtectSystem=strict, PrivateTmpContainer isolation, runs as unprivileged user inside
Agent can self-install packagesNo — only tools on the Nix-provided PATHYes — apt, pip, npm installs persist across restarts
Config surfaceSameSame
When to chooseStandard deployments, maximum security, reproducibilityAgent needs runtime package installation, mutable environment, experimental tools

To enable container mode, add one line:

NIX7 lines
{
  services.hermes-agent = {
    enable = true;
    container.enable = true;
    # ... rest of config is identical
  };
}

---

Configuration

A lookup table. Do not read it all; find the row that applies to you.

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:

NIX12 lines
# 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.

<details> <summary><strong>Full example: all commonly customized settings</strong></summary>

NIX54 lines
{ 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 = {
      "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";
      hostUsers = [ "your-username" ];
      extraVolumes = [ "/home/user/projects:/projects:rw" ];
      extraOptions = [ "--gpus" "all" ];
    };

    # ── Service tuning ─────────────────────────────────────────────────
    addToSystemPackages = true;
    extraArgs = [ "--verbose" ];
    restart = "always";
    restartSec = 5;
  };
}

</details>

Escape Hatch: Bring Your Own Config

If you'd rather manage config.yaml entirely outside Nix, use configFile:

NIX1 line
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...OptionExample
Change the LLM modelsettings.model.default"anthropic/claude-sonnet-4"
Use a different provider endpointsettings.model.base_url"https://openrouter.ai/api/v1"
Add API keysenvironmentFiles[ config.sops.secrets."hermes-env".path ]
Give the agent a personality${services.hermes-agent.stateDir}/.hermes/SOUL.mdmanage the file directly
Add MCP tool serversmcpServers.<name>See MCP Servers ↗
Enable Discord/Telegram/SlackextraDependencyGroups[ "messaging" ]
Mount host directories into containercontainer.extraVolumes[ "/data:/data:rw" ]
Pass GPU access to containercontainer.extraOptions[ "--gpus" "all" ]
Use Podman instead of Dockercontainer.backend"podman"
Share state between host CLI and containercontainer.hostUsers[ "sidbin" ]
Make extra tools available to the agentextraPackages[ pkgs.pandoc pkgs.imagemagick ]
Use a custom base imagecontainer.image"ubuntu:24.04"
Override the hermes packagepackageinputs.hermes-agent.packages.${system}.default.override { ... }
Change state directorystateDir"/opt/hermes"
Set the agent's working directoryworkingDirectory"/home/user/projects"

---

Secrets Management

Carries a warning. Read it before running anything here. The upstream warning appears below.

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

NIX11 lines
{
  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:

YAML5 lines
# secrets/hermes.yaml (encrypted with sops)
hermes-env: |
    OPENROUTER_API_KEY=sk-or-...
    TELEGRAM_BOT_TOKEN=123456:ABC...
    ANTHROPIC_API_KEY=sk-ant-...

agenix

NIX7 lines
{
  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:

NIX6 lines
{
  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

Explains the idea itself. Read it slowly; the later sections build on it.

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:

  • 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.

The agent identity file is separate: Hermes loads its primary SOUL.md from $HERMES_HOME/SOUL.md, which in the NixOS module is ${services.hermes-agent.stateDir}/.hermes/SOUL.md. Putting SOUL.md in documents only creates a workspace file and will not replace the main persona file.

NIX5 lines
{
  services.hermes-agent.documents = {
    "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

Settings you configure once. Change one at a time so you can see what each does. Commands here: hermes mcp add my-oauth-server. Set GITHUB_PERSONAL_ACCESS_TOKEN, GITHUB_TOKEN in your environment, not in the chat.

The mcpServers option declaratively configures MCP (Model Context Protocol) ↗ servers. Each server uses either stdio (local command) or HTTP (remote URL) transport.

Stdio Transport (Local Servers)

NIX13 lines
{
  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
    };
  };
}

HTTP Transport (Remote Servers)

NIX7 lines
{
  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.

NIX6 lines
{
  services.hermes-agent.mcpServers.my-oauth-server = {
    url = "https://mcp.example.com/mcp";
    auth = "oauth";
  };
}

Tokens are stored in $HERMES_HOME/mcp-tokens/<server-name>.json and persist across restarts and rebuilds.

<details> <summary><strong>Initial OAuth authorization on headless servers</strong></summary>

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):

Shell7 lines
# 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:

Shell4 lines
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

</details>

Sampling (Server-Initiated LLM Requests)

Some MCP servers can request LLM completions from the agent:

NIX13 lines
{
  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

A lookup table. Do not read it all; find the row that applies to you.

When hermes runs via the NixOS module, the following CLI commands are blocked with a descriptive error pointing you to configuration.nix:

Blocked commandWhy
hermes setupConfig is declarative — edit settings in your Nix config
hermes config editConfig is generated from settings
hermes config set <key> <value>Config is generated from settings
hermes gateway installThe systemd service is managed by NixOS
hermes gateway uninstallThe 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

Carries a warning. Read it before running anything here. The upstream warning appears below.

When container mode is enabled, hermes runs inside a persistent Ubuntu container with the Nix-built binary bind-mounted read-only from the host:

Text21 lines
Host                                    Container
────                                    ─────────
/nix/store/...-hermes-agent-0.1.0  ──►  /nix/store/... (ro)
~/.hermes -> /var/lib/hermes/.hermes       (symlink bridge, per hostUsers)
/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)
  │   ├── .container-mode                  (routing metadata: backend, exec_user, etc.)
  │   ├── state.db, sessions/, memories/   (runtime state)
  │   └── mcp-tokens/                      (OAuth tokens for MCP servers)
  ├── home/                                ──►  /home/hermes    (rw)
  └── workspace/                           (agent working directory)
      ├── 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

EventContainer recreated?/data (state)/home/hermesWritable layer (apt/pip/npm)
systemctl restart hermes-agentNoPersistsPersistsPersists
nixos-rebuild switch (code change)No (symlink updated)PersistsPersistsPersists
Host rebootNoPersistsPersistsPersists
nix-collect-garbageNo (GC root)PersistsPersistsPersists
Image change (container.image)YesPersistsPersistsLost
Volume/options changeYesPersistsPersistsLost
environment/environmentFiles changeNoPersistsPersistsPersists

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.

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.

---

Plugins

A lookup table. Do not read it all; find the row that applies to you. Commands here: hermes plugins install.

The NixOS module supports declarative plugin installation — no imperative hermes plugins install needed.

Directory Plugins (extraPlugins)

For plugins that are just a source tree with plugin.yaml + __init__.py (e.g., hermes-lcm ↗):

NIX8 lines
services.hermes-agent.extraPlugins = [
  (pkgs.fetchFromGitHub {
    owner = "stephenschoettler";
    repo = "hermes-lcm";
    rev = "v0.7.0";
    hash = "sha256-...";
  })
];

Plugins are symlinked into $HERMES_HOME/plugins/ at activation time. Hermes discovers them via its normal directory scan. Removing a plugin from the list and running nixos-rebuild switch removes the symlink.

Entry-Point Plugins (extraPythonPackages)

For pip-packaged plugins that register via [project.entry-points."hermes_agent.plugins"] (e.g., rtk-hermes ↗):

NIX14 lines
services.hermes-agent.extraPythonPackages = [
  (pkgs.python312Packages.buildPythonPackage {
    pname = "rtk-hermes";
    version = "1.0.0";
    src = pkgs.fetchFromGitHub {
      owner = "ogallotti";
      repo = "rtk-hermes";
      rev = "v1.0.0";
      hash = "sha256-...";
    };
    format = "pyproject";
    build-system = [ pkgs.python312Packages.setuptools ];
  })
];

The package's site-packages is added to PYTHONPATH in the hermes wrapper. importlib.metadata discovers the entry point at session start.

Optional Dependency Groups (extraDependencyGroups)

For optional extras declared in hermes-agent's pyproject.toml, use extraDependencyGroups to include them in the sealed venv at build time. This is required for any extra not in the default [all] set — on Nix, runtime installation into the read-only store is not possible.

NIX2 lines
# Enable Discord, Telegram, Slack
services.hermes-agent.extraDependencyGroups = [ "messaging" ];
NIX5 lines
# Enable a memory provider
services.hermes-agent = {
  extraDependencyGroups = [ "hindsight" ];
  settings.memory.provider = "hindsight";
};

This is resolved by uv alongside core dependencies — no PYTHONPATH patching, no collision risk. Available groups:

GroupWhat it enables
messagingDiscord, Telegram, Slack
matrixMatrix/Element (mautrix with encryption; Linux only)
dingtalkDingTalk
feishuFeishu/Lark
voiceLocal speech-to-text (faster-whisper)
edge-ttsEdge TTS provider
tts-premiumElevenLabs TTS
anthropicNative Anthropic SDK (not needed via OpenRouter)
bedrockAWS Bedrock (boto3)
azure-identityAzure Entra ID auth
honchoHoncho memory provider
hindsightHindsight memory provider
modalModal terminal backend
daytonaDaytona terminal backend
exaExa web search
firecrawlFirecrawl web search
falFAL image generation

Or use the pre-built #messaging or #full flake packages instead of per-extra configuration (see Quick Start ↗).

When to use which:

NeedOption
Enable a pyproject.toml optional extraextraDependencyGroups
Add an external Python plugin not in pyproject.tomlextraPythonPackages
Add a system binary (pandoc, jq, etc.)extraPackages
Add a directory-based plugin source treeextraPlugins

Combining Both

A directory plugin with third-party Python dependencies needs both options:

NIX5 lines
services.hermes-agent = {
  extraPlugins = [ my-plugin-src ];          # plugin source
  extraPythonPackages = [ pkgs.python312Packages.redis ];  # its Python dep
  extraPackages = [ pkgs.redis ];            # system binary it needs
};

Using the Overlay

External flakes can override the package directly:

NIX9 lines
{
  inputs.hermes-agent.url = "github:NousResearch/hermes-agent";
  outputs = { hermes-agent, nixpkgs, ... }: {
    nixpkgs.overlays = [ hermes-agent.overlays.default ];
    # Then:
    #   pkgs.hermes-agent.override { extraPythonPackages = [...]; }
    #   pkgs.hermes-agent.override { extraDependencyGroups = [ "hindsight" ]; }
  };
}

Plugin Configuration

Plugins still need to be enabled in config.yaml. Add them via the declarative settings:

NIX4 lines
services.hermes-agent.settings.plugins.enabled = [
  "hermes-lcm"
  "rtk-rewrite"
];

---

Development

A lookup table. Do not read it all; find the row that applies to you. Commands here: hermes nix develop, hermes direnv allow.

Dev Shell

The flake provides a development shell with Python 3.12, uv, Node.js, and all runtime tools:

Shell10 lines
cd hermes-agent
nix develop

# Shell provides:
#   - Python 3.12 + uv (deps installed into .venv on first entry)
#   - Node.js 26, ripgrep, git, openssh, ffmpeg on PATH
#   - Stamp-file optimization: re-entry is near-instant if deps haven't changed

hermes setup
hermes chat

The included .envrc activates the dev shell automatically:

Shell3 lines
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:

Shell10 lines
# 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

<details> <summary><strong>What each check verifies</strong></summary>

CheckWhat it tests
package-contentshermes and hermes-agent binaries exist and hermes version runs
entry-points-syncEvery [project.scripts] entry in pyproject.toml has a wrapped binary in the Nix package
cli-commandshermes --help exposes gateway and config subcommands
managed-guardHERMES_MANAGED=true hermes config set ... prints the NixOS error
bundled-skillsSkills directory exists, contains SKILL.md files, HERMES_BUNDLED_SKILLS is set in wrapper
config-roundtrip7 merge scenarios: fresh install, Nix override, user key preservation, mixed merge, MCP additive merge, nested deep merge, idempotency

</details>

---

Options Reference

A lookup table. Do not read it all; find the row that applies to you.

Core

OptionTypeDefaultDescription
enableboolfalseEnable the hermes-agent service
packagepackagehermes-agentThe hermes-agent package to use
userstr"hermes"System user
groupstr"hermes"System group
createUserbooltrueAuto-create user/group
stateDirstr"/var/lib/hermes"State directory (HERMES_HOME parent)
workingDirectorystr"${stateDir}/workspace"Agent working directory
addToSystemPackagesboolfalseAdd hermes CLI to system PATH and set HERMES_HOME system-wide

Configuration

OptionTypeDefaultDescription
settingsattrs (deep-merged){}Declarative config rendered as config.yaml. Supports arbitrary nesting; multiple definitions are merged via lib.recursiveUpdate
configFilenull or pathnullPath to an existing config.yaml. Overrides settings entirely if set

Secrets & Environment

OptionTypeDefaultDescription
environmentFileslistOf str[]Paths to env files with secrets. Merged into $HERMES_HOME/.env at activation time
environmentattrsOf str{}Non-secret env vars. Visible in Nix store — do not put secrets here
authFilenull or pathnullOAuth credentials seed. Only copied on first deploy
authFileForceOverwriteboolfalseAlways overwrite auth.json from authFile on activation

Documents

OptionTypeDefaultDescription
documentsattrsOf (either str path){}Workspace files. Keys are filenames, values are inline strings or paths. Installed into workingDirectory on activation

MCP Servers

OptionTypeDefaultDescription
mcpServersattrsOf submodule{}MCP server definitions, merged into settings.mcp_servers
mcpServers.<name>.commandnull or strnullServer command (stdio transport)
mcpServers.<name>.argslistOf str[]Command arguments
mcpServers.<name>.envattrsOf str{}Environment variables for the server process
mcpServers.<name>.urlnull or strnullServer endpoint URL (HTTP/StreamableHTTP transport)
mcpServers.<name>.headersattrsOf str{}HTTP headers, e.g. Authorization
mcpServers.<name>.authnull or "oauth"nullAuthentication method. "oauth" enables OAuth 2.1 PKCE
mcpServers.<name>.enabledbooltrueEnable or disable this server
mcpServers.<name>.timeoutnull or intnullTool call timeout in seconds (default: 120)
mcpServers.<name>.connect_timeoutnull or intnullConnection timeout in seconds (default: 60)
mcpServers.<name>.toolsnull or submodulenullTool filtering (include/exclude lists)
mcpServers.<name>.samplingnull or submodulenullSampling config for server-initiated LLM requests

Service Behavior

OptionTypeDefaultDescription
extraArgslistOf str[]Extra args for hermes gateway
extraPackageslistOf package[]Extra packages available to the agent. Added to the hermes user's per-user profile so terminal commands, skills, and cron jobs all see them
extraPluginslistOf package[]Directory plugin packages to symlink into $HERMES_HOME/plugins/. Each must contain plugin.yaml
extraPythonPackageslistOf package[]Python packages added to PYTHONPATH for entry-point plugin discovery. Build with python312Packages
extraDependencyGroupslistOf str[]pyproject.toml optional extras to include in the sealed venv (e.g. ["hindsight"]). Resolved by uv — no collisions
restartstr"always"systemd Restart= policy
restartSecint5systemd RestartSec= value

Container

OptionTypeDefaultDescription
container.enableboolfalseEnable OCI container mode
container.backendenum ["docker" "podman"]"docker"Container runtime
container.imagestr"ubuntu:24.04"Base image (pulled at runtime)
container.extraVolumeslistOf str[]Extra volume mounts (host:container:mode)
container.extraOptionslistOf str[]Extra args passed to docker create
container.hostUserslistOf str[]Interactive users who get a ~/.hermes symlink to the service stateDir and are auto-added to the hermes group

---

Directory Layout

A lookup table. Do not read it all; find the row that applies to you.

Native Mode

Text18 lines
/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/                       # Agent working directory
    ├── SOUL.md                      # From documents option
    └── (agent-created files)

Container Mode

Same layout, mounted into the container:

Container pathHost pathModeNotes
/nix/store/nix/storeroHermes binary + all Nix deps
/data/var/lib/hermesrwAll state, config, workspace
/home/hermes${stateDir}/homerwPersistent agent home — pip install --user, tool caches
/usr, /usr/local, /tmp(writable layer)rwapt/pip/npm installs — persists across restarts, lost on recreation

---

Updating

Explains the idea itself. Read it slowly; the later sections build on it.

Shell5 lines
# Update the flake input (run from the directory containing flake.nix)
cd /etc/nixos && nix flake update hermes-agent

# 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

A troubleshooting section. Find the symptom that matches yours rather than reading it end to end. Commands here: hermes cat, hermes sudo rm.

Service Logs

Shell5 lines
# Both modes use the same systemd unit
journalctl -u hermes-agent -f

# Container mode: also available directly
docker logs -f hermes-agent

Container Inspection

Shell6 lines
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):

Shell4 lines
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:

Shell5 lines
# Native mode
sudo -u hermes cat /var/lib/hermes/.hermes/.env

# Container mode
docker exec hermes-agent cat /data/.hermes/.env

GC Root Verification

Shell1 line
nix-store --query --roots $(docker exec hermes-agent readlink /data/current-package)

Common Issues

SymptomCauseFix
Cannot save configuration: managed by NixOSCLI guards activeEdit configuration.nix and nixos-rebuild switch
No adapter available for discord (or telegram/slack)Messaging deps missing from the sealed Nix venvInstall #messaging variant: nix profile install ...#messaging. For NixOS module: extraDependencyGroups = [ "messaging" ]. Check journalctl -u hermes-agent for FeatureUnavailable or requirements not met for the underlying error.
Container recreated unexpectedlyextraVolumes, extraOptions, or image changedExpected — writable layer resets. Reinstall packages or use a custom image
hermes version shows old versionContainer not restartedsystemctl restart hermes-agent
Permission denied on /var/lib/hermesState dir is 0750 hermes:hermesUse docker exec or sudo -u hermes
nix-collect-garbage removed hermesGC root missingRestart the service (preStart recreates the GC root)
no container with name or ID "hermes-agent" (Podman)Podman rootful container not visible to regular userAdd passwordless sudo for podman (see Container Mode ↗ section)
unable to find user hermesContainer still starting (entrypoint hasn't created user yet)Wait a few seconds and retry — the CLI retries automatically
Tool added via extraPackages not found in terminalRequires nixos-rebuild switch to update the per-user profileRebuild and restart: nixos-rebuild switch && systemctl restart hermes-agent
Knowledge check

5 questions answered by this page alone.

Every option is a real identifier from the Hermes documentation. The wrong ones are real too, just from other pages.

1. According to this lesson, which command does “shows the generated config”?
2. In this lesson's table, what is the “Who it's for” for “nix run / nix profile install”?
3. Which of these environment variables actually appears in this lesson?
4. Which warning does the source state in this lesson?
5. Which of these headings does not appear in this lesson?