Skip to content

Chat

The browser chat at sudohomes.com/chat. It's the simplest surface to reason about and a great place to test the agent without a Pi.

The two-connection design

Chat uses two connections at once:

  1. A persistent SSE stream for everything coming out of the agent — GET /v1/me/chat/stream?token=<jwt>. It stays open for the whole session.
  2. Fire-and-forget POSTs for everything going inPOST /v1/me/chat/turn.

Chat — SSE out, fire-and-forget POSTs in

Why the token is on the query string

EventSource (the browser SSE client) can't set custom headers, so the Supabase JWT rides as ?token=<jwt> on the stream URL. The POST turns use a normal Authorization header.

Fan-out: multi-tab and proactive for free

The sudo_chat plugin's send() / edit_message() POST per-token deltas back to POST /v1/internal/chat/push, and sudo-api fans them out to every open SSE consumer for that user. Two consequences fall out of this design:

  • Multi-tab just works — open /chat in three tabs, they all show the same stream.
  • Proactive messages appear without typing — a cronjob(deliver="sudo_chat") or send_message(target="sudo_chat") rides the same fan-out, so a scheduled nudge pops into any open tab.

Reactive vs proactive

Direction Path
Reactive (you type) browser → POST /v1/me/chat/turnsudo_chat:8652/inbound → agent → /v1/internal/chat/push → SSE
Proactive (agent starts) cronjob/send_message(target="sudo_chat") → plugin send()/v1/internal/chat/push → SSE

Where to look

Concern File
Chat routes + SSE fan-out cloud/api/main.py (/v1/me/chat/*, /v1/internal/chat/push)
The plugin adapter cloud/hermes/plugins/sudo_chat/
The page cloud/api/templates/chat.html