Skip to main content

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}