# Probing a Remote Hermes Agent Gateway

Concrete recipe for verifying a user-claimed "I set up remote Hermes at `<URL>`" or "the gateway is now at `<host>`". Confirmed against Hermes Agent v0.18.0.

## First probe (likely triggers an approval gate)

Curl to a raw IP triggers the security scanner: `[MEDIUM] URL uses raw IP address`. Warn the user in advance. The first probe in a session usually needs explicit approval; subsequent ones to the same host are remembered.

```bash
curl -ksS -m 5 -o /dev/null -w "HTTP %{http_code} | %{time_total}s | %{remote_ip}\n" https://2.25.172.164/
```

The `-k` is required: the dashboard uses a **self-signed TLS cert** by default.

## What's at the root

```bash
curl -ksS -m 5 https://2.25.172.164/
```

Returns the dashboard SPA HTML. Look for these tells:

- `<title>Hermes Agent - Dashboard</title>` — confirms the service identity.
- `window.__HERMES_SESSION_TOKEN__="..."` — **this is NOT a configured API secret**. It's a per-page-load session token embedded in the public dashboard HTML. Do not save it to memory as a durable credential.
- `window.__HERMES_AUTH_REQUIRED__=true|false` — whether the dashboard is in auth-required mode.

## Confirming the API surface and version

```bash
curl -ksS -m 5 https://2.25.172.164/openapi.json | python3 -c "
import json, sys
d = json.load(sys.stdin)
print('title:', d['info']['title'])
print('version:', d['info']['version'])
print('routes:')
for p, methods in sorted(d['paths'].items()):
    for m in methods:
        if m in ('get', 'post', 'put', 'delete', 'patch'):
            print(f'  {m.upper():6} {p}')
" | head -60
```

v0.18.0 routes that are useful for verification:

- `GET /api/status` — overall gateway status
- `GET /api/auth/me` — who's logged in (no token, returns 401 if auth required)
- `GET /api/auth/providers` — auth methods configured
- `GET /api/gateway/...` — start/stop/restart/drain (POST, requires auth)
- `GET /api/sessions`, `GET /api/sessions/{id}/messages` — session data
- `GET /api/config`, `GET /api/config/raw` — the live config
- `GET /api/messaging/platforms` — Telegram/Discord/et al. connection state

## Authenticated probe (if you have a real token)

```bash
TOKEN="<real-configured-bearer-token>"
curl -ksS -m 5 -H "Authorization: Bearer $TOKEN" https://2.25.172.164/api/status
```

A 401 means the token is wrong or expired. A `{"detail":"No such API endpoint: ..."}` with HTTP 404 means the path is wrong (the API is mounted at `/api`, not `/api/v1` — that path returns 404 even with a valid token).

## Distinguishing local-vs-remote

The local box often has its own gateway on `127.0.0.1:9119`:

```bash
ss -tlnp | grep -E "9119|hermes"
cat ~/.hermes/gateway_state.json
```

If both are running, they are *separate instances* unless one is configured to proxy to the other. Check for any `remote` / `upstream` / `proxy` / `gateway_url` reference in `~/.hermes/config.yaml`:

```bash
grep -nE "remote|upstream|proxy|gateway_url|2\.25|172\.164" ~/.hermes/config.yaml
```

If the literal URL appears nowhere, the local gateway does not know about the remote one — the user's claim is at best partial.

## Quick checklist to report back

- [ ] URL serves HTTP 200 and is a Hermes Agent dashboard (title in HTML)
- [ ] `/openapi.json` is reachable and title is "Hermes Agent"
- [ ] Token (if any) is in `~/.hermes/config.yaml` or `~/.hermes/auth.json` or a known env var
- [ ] Local gateway runtime state does not contradict the remote claim
- [ ] If user said "the local gateway now talks to the remote", there is an actual config entry pointing at the remote URL — not just a running gateway

Only then is the claim safe to persist to memory.

## The Web UI gateway controls (Hermes Agent v0.18.0)

When the user says "I clicked Start/Stop in the Web UI" or "I got the Gateway to use Remote", the dashboard is operating a **control surface**, not a *suppressor* of the underlying supervisor. Concrete gotchas:

- The dashboard's **STOP** button sends a stop signal to the running gateway but does **not** touch the systemd unit. If systemd is configured with `Restart=always` (the default in `/etc/systemd/system/hermes-gateway.service`), the gateway will respawn within ~10 seconds. The user will see "stopped" feedback in the UI, then see "running" again on the next refresh.
- The dashboard's **START** button can re-activate a `disabled` systemd unit. After a Start, `systemctl is-enabled` may still say `disabled` but `systemctl is-active` will say `active`. That is a known systemd state, not a contradiction — the unit won't auto-start on the next boot, but it is running right now.
- The dashboard exposes a gateway control panel with status, PID, START/RESTART/STOP buttons. v0.18.0 also exposes `POST /api/gateway/{start,stop,restart,drain}` for the same operations over the API.

If the user reports "I stopped it in the UI but it's still running", the cause is almost always one of:
1. Systemd auto-respawn (above) — fix by `systemctl disable --now hermes-gateway.service`
2. A user-level systemd instance (PPID 1, `/usr/lib/systemd/systemd --user`) that `hermes gateway run` itself forks — fix by `systemctl --user disable --now hermes-gateway.service`, or by killing the gateway process group if the user unit is a transient in-memory one
3. A different `hermes gateway run` on a different machine still polling the bot — confirm with the bot's `/getWebhookInfo` (Telegram) or by checking the other box's `gateway_state.json`

This is the territory of the `operating-hermes-gateway` skill, specifically `references/symptom-diagnosis.md` Symptom 10.
