Entity: Hermes Agent (AI Agent Service)
This page is the repository hub for the Hermes Agent Proxmox KVM deployment.
⚠️ DECOMMISSIONED (2026-09-26) — VM 501
hermeswas destroyed; it had been unused. The data disk was preserved on purpose aslocal-lvm:vm-501-data-orphan(the whole/home/hermesstate), and./create.shcan recreate the VM and reattach it with zero re-onboarding. Everything below is the historical architecture; see Removed: VM 501 decommissioned for what was deleted and what survives.
Overview
Hermes Agent ran as a dedicated KVM VM (VM 501 hermes) on the Proxmox node tazlab, migrated from the old LXC container (CT 105) on 2026-08-12 via the CRISP project hermes-agent-redesign and decommissioned on 2026-09-26. The deployment lived in a standalone repository (github.com/tazzo/hermes-agent) with Terraform + Ansible + shell orchestrators — no longer part of ephemeral-castle.
Data persistence followed the pet/cattle principle as in the old LXC deployment, adapted to VM semantics: the root disk was ephemeral (destroyed with the VM), while a dedicated 20G data disk survives destroy/create cycles via an lvrename out-of-convention trick. That is exactly the mechanism the decommission relied on.
Removed: VM 501 decommissioned (2026-09-26)
The board destroyed the unused Hermes Agent VM on 2026-09-26 via destroy.sh (run log: /workspace/hermes-agent/logs/destroy_20260926_075300.log).
| Removed | Detail |
|---|---|
VM 501 hermes | KVM guest destroyed (qm destroy 501 --destroy-unreferenced-disks 0) — config and running state gone |
| Root disk | local-lvm:vm-501-disk-0 (30 GB, ephemeral by design) |
| Cloud-init disk | local-lvm:vm-501-disk-1 and local-lvm:vm-501-cloudinit |
| Terraform state | VM resource removed from state (phase 3.5: Successfully removed 1 resource instance(s)) |
Evidence from the run:
[phase2] update VM 501: -delete virtio1 → unused0: local-lvm:vm-501-disk-2
[phase3] Renamed "vm-501-disk-2" to "vm-501-data-orphan"
Logical volume "vm-501-cloudinit" removed / "vm-501-disk-1" removed / "vm-501-disk-0" removed
[phase3.5] Successfully removed 1 resource instance(s) (terraform state)
[phase4] DATA DISK PRESERVED: local-lvm:vm-501-disk-2 (as vm-501-data-orphan)
Preserved on purpose: the data disk local-lvm:vm-501-disk-2 (20 GB), renamed out of the vm-501-* naming convention to local-lvm:vm-501-data-orphan. It holds the whole /home/hermes state (Hermes code and sessions, operator settings, the refurb-collector workspace, the research site). destroy.sh never deletes it, and the orphan name is deliberately outside the convention that qm destroy sweeps.
Recreate with zero re-onboarding: ./create.sh in /workspace/hermes-agent renames vm-501-data-orphan back to vm-501-disk-2, re-creates the VM, reattaches the disk and re-runs Ansible — the pet volume is the same, so no restore or onboarding is needed.
Live verification (2026-09-26, run from CT106 tazpod-proxmox):
ssh root@192.168.1.200 'qm list | grep -E "^\s*501\s"' # → no output: VM 501 NOT PRESENT
ssh root@192.168.1.200 'lvs | grep vm-501'
# → vm-501-data-orphan pve Vwi-a-tz-- 20.00g data 28.59
Historical context worth keeping: this is a clean, reversible retire, not a data loss — the point of the lvrename orphan pattern is precisely that a destroy leaves the state volume intact. The prior LXC deployment (CT 105) had been destroyed on 2026-08-12 and was not recoverable; VM 501 is.
Architecture
Proxmox node "tazlab" (192.168.1.200)
└── VM 501 "hermes" (KVM, Ubuntu 26.04 LTS "resolute")
├── 4 vCPU, 8 GB RAM, static IP 192.168.1.205/24 (gw 192.168.1.1)
├── virtio0: root disk 30 GB (local-lvm) — EPHEMERAL, destroyed with VM
├── virtio1: data disk 20 GB (local-lvm:vm-501-disk-2) — PERSISTENT pet
│ └── /home/hermes (whole home = Hermes state + code, option A)
├── Hermes v0.20.0 (bare-metal via install.sh)
│ ├── hermes-gateway.service
│ ├── hermes-dashboard.service (port 9119, bind 0.0.0.0 + basic auth)
│ └── hermes-restart.service (root oneshot — restarts gateway+dashboard, Hermes self-restart helper)
├── Post-2026-08-12 additions (user space: systemctl --user + loginctl enable-linger hermes)
│ ├── hermes-research.service (Caddy static publisher, :8080, VPN-only)
│ ├── hermes-chrome.service (headless Chrome, CDP 127.0.0.1:9222 — Playwright connect_over_cdp only)
│ ├── /mnt/shared (CIFS from //192.168.1.207/shared, CT 107 lxc-storage)
│ └── Refurb collector (VM-side scripts in /home/hermes, cron every 2 days — NOT in IaC)
└── Users: bootstrap (admin, sudo NOPASSWD), hermes (UID 10000, no sudo)
Repository Structure
hermes-agent/ # github.com/tazzo/hermes-agent
├── create.sh # 8-phase orchestrator (enterprise logging)
├── destroy.sh # unlink data disk → lvrename → qm destroy
├── terraform/ # VM 501 resource (root disk ONLY — data disk outside state)
├── ansible/ # 4 playbooks + roles (baseline, agent, configure, verify)
├── configs/
│ └── runtime.env # VM_ID, IP, CORES, MEMORY, DISK sizes
└── logs/ # Timestamped, rotated (keep 20)
Quick Facts
| Property | Value |
|---|---|
| Status | Decommissioned 2026-09-26 — VM destroyed, data disk preserved as local-lvm:vm-501-data-orphan; ./create.sh recreates it |
| VM ID | 501 (KVM) |
| Hostname | hermes |
| IP | 192.168.1.205 |
| OS | Ubuntu 26.04 LTS (resolute) |
| Resources | 4 vCPU, 8 GB RAM |
| Root disk | 30 GB local-lvm (ephemeral) |
| Data disk | 20 GB local-lvm:vm-501-disk-2 (persistent pet, mounted at /home/hermes) |
| Dashboard | http://192.168.1.205:9119 (basic auth: user hermes, password in gopass infra/hermes-vm/dashboard-password) |
| Base deployment | 2026-08-12 — all rows above; additions below deployed later (topic has the full picture) |
| Self-restart | systemctl start hermes-restart.service (root oneshot, 2026-08-13); hermes gateway restart CLI NOT bypassable (hardcoded root guard in binary) |
| User services | hermes-research.service (Caddy :8080, VPN-only) + hermes-chrome.service (headless Chrome, CDP :9222) — user units, linger enabled (2026-08-18) |
| CIFS mount | //192.168.1.207/shared (CT 107 lxc-storage) → /mnt/shared, uid/gid 10000, x-systemd.automount (2026-08-20) |
| Refurb collector | VM-side only (/home/hermes), cron every 2 days — NOT in hermes-agent repo |
| LLM Backend | Configurato dall’utente via dashboard al primo accesso (30+ provider) |
| Hermes version | v0.20.0 (2026.8.3) |
| User | hermes (UID 10000, non-root, no sudo) |
| Bootstrap user | bootstrap (sudo NOPASSWD, maintenance) |
| Repo | github.com/tazzo/hermes-agent (default branch main) |
| Old deployment | CT 105 LXC — destroyed 2026-08-12 |
Canonical Starting Pages for Agents (historical — see Removed section)
Deployment & Lifecycle
- VM Deployment Architecture — KVM architecture, persistence model, lifecycle, key discoveries
LXC Deployment Architecture— SUPERSEDED (seehermes-lxc-deployment.mdbanner, historical only)
CRISP Project (source of truth)
- Project:
skills/crisp/projects/hermes-agent-redesign/(RESEARCH/DESIGN/PLAN/tasks/retrospective) - Report:
skills/memory/reports/2026-08-12_hermes-agent-vm-redesign.md
Secrets (gopass)
infra/hermes-vm/dashboard-password— dashboard basic authinfra/hermes-vm/bootstrap-password+infra/hermes-vm/hermes-password— SSH passwordsinfra/ssh-host-keys/hermes-agent/ed25519— SSH host key (injected post-boot via Ansible)bootstrap/proxmox/token-id+bootstrap/proxmox/token-secret— Proxmox API for create/destroyinfra/hermes-vm/samba-username+infra/hermes-vm/samba-password— CIFS credentials (/etc/cifs-credentials-hermes, injected via envSAMBA_USERNAME/SAMBA_PASSWORDat playbook run)
Key Discoveries (2026-08-12, destructive tests)
qm destroydeletes by naming convention: removes ALLvm-501-*volumes even unused, even with--destroy-unreferenced-disks 0. Fix:lvrenamethe data volume tovm-501-data-orphanbefore destroy, rename back on create.- Data disk OUTSIDE Terraform state: if declared in
.tf,terraform destroydeletes it. Managed viapvesm alloc+qm set/qm unlink. - gopass multi-line:
gopass show -oreturns only first line (35B vs 411B for SSH keys) — usegopass cat. - Ansible 2.21 become non-root bug:
sudo -u hermespattern instead ofbecome_method: su. - Dashboard auth: scrypt hash must be STANDARD base64 with padding +
maxmem=0(urlsafe fails). Hash computed locally where gopass exists. agent.wait_for_ip.disabled = true: skips IP wait (static via cloud-init) → apply drops from ~15 min to <1 min.
Known Issues / Technical Debt
| TD | Area | Summary |
|---|---|---|
| TD-056 | gopass cache | GPG cache expires ~45 min — long builds fail with “gopass locked”; show -o truncates multi-line secrets (use cat) |
Relationships
- Infrastructure: ephemeral-castle — former home;
ephemeral-castle/hermes/removed (commit 504f353); now standalone repo - GitOps cluster: tazlab-k8s — talks to the cluster via LLM provider
- Semantic memory: mnemosyne-mcp-server
- Context governance: Skills System — operational memory and rules