event-deltas changedmanaged-agents/event-deltas
Nearest release: v2.1.294, published an hour after this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Recorded here
Lines+889added
Lines−0removed
From line
—
no hunk to open at
First seen
8 Oct 2026
this site's first read of the page
Recorded edits1to this page, all time
## Opt in to previews ### Preview events ## Accumulate and reconcile ### SDK accumulator helpers ## Preview session thread events ## Limitations ## Troubleshoot previews ## Next steps
The whole hunk
889 lines, new page
/
lines
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Preview responses with event deltas
url: https://platform.claude.com/docs/en/managed-agents/event-deltas
description: Render the agent's response text as a live preview while the model is still generating it.
featureMetadata:
topic:
title: Managed Agents
url: https://platform.claude.com/docs/en/managed-agents/overview
status: beta
betaHeader: managed-agents-2026-04-01
---
By default, the agent's response text reaches the [session event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming) as buffered `agent.message` events. Each one is emitted only after the model request that produced it finishes. Event deltas let you render that text incrementally, as a live preview, while the model is still generating it.
Previews are a best-effort display aid, and the buffered `agent.message` is always the authoritative record. A client that ignores previews still receives a complete, correct stream.
## Opt in to previews
Previews are opt-in per stream connection. Add the `event_deltas[]` query parameter to the stream you're reading, and repeat it once for each event type you want previewed. The accepted values are `agent.message` and `agent.thinking`. Any other value returns a 400 error, as does a request with more than 100 values.
Both stream endpoints accept the parameter:
* **Session-level stream:** `GET /v1/sessions/{session_id}/events/stream`
* **Session thread stream:** `GET /v1/sessions/{session_id}/threads/{thread_id}/stream`
A subagent's previews appear on [that subagent's own thread stream](https://platform.claude.com/docs/en/managed-agents/event-deltas#preview-session-thread-events).
`[]` is a shell glob pattern, so quote the URL whenever you build the request in a shell. The examples percent-encode the brackets as `%5B%5D`, which also works.
### Preview events
When a previewed event begins, the stream emits an `event_start` carrying the upcoming event's type and `id`:
```json
{
"type": "event_start",
"event": {
"type": "agent.message",
"id": "sevt_01abc..."
}
}
```
For `agent.message`, the start is followed by `event_delta` events carrying incremental text. Each delta names the event it extends in `event_id` and the content block it extends in `delta.index`:
```json
{
"type": "event_delta",
"event_id": "sevt_01abc...",
"delta": {
"type": "content_delta",
"index": 0,
"content": {
"type": "text",
"text": "Here is the summary"
}
}
}
```
For `agent.thinking`, only the `event_start` is emitted, as a signal that a thinking block has started. No `event_delta` events follow. The buffered `agent.thinking` event that concludes the preview is a progress signal and carries no thinking content.
Unlike persisted events, `event_start` and `event_delta` have no `id` or `processed_at` of their own. The only identifier they carry is the `id` of the event they preview. Their type strings are also the exception to the `{domain}.{action}` naming convention of persisted events.
<Note>
Event deltas use a different wire format from [Streaming messages](https://platform.claude.com/docs/en/build-with-claude/streaming), and the difference is intentional. A previewed `agent.message` gets a single `event_start` followed only by `event_delta` events. There are no per-content-block start or stop events and no stop event for the previewed event itself. The delta type is `content_delta`, not `content_block_delta`. Accumulator code written for the Messages API does not carry over unchanged.
</Note>
## Accumulate and reconcile
Every SDK that supports event deltas includes an [accumulator helper](https://platform.claude.com/docs/en/managed-agents/event-deltas#sdk-accumulator-helpers) that handles the `index` bookkeeping for you. The manual pattern in this section works in every language when you need custom bookkeeping. Apply it to the generated event types.
In the manual pattern, hold preview text in a temporary map keyed by `(event_id, index)`, and treat the buffered event as the record. Reconcile the two per model request.
A turn opens with a single `session.status_running` event. On a turn that completes normally, each model request then produces these events, in order:
1. `span.model_request_start`
2. `event_start`
3. The `event_delta` events
4. The buffered `agent.message`
5. [`span.model_request_end`](https://platform.claude.com/docs/en/managed-agents/reference#event-types) (in the Span events tab)
On the wire, this is the previewed portion of that sequence, interleaved with the connection's other buffered events:
```text wrap
event_start {"event": {"type": "agent.message", "id": "sevt_01abc..."}}
event_delta {"event_id": "sevt_01abc...", "delta": {"type": "content_delta", "index": 0, "content": {"type": "text", "text": "..."}}}
...
agent.message {"id": "sevt_01abc...", "content": [...]}
```
The `event_delta` line repeats once per text fragment. Process each event as it arrives:
1. On `event_start`, note the announced `id`. The identifiers always line up: `event_start.event.id`, every `event_delta.event_id`, and the buffered `agent.message`'s `id` are the same value.
2. On each `event_delta`, append `delta.content.text` to the entry at `(event_id, delta.index)` and render the running text. The first delta for an `index` creates that entry.
3. When the buffered `agent.message` arrives, match it by `id`, discard the accumulated preview, and render the message's content instead.
4. On `span.model_request_end`, close any preview that has not been reconciled by its buffered event. No more deltas are coming for it. If the turn errors or is interrupted, the buffered event might never arrive, but `span.model_request_end` still does.
The pattern relies on two guarantees:
* Concatenating a preview's deltas in arrival order, keyed by `(event_id, index)`, gives a prefix of `content[index].text` in the buffered event. It is not necessarily the whole text, because deltas might be [shed under load](https://platform.claude.com/docs/en/managed-agents/event-deltas#limitations).
* A connection emits at most one `event_start` per `event_id`, and the buffered event is the last thing that connection delivers for that `id`.
### SDK accumulator helpers
Each SDK's helper handles the `index` bookkeeping. The Go, Java, Ruby, and C# helpers also key the accumulating preview by the event's `id`. With the Python, TypeScript, and PHP helpers, keep that map yourself and fold each delta into the entry for its `id`.
The following examples opt in to `agent.message` previews and reconcile them with the buffered event:
<CodeGroup>
```bash cURL
# Opt in to agent.message previews via event_deltas, then accumulate manually.
exec {stream}< <(
curl --fail-with-body -sS -N \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events/stream?beta=true&event_deltas%5B%5D=agent.message" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "accept: text/event-stream"
)
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- >/dev/null <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [{"type": "text", "text": "In one short sentence, describe what an event delta is."}]
}
]
}
EOF
# Accumulate deltas keyed by (message id, content index); the final
# agent.message carries the full text, so it replaces every preview for that id.
declare -A preview
while IFS= read -r -u "$stream" event_line; do
[[ $event_line == data:* ]] || continue
event_json=${event_line#data: }
case $(jq -r '.type' <<<"$event_json") in
event_start)
preview_id=$(jq -r '.event.id' <<<"$event_json")
printf '[event_start id=%s]\n' "$preview_id"
;;
event_delta)
preview_key=$(jq -r '.event_id + ":" + (.delta.index | tostring)' <<<"$event_json")
preview[$preview_key]+=$(jq -r '.delta.content.text' <<<"$event_json")
printf '[event_delta] %s\n' "${preview[$preview_key]}"
;;
agent.message)
msg_id=$(jq -r '.id' <<<"$event_json")
for preview_key in "${!preview[@]}"; do
[[ $preview_key == "$msg_id":* ]] && unset "preview[$preview_key]"
done
printf '[agent.message id=%s] ' "$msg_id"
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
printf '\n'
;;
span.model_request_end)
for preview_key in "${!preview[@]}"; do
printf '[closing unreconciled preview for %s]\n' "${preview_key%%:*}"
done
preview=()
;;
session.status_idle)
break
;;
esac
done
exec {stream}<&-
```
```bash CLI
# This workflow does not translate well to a one-off shell command.
# Use one of the SDK examples in this code group instead.
```
```python Python
# Preview snapshots, keyed by event id. accumulate_managed_agents_event folds each
# event_start / event_delta into an agent.message snapshot; the buffered
# agent.message replaces it.
previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}
# Opt in to agent.message previews on this connection
with client.beta.sessions.events.stream(
session.id, event_deltas=["agent.message"]
) as stream:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [{"type": "text", "text": "Describe the repo in one sentence."}],
},
],
)
for event in stream:
match event.type:
case "event_start":
snapshot = accumulate_managed_agents_event(None, event)
if snapshot is not None:
previews[event.event.id] = snapshot
print(f"event_start {event.event.type} {event.event.id}")
case "event_delta":
preview = accumulate_managed_agents_event(previews.get(event.event_id), event)
if preview is not None:
previews[event.event_id] = preview
text = "".join(block.text for block in preview.content)
print(f"event_delta preview: {text!r}")
case "agent.message":
# The buffered event is the record: it replaces and closes the preview
preview = accumulate_managed_agents_event(previews.pop(event.id, None), event)
text = "".join(block.text for block in preview.content)
print(f"agent.message {event.id} {text!r}")
case "span.model_request_end":
# No more deltas are coming. Close any preview whose
# buffered event never arrived.
for event_id in previews:
print(f"span.model_request_end closing preview for {event_id}")
previews.clear()
case "session.status_idle":
break
```
```typescript TypeScript
// Preview snapshots, keyed by event id. `accumulateManagedAgentsEvent`
// folds event_start / event_delta previews into an agent.message snapshot.
const previews = new Map<string, BetaManagedAgentsAgentMessageEvent>();
// Opt in to agent.message previews for this connection only
const stream = await client.beta.sessions.events.stream(session.id, {
event_deltas: ["agent.message"],
});
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [{ type: "text", text: "Summarize the repo README" }]
}
]
});
deltas: for await (const event of stream) {
switch (event.type) {
case "event_start": {
// 1. Note the announced id and open the snapshot. Deltas and the
// buffered event carry the same id.
const preview = accumulateManagedAgentsEvent(undefined, event);
if (preview) previews.set(event.event.id, preview);
console.log(`event_start ${event.event.type} ${event.event.id}`);
break;
}
case "event_delta": {
// 2. Fold the fragment into the snapshot and render it
const preview = accumulateManagedAgentsEvent(previews.get(event.event_id), event);
if (preview) {
previews.set(event.event_id, preview);
const text = preview.content
.map((block) => (block.type === "text" ? block.text : ""))
.join("");
console.log(`event_delta preview: ${JSON.stringify(text)}`);
}
break;
}
case "agent.message": {
// 3. The buffered event is the record: it replaces and closes the preview
const message = accumulateManagedAgentsEvent(previews.get(event.id), event);
previews.delete(event.id);
const text = message.content
.map((block) => (block.type === "text" ? block.text : ""))
.join("");
console.log(`agent.message ${event.id} ${JSON.stringify(text)}`);
break;
}
case "span.model_request_end":
// 4. No more deltas are coming. Close any preview that was never reconciled.
for (const eventId of previews.keys()) {
console.log(`span.model_request_end closing preview for ${eventId}`);
}
previews.clear();
break;
case "session.status_idle":
break deltas;
}
}
stream.controller.abort();
```
```csharp C#
// Opt in to event deltas: agent.message events are previewed as they are produced.
using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(
session.ID,
new() { EventDeltas = [BetaManagedAgentsDeltaType.AgentMessage] }
);
Cut at 300 lines. The page has the rest.
No line in this hunk matches that.