1use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
2use std::sync::Arc;
3
4use crate::runtime::sandboxed_skill::scan_skill_dir;
5use crate::runtime::skill_watcher::SkillWatcher;
6use async_stream::try_stream;
7use deepstrike_core::governance::quota::ResourceQuota;
8use deepstrike_core::mm::memory::{
9 MemoryAuthor, MemoryKind, MemoryPolicy, MemoryProvenance, MemoryQuery, MemoryRecall,
10 MemoryRecord, MemoryScope, MemoryTrustLevel,
11};
12use deepstrike_core::runtime::kernel::{
13 CancellationReason, KernelAction, KernelEffect, KernelInput, KernelInputEvent,
14 KernelObservation, KernelPressureAction, KernelReliabilityConfig, KernelRuntime, KernelStep,
15 RunConfig,
16};
17use deepstrike_core::runtime::session::SessionEvent;
18use deepstrike_core::scheduler::policy::SchedulerBudget as KernelBudget;
19use deepstrike_core::scheduler::policy::SchedulerPolicyConfig;
20use deepstrike_core::types::message::{Message, ToolCall};
21use deepstrike_core::types::milestone::MilestoneCheckResult;
22use deepstrike_core::types::signal::{
23 RuntimeSignal as KernelSignal, SignalSource as KernelSignalSource,
24 SignalType as KernelSignalType, Urgency,
25};
26use deepstrike_core::types::task::RuntimeTask;
27use futures::StreamExt;
28
29use crate::governance::Governance;
30use crate::knowledge::KnowledgeSource;
31use crate::memory::DreamStore;
32use crate::providers::{LLMProvider, StreamEvent};
33use crate::run_event::RunEvent;
34use crate::runtime::archive::ArchiveStore;
35use crate::runtime::execution_plane::{
36 ExecutionPlane, LocalExecutionPlane, PermissionRequest, PermissionRequestHandler,
37 PermissionResponse, RunContext, ToolSuspendHandler,
38};
39use crate::runtime::os_profile::{
40 assert_native_profile, GovernancePolicy, OsProfile, SignalPolicy,
41};
42use crate::runtime::provider_replay::{peek_provider_replay, seed_provider_replay_from_events};
43use crate::runtime::replay::{
44 is_mid_run, repair_entries_with_cap, replay_messages_with_cap,
45 replay_messages_with_cap_and_loader,
46};
47use crate::runtime::session_log::{SessionEntry, SessionLog};
48use crate::{Error, Result};
49use crate::{SignalDeliveryReceipt, SignalSource};
50use deepstrike_core::context::task_state::TaskUpdate;
51use deepstrike_core::runtime::repair::repair_llm_completed;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
57pub enum MilestonePolicy {
58 #[default]
60 RequireVerifier,
61 Terminate,
63 AutoPass,
66}
67
68#[derive(Debug, Clone)]
69pub struct MilestoneEvaluationContext {
70 pub phase_id: String,
71 pub criteria: Vec<String>,
72 pub required_evidence: Vec<String>,
73}
74
75pub type MilestoneEvaluationHandler = std::sync::Arc<
76 dyn Fn(
77 MilestoneEvaluationContext,
78 ) -> futures::future::BoxFuture<'static, Result<MilestoneCheckResult>>
79 + Send
80 + Sync,
81>;
82
83#[derive(Debug, Clone)]
88pub struct TurnMetrics {
89 pub turn: u32,
90 pub tools_exposed: usize,
91 pub tools_called: usize,
92 pub active_skill: Option<String>,
93 pub input_tokens: u32,
94 pub cache_read_tokens: u32,
95 pub cache_creation_tokens: u32,
96 pub cache_read_tokens_by_slot: Option<crate::providers::CacheReadBySlot>,
98}
99
100pub type OnTurnMetricsHandler = std::sync::Arc<dyn Fn(TurnMetrics) + Send + Sync>;
102
103pub struct RuntimeOptions {
105 pub provider: Box<dyn LLMProvider>,
106 pub execution_plane: Option<Box<dyn ExecutionPlane>>,
107 pub session_log: Option<Arc<dyn SessionLog>>,
108 pub compression_store: Option<Arc<dyn ArchiveStore>>,
109 pub spool_dir: Option<std::path::PathBuf>,
112 pub kernel_reliability: Option<KernelReliabilityConfig>,
114 pub session_id: Option<String>,
116 pub max_tokens: u32,
117 pub max_turns: Option<u32>,
118 pub timeout_ms: Option<u64>,
119 pub extensions: Option<serde_json::Value>,
120 pub agent_id: Option<String>,
121 pub memory_scope: Option<MemoryScope>,
123 pub pre_query_memory: Option<std::sync::Arc<dyn Fn(&str) -> Vec<MemoryQuery> + Send + Sync>>,
129 pub system_prompt: Option<String>,
130 pub initial_memory: Vec<String>,
131 pub skill_dir: Option<std::path::PathBuf>,
132 pub dream_store: Option<Box<dyn DreamStore>>,
133 pub knowledge_source: Option<Box<dyn KnowledgeSource>>,
134 pub signal_source: Option<Box<dyn SignalSource>>,
135 pub governance: Option<Arc<tokio::sync::Mutex<Governance>>>,
136 pub os_profile: Option<OsProfile>,
137 pub governance_policy: Option<GovernancePolicy>,
138 pub signal_policy: Option<SignalPolicy>,
139 pub scheduler_policy: Option<SchedulerPolicyConfig>,
140 pub resource_quota: Option<ResourceQuota>,
141 pub memory_policy: Option<MemoryPolicy>,
143 pub tokenizer: Option<String>,
144 pub enable_plan_tool: Option<bool>,
145 pub on_tool_suspend: Option<ToolSuspendHandler>,
146 pub on_permission_request: Option<PermissionRequestHandler>,
147 pub milestone_policy: MilestonePolicy,
149 pub milestone_contract: Option<deepstrike_core::types::milestone::MilestoneContract>,
150 pub run_spec: Option<deepstrike_core::types::agent::AgentRunSpec>,
151 pub allowed_tool_ids: Option<Vec<String>>,
157 pub on_turn_metrics: Option<OnTurnMetricsHandler>,
160 pub stable_core_tool_ids: Vec<String>,
163 pub on_milestone_evaluate: Option<MilestoneEvaluationHandler>,
164}
165
166fn build_run_spec(
171 explicit: Option<deepstrike_core::types::agent::AgentRunSpec>,
172 allowed_tool_ids: Option<&[String]>,
173 agent_id: Option<&str>,
174 session_id: &str,
175 goal: &str,
176) -> Option<deepstrike_core::types::agent::AgentRunSpec> {
177 use deepstrike_core::types::agent::{AgentIdentity, AgentRole, AgentRunSpec};
178 let profile = allowed_tool_ids.filter(|ids| !ids.is_empty());
179 match (explicit, profile) {
180 (Some(mut spec), Some(ids)) => {
181 spec.capability_filter.allowed_ids = ids.iter().map(|s| s.as_str().into()).collect();
182 Some(spec)
183 }
184 (Some(spec), None) => Some(spec),
185 (None, Some(ids)) => {
186 let mut spec = AgentRunSpec::new(
187 AgentIdentity::new(agent_id.unwrap_or("root"), session_id),
188 AgentRole::Custom,
189 goal.to_string(),
190 );
191 spec.capability_filter.allowed_ids = ids.iter().map(|s| s.as_str().into()).collect();
192 Some(spec)
193 }
194 (None, None) => None,
195 }
196}
197
198pub struct RuntimeRunner {
200 opts: RuntimeOptions,
201 plane: Box<dyn ExecutionPlane>,
202 interrupted: AtomicBool,
203 cancellation_reason: AtomicU8,
204 active_kernel: std::sync::Mutex<Option<std::sync::Arc<std::sync::Mutex<KernelRuntime>>>>,
205 local_page_out_cache: std::sync::Mutex<Vec<Message>>,
206}
207
208impl RuntimeRunner {
209 pub fn new(mut opts: RuntimeOptions) -> Self {
210 let plane = opts
211 .execution_plane
212 .take()
213 .unwrap_or_else(|| Box::new(LocalExecutionPlane::new()));
214 Self {
215 opts,
216 plane,
217 interrupted: AtomicBool::new(false),
218 cancellation_reason: AtomicU8::new(0),
219 active_kernel: std::sync::Mutex::new(None),
220 local_page_out_cache: std::sync::Mutex::new(Vec::new()),
221 }
222 }
223
224 pub fn interrupt(&self) {
225 self.interrupt_with_reason(CancellationReason::User);
226 }
227
228 pub fn interrupt_with_reason(&self, reason: CancellationReason) {
229 self.cancellation_reason
230 .store(cancellation_reason_code(reason), Ordering::Relaxed);
231 self.interrupted.store(true, Ordering::Relaxed);
232 }
233
234 pub fn execution_plane(&self) -> &dyn ExecutionPlane {
235 self.plane.as_ref()
236 }
237
238 pub async fn write_memory(
239 &self,
240 memory: MemoryRecord,
241 session_id: Option<&str>,
242 agent_id: Option<&str>,
243 ) -> Result<()> {
244 let Some(store) = &self.opts.dream_store else {
245 return Ok(());
246 };
247 let Some(agent_id) = agent_id.or(self.opts.agent_id.as_deref()) else {
248 return Ok(());
249 };
250
251 let (kernel, requested) = self.begin_memory_syscall(KernelInputEvent::WriteMemory {
252 memory: memory.clone(),
253 });
254 let persist_effect = requested
255 .actions
256 .iter()
257 .find_map(|action| match &action.effect {
258 KernelEffect::PersistMemory { memory } => {
259 Some((action.effect_id.clone(), memory.clone()))
260 }
261 _ => None,
262 });
263 let Some((effect_id, canonical)) = persist_effect else {
264 self.append_memory_syscall_observations(session_id, requested.observations)
265 .await;
266 return Ok(());
267 };
268
269 let io_result: Result<()> = async {
270 store.upsert(agent_id, canonical).await?;
271 Ok(())
272 }
273 .await;
274 let completed =
275 kernel
276 .lock()
277 .unwrap()
278 .step(KernelInput::new(KernelInputEvent::MemoryPersistResult {
279 effect_id,
280 error: io_result.as_ref().err().map(ToString::to_string),
281 }));
282 self.append_memory_syscall_observations(session_id, completed.observations)
283 .await;
284 io_result
285 }
286
287 pub async fn query_memory(
288 &self,
289 query: MemoryQuery,
290 session_id: Option<&str>,
291 agent_id: Option<&str>,
292 ) -> Result<Vec<MemoryRecall>> {
293 let Some(store) = &self.opts.dream_store else {
294 return Ok(Vec::new());
295 };
296 let Some(agent_id) = agent_id.or(self.opts.agent_id.as_deref()) else {
297 return Ok(Vec::new());
298 };
299
300 let (kernel, requested) = self.begin_memory_syscall(KernelInputEvent::QueryMemory {
301 query: query.clone(),
302 });
303 let query_effect = requested
304 .actions
305 .iter()
306 .find_map(|action| match &action.effect {
307 KernelEffect::QueryMemory { query, requested_k } => {
308 Some((action.effect_id.clone(), query.clone(), *requested_k))
309 }
310 _ => None,
311 });
312 let Some((effect_id, mut canonical_query, requested_k)) = query_effect else {
313 self.append_memory_syscall_observations(session_id, requested.observations)
314 .await;
315 return Ok(Vec::new());
316 };
317
318 canonical_query.top_k = requested_k;
319 let hits = store.search(agent_id, &canonical_query).await?;
320 let completed =
321 kernel
322 .lock()
323 .unwrap()
324 .step(KernelInput::new(KernelInputEvent::MemoryQueryResult {
325 effect_id,
326 hits: hits.clone(),
327 error: None,
328 }));
329 self.append_memory_syscall_observations(session_id, completed.observations)
330 .await;
331 self.log_memory_retrieval_result(session_id, hits.clone())
332 .await;
333 Ok(hits)
334 }
335
336 async fn extract_session_memories(
337 &self,
338 session: &deepstrike_core::memory::durable::SessionData,
339 scope: &MemoryScope,
340 ) -> Result<Vec<MemoryRecord>> {
341 let transcript = session
342 .messages
343 .iter()
344 .map(|message| {
345 format!(
346 "[{:?}] {}",
347 message.role,
348 message.content.as_text().unwrap_or_default()
349 )
350 })
351 .collect::<Vec<_>>()
352 .join("\n")
353 .chars()
354 .take(8_000)
355 .collect::<String>();
356 let prompt = format!(
357 "{transcript}\n\nReturn {{\"memories\":[{{\"name\":\"stable-kebab-key\",\"kind\":\"user|feedback|project|reference\",\"content\":\"fact\",\"description\":\"why durable\",\"confidence\":0.0,\"links\":[],\"pinned\":false,\"ttl_days\":null,\"evidence_refs\":[]}}]}} with at most 10 items. Return {{\"memories\":[]}} when nothing is durable."
358 );
359 let context = rendered_context_from_messages(vec![
360 Message::system("Extract durable, reusable facts from this completed session. Return only JSON; do not include transient progress or guesses."),
361 Message::user(prompt),
362 ]);
363 let state = self.opts.provider.create_run_state();
364 let mut stream = self
365 .opts
366 .provider
367 .stream(&context, &[], None, state.as_ref())
368 .await?;
369 let mut output = String::new();
370 while let Some(event) = stream.next().await {
371 if let StreamEvent::TextDelta { delta } = event? {
372 output.push_str(&delta);
373 }
374 }
375 Ok(crate::memory::parse_extracted_memories(
376 &output, session, scope,
377 ))
378 }
379
380 async fn log_memory_retrieval_result(
381 &self,
382 session_id: Option<&str>,
383 hits: Vec<MemoryRecall>,
384 ) {
385 let Some(session_id) = session_id.or(self.opts.session_id.as_deref()) else {
386 return;
387 };
388 self.log(
391 session_id,
392 SessionEvent::MemoryRetrievalResult { hits },
393 )
394 .await;
395 }
396
397 fn begin_memory_syscall(
398 &self,
399 event: KernelInputEvent,
400 ) -> (Arc<std::sync::Mutex<KernelRuntime>>, KernelStep) {
401 if let Some(active) = self.active_kernel.lock().unwrap().clone() {
402 if !active.lock().unwrap().is_terminal() {
403 let step = {
404 let mut kernel = active.lock().unwrap();
405 kernel.step(KernelInput::new(event))
406 };
407 return (active, step);
408 }
409 }
410
411 let mut kernel = KernelRuntime::new(KernelBudget {
412 max_tokens: self.opts.max_tokens,
413 max_turns: self.opts.max_turns.unwrap_or(25),
414 max_wall_ms: self.opts.timeout_ms,
415 ..Default::default()
416 });
417 if let Ok(profile) = assert_native_profile(self.opts.os_profile.clone()) {
418 kernel.step(KernelInput::new(
419 self.opts
420 .governance_policy
421 .clone()
422 .unwrap_or(profile.governance_policy)
423 .into_kernel_event(),
424 ));
425 let signal_policy = self.opts.signal_policy.unwrap_or(profile.signal_policy);
426 kernel.step(KernelInput::new(KernelInputEvent::SetSignalPolicy {
427 policy: signal_policy.into_kernel(),
428 }));
429 }
430 if let Some(scheduler_policy) = self.opts.scheduler_policy {
431 kernel.step(KernelInput::new(KernelInputEvent::ConfigureRun {
432 config: RunConfig {
433 scheduler_policy: Some(scheduler_policy),
434 ..RunConfig::default()
435 },
436 }));
437 }
438 if let Some(quota) = self.opts.resource_quota.clone() {
439 kernel.step(KernelInput::new(KernelInputEvent::SetResourceQuota {
440 quota,
441 }));
442 }
443 if let Some(policy) = self.opts.memory_policy.clone() {
444 kernel.step(KernelInput::new(memory_policy_event(policy)));
445 }
446 let kernel = Arc::new(std::sync::Mutex::new(kernel));
447 let step = kernel.lock().unwrap().step(KernelInput::new(event));
448 (kernel, step)
449 }
450
451 async fn append_memory_syscall_observations(
452 &self,
453 session_id: Option<&str>,
454 observations: Vec<KernelObservation>,
455 ) {
456 let Some(session_id) = session_id.or(self.opts.session_id.as_deref()) else {
457 return;
458 };
459 for obs in observations {
460 match obs {
461 KernelObservation::MemoryWritten {
462 turn,
463 record_id,
464 scope,
465 memory_kind,
466 name,
467 size_bytes,
468 } => {
469 self.log(
470 session_id,
471 SessionEvent::MemoryWritten {
472 turn,
473 record_id,
474 scope,
475 memory_kind,
476 name,
477 size_bytes,
478 },
479 )
480 .await;
481 }
482 KernelObservation::MemoryQueried {
483 turn,
484 scope,
485 query,
486 requested_k,
487 requires_async_response,
488 } => {
489 self.log(
490 session_id,
491 SessionEvent::MemoryQueried {
492 turn,
493 scope,
494 query,
495 requested_k,
496 requires_async_response,
497 },
498 )
499 .await;
500 }
501 KernelObservation::MemoryValidationFailed {
502 turn,
503 record_id,
504 error,
505 } => {
506 self.log(
507 session_id,
508 SessionEvent::MemoryValidationFailed {
509 turn,
510 record_id,
511 error,
512 },
513 )
514 .await;
515 }
516 _ => {}
517 }
518 }
519 }
520
521 pub async fn execute(&self, goal: &str) -> Result<String> {
522 collect_text(self.run_streaming(goal, &[], None, None).await?).await
523 }
524
525 pub async fn execute_with_criteria(&self, goal: &str, criteria: &[String]) -> Result<String> {
526 collect_text(self.run_streaming(goal, criteria, None, None).await?).await
527 }
528
529 pub async fn run_streaming<'a>(
530 &'a self,
531 goal: &'a str,
532 criteria: &'a [String],
533 extensions: Option<&'a serde_json::Value>,
534 session_id: Option<&'a str>,
535 ) -> Result<std::pin::Pin<Box<dyn futures::Stream<Item = Result<RunEvent>> + 'a>>> {
536 self.run_streaming_with_attachments(goal, criteria, extensions, session_id, &[])
537 .await
538 }
539
540 pub async fn run_streaming_with_attachments<'a>(
543 &'a self,
544 goal: &'a str,
545 criteria: &'a [String],
546 extensions: Option<&'a serde_json::Value>,
547 session_id: Option<&'a str>,
548 attachments: &'a [deepstrike_core::types::message::ContentPart],
549 ) -> Result<std::pin::Pin<Box<dyn futures::Stream<Item = Result<RunEvent>> + 'a>>> {
550 let session_id = session_id
551 .map(str::to_string)
552 .or_else(|| self.opts.session_id.clone())
553 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
554
555 let prior = self.read_entries(&session_id).await?;
556 let mid_run = is_mid_run(&prior);
557
558 if !mid_run {
559 self.log(
560 &session_id,
561 SessionEvent::RunStarted {
562 run_id: uuid::Uuid::new_v4().to_string(),
563 goal: goal.to_string(),
564 criteria: criteria.to_vec(),
565 agent_id: self.opts.agent_id.clone(),
566 system_prompt: self.opts.system_prompt.clone(),
567 attachments: attachments.to_vec(),
568 },
569 )
570 .await;
571 }
572
573 let goal_owned = goal.to_string();
574 let criteria_owned = criteria.to_vec();
575 let extensions_owned = extensions.cloned();
576 let attachments_owned = attachments.to_vec();
577 let prior_events = if prior.is_empty() { None } else { Some(prior) };
578
579 Ok(Box::pin(self.execute_inner(
580 session_id,
581 goal_owned,
582 criteria_owned,
583 extensions_owned,
584 prior_events,
585 mid_run,
586 attachments_owned,
587 )))
588 }
589
590 pub async fn wake_streaming(
591 &self,
592 session_id: &str,
593 extensions: Option<&serde_json::Value>,
594 ) -> Result<std::pin::Pin<Box<dyn futures::Stream<Item = Result<RunEvent>> + '_>>> {
595 let prior = self.read_entries(session_id).await?;
596 if prior
597 .iter()
598 .any(|e| matches!(e.event, SessionEvent::RunTerminal { .. }))
599 {
600 return Ok(Box::pin(futures::stream::empty()));
601 }
602 let start = prior
603 .iter()
604 .rev()
605 .find(|e| matches!(e.event, SessionEvent::RunStarted { .. }))
606 .ok_or_else(|| Error::Other(format!("no run_started for session: {session_id}")))?;
607
608 let (goal, criteria, attachments) = match &start.event {
609 SessionEvent::RunStarted {
610 goal,
611 criteria,
612 attachments,
613 ..
614 } => (goal.clone(), criteria.clone(), attachments.clone()),
615 _ => unreachable!(),
616 };
617
618 Ok(Box::pin(self.execute_inner(
619 session_id.to_string(),
620 goal,
621 criteria,
622 extensions.cloned(),
623 Some(prior),
624 true,
625 attachments,
626 )))
627 }
628
629 pub async fn wake(&self, session_id: &str) -> Result<String> {
630 collect_text(self.wake_streaming(session_id, None).await?).await
631 }
632
633 fn execute_inner(
634 &self,
635 session_id: String,
636 goal: String,
637 criteria: Vec<String>,
638 extensions: Option<serde_json::Value>,
639 prior_events: Option<Vec<SessionEntry>>,
640 resume_mid_run: bool,
641 attachments: Vec<deepstrike_core::types::message::ContentPart>,
642 ) -> impl futures::Stream<Item = Result<RunEvent>> + '_ {
643 try_stream! {
644 self.interrupted.store(false, Ordering::Relaxed);
645 self.cancellation_reason.store(0, Ordering::Relaxed);
646
647 if let Some(ks) = &self.opts.knowledge_source {
648 ks.init().await?;
649 }
650
651 let provider_policy = self.opts.provider.runtime_policy();
652 let effective_max_turns = self.opts.max_turns.or(provider_policy.max_turns).unwrap_or(25);
653 let effective_timeout = self.opts.timeout_ms.or(provider_policy.timeout_ms);
654 let policy = KernelBudget {
655 max_tokens: self.opts.max_tokens,
656 max_turns: effective_max_turns,
657 max_wall_ms: effective_timeout,
658 ..Default::default()
659 };
660
661 let mut kernel = std::sync::Arc::new(std::sync::Mutex::new(KernelRuntime::new(policy)));
662 {
663 let mut active = self.active_kernel.lock().unwrap();
664 *active = Some(kernel.clone());
665 }
666
667 struct ActiveKernelGuard<'a> {
668 runner: &'a RuntimeRunner,
669 }
670 impl<'a> Drop for ActiveKernelGuard<'a> {
671 fn drop(&mut self) {
672 if let Ok(mut active) = self.runner.active_kernel.lock() {
673 *active = None;
674 }
675 }
676 }
677 let _guard = ActiveKernelGuard { runner: self };
678
679 let kernel_apply = |kernel_arc: &mut std::sync::Arc<std::sync::Mutex<KernelRuntime>>, pending: &mut Vec<KernelObservation>, event| {
680 kernel_apply(&mut *kernel_arc.lock().unwrap(), pending, event)
681 };
682 let kernel_action = |kernel_arc: &mut std::sync::Arc<std::sync::Mutex<KernelRuntime>>, pending: &mut Vec<KernelObservation>, event| {
683 kernel_action(&mut *kernel_arc.lock().unwrap(), pending, event)
684 };
685
686 let mut pending_observations = Vec::new();
687 let mut pending_page_out_starts = std::collections::VecDeque::new();
688 let mut active_page_out_start = None;
689
690 if self.opts.kernel_reliability.is_some() || self.opts.scheduler_policy.is_some() {
691 let mut step = kernel.lock().unwrap().step(KernelInput::new(
692 KernelInputEvent::ConfigureRun {
693 config: RunConfig {
694 reliability: self.opts.kernel_reliability.clone(),
695 scheduler_policy: self.opts.scheduler_policy,
696 ..RunConfig::default()
697 },
698 },
699 ));
700 if let Some(fault) = step.faults.pop() {
701 Err(Error::Other(format!(
702 "kernel {:?}: {}",
703 fault.code, fault.message
704 )))?;
705 }
706 pending_observations.append(&mut step.observations);
707 }
708
709 if let Some(tokenizer_name) = &self.opts.tokenizer {
710 kernel_apply(
711 &mut kernel,
712 &mut pending_observations,
713 KernelInputEvent::SetTokenizer {
714 name: tokenizer_name.clone(),
715 },
716 );
717 }
718 if let Some(enabled) = self.opts.enable_plan_tool {
719 kernel_apply(
720 &mut kernel,
721 &mut pending_observations,
722 KernelInputEvent::SetPlanToolEnabled { enabled },
723 );
724 }
725
726 kernel_apply(
727 &mut kernel,
728 &mut pending_observations,
729 KernelInputEvent::SetTools {
730 tools: self.plane.schemas(),
731 },
732 );
733
734 if self.opts.dream_store.is_some() && self.opts.agent_id.is_some() {
735 kernel_apply(
736 &mut kernel,
737 &mut pending_observations,
738 KernelInputEvent::SetMemoryEnabled { enabled: true },
739 );
740 }
741 if self.opts.knowledge_source.is_some() {
742 kernel_apply(
743 &mut kernel,
744 &mut pending_observations,
745 KernelInputEvent::SetKnowledgeEnabled { enabled: true },
746 );
747 }
748
749 if let Some(sp) = &self.opts.system_prompt {
750 let tokens = ((sp.len() / 4) as u32).max(1);
751 kernel_apply(
752 &mut kernel,
753 &mut pending_observations,
754 KernelInputEvent::AddSystemMessage {
755 content: sp.clone(),
756 tokens,
757 },
758 );
759 }
760 for mem in &self.opts.initial_memory {
761 let tokens = ((mem.len() / 4) as u32).max(1);
762 kernel_apply(
763 &mut kernel,
764 &mut pending_observations,
765 KernelInputEvent::AddKnowledgeMessage {
766 content: mem.clone(),
767 tokens,
768 key: None,
769 pinned: false,
770 },
771 );
772 }
773
774 let skill_watcher = self.opts.skill_dir.as_deref().and_then(SkillWatcher::start);
775 if let Some(skill_dir) = &self.opts.skill_dir {
776 kernel_apply(
777 &mut kernel,
778 &mut pending_observations,
779 KernelInputEvent::SetAvailableSkills {
780 skills: scan_skill_dir(skill_dir),
781 },
782 );
783 }
784
785 if !self.opts.stable_core_tool_ids.is_empty() {
787 kernel_apply(
788 &mut kernel,
789 &mut pending_observations,
790 KernelInputEvent::SetStableCoreTools {
791 tool_ids: self.opts.stable_core_tool_ids.clone(),
792 },
793 );
794 }
795
796 if let Some(milestones) = self.opts.milestone_contract.clone() {
797 kernel_apply(
798 &mut kernel,
799 &mut pending_observations,
800 KernelInputEvent::LoadMilestoneContract { contract: milestones },
801 );
802 }
803
804 let max_bytes = {
805 let k = kernel.lock().unwrap();
806 k.recovery_content_bytes()
807 };
808
809 if let Some(ref events) = prior_events {
810 let repaired = repair_entries_with_cap(events, max_bytes);
811 seed_provider_replay_from_events(self.opts.provider.as_ref(), &repaired);
812
813 let messages = if let Some(ref store) = self.opts.compression_store {
814 let store_clone = store.clone();
815 replay_messages_with_cap_and_loader(&repaired, max_bytes, move |archive_ref| {
816 store_clone.read(archive_ref).map_err(|_| {
817 deepstrike_core::context::fault::ContextFault::MissingArchive {
818 session_id: String::new(),
819 seq: 0,
820 }
821 })
822 })
823 } else {
824 replay_messages_with_cap(&repaired, max_bytes)
825 };
826
827 let reactivate: Vec<String> = messages
831 .iter()
832 .flat_map(|m| m.tool_calls.iter())
833 .filter(|c| c.name.as_str() == "skill")
834 .filter_map(|c| c.arguments.get("name").and_then(|v| v.as_str()).map(str::to_string))
835 .collect();
836
837 kernel_apply(
838 &mut kernel,
839 &mut pending_observations,
840 KernelInputEvent::PreloadHistory {
841 messages,
842 },
843 );
844
845 for name in reactivate {
846 kernel_apply(
847 &mut kernel,
848 &mut pending_observations,
849 KernelInputEvent::SkillActivated { name, lease_turns: None },
850 );
851 }
852 }
853
854 let ext = merge_extensions(self.opts.extensions.as_ref(), extensions.as_ref());
855 let provider_state = self.opts.provider.create_run_state();
856 let mut next_archive_start = next_archived_seq_start(prior_events.as_deref());
857 let mut active_skill: Option<String> = None;
859 let session_start_ms = std::time::SystemTime::now()
860 .duration_since(std::time::UNIX_EPOCH)
861 .unwrap_or_default()
862 .as_millis() as u64;
863
864 let os_profile = assert_native_profile(self.opts.os_profile.clone())?;
865 let governance_policy = self
866 .opts
867 .governance_policy
868 .clone()
869 .unwrap_or(os_profile.governance_policy);
870 kernel_apply(
871 &mut kernel,
872 &mut pending_observations,
873 governance_policy.into_kernel_event(),
874 );
875
876 let signal_policy = self
877 .opts
878 .signal_policy
879 .unwrap_or(os_profile.signal_policy);
880 kernel_apply(
881 &mut kernel,
882 &mut pending_observations,
883 KernelInputEvent::SetSignalPolicy {
884 policy: signal_policy.into_kernel(),
885 },
886 );
887
888 if let Some(quota) = self.opts.resource_quota.clone() {
889 kernel_apply(
890 &mut kernel,
891 &mut pending_observations,
892 KernelInputEvent::SetResourceQuota { quota },
893 );
894 }
895
896 if let Some(policy) = self.opts.memory_policy.clone() {
897 kernel_apply(
898 &mut kernel,
899 &mut pending_observations,
900 memory_policy_event(policy),
901 );
902 }
903
904 if !resume_mid_run && !attachments.is_empty() {
906 kernel_apply(
907 &mut kernel,
908 &mut pending_observations,
909 KernelInputEvent::AddHistoryMessage {
910 message: Message::user_multimodal(attachments.clone()),
911 tokens: None,
912 },
913 );
914 }
915
916 if !resume_mid_run {
920 if let (Some(pre), Some(store), Some(agent_id)) = (
921 self.opts.pre_query_memory.clone(),
922 self.opts.dream_store.as_ref(),
923 self.opts.agent_id.as_deref(),
924 ) {
925 let queries = pre(goal.as_str());
926 let mut recalled = Vec::new();
927 for q in &queries {
928 if q.query.trim().is_empty() {
929 continue;
930 }
931 if let Ok(hits) = store.search(agent_id, q).await {
932 for hit in hits {
933 recalled.push(format!(
934 "[memory record_id={} trust={} score={:.3}] {}",
935 hit.record.record_id,
936 match hit.record.provenance.trust {
937 MemoryTrustLevel::Untrusted => "untrusted",
938 MemoryTrustLevel::UserAsserted => "user_asserted",
939 MemoryTrustLevel::HostVerified => "host_verified",
940 },
941 hit.score,
942 hit.record.content
943 ));
944 }
945 }
946 }
947 if !recalled.is_empty() {
948 kernel_apply(
949 &mut kernel,
950 &mut pending_observations,
951 KernelInputEvent::AddHistoryMessage {
952 message: Message::user(recalled.join("\n")),
953 tokens: None,
954 },
955 );
956 }
957 }
958 }
959
960 let mut action = if resume_mid_run {
961 kernel_action(
962 &mut kernel,
963 &mut pending_observations,
964 KernelInputEvent::Resume,
965 )
966 } else {
967 let run_spec = build_run_spec(
970 self.opts.run_spec.clone(),
971 self.opts.allowed_tool_ids.as_deref(),
972 self.opts.agent_id.as_deref(),
973 &session_id,
974 &goal,
975 );
976 kernel_action(
977 &mut kernel,
978 &mut pending_observations,
979 KernelInputEvent::StartRun {
980 task: RuntimeTask::new(&goal).with_criteria(criteria),
981 run_spec,
982 },
983 )
984 };
985
986 let mut last_skill_version: u64 = skill_watcher.as_ref().map(|w| w.version()).unwrap_or(0);
987
988 while !kernel.lock().unwrap().is_terminal() {
989 if let (Some(watcher), Some(skill_dir)) =
991 (&skill_watcher, &self.opts.skill_dir)
992 {
993 let cur = watcher.version();
994 if cur != last_skill_version {
995 last_skill_version = cur;
996 kernel_apply(
997 &mut kernel,
998 &mut pending_observations,
999 KernelInputEvent::SetAvailableSkills {
1000 skills: scan_skill_dir(skill_dir),
1001 },
1002 );
1003 }
1004 }
1005
1006 next_archive_start = self
1007 .append_observations(
1008 &session_id,
1009 &kernel,
1010 &mut pending_observations,
1011 &mut pending_page_out_starts,
1012 next_archive_start,
1013 )
1014 .await;
1015
1016 if self.interrupted.load(Ordering::Relaxed) {
1017 kernel_apply(
1018 &mut kernel,
1019 &mut pending_observations,
1020 KernelInputEvent::CancelOperation {
1021 operation_id: "local-operation".into(),
1022 reason: cancellation_reason_from_code(self.cancellation_reason.load(Ordering::Relaxed)),
1023 pending_call_ids: pending_call_ids(&action),
1024 },
1025 );
1026 break;
1027 }
1028
1029 if let Some(ss) = &self.opts.signal_source {
1030 if let Some(claim) = ss.claim_signal().await? {
1031 let urgency = match claim.signal.urgency.as_str() {
1032 "low" => Urgency::Low,
1033 "high" => Urgency::High,
1034 "critical" => Urgency::Critical,
1035 _ => Urgency::Normal,
1036 };
1037 let source = match claim.signal.source.as_str() {
1038 "cron" => KernelSignalSource::Cron,
1039 "gateway" => KernelSignalSource::Gateway,
1040 "heartbeat" => KernelSignalSource::Heartbeat,
1041 _ => KernelSignalSource::Custom,
1042 };
1043 let signal_type = match claim.signal.signal_type.as_str() {
1044 "job" => KernelSignalType::Job,
1045 "alert" => KernelSignalType::Alert,
1046 _ => KernelSignalType::Event,
1047 };
1048 let summary = claim
1049 .signal
1050 .payload
1051 .get("goal")
1052 .and_then(serde_json::Value::as_str)
1053 .unwrap_or("signal");
1054 let mut kernel_sig = KernelSignal::new(
1055 source,
1056 signal_type,
1057 urgency,
1058 summary,
1059 )
1060 .with_payload(claim.signal.payload.clone())
1061 .with_timestamp(
1062 std::time::SystemTime::now()
1063 .duration_since(std::time::UNIX_EPOCH)
1064 .unwrap_or_default()
1065 .as_millis() as u64,
1066 );
1067 kernel_sig.id = uuid::Uuid::parse_str(&claim.signal_id).map_err(|_| {
1068 crate::Error::Other("signal claim requires a UUID signal_id".into())
1069 })?;
1070 if let Some(dedupe_key) = &claim.signal.dedupe_key {
1071 kernel_sig = kernel_sig.with_dedupe(dedupe_key.clone());
1072 }
1073 if let Some(recipient) = &claim.signal.recipient {
1074 kernel_sig = kernel_sig.with_recipient(recipient.clone());
1075 }
1076 if let Some(deadline_ms) = claim.signal.deadline_ms {
1077 kernel_sig = kernel_sig.with_deadline(deadline_ms);
1078 }
1079 if let Some(coalesce_key) = &claim.signal.coalesce_key {
1080 kernel_sig = kernel_sig.with_coalesce(coalesce_key.clone());
1081 }
1082 kernel_sig.coalesced_count = claim.signal.coalesced_count.max(1);
1083 let mut kguard = kernel.lock().unwrap();
1088 let mut step = kguard.step(KernelInput::new(KernelInputEvent::DeliverSignal {
1089 delivery_id: claim.delivery_id.clone(),
1090 attempt: claim.delivery_attempt,
1091 signal: kernel_sig,
1092 }));
1093 drop(kguard);
1094 let disposition_matches = step.observations.iter().filter(|observation| matches!(
1095 observation,
1096 KernelObservation::SignalDeliveryDisposed {
1097 delivery_id,
1098 attempt,
1099 ..
1100 } if delivery_id == &claim.delivery_id && *attempt == claim.delivery_attempt
1101 )).count() == 1;
1102 let receipt = SignalDeliveryReceipt {
1103 delivery_id: claim.delivery_id,
1104 lease_token: claim.lease_token,
1105 };
1106 if !step.faults.is_empty() || !disposition_matches {
1107 let _ = ss.nack_signal(&receipt).await?;
1108 Err(crate::Error::Other(
1109 "kernel did not return the matching signal delivery disposition".into(),
1110 ))?;
1111 }
1112 if !ss.ack_signal(&receipt).await? {
1113 let _ = ss.nack_signal(&receipt).await?;
1114 Err(crate::Error::Other(
1115 "signal lease was lost before acknowledgement".into(),
1116 ))?;
1117 }
1118 pending_observations.append(&mut step.observations);
1119 if let Some(sig_action) = step.actions.pop() {
1120 action = sig_action;
1121 }
1122 }
1124 }
1125 if kernel.lock().unwrap().is_terminal() {
1126 break;
1127 }
1128
1129 match &action.effect {
1130 KernelEffect::CallProvider { context, tools } => {
1131 let provider_effect_id = action.effect_id.clone();
1132 let mut final_text = String::new();
1133 let mut final_tool_calls: Vec<ToolCall> = Vec::new();
1134 let mut turn_tokens: u32 = 0;
1135 let mut turn_input_tokens: u32 = 0;
1136 let mut turn_cache_read_tokens: u32 = 0;
1137 let mut turn_cache_creation_tokens: u32 = 0;
1138 let mut turn_cache_read_by_slot: Option<crate::providers::CacheReadBySlot> = None;
1139 let mut turn_stop_reason: Option<String> = None;
1140 let (filtered_tools, filtered_context_storage);
1144 let (provider_tools, provider_context): (&[_], &_) = if let Some(policy) = self.opts.governance_policy.as_ref() {
1145 if policy.surface_denied_in_system {
1146 let (allowed, denied) = crate::runtime::governance_filter_schema(tools, policy);
1147 if !denied.is_empty() {
1148 filtered_tools = allowed;
1149 let mut cloned = context.clone();
1150 let note = format!("[governance] the following tools are denied for this run and will fail if called: {}.", denied.join(", "));
1151 cloned.system_knowledge = if cloned.system_knowledge.is_empty() {
1152 note
1153 } else {
1154 format!("{}\n\n{}", cloned.system_knowledge, note)
1155 };
1156 filtered_context_storage = cloned;
1157 (&filtered_tools[..], &filtered_context_storage)
1158 } else {
1159 (&tools[..], context)
1160 }
1161 } else { (&tools[..], context) }
1162 } else { (&tools[..], context) };
1163 let tools_exposed = provider_tools.len();
1166
1167 let mut provider_stream = match self
1168 .opts
1169 .provider
1170 .stream(provider_context, provider_tools, ext.as_ref(), provider_state.as_ref())
1171 .await
1172 {
1173 Ok(s) => s,
1174 Err(e) => {
1175 let msg = e.to_string();
1182 action = kernel_action(
1183 &mut kernel,
1184 &mut pending_observations,
1185 KernelInputEvent::ProviderError {
1186 effect_id: provider_effect_id.clone(),
1187 message: msg.clone(),
1188 },
1189 );
1190 if matches!(&action.effect, KernelEffect::Done { .. }) {
1197 yield RunEvent::Error(msg);
1198 }
1199 continue;
1200 }
1201 };
1202
1203 while let Some(evt) = provider_stream.next().await {
1204 if self.interrupted.load(Ordering::Relaxed) {
1205 break;
1206 }
1207 match evt? {
1208 StreamEvent::TextDelta { delta } => {
1209 final_text.push_str(&delta);
1210 yield RunEvent::TextDelta(delta);
1211 }
1212 StreamEvent::ThinkingDelta { delta } => {
1213 yield RunEvent::ThinkingDelta(delta);
1214 }
1215 StreamEvent::ToolCall { id, name, arguments } => {
1216 yield RunEvent::ToolCall { id: id.clone(), name: name.clone() };
1217 final_tool_calls.push(ToolCall {
1218 id: compact_str::CompactString::new(&id),
1219 name: compact_str::CompactString::new(&name),
1220 arguments,
1221 });
1222 }
1223 StreamEvent::Usage {
1224 total_tokens,
1225 input_tokens,
1226 cache_read_input_tokens,
1227 cache_creation_input_tokens,
1228 cache_read_input_tokens_by_slot,
1229 stop_reason,
1230 ..
1231 } => {
1232 turn_tokens = total_tokens;
1233 turn_input_tokens = input_tokens;
1235 turn_cache_read_tokens = cache_read_input_tokens;
1236 turn_cache_creation_tokens = cache_creation_input_tokens;
1237 turn_cache_read_by_slot = cache_read_input_tokens_by_slot;
1238 if stop_reason.is_some() { turn_stop_reason = stop_reason; }
1240 }
1241 StreamEvent::Done => {}
1242 }
1243 }
1244
1245 if self.interrupted.load(Ordering::Relaxed) {
1246 action = kernel_action(
1247 &mut kernel,
1248 &mut pending_observations,
1249 KernelInputEvent::CancelOperation {
1250 operation_id: "local-operation".into(),
1251 reason: cancellation_reason_from_code(self.cancellation_reason.load(Ordering::Relaxed)),
1252 pending_call_ids: vec![provider_effect_id],
1253 },
1254 );
1255 break;
1256 }
1257
1258 let mut assistant = Message {
1259 role: deepstrike_core::types::message::Role::Assistant,
1260 content: deepstrike_core::types::message::Content::Text(final_text.clone()),
1261 tool_calls: final_tool_calls.clone(),
1262 token_count: if turn_tokens > 0 { Some(turn_tokens) } else { None },
1263 };
1264
1265 self.opts.provider.commit_stream_replay(&final_text, &final_tool_calls);
1266 let mut provider_replay = peek_provider_replay(
1267 self.opts.provider.as_ref(),
1268 &final_text,
1269 &final_tool_calls,
1270 );
1271 repair_llm_completed(&mut assistant, &mut provider_replay);
1272
1273 action = kernel_action(
1274 &mut kernel,
1275 &mut pending_observations,
1276 KernelInputEvent::ProviderResult {
1277 effect_id: provider_effect_id,
1278 message: assistant.clone(),
1279 observed_input_tokens: None,
1280 observed_output_tokens: None,
1281 now_ms: None,
1284 stop_reason: turn_stop_reason.clone(),
1286 },
1287 );
1288 self.log(
1289 &session_id,
1290 SessionEvent::LlmCompleted {
1291 turn: kernel.lock().unwrap().turn(),
1292 message: assistant,
1293 provider_replay,
1294 },
1295 )
1296 .await;
1297
1298 if let Some(ref sink) = self.opts.on_turn_metrics {
1302 sink(TurnMetrics {
1303 turn: kernel.lock().unwrap().turn(),
1304 tools_exposed,
1305 tools_called: final_tool_calls.len(),
1306 active_skill: active_skill.clone(),
1307 input_tokens: turn_input_tokens,
1308 cache_read_tokens: turn_cache_read_tokens,
1309 cache_creation_tokens: turn_cache_creation_tokens,
1310 cache_read_tokens_by_slot: turn_cache_read_by_slot.clone(),
1311 });
1312 }
1313 if let Some(skill_call) =
1314 final_tool_calls.iter().find(|c| c.name.as_str() == "skill")
1315 {
1316 if let Some(name) = skill_call.arguments.get("name").and_then(|v| v.as_str()) {
1317 active_skill = Some(name.to_string());
1318 }
1319 }
1320 }
1321 KernelEffect::RequestApproval { requests } => {
1322 let approval_effect_id = action.effect_id.clone();
1323 let mut approved_calls = Vec::new();
1324 let mut denied_calls = Vec::new();
1325 for request in requests {
1326 let arguments = request.arguments.to_string();
1327 self.log(
1328 &session_id,
1329 SessionEvent::PermissionRequested {
1330 turn: kernel.lock().unwrap().turn(),
1331 tool: request.tool.clone(),
1332 arguments: arguments.clone(),
1333 reason: Some(request.reason.clone()),
1334 },
1335 )
1336 .await;
1337 yield RunEvent::PermissionRequest {
1338 call_id: request.call_id.clone(),
1339 tool_name: request.tool.clone(),
1340 arguments: arguments.clone(),
1341 reason: request.reason.clone(),
1342 };
1343
1344 let response = match &self.opts.on_permission_request {
1345 Some(handler) => match handler(PermissionRequest {
1346 call_id: request.call_id.clone(),
1347 tool_name: request.tool.clone(),
1348 arguments,
1349 reason: request.reason.clone(),
1350 })
1351 .await
1352 {
1353 Ok(response) => response,
1354 Err(err) => PermissionResponse {
1355 approved: false,
1356 responder: "permission_handler".to_string(),
1357 reason: Some(format!("permission handler failed: {err}")),
1358 },
1359 },
1360 None => PermissionResponse {
1361 approved: false,
1362 responder: "policy_gate".to_string(),
1363 reason: Some("no permission handler configured".to_string()),
1364 },
1365 };
1366 if response.approved {
1367 approved_calls.push(request.call_id.clone());
1368 } else {
1369 denied_calls.push(request.call_id.clone());
1370 }
1371 let responder = if response.responder.is_empty() {
1372 "host".to_string()
1373 } else {
1374 response.responder
1375 };
1376 self.log(
1377 &session_id,
1378 SessionEvent::PermissionResolved {
1379 turn: kernel.lock().unwrap().turn(),
1380 approved: response.approved,
1381 responder: responder.clone(),
1382 },
1383 )
1384 .await;
1385 yield RunEvent::PermissionResolved {
1386 call_id: request.call_id.clone(),
1387 tool_name: request.tool.clone(),
1388 approved: response.approved,
1389 responder,
1390 reason: response.reason,
1391 };
1392 }
1393 action = kernel_action(
1394 &mut kernel,
1395 &mut pending_observations,
1396 KernelInputEvent::ApprovalResult {
1397 effect_id: approval_effect_id,
1398 approved_calls,
1399 denied_calls,
1400 error: None,
1401 },
1402 );
1403 }
1404 KernelEffect::SpawnWorkflow { nodes, .. } => {
1405 let workflow_effect_id = action.effect_id.clone();
1409 let failures = nodes
1410 .into_iter()
1411 .map(|node| deepstrike_core::runtime::kernel::WorkflowSpawnFailure {
1412 agent_id: node.agent_id.clone(),
1413 error: "Rust RuntimeRunner has no workflow orchestrator".to_string(),
1414 })
1415 .collect();
1416 action = kernel_action(
1417 &mut kernel,
1418 &mut pending_observations,
1419 KernelInputEvent::WorkflowSpawnResult {
1420 effect_id: workflow_effect_id,
1421 started_agent_ids: Vec::new(),
1422 failures,
1423 error: None,
1424 },
1425 );
1426 }
1427 KernelEffect::PreemptSubAgents { .. } => {
1428 let preempt_effect_id = action.effect_id.clone();
1431 action = kernel_action(
1432 &mut kernel,
1433 &mut pending_observations,
1434 KernelInputEvent::PreemptResult {
1435 effect_id: preempt_effect_id,
1436 error: None,
1437 },
1438 );
1439 }
1440 KernelEffect::PersistMemory { .. } => {
1441 let effect_id = action.effect_id.clone();
1442 action = kernel_action(
1443 &mut kernel,
1444 &mut pending_observations,
1445 KernelInputEvent::MemoryPersistResult {
1446 effect_id,
1447 error: Some(
1448 "memory effects must be executed by RuntimeRunner::write_memory"
1449 .to_string(),
1450 ),
1451 },
1452 );
1453 }
1454 KernelEffect::QueryMemory { .. } => {
1455 let effect_id = action.effect_id.clone();
1456 action = kernel_action(
1457 &mut kernel,
1458 &mut pending_observations,
1459 KernelInputEvent::MemoryQueryResult {
1460 effect_id,
1461 hits: Vec::new(),
1462 error: Some(
1463 "memory effects must be executed by RuntimeRunner::query_memory"
1464 .to_string(),
1465 ),
1466 },
1467 );
1468 }
1469 KernelEffect::SpoolLargeResult { call_id, output, .. } => {
1470 let effect_id = action.effect_id.clone();
1471 let spool_dir = self.opts.spool_dir.as_deref().unwrap_or_else(|| std::path::Path::new(".spool"));
1472 let (spool_ref, error) = match crate::runtime::large_result_spool::persist_output(spool_dir, call_id, output) {
1473 Ok(path) => (Some(path), None),
1474 Err(error) => (None, Some(error.to_string())),
1475 };
1476 action = kernel_action(
1477 &mut kernel,
1478 &mut pending_observations,
1479 KernelInputEvent::LargeResultSpoolResult { effect_id, spool_ref, error },
1480 );
1481 }
1482 KernelEffect::ArchivePageOut { archived, tier, action: pressure_action, .. } => {
1483 let effect_id = action.effect_id.clone();
1484 let archived = archived.clone();
1485 let tier = tier.clone();
1486 let action_name = action_str_of(*pressure_action);
1487 let archive_start = *active_page_out_start.get_or_insert_with(|| {
1488 pending_page_out_starts.pop_front().unwrap_or(next_archive_start)
1489 });
1490 let archive_result = if let Some(store) = &self.opts.compression_store {
1491 store.write(&session_id, archive_start, &archived)
1492 .map(|path| (!path.is_empty()).then_some(path))
1493 } else {
1494 Ok(None)
1495 };
1496 let (archive_ref, error) = match archive_result {
1497 Ok(archive_ref) => {
1498 self.local_page_out_cache.lock().unwrap().extend(archived.clone());
1499 if tier == "semantic" {
1500 self.archive_semantic_page_out(archived, Some(action_name)).await;
1501 }
1502 (archive_ref, None)
1503 }
1504 Err(error) => (None, Some(error.to_string())),
1505 };
1506 if error.is_none() {
1507 active_page_out_start = None;
1508 }
1509 action = kernel_action(
1510 &mut kernel,
1511 &mut pending_observations,
1512 KernelInputEvent::PageOutArchiveResult { effect_id, archive_ref, error },
1513 );
1514 }
1515 KernelEffect::ExecuteTool { calls } => {
1516 let tool_effect_id = action.effect_id.clone();
1517 let tool_calls = calls.clone();
1518 self.log(
1519 &session_id,
1520 SessionEvent::ToolRequested {
1521 turn: kernel.lock().unwrap().turn(),
1522 calls: tool_calls.clone(),
1523 },
1524 )
1525 .await;
1526
1527 if let Some(gov) = &self.opts.governance {
1528 let mut g = gov.lock().await;
1529 if let Some(aid) = &self.opts.agent_id {
1530 g.set_identity(aid, &session_id);
1531 }
1532 }
1533
1534 let run_ctx = RunContext {
1535 agent_id: self.opts.agent_id.as_deref(),
1536 memory_scope: self.opts.memory_scope.as_ref(),
1537 skill_dir: self.opts.skill_dir.as_deref(),
1538 dream_store: self.opts.dream_store.as_deref(),
1539 knowledge_source: self.opts.knowledge_source.as_deref(),
1540 governance: self.opts.governance.clone(),
1541 on_tool_suspend: self.opts.on_tool_suspend.clone(),
1542 on_permission_request: self.opts.on_permission_request.clone(),
1543 };
1544
1545 let mut tool_results = Vec::new();
1546 let mut normal_calls = Vec::new();
1547 let mut plan_calls = Vec::new();
1548
1549 for call in &tool_calls {
1550 if call.name == "update_plan" {
1551 plan_calls.push(call);
1552 } else {
1553 normal_calls.push(call.clone());
1554 }
1555 }
1556
1557 for call in plan_calls {
1558 let update = parse_update_plan_args(&call.arguments);
1559 kernel_apply(
1560 &mut kernel,
1561 &mut pending_observations,
1562 KernelInputEvent::UpdateTask { update },
1563 );
1564 tool_results.push(deepstrike_core::types::message::ToolResult {
1565 call_id: call.id.clone(),
1566 output: deepstrike_core::types::message::Content::Text("success".to_string()),
1567 is_error: false,
1568 is_fatal: false,
1569 error_kind: None,
1570 token_count: None,
1571 });
1572 yield RunEvent::ToolResult {
1573 call_id: call.id.to_string(),
1574 content: "success".to_string(),
1575 is_error: false,
1576 is_fatal: false,
1577 error_kind: None,
1578 };
1579 }
1580
1581 if !normal_calls.is_empty() {
1582 let plane_stream = self.plane.execute_all(&normal_calls, run_ctx);
1583 let mut stream = plane_stream;
1584 while let Some(evt) = stream.next().await {
1585 match evt? {
1586 RunEvent::ToolResult {
1587 call_id,
1588 content,
1589 is_error,
1590 is_fatal,
1591 error_kind,
1592 } => {
1593 tool_results.push(deepstrike_core::types::message::ToolResult {
1594 call_id: compact_str::CompactString::new(&call_id),
1595 output: deepstrike_core::types::message::Content::Text(content),
1596 is_error,
1597 is_fatal,
1598 error_kind,
1599 token_count: None,
1600 });
1601 }
1602 RunEvent::ToolArgumentRepaired { call_id, name, original_arguments, repaired_arguments } => {
1603 self.log(
1604 &session_id,
1605 SessionEvent::ToolArgumentRepaired {
1606 turn: kernel.lock().unwrap().turn(),
1607 tool: name.clone(),
1608 original_arguments: original_arguments.clone(),
1609 repaired_arguments: repaired_arguments.clone(),
1610 },
1611 )
1612 .await;
1613 yield RunEvent::ToolArgumentRepaired {
1614 call_id,
1615 name,
1616 original_arguments,
1617 repaired_arguments,
1618 };
1619 }
1620 RunEvent::ToolDenied { call_id, tool_name, reason } => {
1621 self.log(
1622 &session_id,
1623 SessionEvent::ToolDenied {
1624 turn: kernel.lock().unwrap().turn(),
1625 call_id: call_id.clone(),
1626 tool_name: tool_name.clone(),
1627 reason: reason.clone(),
1628 },
1629 )
1630 .await;
1631 yield RunEvent::ToolDenied { call_id, tool_name, reason };
1632 }
1633 RunEvent::PermissionRequest { call_id, tool_name, arguments, reason } => {
1634 let turn = kernel.lock().unwrap().turn();
1635 self.log(
1636 &session_id,
1637 SessionEvent::PermissionRequested {
1638 turn,
1639 tool: tool_name.clone(),
1640 arguments: arguments.clone(),
1641 reason: Some(reason.clone()),
1642 },
1643 )
1644 .await;
1645 yield RunEvent::PermissionRequest { call_id, tool_name, arguments, reason };
1646 }
1647 RunEvent::PermissionResolved { call_id, tool_name, approved, responder, reason } => {
1648 let turn = kernel.lock().unwrap().turn();
1649 self.log(
1650 &session_id,
1651 SessionEvent::PermissionResolved {
1652 turn,
1653 approved,
1654 responder: responder.clone(),
1655 },
1656 )
1657 .await;
1658 yield RunEvent::PermissionResolved { call_id, tool_name, approved, responder, reason };
1659 }
1660 other => yield other,
1661 }
1662 }
1663 let names: Vec<String> = normal_calls.iter().map(|c| c.name.to_string()).collect();
1664 kernel_apply(
1665 &mut kernel,
1666 &mut pending_observations,
1667 KernelInputEvent::UpdateTask {
1668 update: TaskUpdate {
1669 progress: Some(format!("Executed tools: {}", names.join(", "))),
1670 ..Default::default()
1671 },
1672 },
1673 );
1674 }
1675
1676 self.log(
1677 &session_id,
1678 SessionEvent::ToolCompleted {
1679 turn: kernel.lock().unwrap().turn(),
1680 results: tool_results.clone(),
1681 },
1682 )
1683 .await;
1684
1685 for call in &tool_calls {
1688 if call.name.as_str() != "skill" {
1689 continue;
1690 }
1691 let ok = tool_results
1692 .iter()
1693 .any(|r| r.call_id.as_str() == call.id.as_str() && !r.is_error);
1694 if !ok {
1695 continue;
1696 }
1697 if let Some(name) = call.arguments.get("name").and_then(|v| v.as_str()) {
1698 kernel_apply(
1699 &mut kernel,
1700 &mut pending_observations,
1701 KernelInputEvent::SkillActivated { name: name.to_string(), lease_turns: None },
1702 );
1703 }
1704 }
1705
1706 action = kernel_action(
1707 &mut kernel,
1708 &mut pending_observations,
1709 KernelInputEvent::ToolResults {
1710 effect_id: tool_effect_id,
1711 results: tool_results,
1712 },
1713 );
1714 }
1715 KernelEffect::EvaluateMilestone {
1716 phase_id,
1717 criteria,
1718 required_evidence,
1719 ..
1720 } => {
1721 let milestone_effect_id = action.effect_id.clone();
1722 let policy = self.opts.milestone_policy;
1723 if policy == MilestonePolicy::AutoPass {
1724 let result = MilestoneCheckResult::pass(phase_id.clone());
1725 action = kernel_action(
1726 &mut kernel,
1727 &mut pending_observations,
1728 KernelInputEvent::MilestoneResult {
1729 effect_id: milestone_effect_id.clone(),
1730 result,
1731 },
1732 );
1733 next_archive_start = self
1734 .append_observations(
1735 &session_id,
1736 &kernel,
1737 &mut pending_observations,
1738 &mut pending_page_out_starts,
1739 next_archive_start,
1740 )
1741 .await;
1742 } else if let Some(handler) = &self.opts.on_milestone_evaluate {
1743 let context = MilestoneEvaluationContext {
1744 phase_id: phase_id.clone(),
1745 criteria: criteria.clone(),
1746 required_evidence: required_evidence.clone(),
1747 };
1748 let check_future = handler(context);
1749 let result = check_future.await?;
1750 action = kernel_action(
1751 &mut kernel,
1752 &mut pending_observations,
1753 KernelInputEvent::MilestoneResult {
1754 effect_id: milestone_effect_id,
1755 result,
1756 },
1757 );
1758 next_archive_start = self
1759 .append_observations(
1760 &session_id,
1761 &kernel,
1762 &mut pending_observations,
1763 &mut pending_page_out_starts,
1764 next_archive_start,
1765 )
1766 .await;
1767 } else {
1768 next_archive_start = self
1769 .append_observations(
1770 &session_id,
1771 &kernel,
1772 &mut pending_observations,
1773 &mut pending_page_out_starts,
1774 next_archive_start,
1775 )
1776 .await;
1777 self.log(
1778 &session_id,
1779 SessionEvent::RunTerminal {
1780 reason: "milestone_pending".to_string(),
1781 turns_used: kernel.lock().unwrap().turn().max(1),
1782 total_tokens: 0,
1783 },
1784 )
1785 .await;
1786 yield RunEvent::Done {
1787 iterations: kernel.lock().unwrap().turn().max(1),
1788 total_tokens: 0,
1789 status: "milestone_pending".to_string(),
1790 };
1791 return;
1792 }
1793 }
1794 KernelEffect::Done { result } => {
1795 let status = format!("{:?}", result.termination).to_lowercase();
1796 let turns_used = result.turns_used.max(1);
1797 let total_tokens = result.total_tokens_used;
1798
1799 next_archive_start = self
1800 .append_observations(
1801 &session_id,
1802 &kernel,
1803 &mut pending_observations,
1804 &mut pending_page_out_starts,
1805 next_archive_start,
1806 )
1807 .await;
1808
1809 self.log(
1810 &session_id,
1811 SessionEvent::RunTerminal {
1812 reason: status.clone(),
1813 turns_used,
1814 total_tokens,
1815 },
1816 )
1817 .await;
1818
1819 if let (Some(store), Some(agent_id)) =
1820 (&self.opts.dream_store, &self.opts.agent_id)
1821 {
1822 let new_msgs = kernel.lock().unwrap().drain_new_messages();
1823 if !new_msgs.is_empty() {
1824 let now_ms = std::time::SystemTime::now()
1825 .duration_since(std::time::UNIX_EPOCH)
1826 .unwrap_or_default()
1827 .as_millis() as u64;
1828 let session = deepstrike_core::memory::durable::SessionData {
1829 session_id: session_id.clone(),
1830 agent_id: agent_id.clone(),
1831 messages: new_msgs,
1832 metadata: serde_json::Value::Null,
1833 created_at_ms: session_start_ms,
1834 updated_at_ms: now_ms,
1835 };
1836 let _ = store.save_session(session.clone()).await;
1837 if let Some(scope) = self.opts.memory_scope.as_ref() {
1838 if let Ok(memories) = self.extract_session_memories(&session, scope).await {
1839 for memory in memories {
1840 let _ = self.write_memory(memory, Some(&session_id), Some(agent_id)).await;
1841 }
1842 }
1843 }
1844 }
1845 }
1846
1847 yield RunEvent::Done {
1848 iterations: turns_used,
1849 total_tokens,
1850 status,
1851 };
1852 return;
1853 }
1854 }
1855 }
1856
1857 next_archive_start = self
1858 .append_observations(
1859 &session_id,
1860 &kernel,
1861 &mut pending_observations,
1862 &mut pending_page_out_starts,
1863 next_archive_start,
1864 )
1865 .await;
1866
1867 let (status, turns_used, total_tokens) = match &action.effect {
1871 KernelEffect::Done { result } => (
1872 format!("{:?}", result.termination).to_lowercase(),
1873 result.turns_used.max(1),
1874 result.total_tokens_used,
1875 ),
1876 _ => ("error".to_string(), kernel.lock().unwrap().turn().max(1), 0),
1877 };
1878
1879 self.log(
1880 &session_id,
1881 SessionEvent::RunTerminal {
1882 reason: status.clone(),
1883 turns_used,
1884 total_tokens,
1885 },
1886 )
1887 .await;
1888
1889 if let KernelEffect::Done { .. } = &action.effect {
1890 if let (Some(store), Some(agent_id)) =
1891 (&self.opts.dream_store, &self.opts.agent_id)
1892 {
1893 let new_msgs = kernel.lock().unwrap().drain_new_messages();
1894 if !new_msgs.is_empty() {
1895 let now_ms = std::time::SystemTime::now()
1896 .duration_since(std::time::UNIX_EPOCH)
1897 .unwrap_or_default()
1898 .as_millis() as u64;
1899 let session = deepstrike_core::memory::durable::SessionData {
1900 session_id: session_id.clone(),
1901 agent_id: agent_id.clone(),
1902 messages: new_msgs,
1903 metadata: serde_json::Value::Null,
1904 created_at_ms: session_start_ms,
1905 updated_at_ms: now_ms,
1906 };
1907 let _ = store.save_session(session.clone()).await;
1908 if let Some(scope) = self.opts.memory_scope.as_ref() {
1909 if let Ok(memories) = self.extract_session_memories(&session, scope).await {
1910 for memory in memories {
1911 let _ = self.write_memory(memory, Some(&session_id), Some(agent_id)).await;
1912 }
1913 }
1914 }
1915 }
1916 }
1917 }
1918
1919 yield RunEvent::Done {
1920 iterations: turns_used,
1921 total_tokens,
1922 status,
1923 };
1924 }
1925 }
1926
1927 pub(crate) async fn append_observations(
1928 &self,
1929 session_id: &str,
1930 kernel_mutex: &std::sync::Mutex<KernelRuntime>,
1931 observations: &mut Vec<KernelObservation>,
1932 pending_page_out_starts: &mut std::collections::VecDeque<u64>,
1933 mut next_archive_start: u64,
1934 ) -> u64 {
1935 let drained = std::mem::take(observations);
1936 let (turn, preserved_refs, summary_tokens_by_index) = {
1937 let kernel = kernel_mutex.lock().unwrap();
1938 let summary_tokens_by_index = drained
1939 .iter()
1940 .map(|obs| match obs {
1941 KernelObservation::Compressed { summary, .. } => {
1942 summary.as_ref().map(|s| kernel.count_tokens(s))
1943 }
1944 _ => None,
1945 })
1946 .collect::<Vec<_>>();
1947 (
1948 kernel.turn(),
1949 kernel.preserved_refs(),
1950 summary_tokens_by_index,
1951 )
1952 };
1953
1954 for (index, obs) in drained.into_iter().enumerate() {
1955 match obs {
1956 KernelObservation::Compressed {
1957 turn: _,
1958 action,
1959 rho_after: _,
1960 summary,
1961 archived_count,
1962 invalidates_prefix_at: _,
1963 } => {
1964 let Some(log) = &self.opts.session_log else {
1965 continue;
1966 };
1967 let latest = log.latest_seq(session_id).await.unwrap_or(-1) as u64;
1968 if latest < next_archive_start {
1969 continue;
1970 }
1971 let end = latest;
1972 if archived_count > 0 {
1973 pending_page_out_starts.push_back(next_archive_start);
1974 }
1975
1976 let summary_tokens = summary_tokens_by_index.get(index).copied().flatten();
1977 let action_str = action_str_of(action);
1978
1979 if let Ok(compressed_seq) = log
1980 .append(
1981 session_id,
1982 SessionEvent::Compressed {
1983 turn,
1984 archived_seq_range: (next_archive_start, end),
1985 action: Some(action_str),
1986 summary: summary.clone(),
1987 summary_tokens,
1988 preserved_refs: preserved_refs.clone(),
1989 },
1990 )
1991 .await
1992 {
1993 next_archive_start = compressed_seq + 1;
1994 }
1995 }
1996 KernelObservation::PageOutArchived {
1997 turn,
1998 action,
1999 summary,
2000 tier,
2001 message_count,
2002 archive_ref,
2003 } => {
2004 self.log(
2005 session_id,
2006 SessionEvent::PageOut {
2007 turn,
2008 action: Some(action_str_of(action)),
2009 summary,
2010 tier_hint: Some(tier),
2011 message_count,
2012 archive_ref,
2013 },
2014 )
2015 .await;
2016 }
2017 KernelObservation::PageOutArchiveFailed { .. } => {}
2018 KernelObservation::Rollbacked {
2019 turn,
2020 checkpoint_history_len,
2021 reason,
2022 } => {
2023 self.log(
2024 session_id,
2025 SessionEvent::Rollbacked {
2026 turn,
2027 checkpoint_history_len,
2028 reason,
2029 },
2030 )
2031 .await;
2032 }
2033 KernelObservation::CapabilityChanged {
2034 turn,
2035 added,
2036 removed,
2037 change_kind,
2038 capability_id,
2039 version,
2040 mounted_by,
2041 mount_reason,
2042 } => {
2043 self.log(
2044 session_id,
2045 SessionEvent::CapabilityChanged {
2046 turn,
2047 added,
2048 removed,
2049 change_kind,
2050 capability_id,
2051 version,
2052 mounted_by,
2053 mount_reason,
2054 },
2055 )
2056 .await;
2057 }
2058 KernelObservation::MilestoneAdvanced {
2059 turn,
2060 phase_id,
2061 capabilities_unlocked,
2062 } => {
2063 self.log(
2064 session_id,
2065 SessionEvent::MilestoneAdvanced {
2066 turn,
2067 phase_id,
2068 capabilities_unlocked,
2069 },
2070 )
2071 .await;
2072 }
2073 KernelObservation::MilestoneBlocked {
2074 turn,
2075 phase_id,
2076 reason,
2077 } => {
2078 self.log(
2079 session_id,
2080 SessionEvent::MilestoneBlocked {
2081 turn,
2082 phase_id,
2083 reason,
2084 },
2085 )
2086 .await;
2087 }
2088 KernelObservation::Renewed { .. } => {}
2089 KernelObservation::ContextBudgetExceeded { .. } => {}
2090 KernelObservation::KnowledgeSwept { .. } => {}
2091 KernelObservation::KnowledgeBudgetExceeded { .. } => {}
2092 KernelObservation::RepeatFuseTripped { .. } => {}
2093 KernelObservation::CriteriaGateFired { .. } => {}
2094 KernelObservation::CheckpointTaken { turn, history_len } => {
2095 self.log(
2096 session_id,
2097 SessionEvent::CheckpointTaken { turn, history_len },
2098 )
2099 .await;
2100 }
2101 KernelObservation::EntropySample {
2102 turn,
2103 score,
2104 score_version,
2105 rho,
2106 repeat_pressure,
2107 failure_rate,
2108 rollbacks_in_window,
2109 window_turns,
2110 } => {
2111 self.log(
2112 session_id,
2113 SessionEvent::EntropySample {
2114 turn,
2115 score,
2116 score_version,
2117 rho,
2118 repeat_pressure,
2119 failure_rate,
2120 rollbacks_in_window,
2121 window_turns,
2122 },
2123 )
2124 .await;
2125 }
2126 KernelObservation::EntropyAlert {
2127 turn,
2128 score,
2129 threshold,
2130 } => {
2131 self.log(
2132 session_id,
2133 SessionEvent::EntropyAlert {
2134 turn,
2135 score,
2136 threshold,
2137 },
2138 )
2139 .await;
2140 }
2141 KernelObservation::AgentProcessChanged { .. } => {}
2142 KernelObservation::WorkflowBatchSpawned { .. } => {}
2145 KernelObservation::WorkflowSpawnFailed { .. } => {}
2146 KernelObservation::WorkflowCompleted { .. } => {}
2147 KernelObservation::NodesRejected { .. } => {}
2148 KernelObservation::AgentPreempted { .. } => {}
2149 KernelObservation::AgentPreemptFailed { .. } => {}
2150 KernelObservation::MemoryWriteFailed { .. } => {}
2151 KernelObservation::MemoryQueryFailed { .. } => {}
2152 KernelObservation::MemoryRecalled { .. }
2155 | KernelObservation::PromotionSuggested { .. } => {}
2156 KernelObservation::ToolGated { .. } => {}
2159 KernelObservation::SignalDeliveryDisposed { .. } => {}
2163 KernelObservation::SignalDisplaced { .. }
2164 | KernelObservation::SignalExpired { .. }
2165 | KernelObservation::SignalsPending { .. } => {}
2166 KernelObservation::BudgetExceeded {
2167 turn,
2168 operation_id,
2169 reservation_id,
2170 budget,
2171 } => {
2172 self.log(
2173 session_id,
2174 SessionEvent::BudgetExceeded {
2175 turn,
2176 operation_id,
2177 reservation_id,
2178 budget,
2179 },
2180 )
2181 .await;
2182 }
2183 KernelObservation::BudgetUsageReported {
2184 operation_id,
2185 reservation_id,
2186 tokens,
2187 subagents,
2188 rounds,
2189 } => {
2190 self.log(
2191 session_id,
2192 SessionEvent::BudgetUsageReported {
2193 turn,
2194 operation_id,
2195 reservation_id,
2196 tokens,
2197 subagents,
2198 rounds,
2199 },
2200 )
2201 .await;
2202 }
2203 KernelObservation::OperationCancelled {
2204 turn,
2205 operation_id,
2206 reason,
2207 pending_call_ids,
2208 } => {
2209 self.log(
2210 session_id,
2211 SessionEvent::OperationCancelled {
2212 turn,
2213 operation_id,
2214 reason,
2215 pending_call_ids,
2216 },
2217 )
2218 .await;
2219 }
2220 KernelObservation::Suspended { .. }
2221 | KernelObservation::ApprovalResolutionFailed { .. } => {}
2222 KernelObservation::Resumed { .. } => {}
2223 KernelObservation::WorkflowNodesSubmitted { .. } => {}
2226 KernelObservation::RoundPaced { .. } => {}
2229 KernelObservation::MemoryWritten {
2230 turn,
2231 record_id,
2232 scope,
2233 memory_kind,
2234 name,
2235 size_bytes,
2236 } => {
2237 self.log(
2238 session_id,
2239 SessionEvent::MemoryWritten {
2240 turn,
2241 record_id,
2242 scope,
2243 memory_kind,
2244 name,
2245 size_bytes,
2246 },
2247 )
2248 .await;
2249 }
2250 KernelObservation::MemoryQueried {
2251 turn,
2252 scope,
2253 query,
2254 requested_k,
2255 requires_async_response,
2256 } => {
2257 self.log(
2258 session_id,
2259 SessionEvent::MemoryQueried {
2260 turn,
2261 scope,
2262 query,
2263 requested_k,
2264 requires_async_response,
2265 },
2266 )
2267 .await;
2268 }
2269 KernelObservation::MemoryValidationFailed {
2271 turn,
2272 record_id,
2273 error,
2274 } => {
2275 self.log(
2276 session_id,
2277 SessionEvent::MemoryValidationFailed {
2278 turn,
2279 record_id,
2280 error,
2281 },
2282 )
2283 .await;
2284 }
2285 KernelObservation::LargeResultSpooled {
2286 turn,
2287 call_id,
2288 tool,
2289 original_size,
2290 preview_size,
2291 spool_ref,
2292 } => {
2293 self.log(
2294 session_id,
2295 SessionEvent::LargeResultSpooled {
2296 turn,
2297 call_id,
2298 tool,
2299 original_size,
2300 preview_size,
2301 spool_ref,
2302 },
2303 )
2304 .await;
2305 }
2306 KernelObservation::LargeResultSpoolFailed { .. } => {}
2307 }
2308 }
2309 next_archive_start
2310 }
2311
2312 async fn read_entries(&self, session_id: &str) -> Result<Vec<SessionEntry>> {
2313 if let Some(log) = &self.opts.session_log {
2314 log.read(session_id, 0, None).await.map_err(Error::Io)
2315 } else {
2316 Ok(Vec::new())
2317 }
2318 }
2319
2320 async fn log(&self, session_id: &str, event: SessionEvent) {
2321 if let Some(log) = &self.opts.session_log {
2322 let _ = log.append(session_id, event).await;
2323 }
2324 }
2325
2326 async fn archive_semantic_page_out(&self, archived: Vec<Message>, action: Option<String>) {
2327 let (Some(_store), Some(agent_id), Some(scope)) = (
2328 &self.opts.dream_store,
2329 &self.opts.agent_id,
2330 &self.opts.memory_scope,
2331 ) else {
2332 return;
2333 };
2334
2335 let summary = match self.summarize_for_long_term_memory(&archived).await {
2336 Ok(s) => s,
2337 Err(_) => return, };
2339
2340 let now = std::time::SystemTime::now()
2344 .duration_since(std::time::UNIX_EPOCH)
2345 .unwrap_or_default()
2346 .as_millis() as u64;
2347 let name = format!("page-out-{now}");
2348 let request = MemoryRecord {
2349 record_id: format!("{}:{}:project:{}", scope.tenant_id, scope.namespace, name),
2350 scope: scope.clone(),
2351 name,
2352 kind: MemoryKind::Project,
2353 content: summary,
2354 description: format!(
2355 "auto summary of {} archive",
2356 action.as_deref().unwrap_or("compaction")
2357 ),
2358 provenance: MemoryProvenance {
2359 session_id: self.opts.session_id.clone(),
2360 author: MemoryAuthor::Extraction,
2361 trust: MemoryTrustLevel::Untrusted,
2362 evidence_refs: Vec::new(),
2363 },
2364 created_at: now,
2365 updated_at: now,
2366 last_recalled_at: None,
2367 recall_count: 0,
2368 confidence: 0.6,
2369 links: Vec::new(),
2370 pinned: false,
2371 ttl_days: None,
2372 };
2373 let _ = self.write_memory(request, None, Some(agent_id)).await;
2374 }
2375
2376 async fn summarize_for_long_term_memory(&self, archived: &[Message]) -> crate::Result<String> {
2377 let transcript = archived
2378 .iter()
2379 .map(|m| {
2380 let role_str = match m.role {
2381 deepstrike_core::types::message::Role::System => "system",
2382 deepstrike_core::types::message::Role::User => "user",
2383 deepstrike_core::types::message::Role::Assistant => "assistant",
2384 deepstrike_core::types::message::Role::Tool => "tool",
2385 };
2386 let content_str = message_content_as_text(&m.content);
2387 format!("{}: {}", role_str, content_str)
2388 })
2389 .collect::<Vec<_>>()
2390 .join("\n");
2391
2392 let system_prompt_opt = self.opts.system_prompt.as_deref();
2393 let system_text = match system_prompt_opt {
2394 Some(sp) => format!(
2395 "{}\n\nSummarize the following conversation for long-term memory. Preserve key facts, decisions, and open questions.",
2396 sp
2397 ),
2398 None => "Summarize the following conversation for long-term memory. Preserve key facts, decisions, and open questions.".to_string(),
2399 };
2400
2401 let context = deepstrike_core::context::renderer::RenderedContext {
2402 system_text,
2403 system_stable: String::new(),
2404 system_knowledge: String::new(),
2405 turns: vec![deepstrike_core::types::message::Message {
2406 role: deepstrike_core::types::message::Role::User,
2407 content: deepstrike_core::types::message::Content::Text(transcript.clone()),
2408 tool_calls: vec![],
2409 token_count: None,
2410 }],
2411 state_turn: None,
2412 frozen_prefix_len: None,
2413 budget_overflow: None,
2414 };
2415
2416 let synth_state = self.opts.provider.create_run_state();
2417 let mut stream = self
2418 .opts
2419 .provider
2420 .stream(&context, &[], None, synth_state.as_ref())
2421 .await?;
2422
2423 let mut synthesis_text = String::new();
2424 while let Some(evt) = stream.next().await {
2425 if let Ok(StreamEvent::TextDelta { delta }) = evt {
2426 synthesis_text.push_str(&delta);
2427 }
2428 }
2429
2430 let text = synthesis_text.trim();
2431 if text.is_empty() {
2432 Ok(transcript.chars().take(2000).collect())
2433 } else {
2434 Ok(text.to_string())
2435 }
2436 }
2437}
2438
2439fn message_content_as_text(content: &deepstrike_core::types::message::Content) -> String {
2440 match content {
2441 deepstrike_core::types::message::Content::Text(s) => s.clone(),
2442 deepstrike_core::types::message::Content::Parts(parts) => parts
2443 .iter()
2444 .filter_map(|p| match p {
2445 deepstrike_core::types::message::ContentPart::Text { text } => Some(text.as_str()),
2446 deepstrike_core::types::message::ContentPart::ToolResult { output, .. } => {
2447 Some(output.as_str())
2448 }
2449 _ => None,
2450 })
2451 .collect::<Vec<_>>()
2452 .join("\n"),
2453 }
2454}
2455
2456fn action_str_of(action: KernelPressureAction) -> String {
2457 match action {
2458 KernelPressureAction::None => "none".to_string(),
2459 KernelPressureAction::SnipCompact => "snip_compact".to_string(),
2460 KernelPressureAction::MicroCompact => "micro_compact".to_string(),
2461 KernelPressureAction::ContextCollapse => "context_collapse".to_string(),
2462 KernelPressureAction::AutoCompact => "auto_compact".to_string(),
2463 }
2464}
2465
2466fn kernel_apply(
2467 kernel: &mut KernelRuntime,
2468 pending_observations: &mut Vec<KernelObservation>,
2469 event: KernelInputEvent,
2470) {
2471 let step = kernel.step(KernelInput::new(event));
2472 pending_observations.extend(step.observations);
2473}
2474
2475fn kernel_action(
2476 kernel: &mut KernelRuntime,
2477 pending_observations: &mut Vec<KernelObservation>,
2478 event: KernelInputEvent,
2479) -> KernelAction {
2480 let mut step = kernel.step(KernelInput::new(event));
2481 pending_observations.append(&mut step.observations);
2482 take_single_action(step)
2483}
2484
2485fn take_single_action(mut step: KernelStep) -> KernelAction {
2486 step.actions
2487 .pop()
2488 .expect("kernel transition must return one action")
2489}
2490
2491pub async fn collect_text(
2492 mut stream: std::pin::Pin<Box<dyn futures::Stream<Item = Result<RunEvent>> + '_>>,
2493) -> Result<String> {
2494 let mut text = String::new();
2495 while let Some(evt) = stream.next().await {
2496 if let RunEvent::TextDelta(d) = evt? {
2497 text.push_str(&d);
2498 }
2499 }
2500 Ok(text)
2501}
2502
2503fn merge_extensions(
2504 base: Option<&serde_json::Value>,
2505 over: Option<&serde_json::Value>,
2506) -> Option<serde_json::Value> {
2507 match (base, over) {
2508 (Some(b), Some(o)) => {
2509 let mut merged = b.clone();
2510 if let (Some(m), Some(obj)) = (merged.as_object_mut(), o.as_object()) {
2511 for (k, v) in obj {
2512 m.insert(k.clone(), v.clone());
2513 }
2514 }
2515 Some(merged)
2516 }
2517 (Some(b), None) => Some(b.clone()),
2518 (None, Some(o)) => Some(o.clone()),
2519 (None, None) => None,
2520 }
2521}
2522
2523fn cancellation_reason_code(reason: CancellationReason) -> u8 {
2524 match reason {
2525 CancellationReason::User => 0,
2526 CancellationReason::Deadline => 1,
2527 CancellationReason::LeaseLost => 2,
2528 CancellationReason::HostShutdown => 3,
2529 }
2530}
2531
2532fn cancellation_reason_from_code(code: u8) -> CancellationReason {
2533 match code {
2534 1 => CancellationReason::Deadline,
2535 2 => CancellationReason::LeaseLost,
2536 3 => CancellationReason::HostShutdown,
2537 _ => CancellationReason::User,
2538 }
2539}
2540
2541fn pending_call_ids(action: &KernelAction) -> Vec<String> {
2542 match &action.effect {
2543 KernelEffect::CallProvider { .. } => vec![action.effect_id.clone()],
2544 KernelEffect::ExecuteTool { calls } => {
2545 calls.iter().map(|call| call.id.to_string()).collect()
2546 }
2547 KernelEffect::RequestApproval { requests } => requests
2548 .iter()
2549 .map(|request| request.call_id.clone())
2550 .collect(),
2551 KernelEffect::SpawnWorkflow { nodes, .. } => {
2552 nodes.iter().map(|node| node.agent_id.clone()).collect()
2553 }
2554 KernelEffect::PreemptSubAgents { agent_ids, .. } => agent_ids.clone(),
2555 KernelEffect::Done { .. } => Vec::new(),
2556 _ => vec![action.effect_id.clone()],
2557 }
2558}
2559
2560fn memory_policy_event(policy: MemoryPolicy) -> KernelInputEvent {
2562 KernelInputEvent::SetMemoryPolicy {
2563 memory_path: policy.memory_path,
2564 stale_warning_days: policy.stale_warning_days,
2565 retrieval_top_k: policy.retrieval_top_k,
2566 validation_enabled: policy.validation_enabled,
2567 max_content_bytes: policy.max_content_bytes,
2568 max_name_length: policy.max_name_length,
2569 promotion_recall_threshold: policy.promotion_recall_threshold,
2570 }
2571}
2572
2573fn next_archived_seq_start(events: Option<&[SessionEntry]>) -> u64 {
2574 let mut next = 0u64;
2575 for entry in events.unwrap_or_default() {
2576 if let SessionEvent::Compressed {
2577 archived_seq_range, ..
2578 } = &entry.event
2579 {
2580 next = next.max(archived_seq_range.1 + 1);
2581 }
2582 }
2583 next
2584}
2585
2586fn rendered_context_from_messages(
2587 messages: Vec<Message>,
2588) -> deepstrike_core::context::renderer::RenderedContext {
2589 let mut system_parts = Vec::new();
2590 let mut turns = Vec::new();
2591 for message in messages {
2592 if message.role == deepstrike_core::types::message::Role::System {
2593 if let Some(text) = message.content.as_text() {
2594 system_parts.push(text.to_owned());
2595 }
2596 } else {
2597 turns.push(message);
2598 }
2599 }
2600 let system_text = system_parts.join("\n\n");
2601 deepstrike_core::context::renderer::RenderedContext {
2602 system_text: system_text.clone(),
2603 system_stable: system_text,
2604 system_knowledge: String::new(),
2605 turns,
2606 state_turn: None,
2607 frozen_prefix_len: None,
2608 budget_overflow: None,
2609 }
2610}
2611
2612fn parse_update_plan_args(val: &serde_json::Value) -> TaskUpdate {
2613 let plan = val.get("plan").and_then(|v| {
2614 v.as_array().map(|arr| {
2615 arr.iter()
2616 .filter_map(|x| x.as_str().map(|s| s.to_string()))
2617 .collect()
2618 })
2619 });
2620 let current_step = val
2621 .get("current_step")
2622 .or_else(|| val.get("currentStep"))
2623 .and_then(|v| v.as_u64().map(|x| x as usize));
2624 let progress = val
2625 .get("progress")
2626 .and_then(|v| v.as_str().map(|s| s.to_string()));
2627 let scratchpad = val
2628 .get("scratchpad")
2629 .and_then(|v| v.as_str().map(|s| s.to_string()));
2630 let blocked_on = val
2631 .get("blocked_on")
2632 .or_else(|| val.get("blockedOn"))
2633 .and_then(|v| {
2634 v.as_array().map(|arr| {
2635 arr.iter()
2636 .filter_map(|x| x.as_str().map(|s| s.to_string()))
2637 .collect()
2638 })
2639 });
2640 let preserved_refs = val
2641 .get("preserved_refs")
2642 .or_else(|| val.get("preservedRefs"))
2643 .and_then(|v| {
2644 v.as_array().map(|arr| {
2645 arr.iter()
2646 .filter_map(|x| x.as_str().map(|s| s.to_string()))
2647 .collect()
2648 })
2649 });
2650 TaskUpdate {
2651 plan,
2652 current_step,
2653 progress,
2654 scratchpad,
2655 blocked_on,
2656 preserved_refs,
2657 directives: None,
2660 }
2661}