AgentEvent stream
appctl emits structured AgentEvents from the agent loop. They are the single source of truth for UIs (CLI renderer, VS Code panel, web UI) and for the WebSocket and POST /run transports.
Serialization
Section titled “Serialization”Each event is a JSON object with a kind discriminator:
{ "kind": "<variant>", /* fields */ }Variants
Section titled “Variants”user_prompt
Section titled “user_prompt”Emitted once when the user submits a prompt.
{ "kind": "user_prompt", "text": "list widgets" }assistant_delta
Section titled “assistant_delta”Incremental assistant text. Emitted by providers that support streaming; absent otherwise.
{ "kind": "assistant_delta", "text": "Looking " }assistant_message
Section titled “assistant_message”A complete assistant message. Always emitted at the end of an assistant turn.
{ "kind": "assistant_message", "text": "Here are your widgets." }assistant_thought_delta
Section titled “assistant_thought_delta”Incremental reasoning text from providers that expose a separate thought stream. Clients should render this separately from the public answer.
{ "kind": "assistant_thought_delta", "text": "Inspecting available tools..." }assistant_thought
Section titled “assistant_thought”Complete reasoning text for the turn. This is separate from assistant_message
and should not be treated as the answer.
{ "kind": "assistant_thought", "text": "Inspecting available tools..." }tool_call
Section titled “tool_call”The agent chose a tool and is about to call it.
{ "kind": "tool_call", "id": "call_01HV...", "name": "list_widgets", "arguments": { "limit": 10 }}id correlates with the matching tool_result.
awaiting_input
Section titled “awaiting_input”Emitted immediately before appctl applies a safety gate that may need terminal input, such as confirming a mutating CLI tool call.
{ "kind": "awaiting_input" }tool_result
Section titled “tool_result”The tool returned.
{ "kind": "tool_result", "id": "call_01HV...", "result": { "items": [ /* ... */ ] }, "status": "ok", "duration_ms": 120}status is ok or error.
Unrecoverable error during the loop.
{ "kind": "error", "message": "max iterations reached" }session_state
Section titled “session_state”The active in-process chat transcript for HTTP or WebSocket clients.
{ "kind": "session_state", "session_id": "9f8...", "transcript_len": 4, "resumed": true}context_notice
Section titled “context_notice”Informational context management message, for example when older turns were
trimmed by behavior.history_limit.
{ "kind": "context_notice", "message": "Trimmed 2 older message(s) from model context." }Loop finished. No more events will be emitted for this turn.
{ "kind": "done" }Ordering guarantees
Section titled “Ordering guarantees”- Exactly one
user_promptstarts the stream. - Zero or more
session_state,context_notice,assistant_thought_delta/assistant_thought,awaiting_input, andtool_call/tool_resultevents interleave withassistant_delta/assistant_message. - Every
tool_callis followed by atool_resultwith the sameid(unless the loop errors first). - Exactly one
doneterminates the stream.
Consuming events
Section titled “Consuming events”From POST /run
Section titled “From POST /run”The response body buffers every event in the events array plus a final result object. Good for synchronous callers.
From WS /chat
Section titled “From WS /chat”Frames stream live. Good for UIs.
From the CLI
Section titled “From the CLI”appctl chat and appctl run use the same stream internally. The terminal renderer is in crates/appctl/src/term.rs if you want to see how they are formatted.