> For the complete documentation index, see [llms.txt](https://docs.ramsdenj.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ramsdenj.com/introduction-5/herme-agent.md).

# Agents

Setup notes for running the [Hermes agent](https://hermes-agent.nousresearch.com/docs/getting-started/installation) on a dedicated VM, with LXD (ZFS backed) available to it as a sandbox for tool use.

## Provision the VM

Create an Ubuntu **26.04** VM and attach a **1 TiB** block device in addition to the root disk.

Confirm the device name (a virtio disk, `/dev/vdb` below):

```bash
lsblk
```

## Partition the block device

Two partitions:

* `256 GiB` — `/home/hermes`, ext4
* `768 GiB` — btrfs pool for LXD

```bash
sudo parted /dev/vdb --script mklabel gpt
sudo parted /dev/vdb --script mkpart home   ext4  0%    256GiB
sudo parted /dev/vdb --script mkpart lxdpool btrfs 256GiB 100%
```

This yields `/dev/vdb1` (home) and `/dev/vdb2` (btrfs pool).

## Install LXD with a btrfs backed pool

```bash
sudo apt update
sudo apt install -y btrfs-progs
sudo snap install lxd
```

Initialise LXD and let it create the btrfs pool on the second partition:

```bash
sudo lxd init
```

Answer the wizard with:

* Configure a new storage pool: **yes**
* Name: `lxd`
* Backend: `btrfs`
* Create a new btrfs pool: **yes**
* Use an existing empty block device: **yes**, path `/dev/vdb2`

```yaml
config: {}
networks:
- config:
    ipv4.address: auto
    ipv6.address: auto
  description: ""
  name: lxdbr0
  type: ""
  project: default
storage_pools:
- config:
    source: /dev/vdb2
  description: ""
  name: default
  driver: btrfs
storage_volumes: []
profiles:
- config: {}
  description: ""
  devices:
    eth0:
      name: eth0
      network: lxdbr0
      type: nic
    root:
      path: /
      pool: default
      type: disk
  name: default
projects: []
cluster: null
```

## Create and mount /home/hermes

```bash
sudo mkfs.ext4 -L hermes /dev/vdb1
sudo mkdir -p /home/hermes
```

Persist the mount in `/etc/fstab` (by label, to survive device renaming):

```
echo 'LABEL=hermes  /home/hermes  ext4  defaults  0  2' | sudo tee -a /etc/fstab
```

Verify it mounts cleanly:

```bash
sudo systemctl daemon-reload
sudo mount -a && mount
```

## Add the hermes user

Create the user against the already-mounted home directory:

```bash
sudo useradd --home-dir /home/hermes --shell /bin/bash hermes
sudo chown -R hermes:hermes /home/hermes
sudo -iu hermes cp /etc/skel/.bashrc ~/.bashrc
```

Add to sudoers:

```bash
sudo usermod -aG sudo hermes
sudo passwd hermes
```

## Install Hermes

Run it **as the hermes user**

```bash
sudo apt install -y git
sudo -iu hermes bash -c 'curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash' #Full setup
```

## Give hermes LXD access

Add the user to the `lxd` group so it can drive containers without `sudo`:

```bash
sudo usermod -aG lxd hermes
```

Re-log (or `sudo -iu hermes`) for the group to take effect, then confirm:

```bash
sudo -iu hermes lxc list
```

## Configure Hermes

As the hermes user, reload the shell and run the setup wizard:

```bash
sudo -iu hermes
source ~/.bashrc
hermes setup   # full configuration wizard
hermes tools   # enable tools
```

## Web dashboard

[ref](https://hermes-agent.nousresearch.com/docs/user-guide/desktop#connecting-to-a-remote-backend)

Add the following to `~/.hermes/.env`:

```
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD='********'
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
```

Run the dashboard as a system service that restarts on failure. Service — `/etc/systemd/system/hermes-dashboard.service`:

```ini
[Unit]
Description=Hermes web dashboard
After=network-online.target
Wants=network-online.target

[Service]
User=hermes
Group=hermes
ExecStart=/home/hermes/.local/bin/hermes dashboard --host 0.0.0.0 --port 9119 --no-open
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
```

Enable and start it:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now hermes-dashboard.service
```

Access it at `http://<vm-host>:9119`, and check it with:

```bash
systemctl status hermes-dashboard.service
journalctl -u hermes-dashboard.service -f
```

## Backup

Hermes has a native command that bundles config, the SQLite databases (WAL-safe, works while Hermes is running), `.env`, auth tokens, skills, sessions, and cron jobs into one timestamped zip — excluding the reinstallable codebase:

```bash
hermes backup -o ~/hermes-backup-$(date +%F).zip
```

### Daily backup with a systemd timer

Run the backup once a day, storing archives in `~/backup` and pruning anything older than a month. These are **system** units that drop privileges to the `hermes` user via `User=`, so they run without an active login (no lingering required). systemd sets `$HOME` to `/home/hermes` for the service.

Backup script — `/home/hermes/.local/bin/hermes-backup.sh` (owned by `hermes`):

```bash
#!/usr/bin/env bash
set -euo pipefail
backup_dir="$HOME/backup"
mkdir -p "$backup_dir"
"$HOME/.local/bin/hermes" backup -o "$backup_dir/hermes-backup-$(date +%F).zip"
find "$backup_dir" -name 'hermes-backup-*.zip' -mtime +30 -delete
```

```bash
sudo -u hermes chmod +x /home/hermes/.local/bin/hermes-backup.sh
```

Service — `/etc/systemd/system/hermes-backup.service`:

```ini
[Unit]
Description=Hermes config backup and prune

[Service]
Type=oneshot
User=hermes
Group=hermes
ExecStart=/home/hermes/.local/bin/hermes-backup.sh
```

Timer — `/etc/systemd/system/hermes-backup.timer`:

```ini
[Unit]
Description=Daily Hermes config backup

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
```

Enable the timer:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now hermes-backup.timer
```

Check it:

```bash
sudo systemctl list-timers hermes-backup.timer   # next run
sudo systemctl start hermes-backup.service        # run once now to test
journalctl -u hermes-backup.service              # logs
```

Pull on a remote nightly in cron. The trailing check fails the job (non-zero exit, so cron mails you) if the destination has no backups after the sync:

```bash
/usr/bin/bash -c 'set -e; dest=/mnt/tank/backups/hermes; rsync -av --delete "hermes@172.20.30.4:~/backup/hermes-backup-*.zip" "$dest"/; [ -n "$(find "$dest" -maxdepth 1 -name "hermes-backup-*.zip" -print -quit)" ] || { echo "ERROR: no backups in $dest after sync" >&2; exit 1; }'
```
