diff --git a/docs/nixos-setup.md b/docs/nixos-setup.md
index a72dc8dfbc..8244161cbb 100644
--- a/docs/nixos-setup.md
+++ b/docs/nixos-setup.md
@@ -1,5 +1,9 @@
# Nix Setup Guide for Hermes Agent
+> **Canonical version:** This document is maintained in the docs site at
+> [`website/docs/getting-started/nix-setup.md`](../website/docs/getting-started/nix-setup.md).
+> The version below may be out of date.
+
## Prerequisites
- Nix with flakes enabled ([Determinate Nix](https://install.determinate.systems) recommended — enables flakes by default)
diff --git a/website/docs/getting-started/installation.md b/website/docs/getting-started/installation.md
index 83ed9555a7..e3282fa8da 100644
--- a/website/docs/getting-started/installation.md
+++ b/website/docs/getting-started/installation.md
@@ -59,6 +59,10 @@ The only prerequisite is **Git**. The installer automatically handles everything
You do **not** need to install Python, Node.js, ripgrep, or ffmpeg manually. The installer detects what's missing and installs it for you. Just make sure `git` is available (`git --version`).
:::
+:::tip Nix users
+If you use Nix (on NixOS, macOS, or Linux), there's a dedicated setup path with a Nix flake, declarative NixOS module, and optional container mode. See the **[Nix & NixOS Setup](./nix-setup.md)** guide.
+:::
+
---
## Manual Installation
diff --git a/website/docs/getting-started/nix-setup.md b/website/docs/getting-started/nix-setup.md
new file mode 100644
index 0000000000..629565bcb5
--- /dev/null
+++ b/website/docs/getting-started/nix-setup.md
@@ -0,0 +1,817 @@
+---
+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
+The full set of config keys is defined in [`nix/config-keys.json`](https://github.com/NousResearch/hermes-agent/blob/main/nix/config-keys.json) (127 leaf keys). You can paste your existing `config.yaml` into the `settings` attrset — the structure maps 1:1. The build-time `config-drift` check catches any drift between the reference and the Python source.
+:::
+
+
+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" ];
+ };
+}
+```
+
+
+
+### 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-drift # config keys match Python source
+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-drift` | Leaf keys extracted from Python's `DEFAULT_CONFIG` match the committed `nix/config-keys.json` reference |
+| `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)
+├── .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)
+│ ├── 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 exec -it hermes-agent bash
+docker exec hermes-agent readlink /data/current-package
+```
+
+### 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) |
diff --git a/website/sidebars.ts b/website/sidebars.ts
index 92a56bccb1..0665662d2b 100644
--- a/website/sidebars.ts
+++ b/website/sidebars.ts
@@ -9,6 +9,7 @@ const sidebars: SidebarsConfig = {
items: [
'getting-started/quickstart',
'getting-started/installation',
+ 'getting-started/nix-setup',
'getting-started/updating',
'getting-started/learning-path',
],