You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -12,21 +12,51 @@ Agent blocks can emit more than answer text while they run: **provider-exposed t
12
12
Sim does **not** invent thinking for providers that do not stream it. Bedrock Converse, many OpenAI-compat models, and non-reasoning chat models stay text-only (plus tools when a live tool loop is wired).
Sending the header is a statement about the **client**: it understands v1 framing, so answer text can stream live and be retracted with `chunk_reset`. That alone does not expose anything — it only changes cadence, and it applies even when both event policies are off.
17
+
Agent events are governed by two things that do not depend on each other.
18
18
19
-
Thinking and tool frames need the header **plus** their own deployment policy:
19
+
**Policy decides which frames exist.**`includeThinking` turns on `thinking` frames, `includeToolCalls` turns on `tool` frames, and both default to **off**.
| Deployed chat (`/api/chat/{identifier}`) | The chat deployment's **Thinking** and **Tool calls** toggles |
24
+
| Workflow API (`/api/workflows/{id}/execute`) | Per-request `includeThinking` / `includeToolCalls` in the body |
25
+
26
+
**The header declares the protocol version.** Sending it says the client understands v1 framing:
24
27
25
28
```http
26
29
X-Sim-Stream-Protocol: agent-events-v1
27
30
```
28
31
29
-
Legacy clients that omit the header keep today’s text-only SSE even if either deployment policy is enabled. The hosted chat UI always sends the header for its own deployments, so hosted chats stream token by token regardless of the toggles.
32
+
It does two things. It switches answer text to live token-by-token `chunk` frames that `chunk_reset` can retract, and it is **required** for any `thinking` or `tool` frame — a client that never declared a version has no contract for their shape, so it keeps the text-only stream it already understands.
33
+
34
+
Omitting the header is always valid and always safe: you get settled final-turn text and no agent-event frames, which is what every pre-existing integration receives. The response echoes the header back when the protocol was negotiated.
35
+
36
+
The header alone exposes nothing, so a chat with both policies off still streams token by token.
37
+
38
+
<Callouttype="warn">
39
+
On the workflow API, setting `includeThinking` or `includeToolCalls`**without** the header is rejected with `400`. The flags would otherwise be a silent no-op, which is the failure mode this protocol exists to avoid. Deployed chat degrades instead of rejecting, because there the policy comes from the deployment rather than the request.
Both flags default to `false`, so an existing integration receives exactly the frames it does today. The header is required whenever either flag is set; omit it and the request is rejected with `400` rather than silently downgraded.
58
+
59
+
Two differences from deployed chat are worth knowing. Tool frames carry the name and status only on both surfaces, but the API's terminal `final` envelope keeps tool **arguments and results** — public chat redacts them. And thinking is delivered only as `thinking` frames; it is stripped from `providerTiming.timeSegments` in the envelope on every surface, so enabling the policy is the only way to receive it.
30
60
31
61
## Simple SSE frame shapes
32
62
@@ -47,8 +77,8 @@ Answer text stays on `chunk`. Thinking and tools never reuse `chunk` (so older c
47
77
48
78
During a live tool loop, the model can’t be classified mid-turn: text it emits may turn out to be the final answer or preamble before a tool call (the stop reason arrives only at turn end).
49
79
50
-
-**Opted-in clients** (protocol header; no event policy required) receive answer text as `chunk` frames **live**, token by token. If the turn then resolves to tool calls, a `chunk_reset` frame tells the client to discard that block’s streamed text — the final turn re-streams live after tools settle. Append `chunk`, honor `chunk_reset`, and the displayed answer always converges to the block’s final content.
51
-
-**Legacy clients** (no header) never see provisional text: only settled final-turn text is emitted as `chunk`, delivered in one piece when the turn completes. Honoring `chunk_reset` is what buys live cadence, so send the header if you want it.
80
+
-**Clients sending the protocol header** (no event policy required) receive answer text as `chunk` frames **live**, token by token. If the turn then resolves to tool calls, a `chunk_reset` frame tells the client to discard that block’s streamed text — the final turn re-streams live after tools settle. Append `chunk`, honor `chunk_reset`, and the displayed answer always converges to the block’s final content.
81
+
-**Clients without the header** never see provisional text: only settled final-turn text is emitted as `chunk`, delivered in one piece when the turn completes. Honoring `chunk_reset` is what buys live cadence, so send the header if you want it.
52
82
53
83
Logs, memory, and the block’s `content` output always contain final-turn text only — intermediate preamble is never persisted.
When you click **Run** in the builder, the execution-events SSE path forwards the same sink. The canvas is always opted in — it does not send (or need) the `X-Sim-Stream-Protocol` header; the dual gate applies only to the public chat / simple SSE surface:
95
+
When you click **Run** in the builder, the execution-events SSE path forwards the same sink. The canvas is always opted in — it does not send (or need) the `X-Sim-Stream-Protocol` header, and the policy switches do not apply to it:
@@ -73,7 +103,7 @@ The terminal output panel shows Thinking / Tools chrome above the block output w
73
103
74
104
## Chat deployment toggles
75
105
76
-
In **Deploy → Chat**, enable **Include thinking** for provider-exposed thinking and **Include tool calls** for tool names and lifecycle status. The switches are independent, and both event types still require the protocol header. Neither switch affects answer-text cadence.
106
+
In **Deploy → Chat**, enable **Include thinking** for provider-exposed thinking and **Include tool calls** for tool names and lifecycle status. The switches are independent of each other, and both still require the client to send the protocol header. Neither switch affects answer-text cadence.
77
107
78
108
Tool arguments and results are never exposed to a public chat — not in lifecycle frames, and not through the terminal `final` envelope, where the block's own tool calls are reduced to the same name-and-lifecycle shape. The authenticated workflow API still returns full tool results. Redeploy or update the chat after changing Agent models or tools.
79
109
@@ -89,6 +119,68 @@ Per-model support is generated from the model registry on the [Agent block page]
89
119
| OpenAI-compat (Groq, DeepSeek, …) | Only if vendor streams `reasoning` / `reasoning_content`| Live loop where wired (e.g. Groq, DeepSeek) |
90
120
| Bedrock | Not invented | Yes when streaming tool loop is used |
91
121
122
+
## Consuming the stream
123
+
124
+
A conforming client owes the stream four things:
125
+
126
+
1.**Discriminate before appending.** Only a frame with **no**`event` field is answer text. Checking `event === undefined` rather than "has a `chunk` field" is what keeps future frame types from leaking into the answer.
127
+
2.**Accumulate per `blockId`.** A workflow can stream more than one block; frames interleave.
128
+
3.**Honor `chunk_reset`** if you sent the protocol header. Clear that block's accumulated text — it belonged to a turn that resolved to tool calls, and the final turn re-streams.
129
+
4.**Stop at the terminal frame.** Exactly one of `final` or `error` arrives, followed by the literal `"[DONE]"` sentinel. `stream_error` is *not* terminal.
130
+
131
+
### Reference client
132
+
133
+
```ts
134
+
typeFrame=Record<string, unknown>
135
+
136
+
asyncfunction consume(response:Response) {
137
+
const reader =response.body!.getReader()
138
+
const decoder =newTextDecoder()
139
+
const answers =newMap<string, string>()
140
+
const thinking =newMap<string, string>()
141
+
let buffer =''
142
+
143
+
while (true) {
144
+
const { done, value } =awaitreader.read()
145
+
if (done) break
146
+
buffer+=decoder.decode(value, { stream: true })
147
+
148
+
// SSE frames are newline-delimited; keep the trailing partial line.
149
+
const lines =buffer.split('\n')
150
+
buffer=lines.pop() ??''
151
+
152
+
for (const line oflines) {
153
+
if (!line.startsWith('data: ')) continue
154
+
const payload =line.slice(6)
155
+
156
+
const frame =JSON.parse(payload) asFrame|string
157
+
if (frame==='[DONE]') return { answers, thinking }
Copy file name to clipboardExpand all lines: apps/docs/content/docs/en/workflows/deployment/api.mdx
+23Lines changed: 23 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -237,6 +237,28 @@ while (true) {
237
237
</Tab>
238
238
</Tabs>
239
239
240
+
#### Streaming agent thinking and tool calls
241
+
242
+
By default a streaming run carries answer text only. To also receive the Agent block's reasoning and its tool-call lifecycle, set `includeThinking` / `includeToolCalls`:
243
+
244
+
```bash
245
+
curl -N -X POST https://sim.ai/api/workflows/{workflow-id}/execute \
246
+
-H "Content-Type: application/json" \
247
+
-H "x-api-key: $SIM_API_KEY" \
248
+
-H "X-Sim-Stream-Protocol: agent-events-v1" \
249
+
-d '{
250
+
"input": "Research this topic",
251
+
"stream": true,
252
+
"selectedOutputs": ["agent_1.content"],
253
+
"includeThinking": true,
254
+
"includeToolCalls": true
255
+
}'
256
+
```
257
+
258
+
The `X-Sim-Stream-Protocol` header is **required** whenever either flag is set — it declares that your client understands agent-event framing. Setting a flag without it returns `400` rather than silently ignoring the flag. The header also switches answer text to live token-by-token delivery that `chunk_reset` can retract.
259
+
260
+
Both flags default to `false` and the header is optional on its own, so existing integrations keep the exact frames they receive today. Tool frames carry the tool name and status only; arguments and results arrive in the terminal `final` envelope. Not every model exposes thinking. See [Agent stream events](/docs/workflows/deployment/agent-events) for the frame shapes, a reference client, and per-provider support.
261
+
240
262
#### Oversized outputs
241
263
242
264
Workflow execution responses are capped by platform request and response limits. When an internal output, log field, streamed field, or async status payload contains a value that is too large to inline, Sim may replace that nested value with a versioned reference:
@@ -378,6 +400,7 @@ For detailed rate limit information and the logs/webhooks API, see [External API
378
400
{ question: "Can I deploy the same workflow as both an API and a chat?", answer: "Yes. A workflow can be simultaneously deployed as an API, chat, MCP tool, and more. Each deployment type runs against the same active snapshot." },
379
401
{ question: "How do I choose between sync, streaming, and async?", answer: "Use sync for quick workflows that finish in seconds. Use streaming when you want to show progressive output to users as it's generated. Use async for long-running workflows where holding a connection open isn't practical." },
380
402
{ question: "How do I select multiple outputs for streaming?", answer: "Open the Select outputs dropdown in the API tab and check each output field you want to stream. You can choose fields from multiple blocks. The selected fields are reflected as an array in the selectedOutputs request body parameter." },
403
+
{ question: "Can I stream an Agent block's thinking and tool calls over the API?", answer: "Yes. Set includeThinking and/or includeToolCalls to true in the request body and send the X-Sim-Stream-Protocol: agent-events-v1 header, which declares that your client understands agent-event framing. Setting a flag without the header returns 400 rather than silently ignoring it. Both flags default to false, so existing integrations are unaffected. Thinking arrives as thinking frames and tool lifecycle as tool frames carrying name and status only — tool arguments and results stay in the final envelope." },
381
404
{ question: "How does Promote to live work?", answer: "Promote to live sets an older version as the active deployment without creating a new version. Subsequent API calls immediately run against the promoted snapshot. This is the fastest way to roll back to a previous state." },
382
405
{ question: "How long are async job results available?", answer: "Completed and failed job results are retained for 24 hours. After that, the status endpoint returns 404. Retrieve and store results on your end if you need them longer." },
383
406
{ question: "What happens if my API key is compromised?", answer: "Revoke the key immediately in Settings → Sim Keys and generate a new one. Revoked keys stop working instantly." },
0 commit comments