SSE Progress Subscription
This page explains how to subscribe to Memory processing progress with SSE (Server-Sent Events) after calling POST /api/sessions/{session_id}/messages to write a batch of Message input.
SSE is useful for observing asynchronous processing status, such as whether Messages are written, Facts are generated, Summaries are generated, or Topics are updated. It does not return raw Message content, Fact content, or Summary content.
Before You Start
- You have created a GUMem Project and have a server-side API Key.
- You have created a Session and stored its
session_id. - Your server can store the
request_idreturned by the Message write response. - If you need to wait for
facts,summaries, ortopics, the background worker, Elasticsearch, LLM, and embedding configuration should be available.
Native browser EventSource cannot set an Authorization header directly. In production, keep the API Key on your server and proxy the SSE stream, or use a fetch stream / SSE client that supports custom headers.
Call Flow
- Write a batch of Message input.
- Read
data.request_idfrom the write response. - Subscribe to SSE with the same
session_idandrequest_id. - Update your application state based on
progress,completed,timeout, orerrorevents.
Write Messages
Call the Message write endpoint:
export GUMEM_BASE_URL="http://localhost:8000"
export GUMEM_API_KEY="<api_key>"
export GUMEM_SESSION_ID="session_xxx"
curl -sS -X POST "$GUMEM_BASE_URL/api/sessions/$GUMEM_SESSION_ID/messages" \
-H "Authorization: Api-Key $GUMEM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "For team scheduling, use Berlin when I mention Europe."
},
{
"role": "assistant",
"content": "Got it. I will use Berlin for Europe scheduling."
}
]
}'The data.request_id in the response is the progress subscription ID for this Message batch:
{
"code": 0,
"message": "success",
"data": {
"request_id": "req_abc123def4567890"
}
}All Messages written by the same request share one request_id. If you write an empty Message list, request_id may be null; do not start an SSE subscription in that case.
Subscribe To Progress
Use GET /api/sessions/{session_id}/messages/sse to subscribe to processing progress:
export GUMEM_REQUEST_ID="req_abc123def4567890"
curl -N "$GUMEM_BASE_URL/api/sessions/$GUMEM_SESSION_ID/messages/sse?request_id=$GUMEM_REQUEST_ID&wait_until=topics&timeout_seconds=120" \
-H "Authorization: Api-Key $GUMEM_API_KEY" \
-H "Accept: text/event-stream"Query parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
request_id | Yes | - | data.request_id returned by the Message write response. |
wait_until | No | topics | Stage to wait for before sending completed and closing the stream. Allowed values: messages, facts, summaries, topics. |
poll_interval_ms | No | 1000 | Server-side progress polling interval. Range: 250 to 5000 milliseconds. |
timeout_seconds | No | 120 | Maximum wait time. Range: 1 to 600 seconds. |
include_logs | No | false | Whether to include server-side SSE log events for the same request_id. Usually only useful for debugging. |
Authentication is the same as other GUMem APIs:
Authorization: Api-Key <api_key>
Accept: text/event-streamThe response is not a one-time JSON body. It is a continuous text/event-stream. Each event has an event line and a data line: event is the event type, and data is a JSON string that contains the current processing stage and counters. Parse events with an SSE parser, then deserialize data as JSON.
event: progress
data: <json>After deserializing the data string, the JSON object has this shape:
{
"request_id": "req_abc123def4567890",
"session_id": "session_xxx",
"stage": "messages_ready",
"wait_until": "topics",
"message_count": 2,
"processed_message_count": 0,
"facts_count": 0,
"summaries_count": 0,
"topics_count": 0,
"completed": false,
"timestamp": "2026-05-07T12:00:00.000000+00:00"
}Response parts:
| Part | Description |
|---|---|
event | SSE event type, such as progress, completed, timeout, error, or log. |
data | JSON string. See Payload Fields for field descriptions. |
: keep-alive | SSE comment line. It means the connection is still alive but the state has not changed; clients can usually ignore it. |
Processing Stages
wait_until uses public stage names:
messages -> facts -> summaries -> topics| Stage | SSE stage | Description |
|---|---|---|
messages | messages_ready | This Message batch is written and can be found by the progress query. |
facts | facts_ready | Facts have been generated from this Message batch. |
summaries | summaries_ready | Summaries have been generated from the Facts. |
topics | topics_ready | At least one Topic has been extracted from the Summaries. |
If request_id cannot be found yet, SSE returns not_started and keeps waiting until the request times out or the batch appears.
Event Types
| Event | Trigger | Connection behavior |
|---|---|---|
progress | Stage or count changes. | Keeps the stream open. |
completed | Current stage reaches wait_until. | Sends the event and closes the stream. |
timeout | timeout_seconds is reached before completion. | Sends the latest state and closes the stream. |
error | An unhandled error happens after the stream starts. | Sends an error payload and closes the stream. |
log | include_logs=true and logs exist for the same request_id. | Keeps the stream open. |
: keep-alive | State has not changed. | Keeps the stream open. |
Example events:
event: progress
data: <json>{
"request_id": "req_abc123def4567890",
"session_id": "session_xxx",
"stage": "messages_ready",
"wait_until": "topics",
"message_count": 2,
"processed_message_count": 0,
"facts_count": 0,
"summaries_count": 0,
"topics_count": 0,
"completed": false,
"timestamp": "2026-05-07T12:00:00.000000+00:00"
}event: completed
data: <json>{
"request_id": "req_abc123def4567890",
"session_id": "session_xxx",
"stage": "topics_ready",
"wait_until": "topics",
"message_count": 2,
"processed_message_count": 2,
"facts_count": 1,
"summaries_count": 1,
"topics_count": 1,
"completed": true,
"timestamp": "2026-05-07T12:00:08.000000+00:00"
}Payload Fields
SSE payloads return stage state and counts only.
| Field | Description |
|---|---|
request_id | Message batch write ID. |
session_id | Session ID from the URL. |
stage | Current processing stage. Possible values: not_started, messages_ready, facts_ready, summaries_ready, or topics_ready. |
wait_until | Target stage for this subscription. |
message_count | Number of Messages in the batch. |
processed_message_count | Number of Messages in the batch with processed status. |
facts_count | Number of Facts matched to this Message batch. |
summaries_count | Number of Summaries matched to this batch's Facts. |
topics_count | Number of unique Topics extracted from the Summaries. |
completed | Whether the current stage has reached wait_until. |
timestamp | Server UTC time. |
Server-Side Integration Example
If your frontend needs to show progress, proxy the SSE stream from your application server so the GUMem API Key is not exposed to the browser.
async function streamMessageProgress(params: {
baseUrl: string;
apiKey: string;
sessionId: string;
requestId: string;
}) {
const url = new URL(
`/api/sessions/${params.sessionId}/messages/sse`,
params.baseUrl
);
url.searchParams.set("request_id", params.requestId);
url.searchParams.set("wait_until", "topics");
url.searchParams.set("timeout_seconds", "120");
const response = await fetch(url, {
headers: {
Authorization: `Api-Key ${params.apiKey}`,
Accept: "text/event-stream",
},
});
if (!response.ok || !response.body) {
throw new Error(`SSE request failed: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
process.stdout.write(chunk);
}
}Failure Handling
401: API Key is missing or invalid. CheckAuthorization: Api-Key <api_key>.422:request_idis missing or query parameters are outside the allowed range.timeoutevent: the stream did not reachwait_untilin time. Use an earlierwait_untilstage or increasetimeout_seconds.- Stuck at
messages_ready: Messages are written, but later Facts / Summaries / Topics processing has not completed. Check the background worker, Elasticsearch, LLM, and embedding configuration. - Network disconnect: subscribe again with the same
request_id. SSE is an observation channel and should not be the only business completion signal.
Checkpoint
In local environments without complete LLM / embedding configuration, start with wait_until=messages to verify the SSE connection and authentication. In production, wait for facts, summaries, or topics based on your workflow.
Next Step
Read Add Memory to write Message input, or Query Memory to recall Memory in later requests.