Skip to main content

deepstrike_core/runtime/
session.rs

1use serde::{Deserialize, Serialize};
2
3use crate::runtime::kernel::wire::CancellationReason;
4use crate::types::message::{CoreMessage, 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    ContextPrepared {
49        turn: u32,
50        effect_id: String,
51        preparation: Box<crate::context::execution::ContextDispatchPreparation>,
52    },
53    // ─── 1. Execution & Inference Loop ───
54    RunStarted {
55        run_id: String,
56        goal: String,
57        #[serde(default)]
58        criteria: Vec<String>,
59        agent_id: Option<String>,
60        system_prompt: Option<String>,
61        /// Multimodal parts seeded into history before the first render (Node/Python/WASM/Rust).
62        #[serde(default, skip_serializing_if = "Vec::is_empty")]
63        attachments: Vec<crate::types::message::ContentPart>,
64    },
65    LlmCompleted {
66        turn: u32,
67        message: CoreMessage,
68        #[serde(default, skip_serializing_if = "Option::is_none")]
69        provider_replay: Option<ProviderReplay>,
70    },
71    ToolRequested {
72        turn: u32,
73        calls: Vec<ToolCall>,
74    },
75    ToolCompleted {
76        turn: u32,
77        results: Vec<ToolResult>,
78    },
79    Compressed {
80        turn: u32,
81        archived_seq_range: (u64, u64),
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        summary_tokens: Option<u32>,
88        #[serde(default, skip_serializing_if = "Vec::is_empty")]
89        preserved_refs: Vec<String>,
90    },
91    /// Working memory paged out for long-term storage (kernel `page_out`).
92    PageOut {
93        turn: u32,
94        #[serde(default, skip_serializing_if = "Option::is_none")]
95        action: Option<String>,
96        #[serde(default, skip_serializing_if = "Option::is_none")]
97        summary: Option<String>,
98        #[serde(default, skip_serializing_if = "Option::is_none")]
99        tier_hint: Option<String>,
100        #[serde(default)]
101        message_count: u32,
102        #[serde(default, skip_serializing_if = "Option::is_none")]
103        archive_ref: Option<String>,
104    },
105    /// Long-term entries injected into knowledge partition (SDK `page_in`).
106    PageIn {
107        turn: u32,
108        entry_count: u32,
109    },
110    RunTerminal {
111        reason: String,
112        turns_used: u32,
113        total_tokens: u64,
114    },
115
116    // ─── 2. Kernel Governance & Security Gates ───
117    /// Tool arguments automatically repaired under white-listed heuristics.
118    ToolArgumentRepaired {
119        turn: u32,
120        tool: String,
121        original_arguments: String,
122        repaired_arguments: String,
123    },
124    /// Escalated permission gate requested for a tool, suspending current execution.
125    PermissionRequested {
126        turn: u32,
127        tool: String,
128        arguments: String,
129        reason: Option<String>,
130    },
131    /// Permission decision resolved by the user or an automated policy engine.
132    PermissionResolved {
133        turn: u32,
134        approved: bool,
135        responder: String, // "user" | "policy_gate"
136    },
137    /// Tool blocked monotonically by security governance policy or denial of consent.
138    ToolDenied {
139        turn: u32,
140        call_id: String,
141        tool_name: String,
142        reason: String,
143    },
144
145    // ─── 3. Dynamic Capability & Context Restructuring ───
146    /// Model-visible capabilities dynamically updated (e.g., loading skills or mounting MCPs).
147    CapabilityChanged {
148        turn: u32,
149        #[serde(default, skip_serializing_if = "Vec::is_empty")]
150        added: Vec<String>,
151        #[serde(default, skip_serializing_if = "Vec::is_empty")]
152        removed: Vec<String>,
153        #[serde(default, skip_serializing_if = "Option::is_none")]
154        change_kind: Option<String>,
155        #[serde(default, skip_serializing_if = "Option::is_none")]
156        capability_id: Option<String>,
157        #[serde(default, skip_serializing_if = "Option::is_none")]
158        version: Option<String>,
159        #[serde(default, skip_serializing_if = "Option::is_none")]
160        mounted_by: Option<String>,
161        #[serde(default, skip_serializing_if = "Option::is_none")]
162        mount_reason: Option<String>,
163    },
164    /// Context reset and sprint rotated after a context boundary handoff.
165    ContextRenewed {
166        turn: u32,
167        sprint: u32,
168        handoff_ref: String,
169    },
170
171    /// Execution paused (waiting for human-in-the-loop interaction or long-running tasks).
172    Suspended {
173        turn: u32,
174        reason: String,
175        #[serde(default, skip_serializing_if = "Vec::is_empty")]
176        pending_calls: Vec<String>,
177    },
178    /// Execution resumed.
179    Resumed {
180        turn: u32,
181        #[serde(default, skip_serializing_if = "Vec::is_empty")]
182        approved: Vec<String>,
183        #[serde(default, skip_serializing_if = "Vec::is_empty")]
184        denied: Vec<String>,
185    },
186    /// Kernel governance gate: tool requires approval before execution.
187    ToolGated {
188        turn: u32,
189        call_id: String,
190        tool: String,
191        reason: String,
192    },
193    /// In-kernel signal disposition (attention policy).
194    SignalDeliveryDisposed {
195        turn: u32,
196        operation_id: String,
197        delivery_id: String,
198        attempt: u32,
199        signal_id: String,
200        disposition: String,
201        queue_depth: u32,
202    },
203    /// Scheduler budget axis exhausted.
204    BudgetExceeded {
205        turn: u32,
206        operation_id: String,
207        #[serde(default, skip_serializing_if = "Option::is_none")]
208        reservation_id: Option<String>,
209        budget: String,
210    },
211    /// Terminal local usage for a reservation-backed RunGroup budget grant.
212    BudgetUsageReported {
213        turn: u32,
214        operation_id: String,
215        reservation_id: String,
216        tokens: u64,
217        subagents: u32,
218        rounds: u32,
219    },
220    /// Host-owned external I/O was stopped and the correlated operation cancelled.
221    OperationCancelled {
222        turn: u32,
223        operation_id: String,
224        reason: CancellationReason,
225        #[serde(default)]
226        pending_call_ids: Vec<String>,
227    },
228    /// Checkpoint taken at the start of a turn transaction (before LLM call).
229    CheckpointTaken {
230        turn: u32,
231        history_len: u32,
232    },
233    /// Session-entropy sample at a completed turn boundary (see `scheduler::entropy`).
234    EntropySample {
235        turn: u32,
236        score: f64,
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::ContextPrepared { .. } => "context_prepared",
323            Self::RunStarted { .. } => "run_started",
324            Self::LlmCompleted { .. } => "llm_completed",
325            Self::ToolRequested { .. } => "tool_requested",
326            Self::ToolCompleted { .. } => "tool_completed",
327            Self::Compressed { .. } => "compressed",
328            Self::PageOut { .. } => "page_out",
329            Self::PageIn { .. } => "page_in",
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::CapabilityChanged { .. }
366                | Self::ContextRenewed { .. }
367                | Self::Suspended { .. }
368                | Self::Resumed { .. }
369                | Self::ToolGated { .. }
370                | Self::SignalDeliveryDisposed { .. }
371                | Self::BudgetExceeded { .. }
372                | Self::BudgetUsageReported { .. }
373                | Self::OperationCancelled { .. }
374                | Self::CheckpointTaken { .. }
375                | Self::EntropySample { .. }
376                | Self::EntropyAlert { .. }
377                | Self::Rollbacked { .. }
378                | Self::AgentProcessChanged { .. }
379                | Self::MilestoneAdvanced { .. }
380                | Self::MilestoneBlocked { .. }
381                | Self::MemoryWritten { .. }
382                | Self::MemoryQueried { .. }
383                | Self::MemoryValidationFailed { .. }
384        )
385    }
386}