1use serde::{Deserialize, Serialize};
2
3use crate::runtime::kernel::wire::CancellationReason;
4use crate::types::message::{Message, ToolCall, ToolResult};
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11#[serde(deny_unknown_fields)]
12pub struct ProviderReplay {
13 pub protocol: String,
14 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub provider: Option<String>,
16 #[serde(default, skip_serializing_if = "Option::is_none")]
17 pub model: Option<String>,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub native_blocks: Option<Vec<serde_json::Value>>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub reasoning_content: Option<String>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub reasoning_details: Option<serde_json::Value>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub native_message: Option<serde_json::Value>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub tool_calls: Option<Vec<serde_json::Value>>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(tag = "kind", rename_all = "snake_case")]
32pub enum RollbackReason {
33 FatalToolError { tool_name: String, error: String },
34 GovernanceDenied { tool_name: String, reason: String },
35 ProviderFailure { error: String },
36 Timeout,
37 UserInterrupt,
38 MalformedReplay { reason: String },
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(tag = "kind", rename_all = "snake_case")]
47pub enum SessionEvent {
48 RunStarted {
50 run_id: String,
51 goal: String,
52 #[serde(default)]
53 criteria: Vec<String>,
54 agent_id: Option<String>,
55 system_prompt: Option<String>,
56 #[serde(default, skip_serializing_if = "Vec::is_empty")]
58 attachments: Vec<crate::types::message::ContentPart>,
59 },
60 LlmCompleted {
61 turn: u32,
62 message: Message,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 provider_replay: Option<ProviderReplay>,
65 },
66 ToolRequested {
67 turn: u32,
68 calls: Vec<ToolCall>,
69 },
70 ToolCompleted {
71 turn: u32,
72 results: Vec<ToolResult>,
73 },
74 Compressed {
75 turn: u32,
76 archived_seq_range: (u64, u64),
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 action: Option<String>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 summary: Option<String>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
82 summary_tokens: Option<u32>,
83 #[serde(default, skip_serializing_if = "Vec::is_empty")]
84 preserved_refs: Vec<String>,
85 },
86 PageOut {
88 turn: u32,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 action: Option<String>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 summary: Option<String>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
94 tier_hint: Option<String>,
95 #[serde(default)]
96 message_count: u32,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 archive_ref: Option<String>,
99 },
100 PageIn {
102 turn: u32,
103 entry_count: u32,
104 },
105 RunTerminal {
106 reason: String,
107 turns_used: u32,
108 total_tokens: u64,
109 },
110
111 ToolArgumentRepaired {
114 turn: u32,
115 tool: String,
116 original_arguments: String,
117 repaired_arguments: String,
118 },
119 PermissionRequested {
121 turn: u32,
122 tool: String,
123 arguments: String,
124 reason: Option<String>,
125 },
126 PermissionResolved {
128 turn: u32,
129 approved: bool,
130 responder: String, },
132 ToolDenied {
134 turn: u32,
135 call_id: String,
136 tool_name: String,
137 reason: String,
138 },
139
140 CapabilityChanged {
143 turn: u32,
144 #[serde(default, skip_serializing_if = "Vec::is_empty")]
145 added: Vec<String>,
146 #[serde(default, skip_serializing_if = "Vec::is_empty")]
147 removed: Vec<String>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
149 change_kind: Option<String>,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
151 capability_id: Option<String>,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
153 version: Option<String>,
154 #[serde(default, skip_serializing_if = "Option::is_none")]
155 mounted_by: Option<String>,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 mount_reason: Option<String>,
158 },
159 ContextRenewed {
161 turn: u32,
162 sprint: u32,
163 handoff_ref: String,
164 },
165
166 Suspended {
168 turn: u32,
169 reason: String,
170 #[serde(default, skip_serializing_if = "Vec::is_empty")]
171 pending_calls: Vec<String>,
172 },
173 Resumed {
175 turn: u32,
176 #[serde(default, skip_serializing_if = "Vec::is_empty")]
177 approved: Vec<String>,
178 #[serde(default, skip_serializing_if = "Vec::is_empty")]
179 denied: Vec<String>,
180 },
181 ToolGated {
183 turn: u32,
184 call_id: String,
185 tool: String,
186 reason: String,
187 },
188 SignalDeliveryDisposed {
190 turn: u32,
191 operation_id: String,
192 delivery_id: String,
193 attempt: u32,
194 signal_id: String,
195 disposition: String,
196 queue_depth: u32,
197 },
198 BudgetExceeded {
200 turn: u32,
201 operation_id: String,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
203 reservation_id: Option<String>,
204 budget: String,
205 },
206 BudgetUsageReported {
208 turn: u32,
209 operation_id: String,
210 reservation_id: String,
211 tokens: u64,
212 subagents: u32,
213 rounds: u32,
214 },
215 OperationCancelled {
217 turn: u32,
218 operation_id: String,
219 reason: CancellationReason,
220 #[serde(default)]
221 pending_call_ids: Vec<String>,
222 },
223 CheckpointTaken {
225 turn: u32,
226 history_len: u32,
227 },
228 EntropySample {
230 turn: u32,
231 score: f64,
232 rho: f64,
233 repeat_pressure: f64,
234 failure_rate: f64,
235 rollbacks_in_window: u32,
236 window_turns: u32,
237 },
238 EntropyAlert {
240 turn: u32,
241 score: f64,
242 threshold: f64,
243 },
244 Rollbacked {
246 turn: u32,
247 checkpoint_history_len: u32,
248 #[serde(default, skip_serializing_if = "Option::is_none")]
249 reason: Option<RollbackReason>,
250 },
251
252 AgentProcessChanged {
255 turn: u32,
256 agent_id: String,
257 parent_session_id: String,
258 role: String,
259 isolation: String,
260 context_inheritance: String,
261 state: String,
262 #[serde(default, skip_serializing_if = "Vec::is_empty")]
263 permitted_capability_ids: Vec<String>,
264 #[serde(default, skip_serializing_if = "Option::is_none")]
265 result_termination: Option<String>,
266 },
267
268 MilestoneAdvanced {
271 turn: u32,
272 phase_id: String,
273 #[serde(default)]
274 capabilities_unlocked: Vec<String>,
275 },
276 MilestoneBlocked {
278 turn: u32,
279 phase_id: String,
280 reason: String,
281 },
282
283 MemoryWritten {
286 turn: u32,
287 record_id: String,
288 scope: crate::mm::memory::MemoryScope,
289 memory_kind: crate::mm::memory::MemoryKind,
290 name: String,
291 size_bytes: u32,
292 },
293 MemoryQueried {
295 turn: u32,
296 scope: crate::mm::memory::MemoryScope,
297 query: String,
298 requested_k: usize,
299 requires_async_response: bool,
300 },
301 MemoryValidationFailed {
303 turn: u32,
304 record_id: String,
305 error: String,
306 },
307 MemoryRetrievalResult {
309 hits: Vec<crate::mm::memory::MemoryRecall>,
310 },
311}
312
313impl SessionEvent {
314 pub fn kind_str(&self) -> &'static str {
316 match self {
317 Self::RunStarted { .. } => "run_started",
318 Self::LlmCompleted { .. } => "llm_completed",
319 Self::ToolRequested { .. } => "tool_requested",
320 Self::ToolCompleted { .. } => "tool_completed",
321 Self::Compressed { .. } => "compressed",
322 Self::PageOut { .. } => "page_out",
323 Self::PageIn { .. } => "page_in",
324 Self::RunTerminal { .. } => "run_terminal",
325 Self::ToolArgumentRepaired { .. } => "tool_argument_repaired",
326 Self::PermissionRequested { .. } => "permission_requested",
327 Self::PermissionResolved { .. } => "permission_resolved",
328 Self::ToolDenied { .. } => "tool_denied",
329 Self::CapabilityChanged { .. } => "capability_changed",
330 Self::ContextRenewed { .. } => "context_renewed",
331 Self::Suspended { .. } => "suspended",
332 Self::Resumed { .. } => "resumed",
333 Self::ToolGated { .. } => "tool_gated",
334 Self::SignalDeliveryDisposed { .. } => "signal_delivery_disposed",
335 Self::BudgetExceeded { .. } => "budget_exceeded",
336 Self::BudgetUsageReported { .. } => "budget_usage_reported",
337 Self::OperationCancelled { .. } => "operation_cancelled",
338 Self::CheckpointTaken { .. } => "checkpoint_taken",
339 Self::EntropySample { .. } => "entropy_sample",
340 Self::EntropyAlert { .. } => "entropy_alert",
341 Self::Rollbacked { .. } => "rollbacked",
342 Self::AgentProcessChanged { .. } => "agent_process_changed",
343 Self::MilestoneAdvanced { .. } => "milestone_advanced",
344 Self::MilestoneBlocked { .. } => "milestone_blocked",
345 Self::MemoryWritten { .. } => "memory_written",
346 Self::MemoryQueried { .. } => "memory_queried",
347 Self::MemoryValidationFailed { .. } => "memory_validation_failed",
348 Self::MemoryRetrievalResult { .. } => "memory_retrieval_result",
349 }
350 }
351
352 pub fn is_kernel_os_event(&self) -> bool {
354 matches!(
355 self,
356 Self::Compressed { .. }
357 | Self::PageOut { .. }
358 | Self::PageIn { .. }
359 | Self::CapabilityChanged { .. }
360 | Self::ContextRenewed { .. }
361 | Self::Suspended { .. }
362 | Self::Resumed { .. }
363 | Self::ToolGated { .. }
364 | Self::SignalDeliveryDisposed { .. }
365 | Self::BudgetExceeded { .. }
366 | Self::BudgetUsageReported { .. }
367 | Self::OperationCancelled { .. }
368 | Self::CheckpointTaken { .. }
369 | Self::EntropySample { .. }
370 | Self::EntropyAlert { .. }
371 | Self::Rollbacked { .. }
372 | Self::AgentProcessChanged { .. }
373 | Self::MilestoneAdvanced { .. }
374 | Self::MilestoneBlocked { .. }
375 | Self::MemoryWritten { .. }
376 | Self::MemoryQueried { .. }
377 | Self::MemoryValidationFailed { .. }
378 )
379 }
380}