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