1use serde::{Deserialize, Serialize};
2
3use crate::runtime::event_log::{category_for_kind, KernelEventCategory, Primitive};
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 },
51 LlmCompleted {
52 turn: u32,
53 message: Message,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 provider_replay: Option<ProviderReplay>,
56 },
57 ToolRequested {
58 turn: u32,
59 calls: Vec<ToolCall>,
60 },
61 ToolCompleted {
62 turn: u32,
63 results: Vec<ToolResult>,
64 },
65 Compressed {
66 turn: u32,
67 archived_seq_range: (u64, u64),
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 category: Option<KernelEventCategory>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 primitive: Option<Primitive>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 action: Option<String>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 summary: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 summary_tokens: Option<u32>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 archive_ref: Option<String>,
80 #[serde(default, skip_serializing_if = "Vec::is_empty")]
81 preserved_refs: Vec<String>,
82 },
83 PageOut {
85 turn: u32,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 category: Option<KernelEventCategory>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 primitive: Option<Primitive>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 action: Option<String>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
93 summary: Option<String>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 tier_hint: Option<String>,
96 #[serde(default)]
97 message_count: u32,
98 },
99 PageIn {
101 turn: u32,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 category: Option<KernelEventCategory>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 primitive: Option<Primitive>,
106 entry_count: u32,
107 },
108 LargeResultSpooled {
110 turn: u32,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 category: Option<KernelEventCategory>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 primitive: Option<Primitive>,
115 call_id: String,
116 tool: String,
117 original_size: u32,
118 preview_size: u32,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
120 spool_ref: Option<String>,
121 },
122 RunTerminal {
123 reason: String,
124 turns_used: u32,
125 total_tokens: u64,
126 },
127
128 ToolArgumentRepaired {
131 turn: u32,
132 tool: String,
133 original_arguments: String,
134 repaired_arguments: String,
135 },
136 PermissionRequested {
138 turn: u32,
139 tool: String,
140 arguments: String,
141 reason: Option<String>,
142 },
143 PermissionResolved {
145 turn: u32,
146 approved: bool,
147 responder: String, },
149 ToolDenied {
151 turn: u32,
152 call_id: String,
153 tool_name: String,
154 reason: String,
155 },
156
157 CapabilityChanged {
160 turn: u32,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 category: Option<KernelEventCategory>,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 primitive: Option<Primitive>,
165 #[serde(default, skip_serializing_if = "Vec::is_empty")]
166 added: Vec<String>,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
168 removed: Vec<String>,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
170 change_kind: Option<String>,
171 #[serde(default, skip_serializing_if = "Option::is_none")]
172 capability_id: Option<String>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
174 version: Option<String>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
176 mounted_by: Option<String>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 mount_reason: Option<String>,
179 },
180 ContextRenewed {
182 turn: u32,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 category: Option<KernelEventCategory>,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 primitive: Option<Primitive>,
187 sprint: u32,
188 handoff_ref: String,
189 },
190
191 Suspended {
193 turn: u32,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 category: Option<KernelEventCategory>,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 primitive: Option<Primitive>,
198 reason: String,
199 #[serde(default, skip_serializing_if = "Vec::is_empty")]
200 pending_calls: Vec<String>,
201 },
202 Resumed {
204 turn: u32,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 category: Option<KernelEventCategory>,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 primitive: Option<Primitive>,
209 #[serde(default, skip_serializing_if = "Vec::is_empty")]
210 approved: Vec<String>,
211 #[serde(default, skip_serializing_if = "Vec::is_empty")]
212 denied: Vec<String>,
213 },
214 ToolGated {
216 turn: u32,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 category: Option<KernelEventCategory>,
219 #[serde(default, skip_serializing_if = "Option::is_none")]
220 primitive: Option<Primitive>,
221 call_id: String,
222 tool: String,
223 reason: String,
224 },
225 SignalDisposed {
227 turn: u32,
228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 category: Option<KernelEventCategory>,
230 #[serde(default, skip_serializing_if = "Option::is_none")]
231 primitive: Option<Primitive>,
232 signal_id: String,
233 disposition: String,
234 queue_depth: u32,
235 },
236 BudgetExceeded {
238 turn: u32,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
240 category: Option<KernelEventCategory>,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
242 primitive: Option<Primitive>,
243 budget: String,
244 },
245 CheckpointTaken {
247 turn: u32,
248 #[serde(default, skip_serializing_if = "Option::is_none")]
249 category: Option<KernelEventCategory>,
250 #[serde(default, skip_serializing_if = "Option::is_none")]
251 primitive: Option<Primitive>,
252 history_len: u32,
253 },
254 Rollbacked {
256 turn: u32,
257 #[serde(default, skip_serializing_if = "Option::is_none")]
258 category: Option<KernelEventCategory>,
259 #[serde(default, skip_serializing_if = "Option::is_none")]
260 primitive: Option<Primitive>,
261 checkpoint_history_len: u32,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
263 reason: Option<RollbackReason>,
264 },
265 CleanupCompleted {
267 run_id: String,
268 freed_resources: Vec<String>,
269 },
270
271 AgentProcessChanged {
274 turn: u32,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 category: Option<KernelEventCategory>,
277 #[serde(default, skip_serializing_if = "Option::is_none")]
278 primitive: Option<Primitive>,
279 agent_id: String,
280 parent_session_id: String,
281 role: String,
282 isolation: String,
283 context_inheritance: String,
284 state: String,
285 #[serde(default, skip_serializing_if = "Vec::is_empty")]
286 permitted_capability_ids: Vec<String>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
288 result_termination: Option<String>,
289 },
290
291 MilestoneAdvanced {
294 turn: u32,
295 #[serde(default, skip_serializing_if = "Option::is_none")]
296 category: Option<KernelEventCategory>,
297 #[serde(default, skip_serializing_if = "Option::is_none")]
298 primitive: Option<Primitive>,
299 phase_id: String,
300 #[serde(default)]
301 capabilities_unlocked: Vec<String>,
302 },
303 MilestoneBlocked {
305 turn: u32,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
307 category: Option<KernelEventCategory>,
308 #[serde(default, skip_serializing_if = "Option::is_none")]
309 primitive: Option<Primitive>,
310 phase_id: String,
311 reason: String,
312 },
313 MilestoneEvidence {
315 turn: u32,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
317 category: Option<KernelEventCategory>,
318 #[serde(default, skip_serializing_if = "Option::is_none")]
319 primitive: Option<Primitive>,
320 phase_id: String,
321 #[serde(default, skip_serializing_if = "Vec::is_empty")]
322 evidence: Vec<String>,
323 },
324
325 MemoryWritten {
328 turn: u32,
329 #[serde(default, skip_serializing_if = "Option::is_none")]
330 category: Option<KernelEventCategory>,
331 #[serde(default, skip_serializing_if = "Option::is_none")]
332 primitive: Option<Primitive>,
333 memory_id: String,
334 memory_kind: String,
335 size_bytes: u32,
336 },
337 MemoryQueried {
339 turn: u32,
340 #[serde(default, skip_serializing_if = "Option::is_none")]
341 category: Option<KernelEventCategory>,
342 #[serde(default, skip_serializing_if = "Option::is_none")]
343 primitive: Option<Primitive>,
344 query_context: String,
345 requested_k: usize,
346 requires_async_response: bool,
347 },
348 MemoryValidationFailed {
350 turn: u32,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
352 category: Option<KernelEventCategory>,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 primitive: Option<Primitive>,
355 memory_id: String,
356 error: String,
357 },
358 MemoryRetrievalResult {
360 retrieval: crate::mm::memory::MemoryRetrieval,
361 },
362}
363
364impl SessionEvent {
365 pub fn kind_str(&self) -> &'static str {
367 match self {
368 Self::RunStarted { .. } => "run_started",
369 Self::LlmCompleted { .. } => "llm_completed",
370 Self::ToolRequested { .. } => "tool_requested",
371 Self::ToolCompleted { .. } => "tool_completed",
372 Self::Compressed { .. } => "compressed",
373 Self::PageOut { .. } => "page_out",
374 Self::PageIn { .. } => "page_in",
375 Self::LargeResultSpooled { .. } => "large_result_spooled",
376 Self::RunTerminal { .. } => "run_terminal",
377 Self::ToolArgumentRepaired { .. } => "tool_argument_repaired",
378 Self::PermissionRequested { .. } => "permission_requested",
379 Self::PermissionResolved { .. } => "permission_resolved",
380 Self::ToolDenied { .. } => "tool_denied",
381 Self::CapabilityChanged { .. } => "capability_changed",
382 Self::ContextRenewed { .. } => "context_renewed",
383 Self::Suspended { .. } => "suspended",
384 Self::Resumed { .. } => "resumed",
385 Self::ToolGated { .. } => "tool_gated",
386 Self::SignalDisposed { .. } => "signal_disposed",
387 Self::BudgetExceeded { .. } => "budget_exceeded",
388 Self::CheckpointTaken { .. } => "checkpoint_taken",
389 Self::Rollbacked { .. } => "rollbacked",
390 Self::CleanupCompleted { .. } => "cleanup_completed",
391 Self::AgentProcessChanged { .. } => "agent_process_changed",
392 Self::MilestoneAdvanced { .. } => "milestone_advanced",
393 Self::MilestoneBlocked { .. } => "milestone_blocked",
394 Self::MilestoneEvidence { .. } => "milestone_evidence",
395 Self::MemoryWritten { .. } => "memory_written",
396 Self::MemoryQueried { .. } => "memory_queried",
397 Self::MemoryValidationFailed { .. } => "memory_validation_failed",
398 Self::MemoryRetrievalResult { .. } => "memory_retrieval_result",
399 }
400 }
401
402 pub fn event_category(&self) -> KernelEventCategory {
404 let embedded = match self {
405 Self::Compressed { category, .. }
406 | Self::PageOut { category, .. }
407 | Self::PageIn { category, .. }
408 | Self::LargeResultSpooled { category, .. }
409 | Self::CapabilityChanged { category, .. }
410 | Self::ContextRenewed { category, .. }
411 | Self::Suspended { category, .. }
412 | Self::Resumed { category, .. }
413 | Self::ToolGated { category, .. }
414 | Self::SignalDisposed { category, .. }
415 | Self::BudgetExceeded { category, .. }
416 | Self::CheckpointTaken { category, .. }
417 | Self::Rollbacked { category, .. }
418 | Self::AgentProcessChanged { category, .. }
419 | Self::MilestoneAdvanced { category, .. }
420 | Self::MilestoneBlocked { category, .. }
421 | Self::MilestoneEvidence { category, .. }
422 | Self::MemoryWritten { category, .. }
423 | Self::MemoryQueried { category, .. }
424 | Self::MemoryValidationFailed { category, .. } => *category,
425 _ => None,
426 };
427 embedded.unwrap_or_else(|| category_for_kind(self.kind_str()))
428 }
429
430 pub fn is_kernel_os_event(&self) -> bool {
432 matches!(
433 self,
434 Self::Compressed { .. }
435 | Self::PageOut { .. }
436 | Self::PageIn { .. }
437 | Self::LargeResultSpooled { .. }
438 | Self::CapabilityChanged { .. }
439 | Self::ContextRenewed { .. }
440 | Self::Suspended { .. }
441 | Self::Resumed { .. }
442 | Self::ToolGated { .. }
443 | Self::SignalDisposed { .. }
444 | Self::BudgetExceeded { .. }
445 | Self::CheckpointTaken { .. }
446 | Self::Rollbacked { .. }
447 | Self::AgentProcessChanged { .. }
448 | Self::MilestoneAdvanced { .. }
449 | Self::MilestoneBlocked { .. }
450 | Self::MilestoneEvidence { .. }
451 | Self::MemoryWritten { .. }
452 | Self::MemoryQueried { .. }
453 | Self::MemoryValidationFailed { .. }
454 )
455 }
456}