use serde::{Deserialize, Serialize};
use crate::runtime::event_log::{category_for_kind, KernelEventCategory, Primitive};
use crate::types::message::{Message, ToolCall, ToolResult};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ProviderReplay {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub native_blocks: Option<Vec<serde_json::Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,
#[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum RollbackReason {
FatalToolError { tool_name: String, error: String },
GovernanceDenied { tool_name: String, reason: String },
ProviderFailure { error: String },
Timeout,
UserInterrupt,
MalformedReplay { reason: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SessionEvent {
RunStarted {
run_id: String,
goal: String,
#[serde(default)]
criteria: Vec<String>,
agent_id: Option<String>,
system_prompt: Option<String>,
},
LlmCompleted {
turn: u32,
message: Message,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_replay: Option<ProviderReplay>,
},
ToolRequested {
turn: u32,
calls: Vec<ToolCall>,
},
ToolCompleted {
turn: u32,
results: Vec<ToolResult>,
},
Compressed {
turn: u32,
archived_seq_range: (u64, u64),
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
#[serde(default, skip_serializing_if = "Option::is_none")]
action: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
summary_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
archive_ref: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
preserved_refs: Vec<String>,
},
PageOut {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
#[serde(default, skip_serializing_if = "Option::is_none")]
action: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
tier_hint: Option<String>,
#[serde(default)]
message_count: u32,
},
PageIn {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
entry_count: u32,
},
LargeResultSpooled {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
call_id: String,
tool: String,
original_size: u32,
preview_size: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
spool_ref: Option<String>,
},
RunTerminal {
reason: String,
turns_used: u32,
total_tokens: u64,
},
ToolArgumentRepaired {
turn: u32,
tool: String,
original_arguments: String,
repaired_arguments: String,
},
PermissionRequested {
turn: u32,
tool: String,
arguments: String,
reason: Option<String>,
},
PermissionResolved {
turn: u32,
approved: bool,
responder: String, },
ToolDenied {
turn: u32,
call_id: String,
tool_name: String,
reason: String,
},
CapabilityChanged {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
added: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
removed: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
change_kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
capability_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mounted_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mount_reason: Option<String>,
},
ContextRenewed {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
sprint: u32,
handoff_ref: String,
},
Suspended {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
reason: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pending_calls: Vec<String>,
},
Resumed {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
approved: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
denied: Vec<String>,
},
ToolGated {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
call_id: String,
tool: String,
reason: String,
},
SignalDisposed {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
signal_id: String,
disposition: String,
queue_depth: u32,
},
BudgetExceeded {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
budget: String,
},
CheckpointTaken {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
history_len: u32,
},
Rollbacked {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
checkpoint_history_len: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
reason: Option<RollbackReason>,
},
CleanupCompleted {
run_id: String,
freed_resources: Vec<String>,
},
AgentProcessChanged {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
agent_id: String,
parent_session_id: String,
role: String,
isolation: String,
context_inheritance: String,
state: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
permitted_capability_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
result_termination: Option<String>,
},
MilestoneAdvanced {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
phase_id: String,
#[serde(default)]
capabilities_unlocked: Vec<String>,
},
MilestoneBlocked {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
phase_id: String,
reason: String,
},
MilestoneEvidence {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
phase_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
evidence: Vec<String>,
},
MemoryWritten {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
memory_id: String,
memory_kind: String,
size_bytes: u32,
},
MemoryQueried {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
query_context: String,
requested_k: usize,
requires_async_response: bool,
},
MemoryValidationFailed {
turn: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
category: Option<KernelEventCategory>,
#[serde(default, skip_serializing_if = "Option::is_none")]
primitive: Option<Primitive>,
memory_id: String,
error: String,
},
MemoryRetrievalResult {
retrieval: crate::mm::memory::MemoryRetrieval,
},
}
impl SessionEvent {
pub fn kind_str(&self) -> &'static str {
match self {
Self::RunStarted { .. } => "run_started",
Self::LlmCompleted { .. } => "llm_completed",
Self::ToolRequested { .. } => "tool_requested",
Self::ToolCompleted { .. } => "tool_completed",
Self::Compressed { .. } => "compressed",
Self::PageOut { .. } => "page_out",
Self::PageIn { .. } => "page_in",
Self::LargeResultSpooled { .. } => "large_result_spooled",
Self::RunTerminal { .. } => "run_terminal",
Self::ToolArgumentRepaired { .. } => "tool_argument_repaired",
Self::PermissionRequested { .. } => "permission_requested",
Self::PermissionResolved { .. } => "permission_resolved",
Self::ToolDenied { .. } => "tool_denied",
Self::CapabilityChanged { .. } => "capability_changed",
Self::ContextRenewed { .. } => "context_renewed",
Self::Suspended { .. } => "suspended",
Self::Resumed { .. } => "resumed",
Self::ToolGated { .. } => "tool_gated",
Self::SignalDisposed { .. } => "signal_disposed",
Self::BudgetExceeded { .. } => "budget_exceeded",
Self::CheckpointTaken { .. } => "checkpoint_taken",
Self::Rollbacked { .. } => "rollbacked",
Self::CleanupCompleted { .. } => "cleanup_completed",
Self::AgentProcessChanged { .. } => "agent_process_changed",
Self::MilestoneAdvanced { .. } => "milestone_advanced",
Self::MilestoneBlocked { .. } => "milestone_blocked",
Self::MilestoneEvidence { .. } => "milestone_evidence",
Self::MemoryWritten { .. } => "memory_written",
Self::MemoryQueried { .. } => "memory_queried",
Self::MemoryValidationFailed { .. } => "memory_validation_failed",
Self::MemoryRetrievalResult { .. } => "memory_retrieval_result",
}
}
pub fn event_category(&self) -> KernelEventCategory {
let embedded = match self {
Self::Compressed { category, .. }
| Self::PageOut { category, .. }
| Self::PageIn { category, .. }
| Self::LargeResultSpooled { category, .. }
| Self::CapabilityChanged { category, .. }
| Self::ContextRenewed { category, .. }
| Self::Suspended { category, .. }
| Self::Resumed { category, .. }
| Self::ToolGated { category, .. }
| Self::SignalDisposed { category, .. }
| Self::BudgetExceeded { category, .. }
| Self::CheckpointTaken { category, .. }
| Self::Rollbacked { category, .. }
| Self::AgentProcessChanged { category, .. }
| Self::MilestoneAdvanced { category, .. }
| Self::MilestoneBlocked { category, .. }
| Self::MilestoneEvidence { category, .. }
| Self::MemoryWritten { category, .. }
| Self::MemoryQueried { category, .. }
| Self::MemoryValidationFailed { category, .. } => *category,
_ => None,
};
embedded.unwrap_or_else(|| category_for_kind(self.kind_str()))
}
pub fn is_kernel_os_event(&self) -> bool {
matches!(
self,
Self::Compressed { .. }
| Self::PageOut { .. }
| Self::PageIn { .. }
| Self::LargeResultSpooled { .. }
| Self::CapabilityChanged { .. }
| Self::ContextRenewed { .. }
| Self::Suspended { .. }
| Self::Resumed { .. }
| Self::ToolGated { .. }
| Self::SignalDisposed { .. }
| Self::BudgetExceeded { .. }
| Self::CheckpointTaken { .. }
| Self::Rollbacked { .. }
| Self::AgentProcessChanged { .. }
| Self::MilestoneAdvanced { .. }
| Self::MilestoneBlocked { .. }
| Self::MilestoneEvidence { .. }
| Self::MemoryWritten { .. }
| Self::MemoryQueried { .. }
| Self::MemoryValidationFailed { .. }
)
}
}