Q & Hermes
Q In-Feed Dispatch
Slice 1 of "feed as the home for async work" — Q's conversational chat joins Mallory and Moneypenny in one persona-tagged home feed.
Situation
The home feed should become the one place async/agentic work surfaces across personas, decomposed into three independently-shippable slices. Slice 1 builds the dispatcher and the edge↔Tower-in-feed seam the later slices reuse: folding Q's conversational chat into the feed as a persona alongside Mallory and Moneypenny, plus persona dispatch — Mallory routing a turn to Q or Moneypenny when he judges it belongs there.
It deliberately doesn't build the rich live-reference card (Slice 2) or digs-in-feed (Slice 3) — the offline affordance here is a minimal pending indicator, not a live task-action card.
Key decisions
Manual selector + auto-dispatch
A manual addressee selector in the composer, plus Mallory auto-dispatching to Q or Moneypenny via a classifier route field when he judges the message belongs there.
Client-mediated routing
Reuses the existing qsocket/sendQ wholesale rather than adding a new edge→QHub-DO forward path — lower risk, less new code, and /chat never opens a Tower connection.
Precedence: manual > route > mode
Manual addressee selection wins; then the classifier's route; then the existing resolvePersona(mode, 'home') default. Q is never produced by mode alone.
Read-time merge, single-store persist
The home feed unions messages (Mallory/MP) with the general-thread q_messages (task_id IS NULL) at read time; each turn is written to exactly one store by its route, tagged with a new messages.persona column (migration 022).
Scene markers from persona transitions
A pure deriveSceneMarkers emits a divider whenever the responding persona changes across the merged stream — live or async, source-agnostic.
Presence-driven liveness
Q renders live when online/working; offline shows a minimal pending indicator, and qhub drains queued chats on reconnect — the rich queued→working→ready card is Slice 2.
Architecture
The edge classifies before persisting so a Q-routed turn writes a Mallory handoff line instead of the raw utterance, which the client then relays over the existing Q socket.
messages.persona, so the merged stream is fully persona-tagged on reload.route?: 'mallory' | 'moneypenny' | 'q', validated like the existing signals/live dimensions.handleChat reorders to classify before persisting; route:q persists a handoff line + returns meta{route:'q'} with no Anthropic reply; handleGetConversation serves the unioned, persona-tagged page.resolvePersona stays the floor; a small pure helper layers manual/route precedence above it.taskId:null Q frames + presence.buildRows; Auto/Mallory/Moneypenny/Q selector driving the manual send path.Risks & guardrails
Relay durability at send time
A Q-routed utterance depends on the client's qsocket being open; a momentary closed socket needs a retry-on-reconnect or an explicit "Q unreachable" fallback to an edge turn — flagged for review against the alternative (edge-mediated durability via a DO inject method).
Classifier failure
Defaults to mallory — persist and stream as today, never blocks a turn.
No double-persist
The classify-before-persist reorder is the mechanism that keeps each turn in exactly one store; getting the reorder wrong risks either a lost utterance or a duplicate.
Delivery
Worker
Migration 022, classifier route + validation, resolveAddressee precedence helper, merged conversation read, /chat routing + persona persist.
Web
deriveSceneMarkers, home feed hydration onto derived scene markers, composer addressee selector, Q chat send/receive wired into the feed.
Eval battery
A routing test case added (a dev request → route:q handoff, a personal request → moneypenny) as a pre-merge check.
Deploy (held for Alex)
Migration 022 applied remote-first; Worker deploy from the main checkout preserving HERMES_DIGEST_CN; web build+deploy from the same checkout.
Out of scope
docs/superpowers/specs/2026-06-21-q-in-feed-dispatch-design.mdplan ·
docs/superpowers/plans/2026-06-21-q-in-feed-dispatch.md