Wire Managed Agents into a web or mobile product: SSE delta stitching, reasoning and turn-state handling, reconnect without duplicate submissions, and mounting or unmounting attachments mid-session.
This page covers the client-side engineering for wiring Managed Agents into an existing web or mobile product: showing the answer as it is produced, letting one conversation gain and drop files, and recovering from a dropped connection without re-running the task. Examples use REST/curl.
The product already has its own chat UI; Managed Agents now powers the answers. Three experience requirements shape the client protocol:
The API key stays on the backend. The backend persists the mapping from its own conversations to
Sending events and subscribing to the stream are two separate endpoints. Establish the SSE stream first, confirm it is up, then
Engineering details:
With
The minimal text buffer:
Three common pitfalls:
This is an application-defined event, not a Managed Agents API field.
Thinking uses
HTTP 200, one complete assistant message, one reasoning end, or an SSE close — none of them alone proves the turn succeeded: more tool calls or messages may follow the last assistant message. Track tool progress by call ID, and settle any UI activities still waiting when the turn ends or is interrupted.
After a page refresh, restore the backend's saved turns and messages first. If the backend's upstream connection is still alive, have the UI reattach to that business stream instead of starting a new Managed Agents turn.
If the upstream connection is gone: keep the Session ID and recover complete messages through the paginated Event List. Open a fresh realtime subscription first and buffer new events while reconciling history, so results finishing mid-recovery are not lost; insert or update complete events by ID, attach them to the right turn, and replace incomplete text with complete messages. A recovered stream may contain only the tail of a message still being generated — do not treat it as the complete answer; reconcile history once more after the turn reaches a terminal state.
The attachment control needs a lifecycle independent of the answer stream:
These are application UI states, not a list of platform status values. On the backend, tie every attachment to its conversation, the uploaded
Three returned fields to handle carefully:
The agent read the file and returned
Removal calls with the previously stored resource ID:
Show "removing" in the UI while reconciling two layers of state:
Scenario: how a conversation should render in your product
The product already has its own chat UI; Managed Agents now powers the answers. Three experience requirements shape the client protocol:
- Answers should appear incrementally, not only when fully generated; thinking needs an indicator;
- Attachments can be added and removed within the conversation without disturbing it;
- After a refresh or a network drop, the conversation recovers without ever resubmitting input.
Put a business backend between the UI and Managed Agents
session_id and reuses one session across turns; preparing agents and environments is application initialization (see getting started) — never recreate them per message.
Assign a business turn_id when a message arrives. A single turn may produce multiple ordered assistant messages: the agent may speak, call a tool, then speak again — one turn is not necessarily one message. Store each one, in order.
Subscribe first, then submit
Sending events and subscribing to the stream are two separate endpoints. Establish the SSE stream first, confirm it is up, then POST the input over a different connection:
- The frontend may echo the input immediately; while waiting, the backend reports confirmed stages (connecting, submitted, running);
- Flush downstream output streams promptly and disable proxy response buffering — buffering swallows the "streaming" effect;
- Set connection, first-event, idle, and whole-turn timeouts separately; keep-alive comments do not mean the business made progress;
- Parse SSE frame by frame: skip comment lines, accept both
data:anddata:(with a space); merge multi-linedatafirst, decode the JSON, and dispatch on itstype.
Delta stitching and deduplication
With event_deltas[] enabled, a declared event type first emits an event_start frame (event.id is the final complete message's ID), then delta frames in generation order, and finally the aggregated complete message. Dispatch by frame type:
| Upstream frame | Backend handling | UI effect |
|---|---|---|
event_start | Record event.id and event.type | Create a provisional message, or start the thinking indicator |
event_delta | Append text to (event_id, delta.index) in arrival order: message accumulates delta.content, reasoning accumulates delta.reasoning | Update the existing answer or thinking block |
Complete assistant message | Replace the provisional content with the complete content | Mark the same message complete; do not add a copy |
Complete reasoning | End that thinking activity | Clear the thinking indicator — the turn may still be running |
delta.indexidentifies a content block, not a fragment sequence number. Append every fragment under the same key, even when the text repeats —"3"and"23"at index 0 assemble to"323"; when the final complete message"323"arrives, replace the buffer instead of appending into"323323".- Dispatch by the registered event type so reasoning text never lands in the answer; filter the echo of user input — only complete messages with
role: assistantbelong to the assistant transcript. - Route MultiAgent output by thread first before deciding whether it joins the main answer, see multi-agent.
text is the current full snapshot of that content block, so the UI replaces the block on every update. When downstream events may replay or arrive out of order, use stable event/content-block keys plus a backend-assigned revision number; remove provisional blocks that are no longer valid when the final message replaces them. Escape model text before rendering — never inject unprocessed HTML.
Keep thinking and turn completion separate
Thinking uses reasoning events. With event_deltas[]=reasoning enabled, thinking text arrives chunk by chunk in delta.reasoning on frames with delta.type=reasoning_delta; accumulate it by event_id + delta.index. Show the thinking indicator while the activity is live, and validate against the complete Message's content when it ends. An event may contain no event_delta; in that case update state from the start and final frames only.
A turn is genuinely over per the stop_reason in session_status; map it to product state:
| Observed state | Product behavior |
|---|---|
running | Keep the turn active, showing answers or tool progress from events |
idle + end_turn | Mark the turn complete |
idle + requires_action | Show the action-needed state (approval flow: event stream) |
idle + retries_exhausted | Mark the turn failed, keep the conversation |
terminated | Stop loading, show the terminal result |
error | Read top-level error.code / error.message, keep the diagnostic ID, and check session state |
Reconnect without resubmitting
After a page refresh, restore the backend's saved turns and messages first. If the backend's upstream connection is still alive, have the UI reattach to that business stream instead of starting a new Managed Agents turn.
If the upstream connection is gone: keep the Session ID and recover complete messages through the paginated Event List. Open a fresh realtime subscription first and buffer new events while reconciling history, so results finishing mid-recovery are not lost; insert or update complete events by ID, attach them to the right turn, and replace incomplete text with complete messages. A recovered stream may contain only the tail of a message still being generated — do not treat it as the complete answer; reconcile history once more after the turn reaches a terminal state.
Test evidence (2026-10-15): disconnected after the first answer delta, opened a replacement subscription, then fetched paginated history; only one input was submitted, and the multiple assistant messages produced afterward merged by ID matched the final history exactly.
Letting a conversation gain attachments
The attachment control needs a lifecycle independent of the answer stream:
file_id, the returned resource ID, and the full mount path; validate ownership before acting.
Follow the upload flow in getting started until the file is available, then add it to the existing session:
- The
sesrsc_...resource ID must be stored separately from the uploadedfile_id— removal uses the resource ID; - The returned
mount_pathis already the full sandbox path; - The returned
file_idmay point to a server-side copy of the file.
350. Use a deadline, not a fixed propagation delay: in most runs the first tool attempt found the path; one early run waited about 35 seconds. Mark the attachment ready only after runtime access is confirmed — otherwise show a timeout.
When following up in-business, also state whether the new file appends to, replaces, or supplements the previous input — a bare mount does not ask the agent to revise its answer. If the current turn is still running, the client may finish uploading first and queue the follow-up for the next turn; do not promise that an in-flight tool will restart with the new input. The tested flow applied changes between two turns of the same session.
This test workspace rejects
.csv uploads (HTTP 415); the same content uploaded as .txt succeeded. Confirm allowed formats before uploading attachments.Unmounting attachments without resetting the conversation
Removal calls with the previously stored resource ID:
- Paginated
GET /sessions/{id}/resources— confirm the resource ID is gone; - On a later turn, have the agent wait for the full path to disappear within a deadline, explicitly instructing it not to delete the file itself, which would mask an unmount failure.
A memory store must be mounted at session creation and does not follow this dynamic-mount flow — see Advanced: Cross-Session Working Memory Handoff.
Integration acceptance checklist
- Short text answers: stitched deltas match the final complete message, with no duplicate answer;
- Reasoning deltas: thinking text updates chunk by chunk and matches the final complete message;
- Multiple assistant messages around tool calls: each message keeps its correct order within the turn;
- Approval pauses or failures:
requires_actionand errors are never shown as successful completion; - Page refresh and upstream disconnect: history recovery produces no duplicate messages and no re-execution of input;
- Attachment added after a turn: the same session can read it once the runtime check confirms readiness;
- Attachment removal: the resource entry and sandbox path disappear, and prior conversation messages remain.
The SDK version noted here (1.27.3) has no
event_deltas parameter on events.stream() — use the REST SSE query parameters as documented for delta streaming; verify parameter support before adopting other SDK versions.Next steps
- Event stream (SSE): the complete protocol reference for frame structure, approval chain, and recovery.
- Getting Started: Build a Data Analysis Agent: the base upload, mount, and artifact-retrieval chain.
- Advanced: Cross-Session Working Memory Handoff: memory store mounting and how its timing differs from dynamic file mounts.