π Find this useful? β Support development on Ko-fi β
The mobile bridge sidecar for Hermes Dispatch.
It sits next to your self-hosted Hermes agent (fronting the hermes-agent
dashboard β REST + JSON-RPC-over-WebSocket; verified against v0.17.0, see
docs/PHASE0-FINDINGS.md) and gives the Android app
what a phone can't do alone:
- Server-held runs β the bridge owns the agent's SSE stream, so a task keeps running and reporting even when your phone is asleep or offline.
- Background push β progress and completion are POSTed to an ntfy topic (UnifiedPush), driving the app's lock-screen "live update".
- Cron classification β a new task that "sounds recurring" (e.g. "every Monday at 9amβ¦") is turned into a scheduled job instead of a one-shot run.
- One token for the phone β the bridge holds the dashboard session token
server-side; the app only carries a
BRIDGE_TOKEN. - Document attach β proxies a picked file to the dashboard's managed-files area and references its path in the prompt, so the agent can read PDFs/CSVs/text.
- Mobile-shaped
/v1API β stable shape decoupled from dashboard internals.
Status: early development. The
/v1API and the dashboard adapter are validated end-to-end against a live hermes-agent v0.17.0 dashboard (REST + WebSocket chat). Pure logic (cron classification, event mapping, push) is unit-tested.
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
Liveness (+ whether push/auth are configured) |
| GET | /v1/auth/check |
Validate the bridge token |
| GET | /v1/info |
Bridge + gateway (Hermes) versions, for the app's About panel |
| GET | /v1/tasks |
List tasks (sessions), each with a derived run-state |
| POST | /v1/tasks |
Start a task β one-shot run (held server-side) or a cron; accepts images + attachments |
| GET | /v1/tasks/{id}/events |
Foreground SSE for a held run (buffered + replayed) |
| GET | /v1/tasks/{id}/live |
Whether a run is actively streaming (lets the app reattach) |
| GET | /v1/tasks/{id}/messages |
Conversation history for a task |
| POST | /v1/tasks/{id}/{cancel|steer|approve|clarify} |
Control a running task |
| POST | /v1/files/upload |
Upload a document to the dashboard's managed files; returns its path |
| GET | /v1/models Β· POST /v1/models/set |
List / set the profile's model |
| GET | /v1/schedules |
List scheduled jobs |
| POST | /v1/schedules/{id}/{pause|resume|run|delete} |
Control a job |
| POST | /v1/push/register Β· /v1/push/unregister |
Manage a device's push endpoint |
| GET | /v1/mcp |
List connected MCP servers |
Background push fans out to every registered device endpoint plus an
optional statically-configured NTFY_TOPIC.
Split into π§ what a human does and π€ what you can ask your Hermes agent to build.
- Have the hermes-agent dashboard running (
hermes dashboard/ the gateway web UI, default port 9119), reachable by this bridge. - Copy
.env.exampleβ.envand fill it in. Generate the secrets yourself (locally) β don't have an assistant create or store them:HERMES_URLβ your dashboard, e.g.http://127.0.0.1:9119.HERMES_TOKENβ leave empty for a local dashboard (the bridge auto-readswindow.__HERMES_SESSION_TOKEN__); set it only for a remote/authenticated one.BRIDGE_TOKENβ what the app authenticates with. Generate a strong one:python3 -c "import secrets; print(secrets.token_urlsafe(32))" # or: openssl rand -base64 32
NTFY_BASE_URL+NTFY_TOPIC(for push) β use an unguessable topic:echo "hermes-$(python3 -c "import secrets; print(secrets.token_hex(8))")"
- Run it:
uv run uvicorn app.main:app --host 0.0.0.0 --port 8099 # or: docker compose up -d - In the Android app, point the bridge URL at this service and use the
BRIDGE_TOKEN.
Running it all on one machine (the common case). When the bridge and the Hermes dashboard live on the same computer, keep
HERMES_URL=http://127.0.0.1:9119and leaveHERMES_TOKENempty β the bridge reads the dashboard's token locally over loopback. Your phone isn't on that machine, so it can't use127.0.0.1: give the app the machine's Tailscale HTTPS URL (recommended) or its Tailscale/LAN IP β see The bridge URL your phone uses. Then add a run-on-boot service so it's always available.
β οΈ Generate your own secrets.BRIDGE_TOKENandNTFY_TOPICare yours to create (step 2) β don't ask the agent to generate or write them. The prompts below are safe because they don't touch your secrets.
"Add this bridge to my existing docker-compose so it starts alongside my Hermes dashboard and ntfy, on the same network, with a healthcheck on
/health. Leave my.envand secrets to me.""Improve the cron classifier: create a Hermes skill
classify_schedulethat, given a task description, returns{is_cron, cron}and call it from the bridge instead of the regex heuristic."
Give the bridge a real TLS cert (valid Let's Encrypt, auto-renewed) on your tailnet:
./scripts/enable-https.sh
# or directly: tailscale serve --bg 8099This proxies https://<your-machine>.<tailnet>.ts.net β http://127.0.0.1:8099.
Use that https URL in the app β the phone validates the cert with the system
trust store (no special config). Requires "HTTPS Certificates" enabled for your
tailnet in the Tailscale admin console. To turn it off: tailscale serve --https=443 off.
The bridge listens on port 8099. If you skip Serve, point the app at http://<this-machine>:8099:
tailscale ip -4 # encrypted transport β http://100.x.y.z:8099
ipconfig getifaddr en0 # macOS LAN β http://192.168.x.y:8099
hostname -I # Linux LAN (first address)
ipconfig # Windows LAN β the IPv4 Address of your active adapterThe token to enter in the app is your BRIDGE_TOKEN:
grep BRIDGE_TOKEN .envReady-to-use templates are in deploy/. The cross-platform option is
Docker β docker compose up -d (with restart: unless-stopped) starts the
bridge on boot on any OS; the native per-OS recipes below are for non-Docker setups.
sed "s|__BRIDGE_DIR__|$(pwd)|g" deploy/co.hermesdispatch.bridge.plist \
> ~/Library/LaunchAgents/co.hermesdispatch.bridge.plist
launchctl load ~/Library/LaunchAgents/co.hermesdispatch.bridge.plist
launchctl list | grep hermesdispatch # status 0 = healthyRuns scripts/run-bridge.sh (uvicorn on 0.0.0.0:8099), restarts on crash, starts
at login. Logs to bridge.log.
A user service template is in deploy/hermes-dispatch-bridge.service:
mkdir -p ~/.config/systemd/user
sed "s|__BRIDGE_DIR__|$(pwd)|g" deploy/hermes-dispatch-bridge.service \
> ~/.config/systemd/user/hermes-dispatch-bridge.service
systemctl --user daemon-reload
systemctl --user enable --now hermes-dispatch-bridge
systemctl --user status hermes-dispatch-bridge # active = healthy
loginctl enable-linger "$USER" # start at boot without login
journalctl --user -u hermes-dispatch-bridge -f # logsrun-bridge.sh calls uv from your PATH; if uv lives in ~/.local/bin and
isn't on the service PATH, set Environment=UV_BIN=%h/.local/bin/uv in the unit
(a commented line is included). Prefer a system service (/etc/systemd/system/,
with User=)? Drop the --user/linger steps and run as root.
The simplest path is Docker Desktop or WSL2 (use the Linux steps inside WSL). To run natively without Docker:
- Install Python 3.12 +
uv, then in the repo:uv venv && uv pip install -e . - Start it:
uv run uvicorn app.main:app --host 0.0.0.0 --port 8099 - Auto-start on boot β either:
- Task Scheduler β Create Task β trigger "At log on" / "At startup",
action
uvwith argsrun uvicorn app.main:app --host 0.0.0.0 --port 8099, Start in = the repo folder; or - NSSM to run it as a real Windows service:
nssm install HermesDispatchBridge uv "run uvicorn app.main:app --host 0.0.0.0 --port 8099"then set Startup directory to the repo and start the service.
- Task Scheduler β Create Task β trigger "At log on" / "At startup",
action
The
scripts/*.shhelpers (enable-https.sh,run-bridge.sh) are bash and run on macOS/Linux/WSL. On native Windows use the commands directly, and for HTTPS runtailscale serve --bg 8099(the Tailscale client is cross-platform).
uv venv --python 3.12 && uv pip install -e ".[dev]"
uv run ruff check .
uv run pytest -qBranches: main is stable; development is active work (paired with the app's beta pre-releases). PRs target development.
If this is useful to you, please consider supporting development:
β Buy me a coffee on Ko-fi β β thank you! π
GPL-3.0 Β© Andrew Debnar. Free software under the GNU General Public License v3.