Treating AI Agents Like Infrastructure: Provisioning Hermes Agent with Ansible
9 minute read
I’ve spent 18 years making cloud infrastructure reproducible. Terraform modules for databases. Ansible roles for VM configuration. Packer templates for golden images. When I started running an AI agent on a Linux VM, I didn’t think about it — I reached for Ansible.
Three days and 15 commits later, I had a fully idempotent playbook that provisions Hermes Agent from bare Ubuntu to production-ready. And the patterns I used? They’re the same ones I use for databases and caching clusters. That’s not a coincidence — it’s the point.
The Problem Nobody’s Solving Yet
Most AI agent setups look like this: install the tool, configure it by hand, hope you remember what you did when you need to do it again. It’s 2015-era infrastructure management applied to a 2026 tool.
AI agents accumulate state:
- Memory files that grow and evolve across sessions
- Configuration with model endpoints, API keys, MCP server definitions, and security policies
- Profiles — isolated personas with their own config, memory, and gateway connections
- Skills cloned from Git repos
- Hooks and scheduled tasks that trigger behavior at lifecycle events
- Secrets for a dozen different LLM providers and integrations
Lose any of this and you’re hand-restoring for hours. Worse, you can’t reproduce the setup on a new VM without remembering every config set command you ran three weeks ago.
This is the same problem platform engineering solved for databases a decade ago: infrastructure that accumulates state needs to be provisioned declaratively, or it rots.
The Architecture: 11 Tasks, One Role
The playbook is a single Ansible role with 11 task files, each gated by tags and feature flags:
Here’s what each phase handles:
| Phase | What it does |
|---|---|
| harden | SSH lockdown, UFW deny-all + allow 22, unattended-upgrades |
| install | Clone Hermes, install uv + Python venv, npm dependencies, PATH symlinks |
| configure | Render config.yaml, .env, SOUL.md, honcho.json from templates |
| memory | Seed MEMORY.md and USER.md (once — agent owns after first run) |
| profiles | Create isolated agent personas via hermes profile create |
| mcp | Install MCP server runtimes (npm/pip) per hermes_mcp_servers list |
| skills | Clone skill repos into ~/.hermes/skills/ |
| hooks | Seed event hooks (activity logger, boot checklist) |
| services | systemd units and health-check timers for agent daemons |
| gateway | hermes-gateway.service — messaging daemon + OpenAI-compatible API server |
| cron_jobs | Scheduled tasks seeded via hermes cron create |
Every phase can run independently via --tags. After the initial provision, a config change is --tags configure and takes 12 seconds.
The Patterns That Transfer from Cloud Infra
This isn’t a “cool Ansible project” post. The interesting part is that every pattern I used comes directly from provisioning databases and caching clusters. If you’ve built IaC at scale, you already know how to do this.
1. Feature Flags for Subsystems
Every Hermes subsystem gets a toggle in defaults/main.yml:
This is the same pattern I use for Aurora clusters — optional features like Performance Insights or Enhanced Monitoring are boolean flags, not separate modules. You don’t want to fork the role to disable one subsystem.
2. Seed Once, Then Hands Off
Memory files use force: false:
Ansible writes the initial content. After that, the agent rewrites its own memory every session. Re-running the playbook doesn’t clobber evolved state.
This is identical to how I handle RDS parameter groups — Terraform creates them, but the DBA tunes individual parameters at runtime. The IaC tool seeds the starting state; the runtime system owns the drift.
3. The File Ownership Model
This is the pattern I’m most opinionated about. Every file in the system has exactly one owner:
No ambiguity. No “who changed this and why” confusion. If Ansible manages it, the template is the source of truth. If the agent owns it, Ansible doesn’t touch it. This is the same ownership contract I enforce between Terraform and application teams — the module owns the resource lifecycle, the team owns the application config within it.
4. Secrets Management via ansible-vault
All secrets live in group_vars/all/vault.yml, encrypted with ansible-vault:
The .env template conditionally renders each key:
No plaintext secrets committed to Git — the rendered .env exists only on the host, protected by filesystem permissions. No .env files in the repo. No “I’ll just hardcode it for now.” The same discipline you’d apply to database credentials or cloud provider keys.
5. Idempotent Profile Creation
Profiles are created via the Hermes CLI, wrapped in idempotent Ansible tasks:
The playbook runs hermes profile create (skips if the name exists) and re-applies config set on every run. Each profile gets its own systemd gateway service, its own .env, its own SOUL.md. Three isolated agent personas from one data structure.
This is the Terraform module pattern — declare what you want, let the tool figure out whether it needs to create or update.
The Reprovisioning Story
The real test of any IaC setup: can you nuke the VM and rebuild?
Worth being precise about the state boundary, because this is where most “IaC for agents” write-ups get sloppy. Long-term memory lives in Honcho, an external persistent-memory backend that runs alongside Hermes’ built-in local memory — it does not replace it. So the on-box picture is: state.db and logs/ are disposable, config and secrets come from Ansible templates, and the two files that genuinely evolve on-box — MEMORY.md and USER.md — are covered by a scheduled off-box state backup. “Disposable compute” only holds because every byte of state has a named home that isn’t the VM’s disk.
There’s a second half to this contract: drift. The agent rewrites its own memory every session, and some of that is beneficial drift — it learns context rules you never wrote. Backups capture it, but they don’t put it under version control. My rule: when the agent lands on something that should be baseline behavior for every future provision, I promote it by hand into the SOUL.md template in Git. Config lives in Git; memory lives in backups; promotion between the two is a deliberate human act.
This is the same pattern as RDS: the data lives in managed storage, the compute is disposable. Reprovisioning is a terraform apply + ansible-playbook away from a clean slate.
Fallback Routing: Because Primary Providers Go Down
The primary model is GLM-5.1 via Zhipu’s first-class zai provider. But LLM providers have usage windows, rate limits, and occasional outages. The playbook configures automatic failover:
When zai returns a 429, 500, or 503, Hermes switches to MiniMax mid-turn, preserving conversation history. The failover is turn-scoped — the next user message retries the primary, so a provider’s bad hour doesn’t pin you to the fallback. One-shot per turn; if the fallback also fails, normal error handling takes over.
This is the read-replica failover pattern, with one caveat a database engineer should appreciate: the “replica” is not identical. A fallback model can have a different context window and different instruction-following behavior than the primary, so failover is a degradation path, not a transparent one — pick a fallback whose context window covers your typical session length. Not sophisticated, but it covers the 90% case: your agent keeps working when a single provider has a bad hour.
When to Do This
This approach pays off when the conditions are right:
Your agent runs on a VM and accumulates state. Memory files, skills, scheduled tasks, MCP server configs — if losing any of this means hours of manual restoration, you need IaC.
You’re running the same agent for months, not experimenting weekly. The upfront investment in playbook structure pays off when you’re iterating on configuration, not switching agents. Fifteen commits over three days, then every change after that is a one-liner in vars.yml and a 12-second --tags configure run.
You need to reproduce the setup on a new machine. Whether it’s a VM migration, a disaster recovery scenario, or just spinning up a second instance — if the answer to “how do I rebuild this?” is “I don’t remember,” you need declarative provisioning.
You already think in IaC. If you’re comfortable with Ansible roles, Terraform modules, or similar tools, applying them to AI agents is a natural extension. The patterns are identical — the workload is different.
And the inverse: if you’re still evaluating which agent to run, or your setup changes weekly, don’t do this yet. Declarative provisioning pays off on a stable workload. Automating a moving target just gives you a playbook that’s always out of date. Run the agent by hand for a month; when the config stops churning, that’s your signal to write the role.
Conclusion
The platform engineering instinct is simple: if it runs on infrastructure, treat it like infrastructure.
AI agents are infrastructure now. They run on VMs. They accumulate state. They need secrets management, health monitoring, reproducible provisioning, and a clean reprovisioning story. The patterns for all of this already exist — we’ve been using them for databases and caching clusters for years.
None of the patterns here were invented for this project — feature flags, seed-once semantics, ownership contracts, vault-managed secrets, idempotent creation are the same discipline I apply to Aurora clusters and Terraform modules. The workload is new; the discipline isn’t. And as agents get more autonomous, the cost of skipping it goes up: a stateless-microservice mindset applied to an agent doesn’t give you a crash, it gives you catastrophic amnesia. Declarative state management is how that scales.
If you’re running AI agents in production (or getting close), stop treating them like desktop apps. They’re infrastructure. Provision them like it.
The playbook described here provisions Hermes Agent, an open-source AI agent from Nous Research. The patterns apply to any agent that runs on a VM and accumulates state.