1use serde::{Deserialize, Serialize};
2
3use crate::runtime::kernel::wire::CancellationReason;
4use crate::types::message::{CoreMessage, 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 ContextPrepared {
49 turn: u32,
50 effect_id: String,
51 preparation: Box<crate::context::execution::ContextDispatchPreparation>,
52 },
53 RunStarted {
55 run_id: String,
56 goal: String,
57 #[serde(default)]
58 criteria: Vec<String>,
59 agent_id: Option<String>,
60 system_prompt: Option<String>,
61 #[serde(default, skip_serializing_if = "Vec::is_empty")]
63 attachments: Vec<crate::types::message::ContentPart>,
64 },
65 LlmCompleted {
66 turn: u32,
67 message: CoreMessage,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 provider_replay: Option<ProviderReplay>,
70 },
71 ToolRequested {
72 turn: u32,
73 calls: Vec<ToolCall>,
74 },
75 ToolCompleted {
76 turn: u32,
77 results: Vec<ToolResult>,
78 },
79 Compressed {
80 turn: u32,
81 archived_seq_range: (u64, u64),
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 action: Option<String>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 summary: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 summary_tokens: Option<u32>,
88 #[serde(default, skip_serializing_if = "Vec::is_empty")]
89 preserved_refs: Vec<String>,
90 },
91 PageOut {
93 turn: u32,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 action: Option<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 summary: Option<String>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
99 tier_hint: Option<String>,
100 #[serde(default)]
101 message_count: u32,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
103 archive_ref: Option<String>,
104 },
105 PageIn {
107 turn: u32,
108 entry_count: u32,
109 },
110 RunTerminal {
111 reason: String,
112 turns_used: u32,
113 total_tokens: u64,
114 },
115
116 ToolArgumentRepaired {
119 turn: u32,
120 tool: String,
121 original_arguments: String,
122 repaired_arguments: String,
123 },
124 PermissionRequested {
126 turn: u32,
127 tool: String,
128 arguments: String,
129 reason: Option<String>,
130 },
131 PermissionResolved {
133 turn: u32,
134 approved: bool,
135 responder: String, },
137 ToolDenied {
139 turn: u32,
140 call_id: String,
141 tool_name: String,
142 reason: String,
143 },
144
145 CapabilityChanged {
148 turn: u32,
149 #[serde(default, skip_serializing_if = "Vec::is_empty")]
150 added: Vec<String>,
151 #[serde(default, skip_serializing_if = "Vec::is_empty")]
152 removed: Vec<String>,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
154 change_kind: Option<String>,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
156 capability_id: Option<String>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 version: Option<String>,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
160 mounted_by: Option<String>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 mount_reason: Option<String>,
163 },
164 ContextRenewed {
166 turn: u32,
167 sprint: u32,
168 handoff_ref: String,
169 },
170
171 Suspended {
173 turn: u32,
174 reason: String,
175 #[serde(default, skip_serializing_if = "Vec::is_empty")]
176 pending_calls: Vec<String>,
177 },
178 Resumed {
180 turn: u32,
181 #[serde(default, skip_serializing_if = "Vec::is_empty")]
182 approved: Vec<String>,
183 #[serde(default, skip_serializing_if = "Vec::is_empty")]
184 denied: Vec<String>,
185 },
186 ToolGated {
188 turn: u32,
189 call_id: String,
190 tool: String,
191 reason: String,
192 },
193 SignalDeliveryDisposed {
195 turn: u32,
196 operation_id: String,
197 delivery_id: String,
198 attempt: u32,
199 signal_id: String,
200 disposition: String,
201 queue_depth: u32,
202 },
203 BudgetExceeded {
205 turn: u32,
206 operation_id: String,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 reservation_id: Option<String>,
209 budget: String,
210 },
211 BudgetUsageReported {
213 turn: u32,
214 operation_id: String,
215 reservation_id: String,
216 tokens: u64,
217 subagents: u32,
218 rounds: u32,
219 },
220 OperationCancelled {
222 turn: u32,
223 operation_id: String,
224 reason: CancellationReason,
225 #[serde(default)]
226 pending_call_ids: Vec<String>,
227 },
228 CheckpointTaken {
230 turn: u32,
231 history_len: u32,
232 },
233 EntropySample {
235 turn: u32,
236 score: f64,
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::ContextPrepared { .. } => "context_prepared",
323 Self::RunStarted { .. } => "run_started",
324 Self::LlmCompleted { .. } => "llm_completed",
325 Self::ToolRequested { .. } => "tool_requested",
326 Self::ToolCompleted { .. } => "tool_completed",
327 Self::Compressed { .. } => "compressed",
328 Self::PageOut { .. } => "page_out",
329 Self::PageIn { .. } => "page_in",
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::CapabilityChanged { .. }
366 | Self::ContextRenewed { .. }
367 | Self::Suspended { .. }
368 | Self::Resumed { .. }
369 | Self::ToolGated { .. }
370 | Self::SignalDeliveryDisposed { .. }
371 | Self::BudgetExceeded { .. }
372 | Self::BudgetUsageReported { .. }
373 | Self::OperationCancelled { .. }
374 | Self::CheckpointTaken { .. }
375 | Self::EntropySample { .. }
376 | Self::EntropyAlert { .. }
377 | Self::Rollbacked { .. }
378 | Self::AgentProcessChanged { .. }
379 | Self::MilestoneAdvanced { .. }
380 | Self::MilestoneBlocked { .. }
381 | Self::MemoryWritten { .. }
382 | Self::MemoryQueried { .. }
383 | Self::MemoryValidationFailed { .. }
384 )
385 }
386}