mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-10-20 05:15:31 +00:00
docs: remove docs/nixos-setup.md, consolidate into website docs
Backfill missing details (restart/restartSec in full example, gateway.pid, 0750 permissions, docker inspect commands) into the canonical website/docs/getting-started/nix-setup.md and delete the old standalone file.
This commit is contained in:
parent
ca38a51633
commit
08c1fea296
2 changed files with 6 additions and 731 deletions
|
|
@ -1,730 +0,0 @@
|
|||
# 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)
|
||||
- API keys for the services you want to use (at minimum: an OpenRouter or Anthropic key)
|
||||
|
||||
## Quick Start: `nix run`
|
||||
|
||||
```bash
|
||||
nix run github:NousResearch/hermes-agent -- setup
|
||||
nix run github:NousResearch/hermes-agent -- chat
|
||||
```
|
||||
|
||||
No clone needed. Nix fetches and builds everything. All Python dependencies are
|
||||
Nix derivations via uv2nix — no runtime pip.
|
||||
|
||||
## Install to Profile
|
||||
|
||||
```bash
|
||||
# From remote
|
||||
nix profile install github:NousResearch/hermes-agent
|
||||
hermes setup
|
||||
|
||||
# From a clone
|
||||
git clone https://github.com/NousResearch/hermes-agent.git
|
||||
cd hermes-agent
|
||||
nix build
|
||||
./result/bin/hermes setup
|
||||
```
|
||||
|
||||
## Development Shell
|
||||
|
||||
```bash
|
||||
cd hermes-agent
|
||||
nix develop
|
||||
# Shell provides:
|
||||
# - Python 3.11 venv with all deps (via uv)
|
||||
# - npm deps (agent-browser)
|
||||
# - ripgrep, git, node on PATH
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NixOS Module
|
||||
|
||||
The flake exports `nixosModules.default` with two deployment modes:
|
||||
|
||||
| Mode | `container.enable` | How it runs | Use case |
|
||||
|---|---|---|---|
|
||||
| **Native** (default) | `false` | Hardened systemd service, runs directly on host | Standard deployment, maximum security |
|
||||
| **Container** | `true` | Persistent Ubuntu container, hermes binary bind-mounted from `/nix/store` | Agent needs `apt`/`pip`/`npm` self-install capability |
|
||||
|
||||
Both modes share the same option surface. The module manages user creation,
|
||||
directory setup, config generation, secrets, documents, and service lifecycle.
|
||||
|
||||
> **Note:** This module requires NixOS. For non-NixOS systems, use
|
||||
> `nix profile install` + the CLI's built-in `hermes gateway install`.
|
||||
|
||||
### 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 (Native Mode)
|
||||
|
||||
```nix
|
||||
# configuration.nix
|
||||
{
|
||||
services.hermes-agent = {
|
||||
enable = true;
|
||||
settings.model.default = "anthropic/claude-sonnet-4";
|
||||
environmentFiles = [ config.sops.secrets."hermes/env".path ];
|
||||
addToSystemPackages = true; # puts `hermes` CLI on PATH
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> **Important:** `addToSystemPackages = true` also 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.
|
||||
|
||||
### Minimal Configuration (Container Mode)
|
||||
|
||||
```nix
|
||||
{
|
||||
services.hermes-agent = {
|
||||
enable = true;
|
||||
container.enable = true;
|
||||
settings.model.default = "anthropic/claude-sonnet-4";
|
||||
environmentFiles = [ config.sops.secrets."hermes/env".path ];
|
||||
addToSystemPackages = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> Container mode auto-enables `virtualisation.docker.enable` via `mkDefault`.
|
||||
> Override with `virtualisation.docker.enable = false;` if using podman
|
||||
> (`container.backend = "podman"`).
|
||||
|
||||
### Full Example
|
||||
|
||||
```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 ────────────────────────────────────────────────────────
|
||||
# See "Secrets Management" section below
|
||||
environmentFiles = [ config.sops.secrets."hermes/env".path ];
|
||||
|
||||
# ── Documents ──────────────────────────────────────────────────────
|
||||
documents = {
|
||||
"SOUL.md" = builtins.readFile /home/user/.hermes/SOUL.md;
|
||||
"USER.md" = ./documents/USER.md; # path reference
|
||||
};
|
||||
|
||||
# ── MCP Servers ────────────────────────────────────────────────────
|
||||
mcpServers.filesystem = {
|
||||
command = "npx";
|
||||
args = [ "-y" "@modelcontextprotocol/server-filesystem" "/data/workspace" ];
|
||||
};
|
||||
mcpServers.remote-tools = {
|
||||
url = "https://mcp.example.com/mcp";
|
||||
auth = "oauth";
|
||||
};
|
||||
|
||||
# ── Container options ──────────────────────────────────────────────
|
||||
container = {
|
||||
image = "ubuntu:24.04"; # default
|
||||
backend = "docker"; # or "podman"
|
||||
extraVolumes = [
|
||||
"/home/user/projects:/projects:rw"
|
||||
];
|
||||
extraOptions = [
|
||||
"--gpus" "all" # GPU passthrough
|
||||
];
|
||||
};
|
||||
|
||||
# ── Service tuning ─────────────────────────────────────────────────
|
||||
addToSystemPackages = true;
|
||||
extraArgs = [ "--verbose" ];
|
||||
restart = "always";
|
||||
restartSec = 5;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Secrets Management
|
||||
|
||||
**Never put API keys in `environment`** — values end up in the Nix store
|
||||
(world-readable). Use `environmentFiles` with a secrets manager.
|
||||
|
||||
Both `environment` and `environmentFiles` are merged into `$HERMES_HOME/.env`
|
||||
at activation time (`nixos-rebuild switch`). Hermes reads this file on every
|
||||
startup via `load_hermes_dotenv()`, so changes take effect on
|
||||
`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 should contain 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 back to the state directory and preserved across rebuilds.
|
||||
|
||||
---
|
||||
|
||||
## Container Mode: Architecture
|
||||
|
||||
When `container.enable = true`, hermes runs inside a persistent Ubuntu
|
||||
container with the Nix-built binary bind-mounted 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, copied by activation)
|
||||
│ ├── .managed (marker file)
|
||||
│ ├── state.db
|
||||
│ ├── mcp-tokens/ (OAuth tokens for MCP servers)
|
||||
│ ├── sessions/
|
||||
│ ├── memories/
|
||||
│ └── ...
|
||||
├── home/ ──► /home/hermes (rw)
|
||||
└── workspace/ (MESSAGING_CWD)
|
||||
├── SOUL.md (from documents option)
|
||||
└── (agent-created files)
|
||||
|
||||
Container writable layer (apt/pip/npm): /usr, /tmp
|
||||
```
|
||||
|
||||
The container entrypoint is `/data/current-package/bin/hermes gateway run --replace`,
|
||||
which resolves through the symlink to the current Nix store path.
|
||||
|
||||
### 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`. Changes to `environment`,
|
||||
`environmentFiles`, `settings`, `documents`, or the hermes package itself do
|
||||
**not** trigger recreation — environment variables are written to
|
||||
`$HERMES_HOME/.env` by the activation script and read by hermes at startup.
|
||||
A `systemctl restart hermes-agent` is sufficient for env changes.
|
||||
|
||||
### When to Use Container Mode
|
||||
|
||||
Use container mode when:
|
||||
- The agent needs to `apt install`, `pip install`, or `npm install` packages at runtime
|
||||
- You want the agent to have a mutable Linux environment it can customize
|
||||
- You're running untrusted or experimental tool configurations
|
||||
|
||||
Use native mode when:
|
||||
- You want maximum security (systemd hardening: `NoNewPrivileges`, `ProtectSystem=strict`)
|
||||
- The agent only needs tools already on the Nix-provided PATH
|
||||
- You prefer a minimal, reproducible deployment
|
||||
|
||||
---
|
||||
|
||||
## Managed Mode
|
||||
|
||||
When hermes runs via the NixOS module, the following CLI commands are
|
||||
**blocked** with a descriptive error:
|
||||
|
||||
| Blocked command | Reason |
|
||||
|---|---|
|
||||
| `hermes setup` | Config is declarative in `configuration.nix` |
|
||||
| `hermes config edit` | Config is generated from `settings` |
|
||||
| `hermes config set <key> <value>` | Config is generated from `settings` |
|
||||
| `hermes gateway install` | Service is managed by NixOS |
|
||||
| `hermes gateway uninstall` | Service is managed by NixOS |
|
||||
|
||||
Detection uses two signals:
|
||||
1. `HERMES_MANAGED=true` environment variable (set by the systemd service)
|
||||
2. `.managed` marker file in `HERMES_HOME` (set by the activation script,
|
||||
visible to interactive shells)
|
||||
|
||||
If you need to change configuration, edit your `configuration.nix` and run
|
||||
`sudo nixos-rebuild switch`.
|
||||
|
||||
---
|
||||
|
||||
## MCP Servers
|
||||
|
||||
The `mcpServers` option lets you declaratively configure
|
||||
[MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers.
|
||||
Each server uses either **stdio** (local command) or **HTTP** (remote URL)
|
||||
transport.
|
||||
|
||||
### Stdio Transport (Local Servers)
|
||||
|
||||
For MCP servers that run as local subprocesses:
|
||||
|
||||
```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
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Environment variables in `env` values are resolved from `$HERMES_HOME/.env`
|
||||
at runtime. Use `environmentFiles` (with sops-nix or agenix) to inject
|
||||
secrets — never put tokens directly in Nix config.
|
||||
|
||||
### HTTP Transport (Remote Servers)
|
||||
|
||||
For remote MCP servers accessible via HTTP/StreamableHTTP:
|
||||
|
||||
```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
|
||||
|
||||
For remote MCP servers that use OAuth 2.1 for authentication, set
|
||||
`auth = "oauth"`. Hermes implements the full OAuth 2.1 PKCE flow via the
|
||||
MCP SDK — including 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/<server-name>.json` and
|
||||
persist across restarts and rebuilds. Token refresh is automatic.
|
||||
|
||||
#### Initial Authorization (Headless / Container)
|
||||
|
||||
The first OAuth authorization requires completing a browser-based consent
|
||||
flow. In a headless NixOS deployment (native or container), Hermes detects
|
||||
the absence of a display and prints the authorization URL to stdout/logs
|
||||
instead of opening a browser.
|
||||
|
||||
**Option A: Interactive bootstrap** — run the OAuth flow once via `docker exec`
|
||||
(container mode) or `sudo -u hermes` (native mode):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Since the container uses `--network=host`, the OAuth callback listener on
|
||||
`127.0.0.1` is reachable from the host. Open the printed URL in your
|
||||
browser, complete consent, and the callback is received by Hermes inside
|
||||
the container. Tokens are saved and reused automatically from then on.
|
||||
|
||||
**Option B: Pre-seed tokens** — complete the OAuth flow on a workstation
|
||||
first, then copy the token files to the server:
|
||||
|
||||
```bash
|
||||
# On your workstation
|
||||
hermes mcp add my-oauth-server --url https://mcp.example.com/mcp --auth oauth
|
||||
|
||||
# Copy tokens to the server
|
||||
scp ~/.hermes/mcp-tokens/my-oauth-server.json \
|
||||
server:/var/lib/hermes/.hermes/mcp-tokens/
|
||||
scp ~/.hermes/mcp-tokens/my-oauth-server.client.json \
|
||||
server:/var/lib/hermes/.hermes/mcp-tokens/
|
||||
```
|
||||
|
||||
Ensure the files are owned by the hermes user (`chown hermes:hermes`)
|
||||
and have mode `0600`.
|
||||
|
||||
### Sampling (Server-Initiated LLM Requests)
|
||||
|
||||
Some MCP servers can request LLM completions from the agent. Configure
|
||||
this per-server with the `sampling` option:
|
||||
|
||||
```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;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Mixed Example
|
||||
|
||||
```nix
|
||||
{
|
||||
services.hermes-agent = {
|
||||
environmentFiles = [ config.sops.secrets."hermes/env".path ];
|
||||
|
||||
mcpServers = {
|
||||
# Local stdio server
|
||||
filesystem = {
|
||||
command = "npx";
|
||||
args = [ "-y" "@modelcontextprotocol/server-filesystem" "/data/workspace" ];
|
||||
};
|
||||
|
||||
# Remote server with API key auth
|
||||
ink = {
|
||||
url = "https://mcp.ml.ink/mcp";
|
||||
headers.Authorization = "Bearer \${INK_API_KEY}";
|
||||
};
|
||||
|
||||
# Remote server with OAuth
|
||||
cloud-tools = {
|
||||
url = "https://tools.example.com/mcp";
|
||||
auth = "oauth";
|
||||
timeout = 300;
|
||||
connect_timeout = 30;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Options Reference
|
||||
|
||||
### Core
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `enable` | `bool` | `false` | Enable the hermes-agent service |
|
||||
| `package` | `package` | `hermes-agent` | The hermes-agent package |
|
||||
| `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 so CLI and gateway share state |
|
||||
|
||||
### 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 (API keys). Passed as systemd `EnvironmentFile=` or docker `--env-file` |
|
||||
| `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` |
|
||||
|
||||
### 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.<name>.command` | `null` or `str` | `null` | Server command (stdio transport) |
|
||||
| `mcpServers.<name>.args` | `listOf str` | `[]` | Command arguments (stdio transport) |
|
||||
| `mcpServers.<name>.env` | `attrsOf str` | `{}` | Environment variables for the server process (stdio transport) |
|
||||
| `mcpServers.<name>.url` | `null` or `str` | `null` | Server endpoint URL (HTTP/StreamableHTTP transport) |
|
||||
| `mcpServers.<name>.headers` | `attrsOf str` | `{}` | HTTP headers, e.g. `Authorization` (HTTP transport) |
|
||||
| `mcpServers.<name>.auth` | `null` or `"oauth"` | `null` | Authentication method. `"oauth"` enables OAuth 2.1 PKCE |
|
||||
| `mcpServers.<name>.timeout` | `null` or `int` | `null` | Tool call timeout in seconds (default: 120) |
|
||||
| `mcpServers.<name>.connect_timeout` | `null` or `int` | `null` | Initial connection timeout in seconds (default: 60) |
|
||||
| `mcpServers.<name>.sampling` | `null` or `submodule` | `null` | Sampling configuration 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=` |
|
||||
|
||||
### Container
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `container.enable` | `bool` | `false` | Enable OCI container mode |
|
||||
| `container.backend` | `enum ["docker" "podman"]` | `"docker"` | Container runtime. Auto-enables `virtualisation.docker.enable` when `"docker"` |
|
||||
| `container.image` | `str` | `"ubuntu:24.04"` | Base image. Pulled at runtime by Docker/Podman |
|
||||
| `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 (overwritten each rebuild)
|
||||
│ ├── .managed # Marker: CLI config mutation blocked
|
||||
│ ├── .env # (not used — secrets via 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 (container mode: /home/hermes)
|
||||
└── workspace/ # MESSAGING_CWD
|
||||
├── SOUL.md # From documents option
|
||||
└── (agent-created files)
|
||||
```
|
||||
|
||||
### Container Mode
|
||||
|
||||
Same layout, but mounted into the container as `/data`:
|
||||
|
||||
| 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` | (container layer) | `rw` | Persists — `apt`/`pip`/`npm` installs |
|
||||
| `/tmp` | (container layer) | `rw` | Persists across restarts (lost on recreation) |
|
||||
|
||||
---
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
# Update the flake input
|
||||
nix flake update hermes-agent --flake /etc/nixos
|
||||
|
||||
# Rebuild — in container mode, the symlink updates without recreating the container
|
||||
sudo nixos-rebuild switch
|
||||
```
|
||||
|
||||
In container mode, the agent picks up the new binary immediately on restart.
|
||||
No container recreation, no loss of `apt`/`pip`/`npm` installs.
|
||||
|
||||
## Flake Checks
|
||||
|
||||
The flake includes build-time verification:
|
||||
|
||||
```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.cli-commands # gateway/config subcommands
|
||||
nix build .#checks.x86_64-linux.managed-guard # HERMES_MANAGED blocks mutation
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service logs
|
||||
|
||||
```bash
|
||||
# Native mode
|
||||
journalctl -u hermes-agent -f
|
||||
|
||||
# Container mode — same unit name
|
||||
journalctl -u hermes-agent -f
|
||||
|
||||
# Or directly from the container
|
||||
docker logs -f hermes-agent
|
||||
```
|
||||
|
||||
### Container inspection
|
||||
|
||||
```bash
|
||||
# Service status
|
||||
systemctl status hermes-agent
|
||||
|
||||
# Container state
|
||||
docker ps -a --filter name=hermes-agent
|
||||
docker inspect hermes-agent --format='{{.State.Status}}'
|
||||
|
||||
# Shell into the container
|
||||
docker exec -it hermes-agent bash
|
||||
|
||||
# Check symlink
|
||||
docker exec hermes-agent readlink /data/current-package
|
||||
|
||||
# Check identity hash
|
||||
docker exec hermes-agent cat /data/.container-identity
|
||||
```
|
||||
|
||||
### Force container recreation
|
||||
|
||||
If you need to reset the container 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
|
||||
# Container will be recreated from scratch
|
||||
```
|
||||
|
||||
### GC root verification
|
||||
|
||||
```bash
|
||||
# Ensure the running package is protected
|
||||
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 behavior — writable layer is reset. Reinstall packages if needed |
|
||||
| `hermes version` shows old version after rebuild | 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 or broken | Restart the service (`preStart` recreates the GC root) |
|
||||
|
|
@ -253,6 +253,8 @@ The full set of config keys is defined in [`nix/config-keys.json`](https://githu
|
|||
# ── Service tuning ─────────────────────────────────────────────────
|
||||
addToSystemPackages = true;
|
||||
extraArgs = [ "--verbose" ];
|
||||
restart = "always";
|
||||
restartSec = 5;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
@ -706,12 +708,13 @@ nix build .#checks.x86_64-linux.config-roundtrip # merge script preserves use
|
|||
### Native Mode
|
||||
|
||||
```
|
||||
/var/lib/hermes/ # stateDir (owned by hermes:hermes)
|
||||
/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/
|
||||
|
|
@ -773,8 +776,10 @@ docker logs -f hermes-agent
|
|||
```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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue