Agent stack build plans · № 4 · Hermes CI/CD healer
Set it up
Six stages. Do them in order — the webhook is last because a listener with no sandbox and no approvals is a foot-gun with a public URL.
1 · Install Hermes on the server
Linux / macOS / WSL2
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
Run hermes once interactively to connect a model
provider — it’s model-agnostic (Nous Portal, OpenRouter, OpenAI, or your
own endpoint). Config lands in ~/.hermes/config.yaml, secrets in
~/.hermes/.env. The gateway daemon is hermes gateway;
the docs cover installing it as a systemd/launchd service so it survives
reboots — do that now, not after the first missed webhook.
2 · Make port 8644 reachable — you build this
GitHub will POST to http://your-server:8644/webhooks/ci-heal,
so that address must resolve and accept traffic from the public internet.
Hermes provides the listener, not the reachability. Two sane options:
- A small VPS running Hermes + Docker. Boring, always on, and the firewall rule is one line. Put a TLS-terminating reverse proxy in front if it’s naked on the internet.
- A tunnel from a homelab box to a public hostname, if the machine with Docker lives behind NAT. More moving parts; the tunnel going down means failures silently unhealed.
3 · Bake the sandbox image — you build this
The agent’s container needs git, the gh CLI, and your
repo’s toolchain (the same versions CI uses — drift here produces
“works in my sandbox” PRs). Build a small image and point the
Docker backend at it. For GitHub auth, mint a fine-grained token scoped to
one repo with contents + pull-requests write, put it in
~/.hermes/.env, and expose it to sandbox processes via
terminal.env_passthrough — Hermes strips the environment from
child processes by default, which is exactly what you want for everything
else.
4 · Configure the webhook platform and route
~/.hermes/config.yaml — the shape
# Key names below are verified against the Hermes webhook docs,
# July 2026 (v0.19.0). Check docs > messaging > webhooks for the
# exact nesting in your release before pasting blind — or use
# prompt 1 on the prompts page and let the agent write this file.
platforms:
webhook:
extra:
port: 8644
secret: ${WEBHOOK_SECRET} # value lives in ~/.hermes/.env
rate_limit: 30 # deliveries/min — the documented default
max_body_bytes: 1048576
# routes: each has events, filters, a prompt template,
# deliver targets, and an optional script hook.
# ci-heal route: accept workflow-failure events for your repo,
# filter OUT runs on ci-heal/* branches (loop-breaker),
# prompt: see prompts page, stage 2,
# deliver: github_comment (plus telegram or discord if you like).
The prompt template interpolates payload fields — the
docs’ example syntax is {pull_request.title}; fields mirror
the GitHub payload JSON. HMAC validation of
X-Hub-Signature-256 is built in once secret is set.
Generate the secret properly:
Generate the shared secret
openssl rand -hex 32
5 · Lock the approvals before the first delivery
In config.yaml: start with approvals.mode: manual
and sit through one full run approving each command — you’ll learn
exactly what the agent wants to do. Then move to smart (LLM
risk-assessment) for unattended nights. Add approvals.deny
globs for the unforgivables regardless of mode — deny patterns matching
gh pr merge, git push --force, and pushes to your
default branch. Do not run --yolo on an agent
with repo write access; Hermes’s hardline blocklist (fork bombs,
rm -rf /) survives even --yolo, but it guards the
host, not your repository.
6 · Point GitHub at it
Repo → Settings → Webhooks → Add webhook: payload URL
http://your-server:8644/webhooks/ci-heal (https if you put a
proxy in front — do), content type application/json, the secret
from stage 4, and select individual events → Workflow runs
rather than “send me everything.” Then break something on purpose:
push a failing commit to a scratch branch, or use the webhook’s
Redeliver button on a past failed-run delivery. Watch the
gateway logs, approve the commands manually, and read the PR it opens like
you’d review a stranger’s.