Skip to main content

deepstrike_core/runtime/
session.rs

1use serde::{Deserialize, Serialize};
2
3use crate::runtime::event_log::{category_for_kind, KernelEventCategory, Primitive};
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        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    /// Working memory paged out for long-term storage (kernel `page_out`).
84    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    /// Long-term entries injected into knowledge partition (SDK `page_in`).
100    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    /// Large tool result spooled to disk by the SDK (kernel Layer 1).
109    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    // ─── 2. Kernel Governance & Security Gates ───
129    /// Tool arguments automatically repaired under white-listed heuristics.
130    ToolArgumentRepaired {
131        turn: u32,
132        tool: String,
133        original_arguments: String,
134        repaired_arguments: String,
135    },
136    /// Escalated permission gate requested for a tool, suspending current execution.
137    PermissionRequested {
138        turn: u32,
139        tool: String,
140        arguments: String,
141        reason: Option<String>,
142    },
143    /// Permission decision resolved by the user or an automated policy engine.
144    PermissionResolved {
145        turn: u32,
146        approved: bool,
147        responder: String, // "user" | "policy_gate"
148    },
149    /// Tool blocked monotonically by security governance policy or denial of consent.
150    ToolDenied {
151        turn: u32,
152        call_id: String,
153        tool_name: String,
154        reason: String,
155    },
156
157    // ─── 3. Dynamic Capability & Context Restructuring ───
158    /// Model-visible capabilities dynamically updated (e.g., loading skills or mounting MCPs).
159    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    /// Context reset and sprint rotated after a context boundary handoff.
181    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    /// Execution paused (waiting for human-in-the-loop interaction or long-running tasks).
192    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    /// Execution resumed.
203    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    /// Kernel governance gate: tool requires approval before execution.
215    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    /// In-kernel signal disposition (attention policy).
226    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    /// Scheduler budget axis exhausted.
237    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    /// Checkpoint taken at the start of a turn transaction (before LLM call).
246    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    /// Transaction rollback indicating state was restored to a checkpoint.
255    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    /// Host-level resources (temporary workspace trees, MCP child processes) garbage-collected.
266    CleanupCompleted {
267        run_id: String,
268        freed_resources: Vec<String>,
269    },
270
271    // ─── 4. Process Table ───
272    /// Kernel process table changed for a spawned sub-agent.
273    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    // ─── 5. Milestone Contracts ───
292    /// Milestone phase criteria passed — capabilities unlocked, phase advanced.
293    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    /// Milestone phase criteria not met — run continues without advancing the phase.
304    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    /// Evidence collected by the verifier during milestone evaluation.
314    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    // ─── 6. Long-Term Memory (Phase 7) ───
326    /// Memory entry written successfully (SDK → kernel acknowledgment).
327    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    /// Memory query request (kernel → SDK; SDK should respond asynchronously).
338    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    /// Memory validation failed (kernel rejected a write request).
349    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    /// Memory retrieval result (SDK → kernel via Resume or other async mechanism).
359    MemoryRetrievalResult {
360        retrieval: crate::mm::memory::MemoryRetrieval,
361    },
362}
363
364impl SessionEvent {
365    /// Event `kind` string (snake_case tag).
366    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    /// OS event category; uses embedded `category` when present, else infers from `kind`.
403    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    /// Whether this event is a kernel OS decision (replay ignores for message reconstruction).
431    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}