Answer a run with a stream that reaches your caller as it is written, and report the outcome.
An agent can answer POST /invoke with a stream instead of JSON: a Vercel AI SDK UI message stream, AG-UI, server-sent events, NDJSON, plain text — any format. A caller who started the run with ?passthrough=true receives it byte for byte as you write it, so a chat UI can read your agent’s own stream format with no mapping in between.
Post the same JSON you would have answered, with the run token:
POST {api_url}/v0/runs/{run_id}/resultx-agentos-run-token: <context.token>{"status": "completed", "output": {"text": "..."}}
It answers 202 {"status": "accepted"}. The body is one of:
{"status": "completed", "output": {...}} — the output is checked against the capability’s output schema at once; a mismatch answers 422 invalid_output with errors, so you can correct it and post again;
{"status": "suspended", "hold_id": "h_7"} — after asking a person.
A result is at most 1 MiB and is accepted once per invocation: the same result again is accepted too, a different one is 409 result_already_reported. It is scrubbed of secrets like a JSON answer, and recorded when the stream ends.
Post the result before your stream ends, or at the latest within 3 seconds of its end. A later one is refused with 401: the invocation’s token has ended.
Return a stream and set the output; the SDK posts the result for you before the stream ends:
import agentosdef tell(inv: agentos.Invocation): def pieces(): text = "" for delta in inv.llm.stream([{"role": "user", "content": inv.input["topic"]}]): text += delta yield delta inv.set_output({"text": text}) # reported as the run's output return agentos.Stream(pieces(), "text/plain; charset=utf-8")agentos.serve({"tell": tell}, port=8080)
import { serve, stream } from "@agentos/sdk";await serve({ tell(inv) { return stream((async function* () { let text = ""; for await (const delta of inv.llm.stream([{ role: "user", content: inv.input.topic }])) { text += delta; yield delta; } inv.setOutput({ text }); // reported as the run's output })(), { contentType: "text/plain; charset=utf-8" }); },}, { port: 8080 });
If the platform refuses the result (an output that doesn’t match the schema), the SDK reports failed instead, so a run never completes silently with a bad output. A caller that goes away ends the stream and fails the run.
If no result arrives, AgentOS waits those 3 seconds after the stream ends, then decides:
A 2xx body of at most 1 MiB that is the contract’s JSON ({"status": "completed" | "failed" | "suspended", ...}) but sent with another Content-Type — some frameworks send JSON as text/html — is read as the contract, at once.
A stream that ended cleanly with a 2xx status holds the run if this invocation asked a person, and otherwise completes it with output: {"streamed": true, "bytes": ..., "content_type": "..."}. That output isn’t checked against the output schema, and the run page marks the run “streamed”.
Anything else fails the run with agent_interrupted, a non-2xx error page included.
To ask a person in the middle of a stream: create the hold, end your stream the way your format ends it, and report {"status": "suspended", "hold_id": "h_7"}. The resume that follows the decision isn’t streamed to anyone; answer it with JSON, or with a stream and a result, and callers read its output from the run.
What you stream reaches the caller exactly as you wrote it. Unlike the output, error and events AgentOS stores, a stream is not scrubbed of secrets. Never write context.token, a secret or the invoke body into it.
Start the run with ?passthrough=true. The response is exactly what the agent answered — its status code, Content-Type, and body as it arrives — plus an X-AgentOS-Run-Id header:
A chat app’s backend calls this with its API key and hands the response to the browser unchanged, so a UI built on the AI SDK’s useChat, for example, reads it directly. Never put an API key in a browser. Closing the connection doesn’t stop the run; cancel it. After the stream, GET the run for its outcome. See the Runs API for headers, replays and limits.
examples/stream-agent streams a model’s answer as a Vercel AI SDK UI message stream, asks a person mid-stream, and turns a guardrail block into a clean failed run. See Examples.