AgentEvent is the runtime's event vocabulary -- it captures every significant happening in the agent loop that a UI, logger, or analysis consumer might react to. Events are emitted through an mpsc::UnboundedSender<AgentEvent> channel during execution and consumed by SessionRecorder (or any external subscriber) on the receiving end.
Event [EXISTS]
├── AgentEvent [EXISTS] — 15 variants
│ ├── Session: AgentStart/End [EXISTS]
│ ├── Loop: ParallelLoopStart/End, CompactionStarted/Ended [EXISTS]
│ ├── Turn: TurnStart/End [EXISTS]
│ ├── Message: MessageStart/Update/End [EXISTS]
│ ├── Tool: ToolExecutionStart/Update/End, ProgressMessage [EXISTS]
│ └── Input: InputRejected [EXISTS]
├── StreamDelta [EXISTS] — Text/Thinking/ToolCallDelta
├── ContinuationKind [EXISTS] — Initial/Default/Rerun/Branch/Compaction
└── TurnTrigger [EXISTS] — User/SubAgent/Continuation/Branch
15 variants grouped by scope. Each variant carries a loop_id for correlation (except ParallelLoopStart/End which use session_id).
Variant Status Fields Description
AgentStart[EXISTS] agent_id, session_id, loop_id, parent_loop_id, continuation_kind, config_snapshot, timestamp, metadataFires once when agent_loop() is entered, before any LLM call. continuation_kind: ContinuationKind (non-optional). config_snapshot: Option<LoopConfigSnapshot> carries model/provider identity.
AgentEnd[EXISTS] loop_id, messages, usage, timestamp, rejectionFires once when agent_loop() exits; rejection is Some if an InputFilter blocked the input
Variant Status Fields Description
ParallelLoopStart[EXISTS] session_id, loop_ids, timestampEmitted before parallel branch dispatch; lists all branch loop_ids
ParallelLoopEnd[EXISTS] session_id, selected_loop_id, selected_config_index, evaluation_usage, timestampEmitted after evaluation selects a winning branch
CompactionStarted[EXISTS] loop_id, estimated_tokens, message_count, timestampEmitted before compaction strategy runs
CompactionEnded[EXISTS] loop_id, messages_before, messages_after, estimated_tokens_before, estimated_tokens_after, loops_compacted, timestampEmitted after compaction completes
Variant Status Fields Description
TurnStart[EXISTS] loop_id, turn_index, timestamp, triggered_byFires at the start of each LLM turn (one LLM call = one turn)
TurnEnd[EXISTS] loop_id, message, usage, timestamp, tool_resultsFires at the end of each LLM turn
Variant Status Fields Description
MessageStart[EXISTS] loop_id, messageNew message created (assistant: when SSE stream opens; user/tool: immediately)
MessageUpdate[EXISTS] loop_id, message, deltaStreaming token/chunk; delta is the increment, message is the accumulator
MessageEnd[EXISTS] loop_id, messageMessage fully complete; safe to persist
Variant Status Fields Description
ToolExecutionStart[EXISTS] loop_id, tool_call_id, tool_name, argsTool call begins (before execute())
ToolExecutionUpdate[EXISTS] loop_id, tool_call_id, tool_name, partial_resultMid-execution partial result (via ctx.on_update)
ToolExecutionEnd[EXISTS] loop_id, tool_call_id, tool_name, result, is_error, child_loop_idTool finished; child_loop_id is Some for sub-agent tools
ProgressMessage[EXISTS] loop_id, tool_call_id, tool_name, textUser-facing status text (via ctx.on_progress)
Variant Status Fields Description
InputRejected[EXISTS] loop_id, reasonInputFilter rejected the user's message; agent loop returns immediately
Events form a nested bracket structure:
AgentStart (+ config_snapshot) -- session-scoped
TurnStart -- turn-scoped (0-based index)
MessageStart -- message-scoped (assistant message)
MessageUpdate (N times) -- streaming deltas
MessageEnd
ToolExecutionStart -- tool-scoped (per tool call)
ToolExecutionUpdate (0..N) -- partial results
ProgressMessage (0..N) -- status text
ToolExecutionEnd
MessageStart -- message-scoped (tool result message)
MessageEnd
TurnEnd
TurnStart -- next turn (tool round-trip)
...
TurnEnd
AgentEnd -- session-scoped
For parallel evaluation:
ParallelLoopStart -- loop-scoped (lists all branch IDs)
AgentStart (branch 1) -- nested full lifecycle per branch
AgentEnd (branch 1)
AgentStart (branch 2)
AgentEnd (branch 2)
ParallelLoopEnd -- loop-scoped (announces winner)
Incremental token-level updates from the LLM stream. Carried inside MessageUpdate events.
Variant Status Description
Text { delta }[EXISTS] A text token fragment
Thinking { delta }[EXISTS] A thinking/reasoning chunk (extended thinking mode only)
ToolCallDelta { delta }[EXISTS] A fragment of tool call argument JSON (accumulate until MessageEnd)
How an agent_loop_continue call relates to the session's prior loops. Surfaced in AgentStart for observability.
Variant Status Description
Initial[EXISTS] First loop in a session via agent_loop(). The #[default] variant.
Default[EXISTS] Unspecified continuation; preserves original semantics
Rerun { tag }[EXISTS] Retry from equivalent state; tag is RFC 3339 UTC timestamp
Branch { tag }[EXISTS] Exploration of a different path from a branching point
Compaction[EXISTS] Standalone context-compaction pass; no LLM call
Identifies what caused a new turn to begin. Carried in TurnStart.
Variant Status Description
User[EXISTS] First turn triggered by a user message
SubAgent[EXISTS] Invoked as a sub-agent by a parent agent
Continuation[EXISTS] Continuation turn: tool round-trip, steering, or Default/Rerun continuation
Branch[EXISTS] First turn of a Branch continuation; subsequent turns use Continuation
Producer: agent_loop (src/agent_loop/)
|
| mpsc::UnboundedSender<AgentEvent>
v
Consumer: SessionRecorder (src/session/recorder.rs)
|
| on_event() dispatches by variant
v
Storage: Session -> LoopRecord -> LoopEvent[]
The SessionRecorder consumes events and builds a structured tree:
AgentStart opens a LoopRecord (status: Running)
AgentEnd closes it (status: Completed or Rejected)
TurnEnd extracts config snapshots from assistant messages
ToolExecutionEnd records ChildLoopRef for sub-agent traceability
ParallelLoopEnd retroactively sets ParallelGroupRecord on all branch records
MessageUpdate events are optionally recorded (off by default; 100-1000x more numerous)
All other events append to LoopRecord.events as LoopEvent { sequence, event }
Concept File
AgentEvent, StreamDelta, ContinuationKind, TurnTriggersrc/types/event.rs
SessionRecorder, SessionRecorderConfigsrc/session/recorder.rs
Event emission (AgentStart, TurnStart, MessageUpdate, etc.) src/agent_loop/run.rs, src/agent_loop/streaming.rs
Tool lifecycle events (ToolExecutionStart/Update/End) src/agent_loop/tools.rs
LoopRecord, LoopEvent, Sessionsrc/session/model.rs
before_task / after_task callbacks [EXISTS] -- Session-level callbacks on SessionRecorderConfig (G2). BeforeTaskFn fires on first AgentStart with new session_id; AfterTaskFn fires on flush(). These are semantically session-scoped, unlike before_loop/after_loop which fire per-loop.
Session Scope [EXISTS] -- SessionScope enum (Ephemeral / Persistent) on the Session struct (G7). Set via config [session] scope = "persistent".
Error Events -- The current design uses StopReason::Error and the on_error callback for LLM errors. A dedicated AgentEvent::Error variant for more granular error reporting (tool failures, network issues, etc.) is noted as a potential improvement in the source comments.
Event Replay -- LoopRecord.events stores the full event stream (as Vec<LoopEvent>), enabling replay or analysis of past runs. SessionRecorderConfig.include_streaming_events controls whether the high-volume MessageUpdate deltas are included.