Streaming & Detached Runs
The chat stream is the most involved integration surface, so it gets its own page. It delivers tokens as they’re generated over Server-Sent Events (SSE) and — importantly — keeps running server-side even if your connection drops, so a response (or a whole new conversation) is never lost on disconnect.
Start a stream
POST /api/agent/chat/stream
See API Endpoints: Chat (Streaming) for the full request/response reference.
The body is the same as POST /api/agent/chat (message required; optional session_id,
model, profile_id, temperature, use_memory). Two extras route the turn:
workflow_id— run a multi-agent Agent Alloy workflow.target_agent_id— route the turn to a specific agent by its Docker-styleagent_id.
Routing priority is workflow_id > target_agent_id > agent_profile_id > default.
Event sequence
The response is Content-Type: text/event-stream. Events arrive in this order:
| Event | Data | When |
|---|---|---|
run_started | { "run_id": "…" } | First event — identifies the detached run for re-attach |
start | { "task_id": "…", "model": "…" } | Generation begins |
chunk | { "content": "token text" } | Each token |
tool_call | { "tool": "…", "arguments": {…} } | A tool invocation starts |
tool_result | { "tool": "…", "content": "…" } | Tool result (truncated to 500 chars) |
exhibit | { "schema_version": 1, "id": "…", "title?": "…", "layout": "stack", "elements": [{ "type": "mermaid", "content": "…" }] } | A typed, agent-authored exhibit (e.g. a Mermaid diagram) |
done | { "task_id": "…", "thinking": "…", "total_time_ms": …, "session_id": "…" } | Generation complete |
error | { "error": "message" } | On failure |
close | {} | Run settled; the tail ends |
When the agent calls the internal present_exhibit tool, the turn emits an exhibit
event — a declarative Gallery→Exhibit→Element tree the client renders from an element
registry — instead of a tool_call/tool_result pair for that call. Every exhibit
carries a schema_version; unknown element types degrade to a source-as-code fallback,
so a newer server can add element types without breaking older clients. Re-emitting the
same id amends that exhibit in place. Element types:
mermaid—{ "type": "mermaid", "content": "graph TD; A-->B", "title?": "…" }— a diagram.choice—{ "type": "choice", "prompt?": "Which DB?", "options": ["PostgreSQL", "Neo4j"] }— interactive options. Clicking one submits it as the user’s next message (no new endpoint); the agent’s next turn continues from the answer.table—{ "type": "table", "columns": ["Model", "Cost"], "rows": [["opus", "0.40"]], "caption?": "…" }— sortable, scrollable, responsive (collapses to cards on mobile), expandable to a modal.citation—{ "type": "citation", "sources": [{ "label": "NLLB", "url?": "https://…", "quote?": "…", "kind": "active"|"passive", "source_type?": "web"|"memory"|"doc" }] }—activesources fold out with their quote;passivesources are archived record-keeping links. Defaultkindispassive. The card always carries a “Sources” header; a passive-only citation (e.g. auto-captured search results) renders its list inline.
Auto-captured citations. When the agent calls the internal web_search (or web_research)
tool, a passive citation exhibit (one source per result, deduped by URL, source_type: "web") is
emitted automatically right after that tool’s tool_result — so web sources surface in the conversation
without the agent having to present them. The exhibit’s id is exh_src_<tool_call_id>, so it
restores in place from history. Toggle with the citations.auto_capture_web_search config flag
(default on). The agent is steered to spotlight a key source as active (with a quote) rather than
re-listing web results as inline links.
The stack layout (vertical) is the only layout today.
Multi-agent runs additionally emit delegation_start, delegation_chunk,
delegation_tool_call, delegation_tool_result, and delegation_complete — see the
Multi-Agent guide.
Detached runs (survive disconnect)
Each run is driven by a server-side daemon thread that fans its SSE events into a Redis stream and persists the turn on completion — independent of the HTTP connection. Closing the tab does not stop the run; it plays to completion and can be re-attached.
Re-attach
GET /api/agent/chat/stream/attach?run_id=<id>
See API Endpoints: Re-attach to a Run for the full reference.
Replays the buffered events from the start, then follows live until completion. Emits a
run_missing event (instead of replaying) once the buffer has expired — at which point you
restore from conversation history instead.
Discover & cancel
GET /api/agent/chat/runs # the caller's detached runs (newest first)
POST /api/agent/chat/runs/{run_id}/cancel # cooperatively cancel a run
See API Endpoints: List Detached Runs and Cancel a Run for the full reference.
runs powers recovery surfaces (an inbox, a conversation picker) that offer to re-attach runs
whose owning tab was closed. Cancellation is checked at event boundaries.
Prefer polling?
For long jobs where you don’t need live tokens, queue the turn instead and poll:
POST /api/chat/background → { "job_id": "…", "status": "queued" }
GET /api/chat/background/{job_id} → status + result once complete
See API Endpoints: Background Chat for the full reference.