Stream without losing semantics
Consume visible text while preserving reasoning, usage, warnings, and provider events.
Why streaming is different
A stream is the Agent execution loop in motion, not a sequence of text fragments. It can contain model deltas, tool boundaries, retrieval completion, usage updates, warnings, and one terminal outcome.
Use streaming when latency or live progress matters. Use prompt when only the
complete result matters.
Consume a stream
Poll the stream until it ends. Each item can fail, so handle the Result
instead of assuming every event is present.
use futures_util::StreamExt;
use runifold::AgentStreamEvent;
let mut events = agent.stream("Explain the incident", &run);
while let Some(event) = events.next().await {
match event? {
AgentStreamEvent::Model { event, .. } => render_model_event(event),
AgentStreamEvent::UsageUpdated { usage } => update_usage(usage),
AgentStreamEvent::Completed { outcome } => save(outcome),
_ => {}
}
}Render provider-neutral ModelStreamEvent values rather than decoding a
provider's wire protocol in application code.
Terminal state
Do not reconstruct the final answer by concatenating visible deltas. The
Completed event contains the canonical AgentOutcome: final response,
transcript, counters, warnings, and usage.
A healthy consumer handles unknown future variants with a wildcard match and persists the terminal outcome exactly once.
Cancellation and disconnects
A disconnected browser does not automatically mean the server-side run should
continue. Decide explicitly whether to cancel the RunContext, detach and
persist progress, or hand work to a durable workflow.
For HTTP delivery, SSE is convenient for browser text; NDJSON is often simpler for typed server-to-server events. Keep transport framing separate from Runifold event semantics.