Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ async function expressExample() {
})

for await (const event of agent.stream(prompt)) {
// Events automatically serialize to compact JSON via toJSON().
// Only relevant data fields are included — the full Agent instance,
// Tool classes, and mutable hook flags (cancel/retry) are excluded.
res.write(`${JSON.stringify(event)}\n`)
}
res.end()
Expand All @@ -51,4 +54,4 @@ async function expressExample() {
app.post('/stream', handleStreamRequest)
app.listen(3000)
// --8<-- [end:express_example]
}
}
56 changes: 55 additions & 1 deletion src/content/docs/user-guide/concepts/streaming/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,60 @@ See [Graph streaming](../multi-agent/graph.md#streaming-events) and [Swarm strea
</Tab>
</Tabs>

### Event Serialization

<Tabs>
<Tab label="Python">

Python streaming events are plain dictionaries. The SDK does not include a built-in serialization filter — you have full control over which events and fields to forward from your processes and servers.

When serving streamed responses (for example, over SSE or WebSockets), you can filter the yielded events to keep payloads compact:

```python
import json

def filter_event(event: dict) -> dict | None:
"""Filter streaming events to only forward relevant data over the wire."""
# Forward text deltas for real-time display
if "data" in event:
return {"type": "text", "data": event["data"]}

# Forward tool usage for progress indicators
if "current_tool_use" in event and event["current_tool_use"].get("name"):
return {"type": "tool", "name": event["current_tool_use"]["name"]}

# Forward the final result
if "result" in event:
return {"type": "result", "stop_reason": str(event["result"].stop_reason)}

# Skip everything else (lifecycle signals, raw deltas, reasoning, etc.)
return None


async for event in agent.stream_async("Hello"):
filtered = filter_event(event)
if filtered:
await response.write(f"data: {json.dumps(filtered)}\n\n")
```

This approach lets you tailor the streamed output to your use case — for example, forwarding only text deltas for a chat UI or including tool events for a progress dashboard.
</Tab>
<Tab label="TypeScript">

Every event class implements a `toJSON()` method that controls what `JSON.stringify()` produces. This keeps serialized payloads compact for SSE, WebSockets, or HTTP responses — without changing the in-process API.

Each serialized event always includes its `type` discriminator alongside the data fields relevant to that event. For example, a `ModelStreamUpdateEvent` serializes to `{ type, event }` with just the streaming delta, and an `AgentResultEvent` includes `{ type, result }` with the final output. Lifecycle events like `BeforeInvocationEvent` and `AfterInvocationEvent` serialize to just `{ type }`, since they signal state transitions without carrying additional data.
Comment thread
mkmeral marked this conversation as resolved.
Outdated

Properties that hold large runtime references — such as `agent`, `orchestrator`, `state`, and `tool` — are excluded from the serialized output. These remain available for direct programmatic access in-process but are not needed over the wire. Similarly, mutable hook properties like `cancel` and `retry` are omitted. `Error` objects are converted to `{ message: string }` for safe transport.

The same behavior applies to multi-agent and A2A events. Multi-agent events include identifying context like `nodeId` and `nodeType` alongside their data, while the `orchestrator` and `state` references are filtered out. This means you can serialize any event from `agent.stream()` or a multi-agent orchestrator's stream without additional processing:

```typescript
--8<-- "user-guide/concepts/streaming/overview.ts:event_serialization"
```
</Tab>
</Tabs>

## Quick Examples
<Tabs>
<Tab label="Python">
Expand Down Expand Up @@ -346,4 +400,4 @@ orchestrator_callback("What is 3+3?")

- Learn about [Async Iterators](async-iterators.md) for asynchronous streaming
- Explore [Callback Handlers](callback-handlers.md) for synchronous event processing
- See the [Agent API Reference](@api/python/strands.agent.agent) for complete method documentation
- See the [Agent API Reference](@api/python/strands.agent.agent) for complete method documentation
Comment thread
mkmeral marked this conversation as resolved.
31 changes: 31 additions & 0 deletions src/content/docs/user-guide/concepts/streaming/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,34 @@ async function subAgentStreamingExample() {

// --8<-- [end:sub_agent_basic]
}

// Event Serialization Example
async function eventSerializationExample() {
const agent = new Agent()

// --8<-- [start:event_serialization]
for await (const event of agent.stream('Hello')) {
// Every event is a class instance. In-process, it carries the full agent reference:
//
// event.type → "modelStreamUpdateEvent"
Comment thread
mkmeral marked this conversation as resolved.
Outdated
// event.agent → LocalAgent { messages, model, tools, hooks, ... }
// event.event → { type: "modelContentBlockDeltaEvent",
// delta: { type: "textDelta", text: "Hi" } }

// JSON.stringify() calls the event's toJSON() automatically.
// The agent reference and other runtime objects are stripped out,
// leaving only the type discriminator and relevant data fields:
//
// JSON.stringify(event) →
// {
// "type": "modelStreamUpdateEvent",
// "event": {
// "type": "modelContentBlockDeltaEvent",
// "delta": { "type": "textDelta", "text": "Hi" }
// }
// }

console.log(`data: ${JSON.stringify(event)}`)
}
// --8<-- [end:event_serialization]
}