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 but the envelope is strict: protocol identity is mandatory and
9/// removed schema/version fields are rejected before replay can be consumed.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11#[serde(deny_unknown_fields)]
12pub struct ProviderReplay {
13    pub protocol: String,
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub provider: Option<String>,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub model: Option<String>,
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub native_blocks: Option<Vec<serde_json::Value>>,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub reasoning_content: Option<String>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub reasoning_details: Option<serde_json::Value>,
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub native_message: Option<serde_json::Value>,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub tool_calls: Option<Vec<serde_json::Value>>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(tag = "kind", rename_all = "snake_case")]
32pub enum RollbackReason {
33    FatalToolError { tool_name: String, error: String },
34    GovernanceDenied { tool_name: String, reason: String },
35    ProviderFailure { error: String },
36    Timeout,
37    UserInterrupt,
38    MalformedReplay { reason: String },
39}
40
41/// Append-only session event kinds for the unified Agent OS Runtime.
42///
43/// Combines execution loop events with OS-level lifecycle control,
44/// capability manifest auditing, and governance gates.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(tag = "kind", rename_all = "snake_case")]
47pub enum SessionEvent {
48    // ─── 1. Execution & Inference Loop ───
49    RunStarted {
50        run_id: String,
51        goal: String,
52        #[serde(default)]
53        criteria: Vec<String>,
54        agent_id: Option<String>,
55        system_prompt: Option<String>,
56        /// Multimodal parts seeded into history before the first render (Node/Python/WASM/Rust).
57        #[serde(default, skip_serializing_if = "Vec::is_empty")]
58        attachments: Vec<crate::types::message::ContentPart>,
59    },
60    LlmCompleted {
61        turn: u32,
62        message: Message,
63        #[serde(default, skip_serializing_if = "Option::is_none")]
64        provider_replay: Option<ProviderReplay>,
65    },
66    ToolRequested {
67        turn: u32,
68        calls: Vec<ToolCall>,
69    },
70    ToolCompleted {
71        turn: u32,
72        results: Vec<ToolResult>,
73    },
74    Compressed {
75        turn: u32,
76        archived_seq_range: (u64, u64),
77        #[serde(default, skip_serializing_if = "Option::is_none")]
78        action: Option<String>,
79        #[serde(default, skip_serializing_if = "Option::is_none")]
80        summary: Option<String>,
81        #[serde(default, skip_serializing_if = "Option::is_none")]
82        summary_tokens: Option<u32>,
83        #[serde(default, skip_serializing_if = "Vec::is_empty")]
84        preserved_refs: Vec<String>,
85    },
86    /// Working memory paged out for long-term storage (kernel `page_out`).
87    PageOut {
88        turn: u32,
89        #[serde(default, skip_serializing_if = "Option::is_none")]
90        action: Option<String>,
91        #[serde(default, skip_serializing_if = "Option::is_none")]
92        summary: Option<String>,
93        #[serde(default, skip_serializing_if = "Option::is_none")]
94        tier_hint: Option<String>,
95        #[serde(default)]
96        message_count: u32,
97        #[serde(default, skip_serializing_if = "Option::is_none")]
98        archive_ref: Option<String>,
99    },
100    /// Long-term entries injected into knowledge partition (SDK `page_in`).
101    PageIn {
102        turn: u32,
103        entry_count: u32,
104    },
105    RunTerminal {
106        reason: String,
107        turns_used: u32,
108        total_tokens: u64,
109    },
110
111    // ─── 2. Kernel Governance & Security Gates ───
112    /// Tool arguments automatically repaired under white-listed heuristics.
113    ToolArgumentRepaired {
114        turn: u32,
115        tool: String,
116        original_arguments: String,
117        repaired_arguments: String,
118    },
119    /// Escalated permission gate requested for a tool, suspending current execution.
120    PermissionRequested {
121        turn: u32,
122        tool: String,
123        arguments: String,
124        reason: Option<String>,
125    },
126    /// Permission decision resolved by the user or an automated policy engine.
127    PermissionResolved {
128        turn: u32,
129        approved: bool,
130        responder: String, // "user" | "policy_gate"
131    },
132    /// Tool blocked monotonically by security governance policy or denial of consent.
133    ToolDenied {
134        turn: u32,
135        call_id: String,
136        tool_name: String,
137        reason: String,
138    },
139
140    // ─── 3. Dynamic Capability & Context Restructuring ───
141    /// Model-visible capabilities dynamically updated (e.g., loading skills or mounting MCPs).
142    CapabilityChanged {
143        turn: u32,
144        #[serde(default, skip_serializing_if = "Vec::is_empty")]
145        added: Vec<String>,
146        #[serde(default, skip_serializing_if = "Vec::is_empty")]
147        removed: Vec<String>,
148        #[serde(default, skip_serializing_if = "Option::is_none")]
149        change_kind: Option<String>,
150        #[serde(default, skip_serializing_if = "Option::is_none")]
151        capability_id: Option<String>,
152        #[serde(default, skip_serializing_if = "Option::is_none")]
153        version: Option<String>,
154        #[serde(default, skip_serializing_if = "Option::is_none")]
155        mounted_by: Option<String>,
156        #[serde(default, skip_serializing_if = "Option::is_none")]
157        mount_reason: Option<String>,
158    },
159    /// Context reset and sprint rotated after a context boundary handoff.
160    ContextRenewed {
161        turn: u32,
162        sprint: u32,
163        handoff_ref: String,
164    },
165
166    /// Execution paused (waiting for human-in-the-loop interaction or long-running tasks).
167    Suspended {
168        turn: u32,
169        reason: String,
170        #[serde(default, skip_serializing_if = "Vec::is_empty")]
171        pending_calls: Vec<String>,
172    },
173    /// Execution resumed.
174    Resumed {
175        turn: u32,
176        #[serde(default, skip_serializing_if = "Vec::is_empty")]
177        approved: Vec<String>,
178        #[serde(default, skip_serializing_if = "Vec::is_empty")]
179        denied: Vec<String>,
180    },
181    /// Kernel governance gate: tool requires approval before execution.
182    ToolGated {
183        turn: u32,
184        call_id: String,
185        tool: String,
186        reason: String,
187    },
188    /// In-kernel signal disposition (attention policy).
189    SignalDeliveryDisposed {
190        turn: u32,
191        operation_id: String,
192        delivery_id: String,
193        attempt: u32,
194        signal_id: String,
195        disposition: String,
196        queue_depth: u32,
197    },
198    /// Scheduler budget axis exhausted.
199    BudgetExceeded {
200        turn: u32,
201        operation_id: String,
202        #[serde(default, skip_serializing_if = "Option::is_none")]
203        reservation_id: Option<String>,
204        budget: String,
205    },
206    /// Terminal local usage for a reservation-backed RunGroup budget grant.
207    BudgetUsageReported {
208        turn: u32,
209        operation_id: String,
210        reservation_id: String,
211        tokens: u64,
212        subagents: u32,
213        rounds: u32,
214    },
215    /// Host-owned external I/O was stopped and the correlated operation cancelled.
216    OperationCancelled {
217        turn: u32,
218        operation_id: String,
219        reason: CancellationReason,
220        #[serde(default)]
221        pending_call_ids: Vec<String>,
222    },
223    /// Checkpoint taken at the start of a turn transaction (before LLM call).
224    CheckpointTaken {
225        turn: u32,
226        history_len: u32,
227    },
228    /// Session-entropy sample at a completed turn boundary (see `scheduler::entropy`).
229    EntropySample {
230        turn: u32,
231        score: f64,
232        rho: f64,
233        repeat_pressure: f64,
234        failure_rate: f64,
235        rollbacks_in_window: u32,
236        window_turns: u32,
237    },
238    /// The opt-in entropy watch tripped (score crossed the configured threshold).
239    EntropyAlert {
240        turn: u32,
241        score: f64,
242        threshold: f64,
243    },
244    /// Transaction rollback indicating state was restored to a checkpoint.
245    Rollbacked {
246        turn: u32,
247        checkpoint_history_len: u32,
248        #[serde(default, skip_serializing_if = "Option::is_none")]
249        reason: Option<RollbackReason>,
250    },
251
252    // ─── 4. Process Table ───
253    /// Kernel process table changed for a spawned sub-agent.
254    AgentProcessChanged {
255        turn: u32,
256        agent_id: String,
257        parent_session_id: String,
258        role: String,
259        isolation: String,
260        context_inheritance: String,
261        state: String,
262        #[serde(default, skip_serializing_if = "Vec::is_empty")]
263        permitted_capability_ids: Vec<String>,
264        #[serde(default, skip_serializing_if = "Option::is_none")]
265        result_termination: Option<String>,
266    },
267
268    // ─── 5. Milestone Contracts ───
269    /// Milestone phase criteria passed — capabilities unlocked, phase advanced.
270    MilestoneAdvanced {
271        turn: u32,
272        phase_id: String,
273        #[serde(default)]
274        capabilities_unlocked: Vec<String>,
275    },
276    /// Milestone phase criteria not met — run continues without advancing the phase.
277    MilestoneBlocked {
278        turn: u32,
279        phase_id: String,
280        reason: String,
281    },
282
283    // ─── 6. Long-Term Memory (Phase 7) ───
284    /// Memory entry written successfully (SDK → kernel acknowledgment).
285    MemoryWritten {
286        turn: u32,
287        record_id: String,
288        scope: crate::mm::memory::MemoryScope,
289        memory_kind: crate::mm::memory::MemoryKind,
290        name: String,
291        size_bytes: u32,
292    },
293    /// Memory query request (kernel → SDK; SDK should respond asynchronously).
294    MemoryQueried {
295        turn: u32,
296        scope: crate::mm::memory::MemoryScope,
297        query: String,
298        requested_k: usize,
299        requires_async_response: bool,
300    },
301    /// Memory validation failed (kernel rejected a write request).
302    MemoryValidationFailed {
303        turn: u32,
304        record_id: String,
305        error: String,
306    },
307    /// Memory retrieval result (SDK → kernel via Resume or other async mechanism).
308    MemoryRetrievalResult {
309        hits: Vec<crate::mm::memory::MemoryRecall>,
310    },
311}
312
313impl SessionEvent {
314    /// Event `kind` string (snake_case tag).
315    pub fn kind_str(&self) -> &'static str {
316        match self {
317            Self::RunStarted { .. } => "run_started",
318            Self::LlmCompleted { .. } => "llm_completed",
319            Self::ToolRequested { .. } => "tool_requested",
320            Self::ToolCompleted { .. } => "tool_completed",
321            Self::Compressed { .. } => "compressed",
322            Self::PageOut { .. } => "page_out",
323            Self::PageIn { .. } => "page_in",
324            Self::RunTerminal { .. } => "run_terminal",
325            Self::ToolArgumentRepaired { .. } => "tool_argument_repaired",
326            Self::PermissionRequested { .. } => "permission_requested",
327            Self::PermissionResolved { .. } => "permission_resolved",
328            Self::ToolDenied { .. } => "tool_denied",
329            Self::CapabilityChanged { .. } => "capability_changed",
330            Self::ContextRenewed { .. } => "context_renewed",
331            Self::Suspended { .. } => "suspended",
332            Self::Resumed { .. } => "resumed",
333            Self::ToolGated { .. } => "tool_gated",
334            Self::SignalDeliveryDisposed { .. } => "signal_delivery_disposed",
335            Self::BudgetExceeded { .. } => "budget_exceeded",
336            Self::BudgetUsageReported { .. } => "budget_usage_reported",
337            Self::OperationCancelled { .. } => "operation_cancelled",
338            Self::CheckpointTaken { .. } => "checkpoint_taken",
339            Self::EntropySample { .. } => "entropy_sample",
340            Self::EntropyAlert { .. } => "entropy_alert",
341            Self::Rollbacked { .. } => "rollbacked",
342            Self::AgentProcessChanged { .. } => "agent_process_changed",
343            Self::MilestoneAdvanced { .. } => "milestone_advanced",
344            Self::MilestoneBlocked { .. } => "milestone_blocked",
345            Self::MemoryWritten { .. } => "memory_written",
346            Self::MemoryQueried { .. } => "memory_queried",
347            Self::MemoryValidationFailed { .. } => "memory_validation_failed",
348            Self::MemoryRetrievalResult { .. } => "memory_retrieval_result",
349        }
350    }
351
352    /// Whether this event is a kernel OS decision (replay ignores for message reconstruction).
353    pub fn is_kernel_os_event(&self) -> bool {
354        matches!(
355            self,
356            Self::Compressed { .. }
357                | Self::PageOut { .. }
358                | Self::PageIn { .. }
359                | Self::CapabilityChanged { .. }
360                | Self::ContextRenewed { .. }
361                | Self::Suspended { .. }
362                | Self::Resumed { .. }
363                | Self::ToolGated { .. }
364                | Self::SignalDeliveryDisposed { .. }
365                | Self::BudgetExceeded { .. }
366                | Self::BudgetUsageReported { .. }
367                | Self::OperationCancelled { .. }
368                | Self::CheckpointTaken { .. }
369                | Self::EntropySample { .. }
370                | Self::EntropyAlert { .. }
371                | Self::Rollbacked { .. }
372                | Self::AgentProcessChanged { .. }
373                | Self::MilestoneAdvanced { .. }
374                | Self::MilestoneBlocked { .. }
375                | Self::MemoryWritten { .. }
376                | Self::MemoryQueried { .. }
377                | Self::MemoryValidationFailed { .. }
378        )
379    }
380}