#[non_exhaustive]pub enum RunEvent {
Show 14 variants
Started {
run_id: String,
},
Session {
run_id: String,
session_id: Option<String>,
model: Option<String>,
},
Text {
run_id: String,
delta: String,
},
Thinking {
run_id: String,
delta: String,
},
ToolStart {
run_id: String,
tool_call_id: String,
title: String,
tool_kind: ToolKind,
locations: Vec<ToolLocation>,
raw_input: Option<String>,
},
ToolEnd {
run_id: String,
tool_call_id: String,
ok: bool,
content: Option<String>,
raw_output: Option<String>,
locations: Vec<ToolLocation>,
},
SuggestedEdits {
run_id: String,
edits: Vec<SuggestedEdit>,
},
Activity {
run_id: String,
message: String,
},
Usage {
run_id: String,
input_tokens: Option<u64>,
output_tokens: Option<u64>,
total_tokens: Option<u64>,
cache_read_tokens: Option<u64>,
cache_write_tokens: Option<u64>,
cost_usd: Option<f64>,
},
AskQuestion {
run_id: String,
request_id: String,
questions: Vec<Question>,
},
Plan {
run_id: String,
entries: Vec<PlanEntry>,
},
SessionInfoUpdate {
run_id: String,
title: Option<String>,
updated_at: Option<String>,
},
Error {
run_id: String,
message: String,
},
Exited {
run_id: String,
exit_code: Option<i32>,
cancelled: bool,
},
}Expand description
The normalized event stream. #[serde(tag = "kind")] +
camelCase mirrors the existing ProcessEvent wire contract the TS
store already reads (event.kind, event.runId, …), so the
front-end consumes one shape regardless of which harness produced it.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Started
First event, before any output. UI shows “thinking…”. Fired the
instant the process spawns — before the CLI reports its
session/model, which arrive separately as RunEvent::Session.
Session
The agent session is established — its id and the model in use.
Distinct from Started because it arrives a beat later, in the
CLI’s first output line (bob’s init, Claude’s system/init,
codex’s thread.started); keeping Started instant matters for
the “thinking…” feedback. Either field may be absent when the CLI
doesn’t report it (e.g. codex gives a thread id but no model).
Constructible out-of-tree: any Harness (in-tree, or a third-party
crate like openai-compatible) mints this directly, so it is not
variant-#[non_exhaustive] — sealing it would break the open-producer
contract (see the note on RunEvent::Exited).
Text
A chunk of assistant text. Appended to the active message.
Thinking
A chunk of model reasoning (“thinking”), rendered distinctly from
Text so the UI can show reasoning without mixing it into the
answer (e.g. Claude’s thinking_delta).
ToolStart
A tool call started — render a state-ful card keyed by id. Mirrors ACP’s
ToolCall: title + kind + locations (files it touches) + the raw
arguments (raw_input, omitted when streamed separately, e.g. Claude).
Fields
locations: Vec<ToolLocation>ToolEnd
A tool call finished (matched to its start by id). Mirrors ACP’s tool
result: content (human-readable, flattened to text) + raw_output
(structured JSON) + the locations it touched. ok reduces ACP’s
terminal Completed/Failed status to a flag.
Fields
locations: Vec<ToolLocation>SuggestedEdits
One or more proposed edits. The app prepares + previews them.
Activity
A human-readable status line (tool call, file touch, edit count). Replaces the message’s transient activity text.
Usage
Token accounting for the run, emitted near its end (from the
CLI’s result / turn.completed). Neutral tokens only —
harness-specific costs/credits (bob’s coins) are NOT here; a
consumer that wants them reads the harness’s own output. Any
field may be absent when the CLI doesn’t break usage down.
cache_read_tokens / cache_write_tokens are the prompt-cache
counters reported separately from input_tokens (Claude’s
cache_read_input_tokens / cache_creation_input_tokens) — not folded
into input_tokens, and omitted from the wire when the CLI doesn’t
report caching.
Fields
AskQuestion
The agent is asking the user one or more multiple-choice questions
(Claude’s AskUserQuestion, Codex’s tool/requestUserInput). The host
renders the options as selectable chips; the user’s pick is sent back as
their next message on the existing chat path (which resumes the
session), so the agent continues with the answer in hand. Carrying the
questions as a neutral event keeps the harness-specific tool shape in the
adapter — the host never name-checks AskUserQuestion (cf. ToolKind).
Fields
Plan
The agent’s current task plan / todo list (Claude’s TodoWrite,
Codex’s plan items), replacing any prior plan for this run. The host
renders a checklist without knowing the harness’s native plan tool.
The neutral plan vocabulary adapters map onto — the acp adapter from
ACP plan, and openai-compatible from its todowrite tool.
SessionInfoUpdate
A live update to the session’s display metadata — its title and/or
last-updated time — emitted mid-run when the agent (re)names the
conversation. Maps field-for-field onto ACP session_info_update
(title + updatedAt); lets a sessions list show a meaningful title
before the run ends. Both fields optional (a partial update).
Fields
Error
Spawn / IO / parse failure. Terminal — followed by Exited.
Exited
The run finished. Sent exactly once. Like every RunEvent variant it is
constructible out-of-tree: harnesses live in their own crates (the
Registry is open — openai-compatible, plus examples/custom_harness.rs),
so no produced variant is variant-#[non_exhaustive] — that would
close the producer door. The enum itself stays #[non_exhaustive], which
protects consumers (a new variant just needs a _ arm) without
blocking construction of the existing ones.