# Serve-Mode SSE Stream Bridge Design

Status: Proposed
Date: 2026-03-11
Step: `sse-bridge-cli-dedup.sse-bridge-design-doc`

## 1. Problem Statement

`anima serve` exposes MCP endpoints over HTTP, including SSE streams used by clients
that expect incremental events. In the CLI path, we already have stream delivery for
interactive workflows, but serve-mode currently lacks a documented bridge design that
cleanly connects upstream stream producers to multiple downstream HTTP consumers.

Without an explicit bridge model, we risk inconsistent event ordering, duplicate
delivery logic in multiple commands, and brittle behavior under disconnects or slow
clients.

This document defines one serve-mode bridge architecture for implementation.

## 2. Goals and Constraints

- Preserve SSE semantics (ordered events, terminal event behavior).
- Reuse existing HTTP transport and avoid introducing new long-lived protocols unless
  required.
- Support fan-out from one upstream stream to multiple downstream listeners.
- Keep backpressure bounded so one slow client does not stall the producer.
- Defer detailed authn/authz policy to Phase 5 while preserving clear extension points.

## 3. Options Considered

### Option A: HTTP Proxy Bridge (Recommended)

Use an internal HTTP client (`httpx`) to open and maintain streaming requests to an
upstream SSE source, parse events incrementally, and relay them to serve-mode SSE
subscribers.

Pros:

- Aligns with existing serve HTTP architecture.
- Minimal protocol translation (SSE -> SSE).
- Leverages mature timeout/retry controls in `httpx`.
- Straightforward observability at HTTP boundaries.

Cons:

- Requires careful handling of reconnect and partial-line parsing.
- Needs explicit queue/fan-out management in bridge layer.

### Option B: MCP Streaming Bridge

Bridge streams through MCP-native message flow directly (tool/result stream framing),
then adapt to serve responses.

Pros:

- Semantically close to MCP internals.
- Potentially less event translation if consumers are MCP-aware.

Cons:

- Couples serve stream lifecycle to MCP internals more tightly.
- Higher implementation complexity for mixed HTTP/SSE consumers.
- Less reusable for non-MCP downstream clients.

### Option C: WebSocket Bridge

Convert upstream stream flow into a WebSocket transport and optionally expose SSE as a
secondary compatibility layer.

Pros:

- Bidirectional channel available for future control messages.
- Native multiplexing possibilities.

Cons:

- Introduces an additional protocol surface area not required for current serve use.
- Higher operational and compatibility burden for CLI and proxies.
- Requires more client changes than SSE-preserving designs.

## 4. Decision

Choose **Option A: HTTP Proxy Bridge via `httpx` streaming**.

Rationale:

1. Current requirements are one-way server push with SSE compatibility; HTTP streaming
   is sufficient and lowest risk.
2. It keeps the serve data plane protocol-stable for existing clients.
3. It avoids premature protocol expansion while still allowing future WebSocket support
   if needed.

## 5. Proposed Bridge Model

### 5.1 Upstream Ingest

- A per-stream bridge task opens an `httpx.AsyncClient.stream("GET", upstream_url)`
  request.
- The bridge parses upstream SSE line-by-line and forwards framed `event`/`data`
  pairs through `EventSourceResponse`.
- The bridge normalizes SSE framing: event types are extracted and paired with
  their corresponding data lines. Non-event lines are forwarded as data with
  the current pending event type. This phase does not add or validate `seq`
  fields (upstream remains source of truth for ordering).

### 5.2 Fan-Out to Downstream Subscribers

- Current implementation: Single downstream subscriber per bridge request.
- Each request opens a dedicated connection to upstream (no shared session).
- Future enhancement: One upstream stream can map to N downstream subscribers with
  each subscriber receiving its own bounded async queue.
- Non-terminal events are best-effort when subscriber queues are full.
- Terminal events (`done`/`error`) are delivered reliably to connected subscribers
  before session teardown.

### 5.3 Lifecycle

- Session starts on each incoming request to `/agent/{agent_id}/stream`.
- Session closes after terminal event broadcast from upstream.
- Idle/abandoned sessions are garbage-collected with timeout safeguards.

## 6. Error Handling

- **Upstream connect failure (httpx.ConnectError):** Return 502 Bad Gateway JSON error.
- **Upstream 404 response:** Return 502 with UPSTREAM_NOT_FOUND code.
- **Upstream non-200 response:** Return 502 with UPSTREAM_ERROR code.
- **Agent not alive:** Return 404 with AGENT_NOT_FOUND code.
- **Downstream disconnect:** Connection closes gracefully; upstream stream is terminated.
- **Backpressure:** Upstream stream continues; slow client may miss events (best-effort).
- **Malformed SSE frame:** Pass through as data line (lenient parsing).

## 7. Security Considerations (Deferred)

Detailed security controls are **deferred to Phase 5**. This includes:

- Authentication and authorization for subscription endpoints.
- Tenant/session isolation guarantees.
- Rate limiting and abuse protection.
- Header/cookie forwarding policy for upstream proxy requests.
- Audit logging requirements for stream access.

This design intentionally focuses on transport and delivery semantics so Phase 5 can
layer policy without changing the core bridge contract.

## 8. Acceptance Criteria for This Design

- Problem statement and constraints are explicit for serve-mode stream bridging.
- Alternatives include HTTP proxy, MCP streaming, and WebSocket with trade-offs.
- Recommended option is HTTP proxy via `httpx` streaming with rationale.
- Fan-out and queue behavior are defined for multiple downstream subscribers.
- Error handling paths are defined for connect, mid-stream, parse, and disconnect
  failures.
- Security scope is explicitly deferred to Phase 5.
