# Single-Gateway Architecture

The most stable Hermes setup: **one gateway on a VPS that's always on, with the user's Desktop and any other clients as pure clients.** This file documents the pattern, the "what lives where" decision table, and the concrete config to make it stick.

## Why this pattern

Hermes gateways own a *shared exclusive resource*: the Telegram/Discord bot tokens, the cron schedule, the session DB, the auth/credential pool. Two gateways fighting for any of these is the dominant source of instability. A single gateway eliminates the entire class of failure. The VPS makes it survive the user's box being closed, rebooted, or offline.

## What lives where

| Capability | Lives on the VPS gateway | Lives on the user's Desktop |
|---|---|---|
| Telegram bot poller | ✅ sole owner | — |
| Discord bot poller | ✅ sole owner | — |
| Session DB (`state.db`) | ✅ sole owner | read-only mirror via API |
| Cron scheduler | ✅ sole owner | — |
| Provider auth (OpenAI/Nous/etc.) | ✅ sole owner | sent in API calls |
| OAuth refresh state | ✅ sole owner | — |
| Credential pool | ✅ sole owner | — |
| User-facing chat (TUI) | — | ✅ client |
| Browser dashboard | ✅ served by VPS | ✅ consumed in browser |
| File operations, terminal, code exec | scoped to whichever side runs the request | scoped to whichever side runs the request |
| Memory | ✅ sole owner | accessible via VPS API |

The mental model: the VPS is the server, the Desktop is a browser tab. The user can close the lid and the bots keep working.

## The migration checklist

When moving from a local-and-VPS setup to VPS-only:

1. **Inventory both sides.** On the Desktop, see `local-gateway-control.md` for the parallel checks. On the VPS, `ps -ef | grep hermes`, `ss -tlnp | grep -E "9119|gateway"`, `cat ~/.hermes/gateway_state.json`.
2. **Confirm the VPS gateway is healthy** before stopping anything local. Probe `/openapi.json` and `/api/status` (see `verifying-user-claims/references/probing-remote-hermes-gateway.md` for the recipe).
3. **Stop and disable the local gateway triad** in this order: `gateway run` (manual foreground) → systemd unit → `hermes serve` on 9119. The exact commands are in `local-gateway-control.md`. The user must run these from a regular terminal, not from inside a Hermes session (the CLI blocks it on purpose). **Order matters:** kill the manual PIDs first (so the lock is free), then disable systemd, then handle 9119. Otherwise systemd's `Restart=always` respawns the manual one.
   - If the gateway keeps coming back after disabling the system unit, check the **user-level systemd** that `hermes gateway run` itself forks (`/usr/lib/systemd/systemd --user`, PPID 1). `systemctl --user disable --now hermes-gateway.service` if the unit file exists, or kill the gateway process group directly if it's a transient in-memory unit.
   - **Do not kill `hermes serve` on 9119 if it is the TUI session's own local chat backend** (tell: child has `--session-key <sid>`, parent=PID 1). That's not the bot gateway, it's the chat you're using right now.
4. **Verify the local side is fully gone.** Re-run the inventory. Expect zero `Hermes` processes, no port 9119 listener, no `hermes-gateway.service` enabled. If anything is still alive, the bots will still flap.
5. **Bookmark the VPS dashboard** on the user's Desktop browser. The URL is whatever the VPS exposes (e.g. `https://<vps-ip>/`), with a self-signed cert warning to accept once.
6. **Confirm single-fire.** Send a test message to the Telegram bot. Expect exactly one reply. Schedule a 1-minute test cron. Expect one notification. If you see two, a second gateway is still alive somewhere.

## What changes for the user

- The Desktop's `hermes` CLI is now only useful for things that should run *locally* (one-off file ops, local coding tasks). Anything that should reach a bot, fire a cron, or touch a session goes through the VPS — usually via the browser dashboard or by sending a Telegram/Discord message.
- Local `config.yaml` becomes much smaller. Most provider auth, platform tokens, and gateway config can be removed or set to empty, because the Desktop isn't the gateway anymore. Leave the model config if the user runs local `hermes chat` sessions.
- The user can `ssh root@<vps>` for any gateway admin (start/stop/restart, add a platform, rotate a token). For day-to-day chat, the browser dashboard is enough.

## When NOT to use this pattern

- The user's VPS is unreliable (frequent reboots, no static IP, no TLS cert) and they need guaranteed uptime. Then a local gateway is the only thing that can keep the bots alive.
- The user is on a metered / capped VPS and running the gateway is expensive. Then a local gateway (laptop or home server) is cheaper.
- The user is doing development on Hermes itself and needs the gateway to restart often with code changes. Local is faster to iterate on.

In all three cases, the principle is the same: **one gateway, in the place that minimizes pain, with no second copy anywhere.**

## When the user says "I have a remote" — three interpretations

When the user says "remote gateway" or "I set up the remote" or "I got the gateway to use remote", they usually mean one of:

1. **A VPS installation** — separate machine, separate `state.db`, accessed over HTTPS. This is the pattern this file documents.
2. **A relay / connector** — the local gateway tunnels through a relay (e.g. `hermes gateway enroll`). The local gateway is still the gateway; the relay is just a transport. Different config, different commands.
3. **The local box bound to a public IP** — no separate machine, just the same gateway now reachable on a public address. Then there's only one gateway, and the "remote" is just a different way to reach it. Verify with `ss -tlnp` — the same PID listens on the public interface.

If you can't tell which one the user means, ask. The wrong answer sends you down the wrong code path for hours.
