A lightweight REST + SSE bridge that exposes Hermes Agent to mobile apps and any HTTP client. Built for the Agentfy iOS app, but provider-agnostic — it's plain HTTP, no WebSocket or custom handshake required.
The bridge runs alongside a Hermes installation, discovers the agents and skills configured under ~/.hermes/, and streams agent responses over Server-Sent Events.
- Chat with any Hermes agent over HTTP, with token-by-token streaming via SSE
- Multi-agent — list, create, and edit agents; each gets isolated sessions
- Skill management — browse the
~/.hermes/skills/catalog and attach/detach skills per agent - Model routing — list available models, switch an agent's model on the fly
- Provider key management — read/write API keys in
~/.hermes/.envso mobile clients never hold secrets - TTS proxy — ElevenLabs / OpenAI text-to-speech and transcription routed server-side
- Session persistence — message history stored in the Hermes SQLite DB, importable/exportable
- Device pairing — QR-code pairing flow for connecting the Agentfy app
- Python 3.11+ (the same interpreter that runs your Hermes install)
- A working Hermes Agent install at
~/.hermes/hermes-agent(the bridge imports it at runtime and reads config from~/.hermes/)
curl -fsSL https://get.agentfy.app | bashThat's it. The bootstrap clones this repo to ~/.hermes/hermes-bridge-src, runs
install.sh, and prints exactly how to start. Safe to re-run (idempotent) — it
updates an existing checkout in place.
Prefer to clone it yourself?
git clone https://github.com/jcnh74/hermes-bridge.git
cd hermes-bridge
./install.shThe installer:
- Finds your Hermes install (or honors
HERMES_AGENT_ROOT=/path) - Picks the same Python venv that runs Hermes (so imports resolve)
- Installs the bridge + optional QR support
- Runs a preflight check and prints exactly how to start
If anything's off, it tells you precisely what to fix — no stack traces.
hermes-bridge doctor✓ Hermes Agent found: /Users/you/.hermes/hermes-agent
✓ Hermes Python modules import cleanly
✓ Hermes config loaded (default model: claude-opus-4-8)
All checks passed. Start the bridge with: hermes-bridge start
hermes-bridge start # daemonized; runs preflight first
hermes-bridge start --foreground # run in the foreground for debugging
hermes-bridge status # is it running?
hermes-bridge pair # QR + URL to connect the Agentfy app
hermes-bridge stopVerify it's up:
curl http://localhost:8765/api/v1/health
# {"status":"ok","version":"0.1.0","agents_available":5,"sessions_active":0, ...}# Use the Python that runs Hermes
~/.hermes/hermes-agent/venv/bin/python -m pip install -e ".[qr]"
~/.hermes/hermes-agent/venv/bin/python -m hermes_bridge.cli doctorexport HERMES_AGENT_ROOT=/opt/hermes-agent # dir containing run_agent.py
hermes-bridge doctor| Command | Description |
|---|---|
hermes-bridge doctor |
Check the environment is ready (run this first) |
hermes-bridge start [--port 8765] [--host 0.0.0.0] [--foreground] [--skip-checks] |
Start the server (runs preflight unless --skip-checks) |
hermes-bridge stop |
Stop the running server |
hermes-bridge status |
Check whether the server is running |
hermes-bridge restart |
Restart the server |
hermes-bridge pair |
Print pairing URL + QR code for the Agentfy app |
Multiple instances: PID files are port-specific (
~/.hermes/bridge-<port>.pid), so you can run several bridges on different ports without their lifecycle commands (stop/status/restart) clobbering each other.
The bridge containerizes by deriving from the official Hermes Agent image rather than reinventing the runtime. Hermes already ships a Python venv with every dependency the bridge needs (fastapi, uvicorn, sse-starlette, pydantic, httpx), so the image just installs the bridge package into that venv and runs it as an s6-overlay-supervised service alongside the gateway — exactly how Hermes runs its own dashboard.
This means the bridge inherits Hermes' container methodology wholesale:
HERMES_HOME=/opt/data— you mount~/.hermes:/opt/data. Config,.envkeys, andhermes.dblive there and are never baked into the image. The bridge and gateway share the same DB and keys automatically.- UID/GID remap (
HERMES_UID/HERMES_GID) so container-written files stay owned by your host user. - s6 supervision — gateway, bridge, and (optionally) dashboard run in one container, each restarted independently if it crashes.
- Env-gated — the bridge service only starts when
HERMES_BRIDGE=1, mirroring the dashboard'sHERMES_DASHBOARDgate.
# From the repo root:
HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d
docker compose logs -f # follow logs
docker compose down # stopThe bridge is then reachable at http://localhost:8765/api/v1 (host networking).
Pin the Hermes version for reproducible builds:
HERMES_IMAGE=nousresearch/hermes-agent:v1.2.3 \
HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -dBridge-only (no gateway)? Swap the compose command to keep the container
alive while only the supervised bridge runs:
command: ["sleep", "infinity"] # with HERMES_BRIDGE=1An earlier approach built a standalone image that reinstalled Hermes' entire dependency tree into a second venv and mounted the Hermes source separately. Deriving from the official image is simpler and more correct: no duplicate deps, no second venv, no first-boot install, no custom entrypoint — and the bridge stays in lockstep with the Hermes runtime it talks to.
The suite covers the bridge's own logic — discovery, persistence, skill scanning, model validation, and the port-specific PID fix — with no live Hermes install or running server required (a fake Hermes root and temp DBs are fabricated in fixtures).
# Use the Python that runs Hermes
pip install -e ".[test]"
pytest # run the suite
pytest --cov # with coverage reportCoverage focuses on the pure, testable modules:
| Module | Coverage |
|---|---|
models.py |
100% |
hermes_env.py |
~95% |
skills.py |
~88% |
persistence.py |
~81% |
server.py and agent_proxy.py need a live Hermes/uvicorn runtime and are
covered by integration smoke tests rather than the unit suite (they're omitted
from the coverage target in pyproject.toml).
Base path: /api/v1
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check + feature flags |
GET |
/pairing |
Pairing payload for the mobile app |
| Method | Path | Description |
|---|---|---|
GET |
/agents |
List available Hermes agents |
POST |
/agents |
Create a new agent |
PATCH |
/agents/{agent_id} |
Update an agent (name, model, description, emoji) |
GET |
/models |
List available models + status |
| Method | Path | Description |
|---|---|---|
POST |
/sessions |
Create a session with an agent |
GET |
/agents/{agent_id}/sessions |
List an agent's sessions |
POST |
/sessions/{session_key}/messages |
Send a message — returns an SSE stream |
GET |
/sessions/{session_key}/messages |
Get message history |
POST |
/sessions/import |
Import sessions/messages |
| Method | Path | Description |
|---|---|---|
GET |
/skills |
List all skills (search/category filters) |
GET |
/skills/{skill_name} |
Get a skill's detail |
GET |
/skills/{skill_name}/files/{file_path} |
Read a linked skill file |
GET |
/agents/{agent_id}/skills |
List skills attached to an agent |
POST |
/agents/{agent_id}/skills |
Attach a skill to an agent |
DELETE |
/agents/{agent_id}/skills/{skill_name} |
Detach a skill from an agent |
| Method | Path | Description |
|---|---|---|
GET |
/settings/keys |
List provider key status (never returns secret values) |
POST |
/settings/keys |
Set a provider key (written to ~/.hermes/.env) |
GET |
/tts/status |
TTS provider availability + credit status |
GET |
/tts/speak |
Synthesize speech |
POST |
/tts/transcribe |
Transcribe audio |
GET |
/files/{file_id} |
Download a file referenced in an agent response |
Full interactive docs are served at http://localhost:8765/docs (FastAPI/OpenAPI) while the server is running.
In Agentfy, add a bridge connection pointing at:
http://<hermes-host>:8765/api/v1
Or run hermes-bridge pair and scan the QR code. For remote access, put the bridge behind a tunnel (e.g. Cloudflare Tunnel) and point the app at the public HTTPS URL — the API path stays /api/v1.
Agentfy app ──HTTP/SSE──▶ Hermes Bridge (FastAPI) ──sys.path import──▶ Hermes Agent
│ (~/.hermes/)
├── server.py REST + SSE routes
├── agent_proxy.py spawns/streams the agent
├── hermes_env.py locates Hermes, hardens imports
├── skills.py scans ~/.hermes/skills, attach/detach
├── persistence.py SQLite (~/.hermes/hermes.db)
├── models.py pydantic request/response schemas
└── cli.py start/stop/status/restart/pair/doctor
Repo layout also includes:
Dockerfile, docker-compose.yml, docker/s6-rc.d/ derived-image + s6 bridge service
tests/ pytest suite (no live Hermes needed)
install.sh one-command host install
The bridge does not bundle Hermes — it locates an existing install at ~/.hermes/hermes-agent and imports run_agent / hermes_cli at runtime. Keys and config are read from and written to the standard ~/.hermes/ locations so the bridge and the Hermes CLI stay in sync.
- API keys live only in
~/.hermes/.envon the host. The/settings/keysendpoints report status but never echo secret values. - The bridge binds
0.0.0.0by default for LAN access. Do not expose it directly to the public internet — front it with an authenticated tunnel or reverse proxy.
MIT