HyperGen

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:

HeaderRequired ValuePurpose
Content-Typetext/event-streamIdentifies the response as an SSE stream. MUST be set exactly.
Cache-Controlno-cache, no-transformPrevents caching and intermediate transformation of the stream. MUST be set.
Connectionkeep-aliveKeeps the TCP connection open for streaming. SHOULD be set.
X-Accel-BufferingnoDisables 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 the event: field is omitted, the EventSource API treats the event as a message event.
  • Servers MAY use custom event names to target different elements in the iframe.
  • The bootstrap document's sse-swap attribute 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: 5000

Servers 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 a done event 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 done event 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
\n

Reading the example

  1. retry: 5000 sets the reconnection interval to 5 seconds.
  2. The first message event delivers a loading state.
  3. A keep-alive comment maintains the connection during processing.
  4. The second message event replaces the loading state with results (because hx-swap="innerHTML" on the root element).
  5. The done event 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: true

The server SHOULD NOT use Access-Control-Allow-Origin: * when credentials (cookies, auth headers) are required — the CORS specification forbids this combination.

On this page