Skip to content

Real-time event streaming via SSE and WebSocket for mobile clients #59

@jeremyplichta

Description

@jeremyplichta

Summary

Add Server-Sent Events (SSE) and WebSocket endpoints to townhall so mobile and web clients can receive real-time updates without polling. These endpoints read from the Redis Stream event log (#53) and push to connected clients.

Endpoints

SSE (Server-Sent Events)

Simpler, one-directional, works through most proxies/CDNs, auto-reconnects.

GET /api/events/stream?scope=town
GET /api/events/stream?scope=mission&id={mission_id}
GET /api/events/stream?scope=agent&id={agent_id}

Response (text/event-stream):

event: agent.state.changed
data: {"agent_id":"abc","old_state":"idle","new_state":"working","task":"Implement login"}
id: 1711360000000-0

event: mission.help.needed
data: {"mission_id":"xyz","reason":"No agents available for reviewer role","needs_human":true}
id: 1711360001000-0

The id field is the Redis Stream entry ID. On reconnect, the client sends Last-Event-ID header and the server resumes from that position — no missed events.

WebSocket (Bidirectional)

For richer interaction (approvals, commands from mobile).

GET /api/events/ws

Client sends subscription:

{"subscribe": ["town", "mission:xyz"]}

Server pushes events in same format as SSE. Client can also send commands:

{"action": "approve", "mission_id": "xyz", "work_item_id": "abc"}
{"action": "pause", "mission_id": "xyz"}

Implementation

Redis → SSE Bridge

1. Client connects to SSE endpoint
2. Server opens XREAD on relevant Redis Stream(s)
3. On new stream entry → format as SSE event → send to client
4. On disconnect → clean up XREAD
5. On reconnect with Last-Event-ID → XREAD from that ID

Axum already supports SSE via axum::response::sse::Event. Tokio channels bridge the Redis reader to the SSE writer.

Depends On

Acceptance Criteria

  • SSE endpoint streams events in real-time
  • Last-Event-ID reconnection works (no missed events)
  • Scoped streams: town-wide, per-mission, per-agent
  • WebSocket endpoint with subscribe/command support
  • Auth required for both endpoints (bearer token)
  • Graceful backpressure if client is slow
  • Works behind reverse proxy (nginx, Cloudflare)

Metadata

Metadata

Assignees

No one assigned

    Labels

    cloudCloud deployment and scalingenhancementNew feature or requestmobileMobile app and remote control

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions