# Streaming and Prompt Queue

Anteroom streams AI responses in real-time via Server-Sent Events (SSE) and supports prompt queuing so you never have to wait.

## Token-by-Token Streaming

Responses arrive token-by-token and render live:

- Markdown and math render as tokens arrive
- **Raw mode toggle** (eye icon in top bar) --- view unprocessed text during streaming, persists across sessions
- Stop generation mid-response with `Escape` or the stop button
- Context-aware phase indicator: connecting, thinking, writing, running tools
- Error messages show inline with a **Retry** button

## Prompt Queue

Type and submit new messages while the AI is responding. They queue (up to 10) and process in FIFO order when the current response finishes.

When a stream is active for a conversation, new messages return `{"status": "queued"}` instead of opening a new SSE stream. The queued messages process automatically in order.

!!! tip
    This means you can rapid-fire multiple follow-up prompts without waiting for each response to complete.

### Queue Position Feedback

When a message is queued, the UI shows a pill-shaped badge next to the user message with `queued (N)` where N is the position in the queue.

- The badge appears inline next to the "YOU" role label
- When the queued message begins processing, the badge highlights briefly and then removes itself
- If the queue is full (10 messages), a toast notification reads "Queue full --- wait for current work to finish"

The `queued_message` SSE event includes enriched metadata:

| Field | Type | Description |
|---|---|---|
| `content` | string | The queued message text |
| `position` | integer | 1-based position of this message in the current processing batch |
| `queue_depth` | integer | Number of messages remaining in the queue after this one |

## Parallel Tool Execution

When the AI calls multiple tools in one response, they run concurrently via `asyncio.as_completed` instead of sequentially. For example, if the AI calls `read_file` on three different files, all three reads happen simultaneously.

Tool calls render as expandable detail panels:

- Input parameters shown during execution
- Output and status shown when complete
- Spinner animation while tools execute

## Sub-Agent Loading Indicator

When the AI calls `run_agent` to spawn a sub-agent, the tool call panel renders with a distinctive loading state instead of the standard collapsed detail element:

- **Expanded by default** with a pulsing left-border accent animation
- **Loading prompt** shows the first 200 characters of the sub-agent's task
- **Spinner** indicates the sub-agent is actively running

As sub-agent events arrive, the loading prompt is replaced by per-agent progress cards:

| SSE Event | `kind` field | Payload fields |
|---|---|---|
| `subagent_event` | `subagent_start` | `agent_id`, `parent_tool_call_id`, `model`, `prompt` |
| `subagent_event` | `tool_call_start` | `agent_id`, `parent_tool_call_id`, `tool_name` |
| `subagent_event` | `subagent_end` | `agent_id`, `parent_tool_call_id`, `elapsed_seconds`, `tool_calls`, `error`, `status` |

`parent_tool_call_id` binds each child card to the correct top-level `run_agent` wrapper when multiple parent sub-agents run concurrently in one assistant turn.

Cards update in place rather than appending duplicate rows:

- `subagent_start` creates one `.subagent-card.subagent-running` card per `agent_id`
- `tool_call_start` appends tool chips and increments the inline tool count on the existing card
- `subagent_end` swaps the running state for `.subagent-completed`, `.subagent-failed`, or `.subagent-cancelled` and adds a footer with duration plus a short summary/error preview

On completion, the panel summary updates to "Sub-agent complete" (success) or "Sub-agent failed" (error), and the pulsing animation stops once no child cards in that wrapper are still running. Non-sub-agent tool calls are unaffected by these styles.

## Thinking Indicator

Between tool execution and the next API call, Anteroom emits a `"thinking"` event that triggers a pulsing dots animation in the UI. Phase-aware labels provide context about what the AI is doing:

- **connecting** --- waiting for API connection
- **thinking** --- waiting for the first token
- **writing** --- tokens are streaming, with character count
- **running tools** --- during tool execution, showing tool count and summaries for single tools
- **retry N/M** --- retrying after a transient error

An elapsed timer appears after 1.5 seconds. Stall detection highlights when streaming pauses unexpectedly.

## Debug Diagnostics

Web diagnostics are opt-in on both sides. Start the web server with `aroom web --debug`, then add `?debug=1` to the web UI URL to enable browser console debug logs and per-turn diagnostics for chat streams:

```text
http://127.0.0.1:8080/?debug=1
```

When server debug mode and browser debug mode are both active, the browser sends `X-Anteroom-Debug: 1` with chat requests. The server then emits one `debug_summary` SSE event before the terminal `done` event or before a terminal error event. Normal sessions do not send the header and do not receive `debug_summary`; the header alone is ignored unless the server was started in debug mode.

The debug summary is rendered as a compact collapsible block on the assistant message. It includes a generated request/turn id, timing, stop reason, final phase, model/provider, usage totals when available, retry count, configured timeout thresholds, tool names/statuses/durations/shapes, active tools at terminal errors, compaction counters, and notable runtime events. Rendered values are inserted with text-safe DOM APIs.

While `?debug=1` is active, the stream-status check can also return a redacted active-turn snapshot for the current conversation. Active snapshots are in memory only, capped by count and age, updated on lifecycle events instead of every token, and cleared on terminal events, disconnects, cancellation, or stale-stream eviction. Diagnostics are troubleshooting output, not an audit trail. They are redacted by default: raw prompts, token text, raw tool arguments, raw messages, and raw tool output are omitted from summaries and active snapshots. Disable them by omitting `?debug=1` or by starting the server without `--debug`. Audit JSONL remains the governance-grade record.

## Canvas Streaming

When the AI calls `create_canvas`, `update_canvas`, or `patch_canvas`, a panel appears alongside the chat and updates in real-time via the following SSE events:

| Event | When emitted | Payload fields |
|---|---|---|
| `canvas_stream_start` | AI begins generating canvas content | `canvas_id`, `title`, `content_type` |
| `canvas_streaming` | Each streamed content chunk | `canvas_id`, `delta` |
| `canvas_created` | `create_canvas` completes | `canvas_id`, `title`, `content` |
| `canvas_updated` | `update_canvas` completes | `canvas_id`, `content` |
| `canvas_patched` | `patch_canvas` completes | `canvas_id`, `edits_applied` |

Canvas content is streamed token-by-token using `tool_call_args_delta` events from `ai_service.py`, giving the same live-render experience as normal text responses.

## Safety Approval Events

When a destructive tool call is intercepted by the safety gate, the stream emits an `approval_required` event before pausing:

| Event | Payload fields |
|---|---|
| `approval_required` | `approval_id`, `tool_name`, `command` (or `path`) |
| `approval_resolved` | `approval_id`, `approved`, `reason` |

The browser renders an inline Approve / Deny prompt inside the tool call panel. The user's response is submitted to `POST /api/approvals/{approval_id}/respond`. See [Tool Safety](../security/tool-safety.md#web-ui-approval-flow) for the full flow.

**Deduplication**: The browser tracks shown approval IDs in a `_shownApprovalIds` set. Duplicate `approval_required` events for the same `approval_id` (e.g., on SSE reconnect) are ignored. On SSE reconnect, all stale approval prompts are cleared from the DOM and the set is reset.

**Resolution display**: When `approval_resolved` fires, the approval card updates to show "Allowed" (green) or "Denied" (red) status. Timed-out approvals show "Timed out".

## Detached Subagents

When the AI launches a subagent with `detach: true`, the agent runs in the background and the foreground turn ends immediately. The browser receives lifecycle events via SSE:

| Event | Payload fields |
|---|---|
| `agent_run_started` | `run_id`, `prompt` (truncated to 80 chars) |
| `agent_run_completed` | `run_id`, `status`, `summary`, `duration_seconds`, `tool_calls_count` |

**Rendering**: A detached agent chip appears in the conversation timeline — a spinner while running, a checkmark (completed) or X (failed) when done. Unlike inline sub-agents, detached agents do not show a blocking spinner or progress cards in the foreground stream.

**Lifecycle controls**: The cancel and retry buttons on the chip call `DELETE /api/agent-runs/{run_id}` and `POST /api/agent-runs/{run_id}/retry` respectively. Retry creates a new run with `parent_run_id` linkage to the original — the full prompt is preserved for re-execution.

**Status inspection**: `GET /api/agent-runs/{run_id}` returns a `TaskResult`-based payload for completed runs: `status` (`completed`, `failed`, or `cancelled`), `summary` (first 200 chars of output), `error` (human-readable message on failure), `tool_calls` (ordered list of tool names invoked, capped at 20), `truncated` (whether summary was cut), and `duration_seconds`. The response also includes `run_id`, `title`, `started_at`, `completed_at`, `duration_ms`, and `parent_run_id`. Runs still in progress fall back to a legacy dict with `output` (first 2000 chars) and `tool_calls_made`. The `agent_run_status` READ-tier tool also exposes this to the AI for autonomous follow-up.

## Completion Notifications

Both background tasks (`task_completed`) and detached subagents (`agent_run_completed`) emit enriched completion payloads:

- `task_completed` includes `duration_seconds` (elapsed wall-clock time from start to completion, rounded to 2 decimal places).
- `agent_run_completed` includes `duration_seconds`, `tool_calls_count`, and `summary` (first 200 chars of agent output).

**Deduplication**: The browser tracks delivered notification IDs in a `_deliveredNotificationIds` set. Duplicate events for the same `task_id` or `run_id` (e.g., on SSE reconnect) are silently dropped. The set is cleared on each SSE reconnect (`onopen`).

**Off-screen toast**: When a completion arrives and the chip is not visible in the viewport (the chat area is scrolled up more than 80px from the bottom), `Chat.showCompletionToast(message)` renders a fixed-position dismissible banner at the top of the chat area. The banner auto-dismisses after 6 seconds and has `role="alert"` for screen reader accessibility.
