Skip to main content

deepstrike_core/runtime/
session.rs

1use serde::{Deserialize, Serialize};
2
3use crate::runtime::kernel::wire::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    RunTerminal {
100        reason: String,
101        turns_used: u32,
102        total_tokens: u64,
103    },
104
105    // ─── 2. Kernel Governance & Security Gates ───
106    /// Tool arguments automatically repaired under white-listed heuristics.
107    ToolArgumentRepaired {
108        turn: u32,
109        tool: String,
110        original_arguments: String,
111        repaired_arguments: String,
112    },
113    /// Escalated permission gate requested for a tool, suspending current execution.
114    PermissionRequested {
115        turn: u32,
116        tool: String,
117        arguments: String,
118        reason: Option<String>,
119    },
120    /// Permission decision resolved by the user or an automated policy engine.
121    PermissionResolved {
122        turn: u32,
123        approved: bool,
124        responder: String, // "user" | "policy_gate"
125    },
126    /// Tool blocked monotonically by security governance policy or denial of consent.
127    ToolDenied {
128        turn: u32,
129        call_id: String,
130        tool_name: String,
131        reason: String,
132    },
133
134    // ─── 3. Dynamic Capability & Context Restructuring ───
135    /// Model-visible capabilities dynamically updated (e.g., loading skills or mounting MCPs).
136    CapabilityChanged {
137        turn: u32,
138        #[serde(default, skip_serializing_if = "Vec::is_empty")]
139        added: Vec<String>,
140        #[serde(default, skip_serializing_if = "Vec::is_empty")]
141        removed: Vec<String>,
142        #[serde(default, skip_serializing_if = "Option::is_none")]
143        change_kind: Option<String>,
144        #[serde(default, skip_serializing_if = "Option::is_none")]
145        capability_id: Option<String>,
146        #[serde(default, skip_serializing_if = "Option::is_none")]
147        version: Option<String>,
148        #[serde(default, skip_serializing_if = "Option::is_none")]
149        mounted_by: Option<String>,
150        #[serde(default, skip_serializing_if = "Option::is_none")]
151        mount_reason: Option<String>,
152    },
153    /// Context reset and sprint rotated after a context boundary handoff.
154    ContextRenewed {
155        turn: u32,
156        sprint: u32,
157        handoff_ref: String,
158    },
159
160    /// Execution paused (waiting for human-in-the-loop interaction or long-running tasks).
161    Suspended {
162        turn: u32,
163        reason: String,
164        #[serde(default, skip_serializing_if = "Vec::is_empty")]
165        pending_calls: Vec<String>,
166    },
167    /// Execution resumed.
168    Resumed {
169        turn: u32,
170        #[serde(default, skip_serializing_if = "Vec::is_empty")]
171        approved: Vec<String>,
172        #[serde(default, skip_serializing_if = "Vec::is_empty")]
173        denied: Vec<String>,
174    },
175    /// Kernel governance gate: tool requires approval before execution.
176    ToolGated {
177        turn: u32,
178        call_id: String,
179        tool: String,
180        reason: String,
181    },
182    /// In-kernel signal disposition (attention policy).
183    SignalDeliveryDisposed {
184        turn: u32,
185        operation_id: String,
186        delivery_id: String,
187        attempt: u32,
188        signal_id: String,
189        disposition: String,
190        queue_depth: u32,
191    },
192    /// Scheduler budget axis exhausted.
193    BudgetExceeded {
194        turn: u32,
195        operation_id: String,
196        #[serde(default, skip_serializing_if = "Option::is_none")]
197        reservation_id: Option<String>,
198        budget: String,
199    },
200    /// Terminal local usage for a reservation-backed RunGroup budget grant.
201    BudgetUsageReported {
202        turn: u32,
203        operation_id: String,
204        reservation_id: String,
205        tokens: u64,
206        subagents: u32,
207        rounds: u32,
208    },
209    /// Host-owned external I/O was stopped and the correlated operation cancelled.
210    OperationCancelled {
211        turn: u32,
212        operation_id: String,
213        reason: CancellationReason,
214        #[serde(default)]
215        pending_call_ids: Vec<String>,
216    },
217    /// Checkpoint taken at the start of a turn transaction (before LLM call).
218    CheckpointTaken {
219        turn: u32,
220        history_len: u32,
221    },
222    /// Session-entropy sample at a completed turn boundary (see `scheduler::entropy`).
223    EntropySample {
224        turn: u32,
225        score: f64,
226        score_version: u32,
227        rho: f64,
228        repeat_pressure: f64,
229        failure_rate: f64,
230        rollbacks_in_window: u32,
231        window_turns: u32,
232    },
233    /// The opt-in entropy watch tripped (score crossed the configured threshold).
234    EntropyAlert {
235        turn: u32,
236        score: f64,
237        threshold: f64,
238    },
239    /// Transaction rollback indicating state was restored to a checkpoint.
240    Rollbacked {
241        turn: u32,
242        checkpoint_history_len: u32,
243        #[serde(default, skip_serializing_if = "Option::is_none")]
244        reason: Option<RollbackReason>,
245    },
246
247    // ─── 4. Process Table ───
248    /// Kernel process table changed for a spawned sub-agent.
249    AgentProcessChanged {
250        turn: u32,
251        agent_id: String,
252        parent_session_id: String,
253        role: String,
254        isolation: String,
255        context_inheritance: String,
256        state: String,
257        #[serde(default, skip_serializing_if = "Vec::is_empty")]
258        permitted_capability_ids: Vec<String>,
259        #[serde(default, skip_serializing_if = "Option::is_none")]
260        result_termination: Option<String>,
261    },
262
263    // ─── 5. Milestone Contracts ───
264    /// Milestone phase criteria passed — capabilities unlocked, phase advanced.
265    MilestoneAdvanced {
266        turn: u32,
267        phase_id: String,
268        #[serde(default)]
269        capabilities_unlocked: Vec<String>,
270    },
271    /// Milestone phase criteria not met — run continues without advancing the phase.
272    MilestoneBlocked {
273        turn: u32,
274        phase_id: String,
275        reason: String,
276    },
277
278    // ─── 6. Long-Term Memory (Phase 7) ───
279    /// Memory entry written successfully (SDK → kernel acknowledgment).
280    MemoryWritten {
281        turn: u32,
282        record_id: String,
283        scope: crate::mm::memory::MemoryScope,
284        memory_kind: crate::mm::memory::MemoryKind,
285        name: String,
286        size_bytes: u32,
287    },
288    /// Memory query request (kernel → SDK; SDK should respond asynchronously).
289    MemoryQueried {
290        turn: u32,
291        scope: crate::mm::memory::MemoryScope,
292        query: String,
293        requested_k: usize,
294        requires_async_response: bool,
295    },
296    /// Memory validation failed (kernel rejected a write request).
297    MemoryValidationFailed {
298        turn: u32,
299        record_id: String,
300        error: String,
301    },
302    /// Memory retrieval result (SDK → kernel via Resume or other async mechanism).
303    MemoryRetrievalResult {
304        hits: Vec<crate::mm::memory::MemoryRecall>,
305    },
306}
307
308impl SessionEvent {
309    /// Event `kind` string (snake_case tag).
310    pub fn kind_str(&self) -> &'static str {
311        match self {
312            Self::RunStarted { .. } => "run_started",
313            Self::LlmCompleted { .. } => "llm_completed",
314            Self::ToolRequested { .. } => "tool_requested",
315            Self::ToolCompleted { .. } => "tool_completed",
316            Self::Compressed { .. } => "compressed",
317            Self::PageOut { .. } => "page_out",
318            Self::PageIn { .. } => "page_in",
319            Self::RunTerminal { .. } => "run_terminal",
320            Self::ToolArgumentRepaired { .. } => "tool_argument_repaired",
321            Self::PermissionRequested { .. } => "permission_requested",
322            Self::PermissionResolved { .. } => "permission_resolved",
323            Self::ToolDenied { .. } => "tool_denied",
324            Self::CapabilityChanged { .. } => "capability_changed",
325            Self::ContextRenewed { .. } => "context_renewed",
326            Self::Suspended { .. } => "suspended",
327            Self::Resumed { .. } => "resumed",
328            Self::ToolGated { .. } => "tool_gated",
329            Self::SignalDeliveryDisposed { .. } => "signal_delivery_disposed",
330            Self::BudgetExceeded { .. } => "budget_exceeded",
331            Self::BudgetUsageReported { .. } => "budget_usage_reported",
332            Self::OperationCancelled { .. } => "operation_cancelled",
333            Self::CheckpointTaken { .. } => "checkpoint_taken",
334            Self::EntropySample { .. } => "entropy_sample",
335            Self::EntropyAlert { .. } => "entropy_alert",
336            Self::Rollbacked { .. } => "rollbacked",
337            Self::AgentProcessChanged { .. } => "agent_process_changed",
338            Self::MilestoneAdvanced { .. } => "milestone_advanced",
339            Self::MilestoneBlocked { .. } => "milestone_blocked",
340            Self::MemoryWritten { .. } => "memory_written",
341            Self::MemoryQueried { .. } => "memory_queried",
342            Self::MemoryValidationFailed { .. } => "memory_validation_failed",
343            Self::MemoryRetrievalResult { .. } => "memory_retrieval_result",
344        }
345    }
346
347    /// Whether this event is a kernel OS decision (replay ignores for message reconstruction).
348    pub fn is_kernel_os_event(&self) -> bool {
349        matches!(
350            self,
351            Self::Compressed { .. }
352                | Self::PageOut { .. }
353                | Self::PageIn { .. }
354                | Self::CapabilityChanged { .. }
355                | Self::ContextRenewed { .. }
356                | Self::Suspended { .. }
357                | Self::Resumed { .. }
358                | Self::ToolGated { .. }
359                | Self::SignalDeliveryDisposed { .. }
360                | Self::BudgetExceeded { .. }
361                | Self::BudgetUsageReported { .. }
362                | Self::OperationCancelled { .. }
363                | Self::CheckpointTaken { .. }
364                | Self::EntropySample { .. }
365                | Self::EntropyAlert { .. }
366                | Self::Rollbacked { .. }
367                | Self::AgentProcessChanged { .. }
368                | Self::MilestoneAdvanced { .. }
369                | Self::MilestoneBlocked { .. }
370                | Self::MemoryWritten { .. }
371                | Self::MemoryQueried { .. }
372                | Self::MemoryValidationFailed { .. }
373        )
374    }
375}