Streaming

Pass a block to run and it receives every event in the exchange, as it happens. Text, thinking, and half-written tool arguments stream mid-turn; the loop adds its own events around tools, approvals, and compaction.

agent.run("Summarize this thread.") do |event|
case event.type
when :text_delta then print event.delta
when :tool_result then render(event.message.ui)
when :approval_needed then notify_reviewers(event)
end
end

The event types

Stream events from the provider:

:start
:text_start :text_delta :text_end
:thinking_start :thinking_delta :thinking_end
:toolcall_start :toolcall_delta :toolcall_end
:done :error :retry

Loop events from the harness: :tool_result after each tool runs, :approval_needed when a gated call parks, and :compacting / :compaction around a context fold. Events carry origin when they come from a sub-agent, and duration where timing is meaningful.

Sinks

Sinks bridge the event stream to a transport and compose as blocks:

cable = Mistri::Sinks::ActionCable.new("agent_#{session.id}")
sink = Mistri::Sinks::Coalesced.new(cable) # merge token bursts
agent.run(input, &sink)

Mistri::Sinks::SSE.new(response.stream) does the same for ActionController::Live.