Skip to main content

deepstrike_core/runtime/
session.rs

1use serde::{Deserialize, Serialize};
2
3
4use crate::types::message::{Message, ToolCall, ToolResult};
5
6/// Provider-native replay payload persisted in `llm_completed` for wake/preload recovery.
7///
8/// The core is provider-neutral: it persists and round-trips the replay envelope
9/// verbatim without interpreting protocol-specific shapes. `native_blocks` and
10/// `reasoning_content` are modeled explicitly because the recovery path reads
11/// them; every other envelope field (`schema_version`, `provider`, `protocol`,
12/// `model`, `reasoning_details`, `native_message`, `tool_calls`, …) is preserved
13/// through `extra` so SDK-owned protocol metadata is never dropped.
14#[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/// Append-only session event kinds for the unified Agent OS Runtime.
36///
37/// Combines execution loop events with OS-level lifecycle control,
38/// capability manifest auditing, and governance gates.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(tag = "kind", rename_all = "snake_case")]
41pub enum SessionEvent {
42    // ─── 1. Execution & Inference Loop ───
43    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        action: Option<String>,
70        #[serde(default, skip_serializing_if = "Option::is_none")]
71        summary: Option<String>,
72        #[serde(default, skip_serializing_if = "Option::is_none")]
73        summary_tokens: Option<u32>,
74        #[serde(default, skip_serializing_if = "Option::is_none")]
75        archive_ref: Option<String>,
76        #[serde(default, skip_serializing_if = "Vec::is_empty")]
77        preserved_refs: Vec<String>,
78    },
79    /// Working memory paged out for long-term storage (kernel `page_out`).
80    PageOut {
81        turn: u32,
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        tier_hint: Option<String>,
88        #[serde(default)]
89        message_count: u32,
90    },
91    /// Long-term entries injected into knowledge partition (SDK `page_in`).
92    PageIn {
93        turn: u32,
94        entry_count: u32,
95    },
96    /// Large tool result spooled to disk by the SDK (kernel Layer 1).
97    LargeResultSpooled {
98        turn: u32,
99        call_id: String,
100        tool: String,
101        original_size: u32,
102        preview_size: u32,
103        #[serde(default, skip_serializing_if = "Option::is_none")]
104        spool_ref: Option<String>,
105    },
106    RunTerminal {
107        reason: String,
108        turns_used: u32,
109        total_tokens: u64,
110    },
111
112    // ─── 2. Kernel Governance & Security Gates ───
113    /// Tool arguments automatically repaired under white-listed heuristics.
114    ToolArgumentRepaired {
115        turn: u32,
116        tool: String,
117        original_arguments: String,
118        repaired_arguments: String,
119    },
120    /// Escalated permission gate requested for a tool, suspending current execution.
121    PermissionRequested {
122        turn: u32,
123        tool: String,
124        arguments: String,
125        reason: Option<String>,
126    },
127    /// Permission decision resolved by the user or an automated policy engine.
128    PermissionResolved {
129        turn: u32,
130        approved: bool,
131        responder: String, // "user" | "policy_gate"
132    },
133    /// Tool blocked monotonically by security governance policy or denial of consent.
134    ToolDenied {
135        turn: u32,
136        call_id: String,
137        tool_name: String,
138        reason: String,
139    },
140
141    // ─── 3. Dynamic Capability & Context Restructuring ───
142    /// Model-visible capabilities dynamically updated (e.g., loading skills or mounting MCPs).
143    CapabilityChanged {
144        turn: u32,
145        #[serde(default, skip_serializing_if = "Vec::is_empty")]
146        added: Vec<String>,
147        #[serde(default, skip_serializing_if = "Vec::is_empty")]
148        removed: Vec<String>,
149        #[serde(default, skip_serializing_if = "Option::is_none")]
150        change_kind: Option<String>,
151        #[serde(default, skip_serializing_if = "Option::is_none")]
152        capability_id: Option<String>,
153        #[serde(default, skip_serializing_if = "Option::is_none")]
154        version: Option<String>,
155        #[serde(default, skip_serializing_if = "Option::is_none")]
156        mounted_by: Option<String>,
157        #[serde(default, skip_serializing_if = "Option::is_none")]
158        mount_reason: Option<String>,
159    },
160    /// Context reset and sprint rotated after a context boundary handoff.
161    ContextRenewed {
162        turn: u32,
163        sprint: u32,
164        handoff_ref: String,
165    },
166
167    /// Execution paused (waiting for human-in-the-loop interaction or long-running tasks).
168    Suspended {
169        turn: u32,
170        reason: String,
171        #[serde(default, skip_serializing_if = "Vec::is_empty")]
172        pending_calls: Vec<String>,
173    },
174    /// Execution resumed.
175    Resumed {
176        turn: u32,
177        #[serde(default, skip_serializing_if = "Vec::is_empty")]
178        approved: Vec<String>,
179        #[serde(default, skip_serializing_if = "Vec::is_empty")]
180        denied: Vec<String>,
181    },
182    /// Kernel governance gate: tool requires approval before execution.
183    ToolGated {
184        turn: u32,
185        call_id: String,
186        tool: String,
187        reason: String,
188    },
189    /// In-kernel signal disposition (attention policy).
190    SignalDisposed {
191        turn: u32,
192        signal_id: String,
193        disposition: String,
194        queue_depth: u32,
195    },
196    /// Scheduler budget axis exhausted.
197    BudgetExceeded {
198        turn: u32,
199        budget: String,
200    },
201    /// Checkpoint taken at the start of a turn transaction (before LLM call).
202    CheckpointTaken {
203        turn: u32,
204        history_len: u32,
205    },
206    /// Session-entropy sample at a completed turn boundary (see `scheduler::entropy`).
207    EntropySample {
208        turn: u32,
209        score: f64,
210        score_version: u32,
211        rho: f64,
212        repeat_pressure: f64,
213        failure_rate: f64,
214        rollbacks_in_window: u32,
215        window_turns: u32,
216    },
217    /// The opt-in entropy watch tripped (score crossed the configured threshold).
218    EntropyAlert {
219        turn: u32,
220        score: f64,
221        threshold: f64,
222    },
223    /// Transaction rollback indicating state was restored to a checkpoint.
224    Rollbacked {
225        turn: u32,
226        checkpoint_history_len: u32,
227        #[serde(default, skip_serializing_if = "Option::is_none")]
228        reason: Option<RollbackReason>,
229    },
230
231    // ─── 4. Process Table ───
232    /// Kernel process table changed for a spawned sub-agent.
233    AgentProcessChanged {
234        turn: u32,
235        agent_id: String,
236        parent_session_id: String,
237        role: String,
238        isolation: String,
239        context_inheritance: String,
240        state: String,
241        #[serde(default, skip_serializing_if = "Vec::is_empty")]
242        permitted_capability_ids: Vec<String>,
243        #[serde(default, skip_serializing_if = "Option::is_none")]
244        result_termination: Option<String>,
245    },
246
247    // ─── 5. Milestone Contracts ───
248    /// Milestone phase criteria passed — capabilities unlocked, phase advanced.
249    MilestoneAdvanced {
250        turn: u32,
251        phase_id: String,
252        #[serde(default)]
253        capabilities_unlocked: Vec<String>,
254    },
255    /// Milestone phase criteria not met — run continues without advancing the phase.
256    MilestoneBlocked {
257        turn: u32,
258        phase_id: String,
259        reason: String,
260    },
261
262    // ─── 6. Long-Term Memory (Phase 7) ───
263    /// Memory entry written successfully (SDK → kernel acknowledgment).
264    MemoryWritten {
265        turn: u32,
266        memory_id: String,
267        memory_kind: String,
268        size_bytes: u32,
269    },
270    /// Memory query request (kernel → SDK; SDK should respond asynchronously).
271    MemoryQueried {
272        turn: u32,
273        query_context: String,
274        requested_k: usize,
275        requires_async_response: bool,
276    },
277    /// Memory validation failed (kernel rejected a write request).
278    MemoryValidationFailed {
279        turn: u32,
280        memory_id: String,
281        error: String,
282    },
283    /// Memory retrieval result (SDK → kernel via Resume or other async mechanism).
284    MemoryRetrievalResult {
285        retrieval: crate::mm::memory::MemoryRetrieval,
286    },
287}
288
289impl SessionEvent {
290    /// Event `kind` string (snake_case tag).
291    pub fn kind_str(&self) -> &'static str {
292        match self {
293            Self::RunStarted { .. } => "run_started",
294            Self::LlmCompleted { .. } => "llm_completed",
295            Self::ToolRequested { .. } => "tool_requested",
296            Self::ToolCompleted { .. } => "tool_completed",
297            Self::Compressed { .. } => "compressed",
298            Self::PageOut { .. } => "page_out",
299            Self::PageIn { .. } => "page_in",
300            Self::LargeResultSpooled { .. } => "large_result_spooled",
301            Self::RunTerminal { .. } => "run_terminal",
302            Self::ToolArgumentRepaired { .. } => "tool_argument_repaired",
303            Self::PermissionRequested { .. } => "permission_requested",
304            Self::PermissionResolved { .. } => "permission_resolved",
305            Self::ToolDenied { .. } => "tool_denied",
306            Self::CapabilityChanged { .. } => "capability_changed",
307            Self::ContextRenewed { .. } => "context_renewed",
308            Self::Suspended { .. } => "suspended",
309            Self::Resumed { .. } => "resumed",
310            Self::ToolGated { .. } => "tool_gated",
311            Self::SignalDisposed { .. } => "signal_disposed",
312            Self::BudgetExceeded { .. } => "budget_exceeded",
313            Self::CheckpointTaken { .. } => "checkpoint_taken",
314            Self::EntropySample { .. } => "entropy_sample",
315            Self::EntropyAlert { .. } => "entropy_alert",
316            Self::Rollbacked { .. } => "rollbacked",
317            Self::AgentProcessChanged { .. } => "agent_process_changed",
318            Self::MilestoneAdvanced { .. } => "milestone_advanced",
319            Self::MilestoneBlocked { .. } => "milestone_blocked",
320            Self::MemoryWritten { .. } => "memory_written",
321            Self::MemoryQueried { .. } => "memory_queried",
322            Self::MemoryValidationFailed { .. } => "memory_validation_failed",
323            Self::MemoryRetrievalResult { .. } => "memory_retrieval_result",
324        }
325    }
326
327    /// Whether this event is a kernel OS decision (replay ignores for message reconstruction).
328    pub fn is_kernel_os_event(&self) -> bool {
329        matches!(
330            self,
331            Self::Compressed { .. }
332                | Self::PageOut { .. }
333                | Self::PageIn { .. }
334                | Self::LargeResultSpooled { .. }
335                | Self::CapabilityChanged { .. }
336                | Self::ContextRenewed { .. }
337                | Self::Suspended { .. }
338                | Self::Resumed { .. }
339                | Self::ToolGated { .. }
340                | Self::SignalDisposed { .. }
341                | Self::BudgetExceeded { .. }
342                | Self::CheckpointTaken { .. }
343                | Self::EntropySample { .. }
344                | Self::EntropyAlert { .. }
345                | Self::Rollbacked { .. }
346                | Self::AgentProcessChanged { .. }
347                | Self::MilestoneAdvanced { .. }
348                | Self::MilestoneBlocked { .. }
349                | Self::MemoryWritten { .. }
350                | Self::MemoryQueried { .. }
351                | Self::MemoryValidationFailed { .. }
352        )
353    }
354}