Skip to main content

deepstrike_core/runtime/
session.rs

1use serde::{Deserialize, Serialize};
2
3use crate::runtime::kernel::CancellationReason;
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        /// Multimodal parts seeded into history before the first render (Node/Python/WASM/Rust).
51        #[serde(default, skip_serializing_if = "Vec::is_empty")]
52        attachments: Vec<crate::types::message::ContentPart>,
53    },
54    LlmCompleted {
55        turn: u32,
56        message: Message,
57        #[serde(default, skip_serializing_if = "Option::is_none")]
58        provider_replay: Option<ProviderReplay>,
59    },
60    ToolRequested {
61        turn: u32,
62        calls: Vec<ToolCall>,
63    },
64    ToolCompleted {
65        turn: u32,
66        results: Vec<ToolResult>,
67    },
68    Compressed {
69        turn: u32,
70        archived_seq_range: (u64, u64),
71        #[serde(default, skip_serializing_if = "Option::is_none")]
72        action: Option<String>,
73        #[serde(default, skip_serializing_if = "Option::is_none")]
74        summary: Option<String>,
75        #[serde(default, skip_serializing_if = "Option::is_none")]
76        summary_tokens: Option<u32>,
77        #[serde(default, skip_serializing_if = "Vec::is_empty")]
78        preserved_refs: Vec<String>,
79    },
80    /// Working memory paged out for long-term storage (kernel `page_out`).
81    PageOut {
82        turn: u32,
83        #[serde(default, skip_serializing_if = "Option::is_none")]
84        action: Option<String>,
85        #[serde(default, skip_serializing_if = "Option::is_none")]
86        summary: Option<String>,
87        #[serde(default, skip_serializing_if = "Option::is_none")]
88        tier_hint: Option<String>,
89        #[serde(default)]
90        message_count: u32,
91        #[serde(default, skip_serializing_if = "Option::is_none")]
92        archive_ref: Option<String>,
93    },
94    /// Long-term entries injected into knowledge partition (SDK `page_in`).
95    PageIn {
96        turn: u32,
97        entry_count: u32,
98    },
99    /// Large tool result spooled to disk by the SDK (kernel Layer 1).
100    LargeResultSpooled {
101        turn: u32,
102        call_id: String,
103        tool: String,
104        original_size: u32,
105        preview_size: u32,
106        #[serde(default, skip_serializing_if = "Option::is_none")]
107        spool_ref: Option<String>,
108    },
109    RunTerminal {
110        reason: String,
111        turns_used: u32,
112        total_tokens: u64,
113    },
114
115    // ─── 2. Kernel Governance & Security Gates ───
116    /// Tool arguments automatically repaired under white-listed heuristics.
117    ToolArgumentRepaired {
118        turn: u32,
119        tool: String,
120        original_arguments: String,
121        repaired_arguments: String,
122    },
123    /// Escalated permission gate requested for a tool, suspending current execution.
124    PermissionRequested {
125        turn: u32,
126        tool: String,
127        arguments: String,
128        reason: Option<String>,
129    },
130    /// Permission decision resolved by the user or an automated policy engine.
131    PermissionResolved {
132        turn: u32,
133        approved: bool,
134        responder: String, // "user" | "policy_gate"
135    },
136    /// Tool blocked monotonically by security governance policy or denial of consent.
137    ToolDenied {
138        turn: u32,
139        call_id: String,
140        tool_name: String,
141        reason: String,
142    },
143
144    // ─── 3. Dynamic Capability & Context Restructuring ───
145    /// Model-visible capabilities dynamically updated (e.g., loading skills or mounting MCPs).
146    CapabilityChanged {
147        turn: u32,
148        #[serde(default, skip_serializing_if = "Vec::is_empty")]
149        added: Vec<String>,
150        #[serde(default, skip_serializing_if = "Vec::is_empty")]
151        removed: Vec<String>,
152        #[serde(default, skip_serializing_if = "Option::is_none")]
153        change_kind: Option<String>,
154        #[serde(default, skip_serializing_if = "Option::is_none")]
155        capability_id: Option<String>,
156        #[serde(default, skip_serializing_if = "Option::is_none")]
157        version: Option<String>,
158        #[serde(default, skip_serializing_if = "Option::is_none")]
159        mounted_by: Option<String>,
160        #[serde(default, skip_serializing_if = "Option::is_none")]
161        mount_reason: Option<String>,
162    },
163    /// Context reset and sprint rotated after a context boundary handoff.
164    ContextRenewed {
165        turn: u32,
166        sprint: u32,
167        handoff_ref: String,
168    },
169
170    /// Execution paused (waiting for human-in-the-loop interaction or long-running tasks).
171    Suspended {
172        turn: u32,
173        reason: String,
174        #[serde(default, skip_serializing_if = "Vec::is_empty")]
175        pending_calls: Vec<String>,
176    },
177    /// Execution resumed.
178    Resumed {
179        turn: u32,
180        #[serde(default, skip_serializing_if = "Vec::is_empty")]
181        approved: Vec<String>,
182        #[serde(default, skip_serializing_if = "Vec::is_empty")]
183        denied: Vec<String>,
184    },
185    /// Kernel governance gate: tool requires approval before execution.
186    ToolGated {
187        turn: u32,
188        call_id: String,
189        tool: String,
190        reason: String,
191    },
192    /// In-kernel signal disposition (attention policy).
193    SignalDeliveryDisposed {
194        turn: u32,
195        operation_id: String,
196        delivery_id: String,
197        attempt: u32,
198        signal_id: String,
199        disposition: String,
200        queue_depth: u32,
201    },
202    /// Scheduler budget axis exhausted.
203    BudgetExceeded {
204        turn: u32,
205        operation_id: String,
206        #[serde(default, skip_serializing_if = "Option::is_none")]
207        reservation_id: Option<String>,
208        budget: String,
209    },
210    /// Terminal local usage for a reservation-backed RunGroup budget grant.
211    BudgetUsageReported {
212        turn: u32,
213        operation_id: String,
214        reservation_id: String,
215        tokens: u64,
216        subagents: u32,
217        rounds: u32,
218    },
219    /// Host-owned external I/O was stopped and the correlated operation cancelled.
220    OperationCancelled {
221        turn: u32,
222        operation_id: String,
223        reason: CancellationReason,
224        #[serde(default)]
225        pending_call_ids: Vec<String>,
226    },
227    /// Checkpoint taken at the start of a turn transaction (before LLM call).
228    CheckpointTaken {
229        turn: u32,
230        history_len: u32,
231    },
232    /// Session-entropy sample at a completed turn boundary (see `scheduler::entropy`).
233    EntropySample {
234        turn: u32,
235        score: f64,
236        score_version: u32,
237        rho: f64,
238        repeat_pressure: f64,
239        failure_rate: f64,
240        rollbacks_in_window: u32,
241        window_turns: u32,
242    },
243    /// The opt-in entropy watch tripped (score crossed the configured threshold).
244    EntropyAlert {
245        turn: u32,
246        score: f64,
247        threshold: f64,
248    },
249    /// Transaction rollback indicating state was restored to a checkpoint.
250    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    // ─── 4. Process Table ───
258    /// Kernel process table changed for a spawned sub-agent.
259    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    // ─── 5. Milestone Contracts ───
274    /// Milestone phase criteria passed — capabilities unlocked, phase advanced.
275    MilestoneAdvanced {
276        turn: u32,
277        phase_id: String,
278        #[serde(default)]
279        capabilities_unlocked: Vec<String>,
280    },
281    /// Milestone phase criteria not met — run continues without advancing the phase.
282    MilestoneBlocked {
283        turn: u32,
284        phase_id: String,
285        reason: String,
286    },
287
288    // ─── 6. Long-Term Memory (Phase 7) ───
289    /// Memory entry written successfully (SDK → kernel acknowledgment).
290    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    /// Memory query request (kernel → SDK; SDK should respond asynchronously).
299    MemoryQueried {
300        turn: u32,
301        scope: crate::mm::memory::MemoryScope,
302        query: String,
303        requested_k: usize,
304        requires_async_response: bool,
305    },
306    /// Memory validation failed (kernel rejected a write request).
307    MemoryValidationFailed {
308        turn: u32,
309        record_id: String,
310        error: String,
311    },
312    /// Memory retrieval result (SDK → kernel via Resume or other async mechanism).
313    MemoryRetrievalResult {
314        hits: Vec<crate::mm::memory::MemoryRecall>,
315    },
316}
317
318impl SessionEvent {
319    /// Event `kind` string (snake_case tag).
320    pub fn kind_str(&self) -> &'static str {
321        match self {
322            Self::RunStarted { .. } => "run_started",
323            Self::LlmCompleted { .. } => "llm_completed",
324            Self::ToolRequested { .. } => "tool_requested",
325            Self::ToolCompleted { .. } => "tool_completed",
326            Self::Compressed { .. } => "compressed",
327            Self::PageOut { .. } => "page_out",
328            Self::PageIn { .. } => "page_in",
329            Self::LargeResultSpooled { .. } => "large_result_spooled",
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    /// Whether this event is a kernel OS decision (replay ignores for message reconstruction).
359    pub fn is_kernel_os_event(&self) -> bool {
360        matches!(
361            self,
362            Self::Compressed { .. }
363                | Self::PageOut { .. }
364                | Self::PageIn { .. }
365                | Self::LargeResultSpooled { .. }
366                | Self::CapabilityChanged { .. }
367                | Self::ContextRenewed { .. }
368                | Self::Suspended { .. }
369                | Self::Resumed { .. }
370                | Self::ToolGated { .. }
371                | Self::SignalDeliveryDisposed { .. }
372                | Self::BudgetExceeded { .. }
373                | Self::BudgetUsageReported { .. }
374                | Self::OperationCancelled { .. }
375                | Self::CheckpointTaken { .. }
376                | Self::EntropySample { .. }
377                | Self::EntropyAlert { .. }
378                | Self::Rollbacked { .. }
379                | Self::AgentProcessChanged { .. }
380                | Self::MilestoneAdvanced { .. }
381                | Self::MilestoneBlocked { .. }
382                | Self::MemoryWritten { .. }
383                | Self::MemoryQueried { .. }
384                | Self::MemoryValidationFailed { .. }
385        )
386    }
387}