fix: bind server/adapter contextvars per WS task; fix session pre-create call

Two live-deployment bugs found during end-to-end verification:

1. server.py: ContextVars set in adapter.connect() do not propagate into
   aiohttp handler tasks, so hermes_bridge._current_server()/_current_adapter()
   resolved to None inside message handlers (conversation.list returned empty
   snapshots, broadcast fan-out and notification paths silently no-oped).
   Bind both contextvars at the top of _handle_ws — the per-connection task
   owns the context for all its handler dispatch and agent-thread bridges.

2. hermes_bridge.py: create_conversation passed two extra positional args to
   SessionStore.get_or_create_session() (source, False, False) — the actual
   signature is (source, force_new=False). TypeError was swallowed by the
   guard, so pre-creating an empty Hermes session at conversation.create
   always failed; sessions were only created on first message.

Verified live against the running gateway: full 11-point E2E (auth 401,
hello/ready, conversation list/create/delete with persistence, chat.send
round-trip through the agent, run lifecycle events, models.current,
reasoning.current). 46/46 unit tests pass.
This commit is contained in:
2026-09-02 23:30:01 +00:00
parent 5130152335
commit 5725d635b2
2 changed files with 9 additions and 1 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ async def create_conversation(server: Any, name: Optional[str]) -> str:
if store is not None:
try:
await asyncio.to_thread(
store.get_or_create_session, _source_for(cid), False, False)
store.get_or_create_session, _source_for(cid))
if name:
await rename_conversation(cid, name)
except Exception:
+8
View File
@@ -173,6 +173,14 @@ class PhebyServer:
# ── WebSocket handler ────────────────────────────────────────────────
async def _handle_ws(self, request: web.Request) -> web.WebSocketResponse:
# Bind server/adapter identity to THIS task's context. ContextVars set
# in adapter.connect() do not propagate into aiohttp handler tasks, so
# every per-connection task must bind them itself — hermes_bridge's
# _current_server()/_current_adapter() resolve from here in all
# message handlers and agent-thread callbacks.
hermes_bridge.set_server(self)
if self.adapter is not None:
hermes_bridge.set_adapter(self.adapter)
ws = web.WebSocketResponse(max_msg_size=proto.MAX_WS_MESSAGE_BYTES,
heartbeat=30.0, autoping=True)
await ws.prepare(request)