Concepts

Streams (SSE)

Two endpoints stream long-running progress to the browser. Both use Server-Sent Events, both carry a single JSON object per event, and both are designed to be reconnect-safe.

The two streams

  • GET /api/issues/{issue_id}/stream — emits one event per pipeline step, plus status transitions (e.g. awaiting_approval, pr_ready). Used by the Issue detail view to render the pipeline in real time.
  • GET /api/repos/{repo_id}/status — emits one event per indexing phase (cloning, analyzing, building_graph, embedding, done). Used by the onboarding flow to render the indexing progress.

Both endpoints respond with Content-Type: text/event-stream and require the session cookie. They never use the standard SSE event: or id: framing — every line is plain data: <json>. Event types are distinguished by the JSON shape.

Pipeline stream — event shapes

Two payload shapes are interleaved on the issue pipeline stream:

Step event (one per completed agent)

{
  "step": "locator",
  "status": "completed",
  "description": "Searching relevant files in the repository",
  "started_at": "2026-05-22T18:04:11.230Z",
  "completed_at": "2026-05-22T18:04:18.812Z",
  "output": { "relevant_symbols": [ ... ] }
}

Status event (overall status transition)

{
  "status": "awaiting_approval",
  "plan": { "summary": "...", "files_to_modify": [...] },
  "rejection_count": 0
}

The frontend distinguishes them by the presence of the step key. Terminal status values are pr_ready, failed and cancelled; when one of these is observed the client closes the connection and the backend stops emitting.

Snapshot, replay and live tail

Opening the pipeline stream triggers three sequential phases on the backend:

  1. Snapshot— a single event with the issue's current persistent status, so a late subscriber immediately knows where the issue stands.
  2. Replay — the recent event buffer (up to 50 events kept in a Redis list with one-hour TTL) is replayed in chronological order. If a terminal status appears in the replay the stream closes immediately and step 3 is skipped.
  3. Live tail — the backend subscribes to the Redis pub/sub channel pipeline:{issue_id} and forwards each new event as it is published by the runner.

Idempotency matters: the replay re-delivers events the client may have already seen. The frontend reducer is written so that applying the same event twice produces the same state.

Indexing stream — event shapes

The indexing stream is simpler: no snapshot, no replay, just a live tail. Each event has the shape:

{
  "step": "embedding",
  "status": "active",
  "symbols_indexed": 128,
  "symbols_total": 512
}

Terminal values are step: "done" (a final event carries the aggregate counts: files_count, symbols_count, edges_count, detected_languages, detected_frameworks) and step: "failed" (carries an error message).

Because there is no replay, a client that reconnects mid-run will miss the events already emitted. The application uses the SSR snapshot from GET /api/repos/{repo_id} to seed the initial state and then folds live events on top.

Reconnection behaviour

EventSource reconnects on its own after a transient drop. For the pipeline stream this is safe out of the box: the backend resends the snapshot and the replay buffer on every connect, so the client re-synchronises automatically. For the indexing stream the client may have to refresh the page to pick up the latest persisted state if it disconnects for long enough.