| name | hermes-remote |
| description | Manage a remote Hermes Agent deployment from the CLI — list/create/pause/trigger cron jobs, check status, inspect env vars, swap models, and send one-shot chat calls to the OpenAI-compatible /v1/chat/completions endpoint. Use this skill whenever you need to control a Hermes instance that lives on another machine (VPS, Coolify app, etc.), since the upstream `hermes` CLI only talks to a local gateway. |
| when_to_use | Trigger on phrases like "list the hermes crons", "pause the hermes cron", "check the hermes model", "what is hermes running", "swap the hermes model to X", "trigger the gbrain sync cron", "what's on the remote hermes", "show hermes env", "restart hermes", "ask hermes", "chat with hermes", "one-shot to hermes", anything about scheduled tasks or a direct chat call to a remote Hermes. |
| allowed-tools | Bash(hermes-remote:*) Bash(python3 *hermes-remote*:*) |
hermes-remote
Remote CLI for talking to a Hermes Agent deployment over its REST API. Stdlib-only Python 3. No pip installs.
The binary lives at ${CLAUDE_SKILL_DIR}/../../bin/hermes-remote inside this plugin, and bin/ is auto-added to PATH when the plugin is enabled — so call it as hermes-remote.
When to reach for this
- The user asks about the state of a remote Hermes instance (status, model, crons, env).
- The user wants to pause, resume, trigger, or delete a Hermes cron job.
- The user wants to create a new cron — always use the skillify pattern below, not a prompt-only job.
- The user wants to inspect which env vars Hermes can see vs has missing.
For one-shot chat calls (/v1/chat/completions), use hermes-remote chat "<prompt>" — needs HERMES_CHAT_API_KEY set (distinct from the admin password).
hermes-remote chat "status?"
hermes-remote chat --model anthropic/claude-haiku-4-5 ...
hermes-remote chat --max-tokens 200 "..."
hermes-remote chat --system "Be terse." "what is k8s?"
hermes-remote chat --json "..."
For multi-turn conversations or streaming, use an OpenAI-compatible SDK pointed at $HERMES_URL/v1 — this wrapper is for one-shot calls and cron-friendly scripting.
When NOT to use this — fall back to the Hermes REST API directly
This wrapper covers a deliberately narrow slice: status, model, env list (read-only), config get/set, cron CRUD, and one-shot chat. It does NOT wrap a local CLI — there is no hermes remote-control CLI to fall back to. "Fall back" here means hit the Hermes REST API directly with curl (or http.client / requests) using the same cookie + bearer token flow hermes-remote uses internally. There is no other route.
Specific cases where you should talk to the Hermes endpoint directly, not through hermes-remote:
- Streaming chat responses.
hermes-remote chat buffers the full response then prints. For live token streaming, POST to $HERMES_URL/v1/chat/completions with "stream": true and an SSE-capable client.
- Multi-turn conversations / conversation history. The
chat subcommand is one-shot (system + user only). Maintain a messages[] array yourself and POST to /v1/chat/completions directly, or use an OpenAI-compatible SDK pointed at $HERMES_URL/v1.
- File uploads, attachments, or anything multipart. Not wrapped. Hit the relevant Hermes endpoint with
curl -F or a multipart-capable HTTP client.
- Mutating env vars, editing existing cron jobs, managing deliveries/platforms/skills, or triggering a gateway restart.
hermes-remote env list is read-only; cron create exists but there is no cron edit; gateway/platform/skill/delivery management isn't wrapped at all. Use curl against /api/env, /api/cron/jobs/<id> (PUT/PATCH), /api/gateway/*, /api/platforms/*, /api/skills/* with the session cookie + bearer token.
- Authentication/login flows or session management.
hermes-remote logs in silently on every call using HERMES_PASSWORD. If you need to inspect the raw /login response, manage cookies manually, or debug auth, hit /login directly.
- Any Hermes endpoint the wrapper simply doesn't expose yet — job run history, raw logs, delivery channels, webhooks. Grab the cookie + token the same way (
POST /login, scrape __HERMES_SESSION_TOKEN__ from /) and call the endpoint directly.
Don't get stuck in a loop. If the user needs something the wrapper clearly doesn't cover, switch to curl against the Hermes REST API immediately rather than contorting hermes-remote flags. The wrapper's job is to make the common read/cron paths ergonomic — not to be the only way in.
Configure
Config is layered. Highest precedence first:
--env-file <path> passed on the CLI
.env.local / .env found by walking up from the current working directory
- Shell environment (including vars exported via Claude Code settings)
Project .env files override the shell — opposite of the usual rule. This
lets you drop a .env in whichever repo you're working in and have it win
over any globals, without having to unset anything.
Only HERMES_* and COOLIFY_* keys are picked up from .env files.
If no URL or password is configured, the CLI prints a suggestion showing
exactly where to put them (project .env preferred, or ~/.claude/settings.json
for a global default).
Set one URL mode and one password source.
URL — one of:
export HERMES_URL="https://hermes.example.com"
export HERMES_VPS_IP="1.2.3.4"
export HERMES_HOST="hermes.example.com"
The second form hits the IP on port 80 directly and passes the Host header, letting Traefik/Caddy/Nginx route. Skips DNS.
Password — one of:
export HERMES_PASSWORD="..."
export HERMES_PASSWORD_CMD="pass show hermes"
If the Hermes instance is managed by Coolify, the script can fetch the password automatically:
export COOLIFY_URL="http://your-vps:8000"
export COOLIFY_API_KEY="..."
export HERMES_APP_UUID="..."
Admin user (optional, default admin@example.com):
export HERMES_ADMIN_USER="you@example.com"
Commands
hermes-remote status
hermes-remote model
hermes-remote env list [--all]
hermes-remote config get [key]
hermes-remote config set <key> <value>
hermes-remote cron list
hermes-remote cron show <id-or-name>
hermes-remote cron pause <id>
hermes-remote cron resume <id>
hermes-remote cron trigger <id>
hermes-remote cron remove <id> -y
hermes-remote cron create \
--name <name> --schedule "every 15m" \
--script /path/to/work.sh \
--prompt "If script output contains ERROR, report it. Otherwise [SILENT]." \
--deliver origin --model anthropic/claude-haiku-4-5
Most subcommands take --json for machine-parseable output. Recommended: pipe through jq for field extraction and filtering — e.g. hermes-remote cron list --json | jq '.[] | select(.state=="paused") | .id', hermes-remote config get --json | jq -r .model, hermes-remote status --json | jq .gateway_state. Every JSON payload is prefixed with a tool: {name, version} field so you can self-diagnose version drift (hermes-remote --version also works).
Changing the LLM model (READ THIS)
Hermes's config.yaml overrides env vars for keys it knows about. The LLM_MODEL env var only sets the default at first boot. Once config.yaml has a model, LLM_MODEL is ignored — so setting it in Coolify / Docker env and redeploying won't change anything.
To actually change the model, write it via the config API:
hermes-remote config set model anthropic/claude-haiku-4-5
No redeploy needed — it's live. Verify:
hermes-remote model
Same applies to any other config.yaml key (agent.max_turns, compression.threshold, telegram.channel_prompts, etc.) — config set is the mutation path.
Skillify cron pattern (READ BEFORE CREATING A CRON)
A Hermes cron job has two input fields: prompt and script.
prompt runs inside a full agent LLM session on every tick. Default model, full context, tool loop. Expensive.
script runs before the agent starts — pure shell/python, zero LLM tokens. Its stdout becomes context for the agent.
If your cron does deterministic work (sync, embed, poll, sanity check), the work belongs in --script. The agent should only be invoked to decide "report or stay quiet" based on the script's output.
Template for any recurring deterministic cron:
hermes-remote cron create \
--name <job-name> \
--schedule "every 15m" \
--script /path/on/hermes/host/to/worker.sh \
--prompt "If the script output contains ERROR, report the relevant lines. Otherwise respond with only [SILENT]." \
--model anthropic/claude-haiku-4-5 \
--deliver origin
When the agent's response starts with [SILENT], Hermes suppresses delivery — so successful quiet runs cost essentially nothing.
Why this matters (real incident): A gbrain sync cron on our Hermes deployment was created with --prompt only, using the default Sonnet 4.6 model, running every 15 minutes. Each run invoked the full reasoning loop to execute two shell commands masquerading as reasoning work — burning OpenRouter credit fast. Fixing it to --script + minimal Haiku prompt was a roughly three-orders-of-magnitude cost reduction.
Rules of thumb for cron models:
- Cheap recurring jobs →
anthropic/claude-haiku-4-5 (fast, cheap, fine for "is this output an error?")
- Genuinely latent work (entity extraction, summarisation, non-trivial judgment) → stay on Sonnet but cap
max_tokens in Hermes config.
Quirks
is_build_time is not a valid field on Hermes env POSTs (will 422). Stick to {"key":..., "value":..., "is_preview": false, "is_literal": true}.
- urllib.request overrides Host based on URL netloc; this script uses
http.client with skip_host=True to honour explicit Host headers.
- Hermes's
/api/env only lists vars that match its config schema. A var can be in the container's shell env but not in this list — they're different surfaces.
Troubleshooting
HTTP 401 from /login: password wrong, or you're hitting the :3000 port directly and it has a separate basic-auth gate in front. Route through the main reverse-proxy URL.
Couldn't extract __HERMES_SESSION_TOKEN__: login worked but the SPA HTML didn't include the token. Probably a version mismatch; upstream Hermes < 0.10 may not expose it in the same place.
Unauthorized on specific /api/* endpoints with cookie + token set: that endpoint isn't wired up in your Hermes version, or you're on a preview that renamed it. Try listing with --json on a known-good endpoint first to confirm auth is working.