Skip to content

State subscription endpoint: SSE stream for app state events #526

Description

@enricopiovesan

Summary

Add a Server-Sent Events (SSE) endpoint that lets any client subscribe to the app state machine events for a given workspace and app. This is the primary mechanism by which all Traverse clients (web, mobile, desktop, CLI) receive state updates from the runtime.

Depends on: #525 (app state machine spec)

Why SSE over WebSocket

  • SSE is unidirectional (server → client), which matches the state machine model: the runtime pushes state, clients pull UI from it
  • Native browser support with EventSource — no library needed in web clients
  • Works over standard HTTP/1.1 — simpler to implement than WebSocket upgrade
  • WebSocket can be added later for bidirectional use cases

Proposed endpoint

GET /v1/workspaces/{workspace_id}/apps/{app_id}/events

Response: Content-Type: text/event-stream

Event types

state_changed

event: state_changed
data: {
  "app_id": "traverse-starter",
  "session_id": "sess_abc123",
  "state": "processing",
  "previous_state": "idle",
  "timestamp": "2026-07-03T12:00:00Z",
  "context": {
    "command": "submit",
    "payload": { "note": "..." }
  }
}

capability_invoked

event: capability_invoked
data: {
  "capability_id": "traverse-starter.process",
  "execution_id": "exec_xyz789",
  "state": "processing"
}

capability_result

event: capability_result
data: {
  "execution_id": "exec_xyz789",
  "state": "results",
  "output": {
    "title": "...",
    "tags": ["..."],
    "noteType": "fleeting",
    "suggestedNextAction": "review",
    "status": "complete"
  }
}

error

event: error
data: {
  "state": "error",
  "error_code": "CAPABILITY_TIMEOUT",
  "message": "..."
}

heartbeat (every 30s to keep connection alive)

event: heartbeat
data: { "timestamp": "..." }

Definition of Done

  • GET /v1/workspaces/{workspace_id}/apps/{app_id}/events returns text/event-stream
  • Emits state_changed, capability_invoked, capability_result, error, heartbeat events
  • Multiple clients can subscribe simultaneously — all receive the same events
  • Client reconnect: on reconnect with Last-Event-ID header, runtime replays the current state
  • Spec 033 updated with the new endpoint and all event payload schemas
  • Integration test: two simultaneous SSE clients both receive state_changed when a command is dispatched
  • Works from a browser EventSource without CORS issues (see CORS: add Access-Control-Allow-Origin headers to runtime HTTP server #522)

Client implementation pattern

// web-react (after this ticket ships)
const es = new EventSource(
  `${BASE_URL}/v1/workspaces/${workspace}/apps/traverse-starter/events`
)
es.addEventListener('state_changed', e => {
  const { state, context } = JSON.parse(e.data)
  dispatch({ type: 'STATE_CHANGED', state, context })
})
// ios-swift
URLSession.shared.dataTask(with: eventsURL) { ... } // URLSessionDataDelegate streaming

Downstream unblocked

  • App-References: web-react SSE hook
  • App-References: ios-swift state subscription
  • App-References: all other platform clients

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:codexClaimed by CodexenhancementNew feature or requestspecSpec and planning related work

    Type

    No type

    Fields

    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