Fix Hermes adapter integration and recovery

This commit is contained in:
Codex
2026-09-02 13:59:00 -07:00
parent 53ffdb8aa2
commit 5130152335
10 changed files with 900 additions and 357 deletions
+27 -27
View File
@@ -29,7 +29,7 @@ Pheby plugin (in Hermes gateway process)
├─ aiohttp server: GET /ws, GET /attachments/{id}, GET /health
├─ protocol layer: JSON message types, auth, limits, errors
├─ PhebyAdapter (BasePlatformAdapter subclass)
│ outbound: send/edit → chat events, format_tool_event → tool.event,
│ outbound: send/edit → chat events, tool hooks → tool.event,
│ send_clarify → clarify.request, send_document/… → attachments
│ inbound: WS messages → MessageEvent → gateway pipeline
└─ hermes_bridge: sessions, approvals (tools.approval),
@@ -44,9 +44,12 @@ Key properties:
- **Hermes is authoritative.** Conversations are Hermes sessions keyed
`agent:main:pheby:dm:<conversation_id>`; history/titles live in Hermes
`state.db`. Pheby keeps only a thin, rebuildable name index.
- **The full gateway pipeline works unchanged** — auth/pairing, tool
- **The full gateway pipeline works unchanged** — tool
approval, clarify, deliverables, streaming, cron delivery — because inbound
messages are ordinary `MessageEvent`s on a registered platform.
messages are ordinary `MessageEvent`s on a registered platform. The adapter
marks its authenticated WebSocket as the upstream authorization boundary,
so a valid Pheby secret is not rejected by Hermes's separate messaging-
platform allowlist layer.
- **Single-user, shared-secret.** One `PHEBY_SECRET` gates every WS
connection and attachment download (constant-time compare). No accounts,
no registration — but IDs and message shapes are multi-client friendly.
@@ -99,8 +102,7 @@ platforms:
Env vars override YAML: `PHEBY_SECRET`, `PHEBY_BIND_HOST`, `PHEBY_PORT`,
`PHEBY_DEBUG`, `PHEBY_LOG_CHAT_CONTENT`. Optional: `PHEBY_HOME_CHANNEL`
(conversation ID receiving cron deliveries), `PHEBY_ALLOWED_USERS`,
`PHEBY_ALLOW_ALL_USERS`.
(conversation ID receiving cron deliveries).
### 4. Start Hermes with the gateway
@@ -134,7 +136,7 @@ pheby.example.com {
# WebSocket + API
reverse_proxy /ws 127.0.0.1:8620
reverse_proxy /attachments 127.0.0.1:8620
reverse_proxy /attachments/* 127.0.0.1:8620
reverse_proxy /health 127.0.0.1:8620
# Optionally restrict by source when on a public VPS:
@@ -153,10 +155,10 @@ pheby.example.com {
Notes:
- Caddy provides HTTPS + automatic certificates; the plugin never sees TLS.
- `X-Forwarded-For` is not used for auth decisions (the lockout key is the
direct peer address — behind Caddy that is Caddy itself, so lockout is
effectively global; that is acceptable for a single-user deployment and
still stops brute force).
- Authentication always depends on the shared secret. For lockout accounting,
`X-Forwarded-For` is accepted only when the direct peer is loopback (the
documented local-Caddy setup). A directly exposed non-loopback client cannot
spoof that header to evade rate limiting.
- The Android client connects to `wss://pheby.example.com/ws` and downloads
attachments from `https://pheby.example.com/attachments/{id}` with
`Authorization: Bearer <PHEBY_SECRET>`.
@@ -243,23 +245,21 @@ around. None require core modifications; all are handled cleanly.
valid effort levels. Pheby exposes Hermes's canonical level set
(`none, minimal, low, medium, high, xhigh, max, ultra`) and documents that
an unsupported level surfaces as a provider error on the next turn.
3. **Conversation delete is a documented approximation.** Hermes's
SessionStore has no public per-routing-key delete; Pheby deletes the
authoritative transcript row (`SessionDB.delete_session`) and resets the
routing entry, which yields the same user-visible behavior.
4. **Tool `running` → `completed` ID correlation.** Start events get
adapter-generated IDs; completion events carry Hermes's authoritative
`tool_call_id` from the `post_tool_call` hook. The protocol documents
correlating by `(tool_name, conversation)` when IDs differ. (Gateway
tool-start events don't carry Hermes's call ID yet.)
5. **Standalone cron delivery.** Cron jobs targeting `pheby` are delivered
3. **Conversation routing deletion uses a private Hermes detail.** Hermes's
SessionStore has no public per-routing-key delete. Pheby deletes the
authoritative transcript through `SessionDB.delete_session`, then removes
the exact routing entry under SessionStore's own lock/save discipline. It
does not call `reset_session` (which would recreate the deleted chat). This
is isolated in `hermes_bridge.py` but may need adjustment after a Hermes
SessionStore refactor.
4. **Standalone cron delivery.** Cron jobs targeting `pheby` are delivered
in-process with the gateway. A `standalone_sender_fn` hook exists but
cannot push to a WS server it isn't hosting; out-of-process cron delivery
to Pheby is not supported (documented, fail-loud).
6. **Reconnect recovery is state-based, not event-replay.** Missed events are
recovered by re-opening conversations (authoritative history), not by
replaying a server-side event log. This is the spec's preferred approach
and keeps the protocol simple.
5. **Reconnect recovery is state-based, not event-replay.** Re-opening a
conversation returns authoritative history plus unexpired attachments and
the current run, tool, approval, and clarification snapshot. The server
does not retain a replay log of every transient delta.
---
@@ -270,9 +270,9 @@ python3 -m venv .venv && .venv/bin/pip install pytest pytest-asyncio aiohttp pyy
.venv/bin/python -m pytest tests/ -o addopts= -q --asyncio-mode=auto
```
Tests are gateway-free (fakes; real Hermes primitives exercised in-process
where safe — no LLM calls). Two live smoke tests bind an ephemeral localhost
port.
The 46 tests are gateway-free (fakes; real Hermes primitives exercised
in-process where safe — no LLM calls). Two live smoke tests bind an ephemeral
localhost port.
```
repo layout