SSE Message Format
Server-Sent Events wire format for streaming HTML fragments from agent servers to HyperGen iframes.
This section defines the Server-Sent Events (SSE) wire format used to stream HTML fragments from an agent server to a HyperGen iframe. The format follows the WHATWG EventSource specification with HyperGen-specific conventions.
Content-Type and Required Headers
The agent server's SSE endpoint MUST return the following headers:
| Header | Required Value | Purpose |
|---|---|---|
Content-Type | text/event-stream | Identifies the response as an SSE stream. MUST be set exactly. |
Cache-Control | no-cache, no-transform | Prevents caching and intermediate transformation of the stream. MUST be set. |
Connection | keep-alive | Keeps the TCP connection open for streaming. SHOULD be set. |
X-Accel-Buffering | no | Disables buffering in nginx reverse proxies. SHOULD be set. |
Servers behind reverse proxies (nginx, Cloudflare, etc.) SHOULD also disable response buffering at the proxy layer, as buffering defeats the purpose of progressive streaming.
Event Format
Each SSE event consists of one or more field lines followed by a blank line (\n\n). HyperGen events carry raw HTML in the data: field.
Data Field
The data: field contains raw HTML — not JSON-wrapped, not base64-encoded. Multi-line HTML MUST be split across multiple data: lines. The EventSource API concatenates them with newlines.
data: <div class="card">
data: <h2>Weather Report</h2>
data: <p>Tokyo: 18°C, partly cloudy</p>
data: </div>The above is a single SSE event containing one HTML fragment. The trailing blank line terminates the event.
Event Name
The event: field specifies the SSE event name. HTMX's SSE extension uses this to match sse-swap attributes.
- The default event name is
message. When theevent:field is omitted, the EventSource API treats the event as amessageevent. - Servers MAY use custom event names to target different elements in the iframe.
- The bootstrap document's
sse-swapattribute MUST match the event name used by the server.
event: message
data: <p>This targets the default sse-swap="message" element</p>event: sidebar
data: <nav>This targets an element with sse-swap="sidebar"</nav>Event ID
The id: field is OPTIONAL. When present, the browser stores it as the last event ID and sends it in the Last-Event-ID header on reconnection. Servers MAY use this for resumable streams.
id: 42
event: message
data: <p>Fragment 42</p>Retry Interval
The retry: field is OPTIONAL. It specifies the reconnection interval in milliseconds. The browser uses this value when the connection is lost.
retry: 5000Servers SHOULD send a retry: field early in the stream if the default reconnection behavior (typically 3 seconds) is not appropriate.
Keep-Alive
Long-lived SSE connections may be closed by proxies, load balancers, or browsers if no data flows for an extended period. To prevent this, the server SHOULD send periodic keep-alive comments.
A keep-alive is an SSE comment line (starting with :) followed by a blank line:
: keepalive- Servers SHOULD send keep-alive comments at intervals of 15 seconds or less.
- The comment text (
keepalive) is conventional but not significant; any comment works. - Keep-alive comments MUST NOT appear between the
data:lines of a single event.
The done Event
When the agent has finished generating all content for the current stream, the server SHOULD send a done event to signal completion:
event: done
data:- The
data:field of adoneevent MAY be empty. - The
data:field MAY contain final HTML (e.g., a completion indicator). - After sending
done, the server SHOULD close the connection. - The client MAY use the
doneevent to update UI state (e.g., hide loading indicators, enable input fields).
To handle the done event in the iframe, agents can include an element with sse-swap="done":
<div sse-swap="done" hx-swap="innerHTML">
<span class="hg-indicator">Generating...</span>
</div>When the done event arrives, HTMX swaps its data: content into this element, replacing the loading indicator.
Stream Lifecycle
1. Connection
The iframe's bootstrap document contains an element with sse-connect:
<div id="hg-root"
hx-ext="sse"
sse-connect="/api/agent/stream"
sse-swap="message"
hx-swap="innerHTML">
</div>When HTMX processes this element, it opens an EventSource connection to the specified URL. The connection uses GET by default.
2. Streaming
As the agent generates HTML, the server sends SSE events. HTMX receives each event, parses the HTML from the data: field, and swaps it into the target element using the configured hx-swap strategy.
Between events, the server sends keep-alive comments to maintain the connection.
3. Completion
The server signals completion by sending the done event and closing the connection. The browser's EventSource will attempt to reconnect by default — agents that produce a single response stream SHOULD NOT rely on auto-reconnection. Instead, the done event handler can remove the sse-connect attribute to prevent reconnection:
<div id="hg-root"
hx-ext="sse"
sse-connect="/api/agent/stream"
sse-swap="message"
hx-swap="innerHTML"
hx-on::sse-close="this.removeAttribute('sse-connect')">
</div>4. Error
If the SSE connection fails, the browser's built-in reconnection logic activates (respecting the retry: interval if one was sent). HTMX fires the htmx:sseError event, which the bootstrap document uses to toggle the hg-disconnected CSS class.
5. Reconnection
The EventSource API automatically reconnects on connection loss. If the server sent an id: field, the browser includes Last-Event-ID in the reconnection request. The server MAY use this to resume the stream from where it left off.
Servers that do not support resumption SHOULD send a fresh stream on reconnection.
Complete Wire Format Example
Below is a complete SSE byte stream as it would appear on the wire. Each \n represents a literal newline character (0x0A).
retry: 5000\n
\n
event: message\n
data: <div class="card">\n
data: <h2>Analyzing your data...</h2>\n
data: <div class="hg-indicator">Working...</div>\n
data: </div>\n
\n
: keepalive\n
\n
event: message\n
data: <div class="card">\n
data: <h2>Analysis Complete</h2>\n
data: <p>Found 3 anomalies in your dataset.</p>\n
data: <button hx-post="/api/agent/action"\n
data: hx-vals='{"action":"show_details"}'\n
data: hx-target="closest .card"\n
data: hx-swap="outerHTML">\n
data: Show Details\n
data: </button>\n
data: </div>\n
\n
event: done\n
data: \n
\nReading the example
retry: 5000sets the reconnection interval to 5 seconds.- The first
messageevent delivers a loading state. - A keep-alive comment maintains the connection during processing.
- The second
messageevent replaces the loading state with results (becausehx-swap="innerHTML"on the root element). - The
doneevent signals that the agent has finished.
CORS Considerations
When the agent server is on a different origin than the iframe content, the server MUST include appropriate CORS headers:
Access-Control-Allow-Origin: <iframe-origin>
Access-Control-Allow-Credentials: trueThe server SHOULD NOT use Access-Control-Allow-Origin: * when credentials (cookies, auth headers) are required — the CORS specification forbids this combination.
Specification Overview
The HyperGen protocol specification: how AI agents stream interactive HTML via SSE into sandboxed iframes.
Iframe Bootstrap Document
The complete HTML document that MUST be loaded into the HyperGen sandboxed iframe, including HTMX, SSE extension, postMessage bridge, and base styles.