We’re building a product (Nolby) on Cloud Agents (bc-…) via CURSOR_API_KEY + @cursor/sdk. The web UI at /agents/{bcId} shows assistant bubbles almost immediately (~2–4s). The public run stream / SDK onStep/onDelta delivers the same text much later (often after Get A Run is already FINISHED), so we cannot match UI latency for intermediate user-facing updates.
POST /v1/agents/{bcId}/runs with API key; prompt something like: first message exactly “starting work”, then 10s wait, then a final message.
Parallel: poll Get A Run; open SSE with curl --no-buffer (or SDK onDelta/onStep).
Compare timing of “starting work” intermediate message in UI vs first SSE/onStep text vs Get A Run FINISHED.
Expected Behavior
Parity with the Cloud Agents web UI
For the same agent/run, assistant intermediate text delivered via the public Cloud Agents API (GET /v1/agents/{id}/runs/{runId}/stream) and @cursor/sdk (onDelta / onStep) should appear at roughly the same wall time as the corresponding bubbles on /agents/{bcId} (within a small, documented tolerance — e.g. a few seconds), not tens of seconds later.
Live (or near-live) intermediates during RUNNING
While a run is RUNNING, stream consumers should receive incremental assistant updates as they are produced (including early progress lines such as starting work), not only in a late batch near or after stream FINISHED.
Consistent ordering of completion signals
Terminal run state from GET /v1/agents/{id}/runs/{runId} (FINISHED + result) and the run stream / SDK wait path should not reverse in a way that leaves clients without intermediates until long after the run has already finished. If stream delivery can lag Get A Run, that should be documented; ideally stream content for a finished run is available promptly once the run is terminal.
Documented, supported channel for API-key clients
If the web UI uses a different realtime path than the public run stream, either:
that path (or an equivalent) is available and documented for CURSOR_API_KEY clients, or
the docs clearly state that UI-speed live intermediates are not available via the public API/SDK, and recommend Get A Run (or similar) for completion-only UX.
Stream availability during the run lifecycle
Connecting to the run stream after the run is available (RUNNING / as soon as streaming is supported) should yield a stable SSE session without immediate stream_unavailable / “Run stream is no longer available,” unless the run is truly gone; any required connect/reconnect timing should be documented.
Operating System
MacOS
Version Information
@cursor/sdk 1.0.26
Cloud Agents API: v
For AI issues: which model did you use?
Composer 2.5 Fast
For AI issues: add Request ID with privacy disabled
NA - this ticket pertains to Cloud Agent.
Additional Information
On cursor.com/agents/{bcId} (session cookie auth, not API key), the page uses internal endpoints such as:
POST /api/auth/attachBackgroundComposerLogs → application/x-ndjson (observed mostly setup/pod logs, not plaintext chat bubbles in our tees)
POST /api/background-composer/get-detailed-composer (includes prompt.conversationHistory, but did not appear to be the live bubble source for our probes)
Frequent POST /api/background-composer/list
We did not see the page call </v1/agents/…/stream. So the UI’s live channel appears to be a separate, session-authenticated web path, not the public Cloud Agents stream.
Did the raw curl stream lag by the same amount as onStep/onDelta? The shared run ID makes this a useful comparison. I’d be interested in the gap between the first SSE byte and the first complete event—those could point to different buffering problems.
I checked your runs on bc-7bc40663-6255-43d8-a8c3-1f3169526b6c. The run stream and the web UI read from the same live event stream, so intermediate text isn’t batched for API clients. The problem is getting connected: 41 of your 53 first stream connections on Sep 6 failed with stream_unavailable (“Run stream is no longer available”) right after POST /runs, while the run was RUNNING and finished normally. That’s a race at run start on our side, and I reproduced it at a similar rate today.
The SDK then handles that failure badly.
If it arrives as an HTTP 409, @cursor/sdk gives up: run.wait() resolves status: "error" with error.code: "stream_unavailable", nothing streams, and your next send() gets a 409 because the run is still going.
If it arrives inside the SSE stream, the SDK reconnects after a Get A Run call plus backoff and then replays everything at once. That burst is your 20–40 s gap, and if the run finishes before the reattach, the intermediates never arrive at all.
Both are filed with the team. In the meantime:
Treat stream_unavailable in the first few seconds as “retry”, not final: reconnect after ~1 s while Get A Run says RUNNING. The stream replays from the start, so nothing is lost. A fixed delay before opening the stream is not enough; I still got 409s with a 1 s wait.
On the SDK, if wait() resolves with error.code === "stream_unavailable", poll Get A Run for the real status before your next send(), or open the raw stream endpoint yourself for the replay.
Get A Run showing FINISHED a second or two before the stream’s result event is expected.
If you still see a gap on a run whose first connection succeeded, send me the runId and the wall time your client got the first assistant frame and I’ll line it up!
Yes, raw unbuffered curl lagged identically to the SDK. The gap between first SSE byte and first complete SSE event is 0–1 ms (no client-side buffering). Heartbeats and status: RUNNING events arrive promptly. What’s delayed is the assistant text itself, which the server doesn’t seem to publish to the stream until much later (often in a single burst near or after FINISHED). From what I can see: the bottleneck is server-side event publication, not SSE framing or client buffering.
Thanks, Kannan. I saw your follow-up to Colin too: the reconnect held, but the text still arrived about 37 seconds after FINISHED. That leaves a gap the initial connection race doesn’t explain on its own. I wouldn’t mark this resolved from the reconnect alone.
Hey @Kannan_Mavila, thanks for that run, it pinned it down.
It looks like the delay is on our side and is tied to the agent’s history. When a stream connects, the server loads the agent’s full conversation history before it starts forwarding live events, and on the API path that load blocks the stream. On an agent with a handful of turns it takes under a second. An agent with several hundred turns, like bc-7bc40663-6255-43d8-a8c3-1f3169526b6c, takes 30 seconds or more, and everything the agent said in the meantime arrives in one burst when the load finishes. The web UI does not hit this because the browser tells the server which history it already has, so it skips the load.
I can see the same pattern across the agents you have been testing with today: the ones with a small number of turns delivered intermediate text within a second or two of connecting, and the two with a few hundred turns paused for 25 to 35 seconds before the first text.
Would you mind doing one quick run to confirm it from your side? Spin up a brand-new agent (please share the run/ bc- ID), run your usual prompt (the “starting work”, sleep 10, “done” one) on the stream, and just note how long it takes from the run being created to the first assistant text arriving. Then do the same against bc-7bc40663-6255-43d8-a8c3-1f3169526b6c and note that timing too.
If the fresh agent delivers “starting work” within a few seconds and the long-running one still takes 30 seconds or more, that confirms it and I can share it with the team!