swink_agent/loop_/event.rs
1//! Public event types emitted by the agent loop.
2
3use std::sync::Arc;
4
5use crate::stream::AssistantMessageDelta;
6use crate::tool::AgentToolResult;
7use crate::types::{AgentMessage, AssistantMessage, LlmMessage, ModelSpec, ToolResultMessage};
8
9// ─── TurnEndReason ───────────────────────────────────────────────────────────
10
11/// Why a turn ended.
12#[non_exhaustive]
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum TurnEndReason {
16 /// Assistant completed without requesting tool calls.
17 Complete,
18 /// Tools were executed (loop continues).
19 ToolsExecuted,
20 /// Turn was interrupted by a steering message during tool execution.
21 SteeringInterrupt,
22 /// LLM returned an error stop reason.
23 Error,
24 /// External cancellation via `CancellationToken`.
25 Cancelled,
26 /// Stream was aborted mid-generation.
27 Aborted,
28 /// Turn ended due to a transfer signal from a tool.
29 Transfer,
30}
31
32// ─── AgentEvent ──────────────────────────────────────────────────────────────
33
34/// Fine-grained lifecycle event emitted by the agent loop.
35///
36/// Consumers subscribe to these events for observability, UI updates, and
37/// logging. The harness never calls back into application logic for display
38/// concerns.
39#[non_exhaustive]
40#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
41#[serde(tag = "event", rename_all = "snake_case")]
42#[allow(clippy::large_enum_variant)]
43pub enum AgentEvent {
44 /// Emitted once when the loop begins.
45 AgentStart,
46
47 /// Emitted once when the loop exits, carrying the final message context.
48 AgentEnd { messages: Arc<Vec<AgentMessage>> },
49
50 /// Emitted at the beginning of each assistant turn.
51 TurnStart,
52
53 /// Emitted at the end of each turn with the assistant message and tool results.
54 TurnEnd {
55 assistant_message: AssistantMessage,
56 tool_results: Vec<ToolResultMessage>,
57 reason: TurnEndReason,
58 /// Full context snapshot at the turn boundary for replay/auditing.
59 snapshot: crate::types::TurnSnapshot,
60 },
61
62 /// Emitted after context transform, before the LLM streaming call.
63 /// Allows plugins to observe/log the final prompt.
64 BeforeLlmCall {
65 system_prompt: String,
66 messages: Vec<LlmMessage>,
67 model: ModelSpec,
68 },
69
70 /// Emitted when a message begins streaming.
71 MessageStart,
72
73 /// Emitted for each incremental delta during assistant streaming.
74 MessageUpdate { delta: AssistantMessageDelta },
75
76 /// Emitted when a message is complete.
77 MessageEnd { message: AssistantMessage },
78
79 /// Emitted when a tool call begins execution.
80 ToolExecutionStart {
81 id: String,
82 name: String,
83 arguments: serde_json::Value,
84 },
85
86 /// Emitted for intermediate partial results from a streaming tool.
87 ToolExecutionUpdate {
88 /// The tool call ID so observers can attribute concurrent updates.
89 id: String,
90 /// The tool name (matches `ToolExecutionStart.name`, including any
91 /// plugin namespace prefix) so observers can attribute concurrent
92 /// updates without inspecting tool output content.
93 name: String,
94 /// The partial tool result payload.
95 partial: AgentToolResult,
96 },
97
98 /// Emitted when a tool call is pending approval.
99 ToolApprovalRequested {
100 id: String,
101 name: String,
102 arguments: serde_json::Value,
103 },
104
105 /// Emitted when a tool call approval decision is made.
106 ToolApprovalResolved {
107 id: String,
108 name: String,
109 approved: bool,
110 },
111
112 /// Emitted when a tool call completes.
113 ToolExecutionEnd {
114 id: String,
115 /// The tool name (matches `ToolExecutionStart.name`, including any
116 /// plugin namespace prefix) so observers can correlate end events
117 /// with the tool that produced them.
118 name: String,
119 result: AgentToolResult,
120 is_error: bool,
121 },
122
123 /// Emitted when context compaction drops messages.
124 ContextCompacted {
125 report: crate::context::CompactionReport,
126 },
127
128 /// Emitted when the agent falls back to a different model after exhausting
129 /// retries on the current one.
130 ModelFallback {
131 from_model: ModelSpec,
132 to_model: ModelSpec,
133 },
134
135 /// Emitted when the agent switches to a different model during a retry cycle.
136 ModelCycled {
137 old: ModelSpec,
138 new: ModelSpec,
139 reason: String,
140 },
141
142 /// Emitted when session state delta is flushed (non-empty only).
143 /// Fired immediately before `TurnEnd`.
144 StateChanged { delta: crate::StateDelta },
145
146 /// Emitted when context caching acts on a turn (write or read).
147 CacheAction {
148 hint: crate::context_cache::CacheHint,
149 prefix_tokens: usize,
150 },
151
152 /// Emitted when an MCP server connects successfully.
153 McpServerConnected { server_name: String },
154
155 /// Emitted when an MCP server disconnects.
156 McpServerDisconnected { server_name: String, reason: String },
157
158 /// Emitted when tools are discovered from an MCP server.
159 McpToolsDiscovered {
160 server_name: String,
161 tool_count: usize,
162 },
163
164 /// Emitted when an MCP tool call begins execution.
165 McpToolCallStarted {
166 server_name: String,
167 tool_name: String,
168 },
169
170 /// Emitted when an MCP tool call completes.
171 McpToolCallCompleted {
172 server_name: String,
173 tool_name: String,
174 is_error: bool,
175 },
176
177 /// Emitted when an artifact version is successfully saved.
178 #[cfg(feature = "artifact-store")]
179 ArtifactSaved {
180 /// The session this artifact belongs to.
181 session_id: String,
182 /// The artifact name.
183 name: String,
184 /// The version number that was saved.
185 version: u32,
186 },
187
188 /// Emitted when a tool signals a transfer to another agent.
189 ///
190 /// Contains the enriched [`TransferSignal`](crate::transfer::TransferSignal)
191 /// with conversation history. Emitted immediately before the `TurnEnd` event
192 /// with `TurnEndReason::Transfer`.
193 TransferInitiated {
194 signal: crate::transfer::TransferSignal,
195 },
196
197 /// A custom event emitted via [`Agent::emit`](crate::Agent::emit).
198 Custom(crate::emit::Emission),
199}