1use std::{
2 collections::{HashMap, HashSet, VecDeque},
3 sync::{Arc, Mutex, atomic::Ordering},
4};
5
6use base64::{Engine as _, engine::general_purpose::STANDARD};
7use cookie_agent_protocol::{
8 ApprovalStatus, EventOrigin, EventSubscriptionMessage, EventsSubscribeResult,
9 ExtensionBusEventParams, ExtensionEmitStatus, PersistedToolResult as ToolResult,
10 PluginDiagnosticKind, RunCancelResult, RunId, RunRecallSteerResult, RunSteerResult,
11 RunToolStdinResult, SafeToolError, SessionId, SessionRenameChange, SessionRenameResult,
12 SessionRevertResult, SessionStatus, SessionTitleChange, StoredEvent, ToolCallId,
13 ToolCallTermination, ToolTerminationOutcome,
14};
15use tokio::sync::{mpsc, oneshot};
16
17#[cfg(test)]
18use super::PagingRaceHook;
19use super::ToolCallFailureCode;
20use super::{
21 ApprovalTerminal, Engine, EngineError, Event, MAX_COMPACTION_DEFERRED_COMMANDS, PendingInput,
22 PendingPromotionState, PredictiveCompactionInput, SESSION_MAILBOX_CAPACITY, SessionCommand,
23 ToolFailure,
24 approval_projection::{approval_records, approval_run_id},
25 helpers::safe_error,
26 model_loop,
27};
28use crate::{actor::SessionActor, events, session::SessionError, tool_api::StdinWrite};
29
30use super::{ActiveRun, producers};
31
32#[derive(Default)]
34pub(crate) struct SessionRuntimeState {
35 pub(super) actors: Mutex<HashMap<SessionId, SessionActor<SessionCommand>>>,
36 pub(super) active: Mutex<HashMap<RunId, Arc<ActiveRun>>>,
37 pub(super) producers: Mutex<HashMap<SessionId, producers::SessionProducers>>,
38 pub(super) run_tasks: Mutex<HashMap<RunId, tokio::task::JoinHandle<()>>>,
45 pub(crate) residency_mutation: tokio::sync::Mutex<()>,
46}
47
48pub(super) struct RunTaskSlot {
50 pub(super) engine: Engine,
51 pub(super) run_id: RunId,
52}
53
54impl Drop for RunTaskSlot {
55 fn drop(&mut self) {
56 if let Ok(mut tasks) = self.engine.inner.sessions.run_tasks.lock() {
57 tasks.remove(&self.run_id);
58 }
59 }
60}
61
62impl Engine {
63 pub(super) fn register_run_task(&self, run_id: RunId, task: tokio::task::JoinHandle<()>) {
68 let mut tasks = self
69 .inner
70 .sessions
71 .run_tasks
72 .lock()
73 .unwrap_or_else(|poisoned| poisoned.into_inner());
74 tasks.retain(|_, task| !task.is_finished());
75 tasks.insert(run_id, task);
76 }
77
78 pub(super) async fn join_run_tasks(&self, bound: std::time::Duration) {
84 let tasks = self
85 .inner
86 .sessions
87 .run_tasks
88 .lock()
89 .map(|mut tasks| tasks.drain().map(|(_, task)| task).collect::<Vec<_>>())
90 .unwrap_or_default();
91 if tasks.is_empty() {
92 return;
93 }
94 let deadline = tokio::time::Instant::now() + bound;
95 for mut task in tasks {
96 if tokio::time::timeout_at(deadline, &mut task).await.is_err() {
97 task.abort();
98 let _ = task.await;
99 }
100 }
101 }
102
103 pub async fn subscribe(
104 &self,
105 session: SessionId,
106 cursor: Option<u64>,
107 ) -> Result<
108 (
109 EventsSubscribeResult,
110 mpsc::Receiver<EventSubscriptionMessage>,
111 ),
112 EngineError,
113 > {
114 self.inner.store.get(session)?;
115 if !self.inner.store.is_owned(session) {
116 return Err(EngineError::SessionOwnedByAnotherProcess(session));
117 }
118 self.request(session, |reply| SessionCommand::Subscribe { cursor, reply })
119 .await
120 }
121
122 pub fn snapshot_events(
123 &self,
124 session: SessionId,
125 cursor: Option<u64>,
126 ) -> Result<EventsSubscribeResult, EngineError> {
127 let events = self
128 .inner
129 .store
130 .get(session)?
131 .log
132 .all_events()
133 .into_iter()
134 .filter(|event| cursor.is_none_or(|cursor| event.seq > cursor))
135 .collect();
136 Ok(EventsSubscribeResult { events })
137 }
138
139 pub fn subscribe_tool_output(
142 &self,
143 call: ToolCallId,
144 stream: cookie_agent_protocol::OutputStream,
145 ) -> Option<(
146 cookie_agent_protocol::OutputSnapshot,
147 mpsc::Receiver<events::OutputMessage>,
148 )> {
149 self.inner
150 .output
151 .hubs
152 .lock()
153 .unwrap_or_else(|poisoned| poisoned.into_inner())
154 .get(&call)
155 .cloned()
156 .map(|hub| hub.subscribe(stream, 256))
157 }
158
159 pub fn tool_output_streams(
160 &self,
161 call_id: ToolCallId,
162 ) -> Option<Vec<cookie_agent_protocol::OutputStream>> {
163 self.inner
164 .output
165 .hubs
166 .lock()
167 .unwrap_or_else(|p| p.into_inner())
168 .get(&call_id)
169 .map(|hub| hub.streams())
170 }
171
172 pub(super) fn retain_finalized_output_hub(&self, call: ToolCallId) {
173 const FINALIZED_HUB_RETENTION: usize = 128;
174 let mut finalized = self
175 .inner
176 .output
177 .finalized_hubs
178 .lock()
179 .unwrap_or_else(|poisoned| poisoned.into_inner());
180 finalized.push_back(call);
181 if finalized.len() > FINALIZED_HUB_RETENTION
182 && let Some(expired) = finalized.pop_front()
183 {
184 self.inner
185 .output
186 .hubs
187 .lock()
188 .unwrap_or_else(|poisoned| poisoned.into_inner())
189 .remove(&expired);
190 }
191 }
192
193 pub async fn append(
194 &self,
195 session: SessionId,
196 run: Option<RunId>,
197 origin: EventOrigin,
198 event: Event,
199 ) -> Result<(), EngineError> {
200 self.request(session, |reply| SessionCommand::Append {
201 run,
202 origin,
203 event: Box::new(event),
204 reply,
205 })
206 .await
207 }
208
209 pub(super) async fn enqueue_append(
210 &self,
211 session: SessionId,
212 run: Option<RunId>,
213 origin: EventOrigin,
214 event: Event,
215 ) -> Result<oneshot::Receiver<Result<(), EngineError>>, EngineError> {
216 let (reply, receiver) = oneshot::channel();
217 let _residency = self.inner.sessions.residency_mutation.lock().await;
218 self.ensure_session_owned(session)?;
219 self.spawn_actor(session)?;
220 let actor = self
221 .inner
222 .sessions
223 .actors
224 .lock()
225 .unwrap_or_else(|poisoned| poisoned.into_inner())
226 .get(&session)
227 .cloned()
228 .ok_or(EngineError::MissingActor(session))?;
229 actor
230 .send(SessionCommand::Append {
231 run,
232 origin,
233 event: Box::new(event),
234 reply,
235 })
236 .await
237 .map_err(|_| EngineError::ActorStopped)?;
238 Ok(receiver)
239 }
240
241 pub fn append_blocking(
243 &self,
244 session: SessionId,
245 run: Option<RunId>,
246 origin: EventOrigin,
247 event: Event,
248 ) -> Result<(), EngineError> {
249 self.request_blocking(session, |reply| SessionCommand::Append {
250 run,
251 origin,
252 event: Box::new(event),
253 reply,
254 })
255 }
256
257 pub async fn submit_tool_result(
259 &self,
260 session: SessionId,
261 run: RunId,
262 tool_call_id: ToolCallId,
263 result: Result<ToolResult, String>,
264 ) -> Result<(), EngineError> {
265 let result = result.map_err(|message| ToolFailure {
266 partial_output: None,
267 code: ToolCallFailureCode::ExecutionFailed,
268 message,
269 });
270 self.submit_tool_result_status(session, run, tool_call_id, result, false)
271 .await
272 .and_then(|committed| {
273 committed
274 .then_some(())
275 .ok_or_else(|| EngineError::ToolFailed("tool call is no longer pending".into()))
276 })
277 }
278
279 pub(super) async fn submit_tool_result_status(
280 &self,
281 session: SessionId,
282 run: RunId,
283 tool_call_id: ToolCallId,
284 result: Result<ToolResult, ToolFailure>,
285 cancelled: bool,
286 ) -> Result<bool, EngineError> {
287 self.request(session, |reply| SessionCommand::ToolResult {
288 run,
289 tool_call_id,
290 result,
291 cancelled,
292 reply,
293 })
294 .await
295 }
296
297 pub(crate) fn append_direct(
298 &self,
299 session: SessionId,
300 run: Option<RunId>,
301 origin: EventOrigin,
302 event: Event,
303 ) -> Result<(), EngineError> {
304 self.append_direct_with_mode(session, run, origin, event, false)
305 .map(|_| ())
306 }
307
308 pub(super) fn append_direct_record(
309 &self,
310 session: SessionId,
311 run: Option<RunId>,
312 origin: EventOrigin,
313 event: Event,
314 ) -> Result<StoredEvent, EngineError> {
315 self.append_direct_with_mode(session, run, origin, event, false)
316 }
317
318 pub(crate) fn append_recovery_direct(
319 &self,
320 session: SessionId,
321 run: Option<RunId>,
322 origin: EventOrigin,
323 event: Event,
324 ) -> Result<(), EngineError> {
325 self.append_direct_with_mode(session, run, origin, event, true)
326 .map(|_| ())
327 }
328
329 fn append_direct_with_mode(
330 &self,
331 session: SessionId,
332 run: Option<RunId>,
333 origin: EventOrigin,
334 mut event: Event,
335 recovery: bool,
336 ) -> Result<StoredEvent, EngineError> {
337 if let Event::ToolCallTerminated { termination } = &mut event {
338 let cancelled = run.is_some_and(|run| {
341 self.inner
342 .sessions
343 .active
344 .lock()
345 .unwrap_or_else(|p| p.into_inner())
346 .get(&run)
347 .is_some_and(|active| {
348 active.session == session && active.cancellation.is_cancelled()
349 })
350 });
351 if cancelled
352 && matches!(
353 termination.outcome,
354 ToolTerminationOutcome::Completed | ToolTerminationOutcome::Failed
355 )
356 {
357 let finalized = termination.outcome == ToolTerminationOutcome::Completed;
358 termination.outcome = ToolTerminationOutcome::Cancelled;
359 termination.error.get_or_insert_with(|| SafeToolError {
360 code: if finalized {
361 super::safe_code(super::CANCELLED_AFTER_COMPLETION)
362 } else {
363 ToolCallFailureCode::ExecutionFailed.safe_code()
364 },
365 message: safe_error("tool call cancelled after it started"),
366 });
367 }
368 if termination.outcome != ToolTerminationOutcome::Completed
369 && let Some(result) = &mut termination.result
370 {
371 let was_incomplete = result.retained_output.as_mut().map(|retained| {
372 let was_incomplete = retained.incomplete;
373 retained.incomplete = true;
374 was_incomplete
375 });
376 if termination.outcome == ToolTerminationOutcome::Cancelled
377 || was_incomplete.is_some_and(|incomplete| {
378 !incomplete
379 || result
380 .display
381 .as_ref()
382 .is_none_or(|display| display.trim().is_empty())
383 })
384 {
385 result.display = Some(
386 match termination.outcome {
387 ToolTerminationOutcome::Cancelled => {
388 if result.retained_output.is_some() {
389 "Tool cancelled; retained output is incomplete."
390 } else {
391 "Tool cancelled; output is incomplete."
392 }
393 }
394 ToolTerminationOutcome::Interrupted => {
395 "Tool interrupted; retained output is incomplete."
396 }
397 _ => "Tool failed; retained output is incomplete.",
398 }
399 .into(),
400 );
401 }
402 }
403 }
404 let was_persisted = self.inner.store.is_persisted(session)?;
405 let envelope = if recovery {
406 self.inner
407 .store
408 .append_recovery(session, run, origin, event)?
409 } else {
410 self.inner.store.append(session, run, origin, event)?
411 };
412 if !was_persisted && self.inner.store.is_persisted(session)? {
413 for durable in self.inner.store.get(session)?.log.all_events() {
414 let drops = self
415 .inner
416 .plugins
417 .stream_session_event(&durable, durable.origin.as_ref());
418 self.record_plugin_drops(session, drops);
419 }
420 } else {
421 let drops = self
422 .inner
423 .plugins
424 .stream_session_event(&envelope, envelope.origin.as_ref());
425 self.record_plugin_drops(session, drops);
426 }
427 Ok(envelope)
428 }
429
430 fn record_plugin_drops(
431 &self,
432 session: SessionId,
433 drops: Vec<crate::plugin::PluginDeliveryDrop>,
434 ) {
435 for drop in drops {
436 let class = match drop.class {
437 crate::plugin::PluginDeliveryClass::Chunk => "chunk",
438 crate::plugin::PluginDeliveryClass::Ordinary => "ordinary",
439 crate::plugin::PluginDeliveryClass::Terminal => "terminal",
440 };
441 self.queue_plugin_diagnostic(
442 session,
443 drop.plugin,
444 PluginDiagnosticKind::EventDrop,
445 format!("plugin {class} event buffer overflow; event delivery was dropped"),
446 1,
447 );
448 }
449 }
450
451 pub(crate) fn record_plugin_diagnostic(
452 &self,
453 session: SessionId,
454 plugin: String,
455 kind: PluginDiagnosticKind,
456 message: String,
457 ) {
458 self.queue_plugin_diagnostic(session, plugin, kind, message, 1);
459 }
460
461 fn queue_plugin_diagnostic(
462 &self,
463 session_id: SessionId,
464 plugin: String,
465 kind: PluginDiagnosticKind,
466 message: String,
467 count: u64,
468 ) {
469 let message = cookie_agent_protocol::diagnostics::detail(&message).to_string();
470 self.inner
471 .plugin_diagnostics
472 .accumulator
473 .record((session_id, plugin, kind, message), count);
474 }
475
476 pub(crate) async fn publish_plugin_emit(
477 &self,
478 request: crate::plugin::PluginEmitRequest,
479 ) -> crate::plugin::PluginEmitOutcome {
480 let session_id = request.session_id;
481 if let crate::plugin::PluginEmitContext::Rejected {
482 diagnostic_session_id,
483 reason,
484 } = &request.context
485 {
486 let message = format!("plugin emit context rejected: {reason}");
487 if let Some(diagnostic_session_id) = diagnostic_session_id {
488 self.queue_plugin_diagnostic(
489 *diagnostic_session_id,
490 request.plugin.clone(),
491 PluginDiagnosticKind::ContextMismatch,
492 message,
493 1,
494 );
495 } else {
496 self.inner
497 .plugins
498 .note_offender_diagnostic(&request.plugin, message);
499 }
500 return crate::plugin::PluginEmitOutcome {
501 bus: ExtensionEmitStatus::Dropped,
502 durable: ExtensionEmitStatus::Rejected,
503 reason: Some(reason.clone()),
504 };
505 }
506 let payload_bytes =
507 serde_json::to_vec(&request.payload).map_or(usize::MAX, |bytes| bytes.len());
508 let event_bytes = serde_json::to_vec(&Event::PluginEventAdded {
509 plugin: request.plugin.clone(),
510 name: request.name.clone(),
511 payload: request.payload.clone(),
512 })
513 .map_or(usize::MAX, |bytes| bytes.len());
514 let invalid_name = request.name.is_empty()
515 || request.name.chars().count() > crate::plugin::MAX_PLUGIN_EVENT_NAME_CHARS
516 || request.name.chars().any(char::is_control);
517 if invalid_name
518 || payload_bytes > crate::plugin::MAX_PLUGIN_EVENT_PAYLOAD_BYTES
519 || event_bytes > crate::plugin::MAX_PLUGIN_EVENT_TOTAL_BYTES
520 {
521 self.queue_plugin_diagnostic(
522 session_id,
523 request.plugin.clone(),
524 PluginDiagnosticKind::OversizedEvent,
525 "plugin event name or serialized event exceeds its protocol limit".into(),
526 1,
527 );
528 return crate::plugin::PluginEmitOutcome {
529 bus: ExtensionEmitStatus::Dropped,
530 durable: ExtensionEmitStatus::Rejected,
531 reason: Some(format!(
532 "event name must be 1-{} control-free characters, payload at most {} bytes, and total event at most {} bytes",
533 crate::plugin::MAX_PLUGIN_EVENT_NAME_CHARS,
534 crate::plugin::MAX_PLUGIN_EVENT_PAYLOAD_BYTES,
535 crate::plugin::MAX_PLUGIN_EVENT_TOTAL_BYTES,
536 )),
537 };
538 }
539 if let Err(reason) =
540 self.inner
541 .plugins
542 .check_publish_quota(&request.plugin, session_id, event_bytes)
543 {
544 self.queue_plugin_diagnostic(
545 session_id,
546 request.plugin.clone(),
547 PluginDiagnosticKind::RateLimited,
548 reason.into(),
549 1,
550 );
551 return crate::plugin::PluginEmitOutcome {
552 bus: ExtensionEmitStatus::Dropped,
553 durable: ExtensionEmitStatus::Rejected,
554 reason: Some(reason.into()),
555 };
556 }
557
558 let mut bus = ExtensionEmitStatus::Rejected;
559 let durable = ExtensionEmitStatus::Rejected;
560 let mut reason = None;
561 if request.publish_bus {
562 let event = crate::EngineEvent::PluginEvent {
563 session_id,
564 plugin: request.plugin.clone(),
565 name: request.name.clone(),
566 payload: request.payload.clone(),
567 };
568 let _ = self.inner.engine_events.send(event);
569 let drops = self.inner.plugins.stream_bus_event(
570 &ExtensionBusEventParams {
571 session_id,
572 context_id: None,
573 plugin: request.plugin.clone(),
574 name: request.name.clone(),
575 payload: request.payload.clone(),
576 },
577 Some(&request.plugin),
578 );
579 self.record_plugin_drops(session_id, drops);
580 bus = ExtensionEmitStatus::Published;
581 }
582 if request.publish_session_events {
583 reason = Some("model-bound session emission requires explicit producer.register and producer.send".into());
584 }
585 if !request.publish_bus && !request.publish_session_events {
586 reason = Some("plugin did not declare an event publishing capability".into());
587 }
588 crate::plugin::PluginEmitOutcome {
589 bus,
590 durable,
591 reason,
592 }
593 }
594
595 pub(super) async fn request<T>(
596 &self,
597 session: SessionId,
598 command: impl FnOnce(oneshot::Sender<Result<T, EngineError>>) -> SessionCommand,
599 ) -> Result<T, EngineError> {
600 let (reply, receiver) = oneshot::channel();
601 {
602 let _residency = self.inner.sessions.residency_mutation.lock().await;
603 self.ensure_session_owned(session)?;
604 self.spawn_actor(session)?;
605 let actor = self
606 .inner
607 .sessions
608 .actors
609 .lock()
610 .unwrap_or_else(|poisoned| poisoned.into_inner())
611 .get(&session)
612 .cloned()
613 .ok_or(EngineError::MissingActor(session))?;
614 actor
615 .send(command(reply))
616 .await
617 .map_err(|_| EngineError::ActorStopped)?;
618 }
619 receiver.await.map_err(|_| EngineError::ActorStopped)?
620 }
621
622 pub(super) async fn prompt_events(
623 &self,
624 session: SessionId,
625 run: RunId,
626 ) -> Result<super::producer_claims::ClaimedPrompt, EngineError> {
627 #[cfg(test)]
628 if let Some(hook) = {
629 self.inner
630 .test_hooks
631 .prompt_before_claim_hook
632 .lock()
633 .expect("prompt before-claim hook lock poisoned")
634 .take()
635 } {
636 if let Some(reached) = hook
637 .reached
638 .lock()
639 .expect("prompt before-claim reached lock poisoned")
640 .take()
641 {
642 let _ = reached.send(());
643 }
644 hook.release.notified().await;
645 }
646 let events = self
647 .request(session, |reply| SessionCommand::PromotePendingInputs {
648 run,
649 reply,
650 })
651 .await?;
652 #[cfg(test)]
653 if let Some(hook) = {
654 self.inner
655 .test_hooks
656 .prompt_snapshot_hook
657 .lock()
658 .expect("prompt snapshot hook lock poisoned")
659 .take()
660 } {
661 if let Some(reached) = hook
662 .reached
663 .lock()
664 .expect("prompt snapshot reached lock poisoned")
665 .take()
666 {
667 let _ = reached.send(());
668 }
669 hook.release.notified().await;
670 }
671 Ok(events)
672 }
673
674 #[cfg(test)]
675 pub(crate) fn install_prompt_before_claim_hook_for_test(
676 &self,
677 ) -> (oneshot::Receiver<()>, std::sync::Arc<tokio::sync::Notify>) {
678 let (reached, receiver) = oneshot::channel();
679 let release = std::sync::Arc::new(tokio::sync::Notify::new());
680 *self
681 .inner
682 .test_hooks
683 .prompt_before_claim_hook
684 .lock()
685 .expect("before-claim hook lock") =
686 Some(std::sync::Arc::new(super::PromptSnapshotHook {
687 reached: std::sync::Mutex::new(Some(reached)),
688 release: release.clone(),
689 }));
690 (receiver, release)
691 }
692
693 #[cfg(test)]
694 pub(crate) fn install_prompt_snapshot_hook_for_test(
695 &self,
696 ) -> (oneshot::Receiver<()>, std::sync::Arc<tokio::sync::Notify>) {
697 let (reached, receiver) = oneshot::channel();
698 let release = std::sync::Arc::new(tokio::sync::Notify::new());
699 *self
700 .inner
701 .test_hooks
702 .prompt_snapshot_hook
703 .lock()
704 .expect("snapshot hook lock") = Some(std::sync::Arc::new(super::PromptSnapshotHook {
705 reached: std::sync::Mutex::new(Some(reached)),
706 release: release.clone(),
707 }));
708 (receiver, release)
709 }
710
711 #[cfg(test)]
712 pub(crate) async fn reconcile_producers_for_test(
713 &self,
714 session: SessionId,
715 ) -> Result<(), EngineError> {
716 self.reconcile_producers(session).await
717 }
718
719 #[cfg(test)]
720 pub(crate) fn compaction_reserved_for_test(&self, session: SessionId) -> bool {
721 self.inner
722 .compaction
723 .in_progress
724 .lock()
725 .expect("compaction reservation lock poisoned")
726 .contains(&session)
727 }
728
729 #[cfg(test)]
730 pub(crate) fn run_active_for_test(&self, run: RunId) -> bool {
731 self.inner
732 .sessions
733 .active
734 .lock()
735 .expect("active runs lock poisoned")
736 .contains_key(&run)
737 }
738
739 #[cfg(test)]
740 pub(crate) fn actor_resident_for_test(&self, session: SessionId) -> bool {
741 self.inner
742 .sessions
743 .actors
744 .lock()
745 .expect("actor registry lock poisoned")
746 .contains_key(&session)
747 }
748
749 #[cfg(test)]
750 pub(crate) fn install_compaction_execution_hook_for_test(
751 &self,
752 ) -> (oneshot::Receiver<()>, std::sync::Arc<tokio::sync::Notify>) {
753 let (reached, receiver) = oneshot::channel();
754 let release = std::sync::Arc::new(tokio::sync::Notify::new());
755 *self
756 .inner
757 .test_hooks
758 .compaction_execution_hook
759 .lock()
760 .expect("compaction execution hook lock poisoned") =
761 Some(std::sync::Arc::new(PagingRaceHook {
762 reached: std::sync::Mutex::new(Some(reached)),
763 release: std::sync::Arc::clone(&release),
764 }));
765 (receiver, release)
766 }
767
768 #[cfg(test)]
769 pub(crate) async fn enqueue_compact_without_residency_for_test(
770 &self,
771 session: SessionId,
772 ) -> Result<
773 oneshot::Receiver<Result<cookie_agent_protocol::SessionCompactResult, EngineError>>,
774 EngineError,
775 > {
776 let actor = self
777 .inner
778 .sessions
779 .actors
780 .lock()
781 .expect("actor registry lock poisoned")
782 .get(&session)
783 .cloned()
784 .ok_or(EngineError::MissingActor(session))?;
785 let (reply, receiver) = oneshot::channel();
786 actor
787 .send(SessionCommand::Compact {
788 focus: None,
789 origin: super::event_origin("engine:auto-compact"),
790 reply,
791 })
792 .await
793 .map_err(|_| EngineError::ActorStopped)?;
794 Ok(receiver)
795 }
796
797 pub(super) fn request_blocking<T>(
798 &self,
799 session: SessionId,
800 command: impl FnOnce(oneshot::Sender<Result<T, EngineError>>) -> SessionCommand,
801 ) -> Result<T, EngineError> {
802 let (reply, receiver) = oneshot::channel();
803 {
804 let _residency = self.inner.sessions.residency_mutation.blocking_lock();
805 self.ensure_session_owned(session)?;
806 self.spawn_actor(session)?;
807 let actor = self
808 .inner
809 .sessions
810 .actors
811 .lock()
812 .unwrap_or_else(|poisoned| poisoned.into_inner())
813 .get(&session)
814 .cloned()
815 .ok_or(EngineError::MissingActor(session))?;
816 actor
817 .blocking_send(command(reply))
818 .map_err(|_| EngineError::ActorStopped)?;
819 }
820 receiver
821 .blocking_recv()
822 .map_err(|_| EngineError::ActorStopped)?
823 }
824
825 pub(crate) fn spawn_actor(&self, session: SessionId) -> Result<(), EngineError> {
826 let mut actors = self
827 .inner
828 .sessions
829 .actors
830 .lock()
831 .unwrap_or_else(|poisoned| poisoned.into_inner());
832 self.ensure_not_shutting_down()?;
833 if !self.inner.store.is_owned(session) {
834 return Err(EngineError::SessionOwnedByAnotherProcess(session));
835 }
836 if actors.contains_key(&session) {
837 return Ok(());
838 }
839 self.inner
840 .compaction
841 .context_token_estimators
842 .lock()
843 .unwrap_or_else(|poisoned| poisoned.into_inner())
844 .entry(session)
845 .or_default();
846 let engine = self.clone();
847 let actor = SessionActor::spawn(SESSION_MAILBOX_CAPACITY, move |command| {
848 let engine = engine.clone();
849 async move { engine.handle_actor_command(session, command).await }
850 });
851 actors.insert(session, actor);
852 Ok(())
853 }
854
855 pub(crate) fn ensure_session_owned(&self, session: SessionId) -> Result<(), EngineError> {
856 self.ensure_not_shutting_down()?;
857 let adoption_lock = self.inner.store.adoption_lock(session);
858 let _adoption = adoption_lock
859 .lock()
860 .unwrap_or_else(|poisoned| poisoned.into_inner());
861 self.ensure_not_shutting_down()?;
862 let write_open = self
863 .inner
864 .store
865 .begin_write(session)
866 .map_err(|error| match error {
867 SessionError::SessionLocked(_) => {
868 EngineError::SessionOwnedByAnotherProcess(session)
869 }
870 SessionError::StoreClosed => EngineError::ActorStopped,
871 error => error.into(),
872 })?;
873 if let Err(error) = self.ensure_not_shutting_down() {
874 if write_open == crate::session::WriteOpen::Adopting {
875 self.inner.store.rollback_adoption(session);
876 }
877 return Err(error);
878 }
879 if write_open == crate::session::WriteOpen::Adopting {
880 if let Err(error) = self.reconcile_session(session) {
881 self.inner.store.rollback_adoption(session);
882 return Err(error);
883 }
884 if let Err(error) = self.ensure_not_shutting_down() {
885 self.inner.store.rollback_adoption(session);
886 return Err(error);
887 }
888 if let Err(error) = self.inner.store.commit_adoption(session) {
889 self.inner.store.rollback_adoption(session);
890 return Err(if matches!(error, SessionError::StoreClosed) {
891 EngineError::ActorStopped
892 } else {
893 error.into()
894 });
895 }
896 self.ensure_not_shutting_down()?;
897 if let Err(error) = self.recover_missing_delegation_children(session) {
898 eprintln!("session {session} missing-child recovery deferred: {error}");
899 }
900 if let Err(error) =
901 self.rebuild_delegation_registry(&self.inner.delegation_events.entries(), true)
902 {
903 eprintln!("session {session} delegation recovery deferred: {error}");
904 }
905 }
906 Ok(())
907 }
908
909 pub(super) fn ensure_not_shutting_down(&self) -> Result<(), EngineError> {
910 if self
911 .inner
912 .delegation
913 .admission_tasks_closing
914 .load(std::sync::atomic::Ordering::Acquire)
915 {
916 Err(EngineError::ActorStopped)
917 } else {
918 Ok(())
919 }
920 }
921
922 pub(super) fn reserve_compaction(&self, session: SessionId) -> bool {
923 self.inner
924 .compaction
925 .in_progress
926 .lock()
927 .unwrap_or_else(|poisoned| poisoned.into_inner())
928 .insert(session)
929 }
930
931 pub(super) async fn finish_compaction(&self, session: SessionId) -> Result<(), EngineError> {
932 self.request(session, |reply| SessionCommand::CompactionFinished {
933 reply,
934 })
935 .await
936 }
937
938 async fn release_compaction_direct(&self, session: SessionId) {
939 if let Some(state) = self
940 .inner
941 .sessions
942 .producers
943 .lock()
944 .unwrap_or_else(|e| e.into_inner())
945 .get_mut(&session)
946 {
947 state.starting = false;
948 }
949 self.inner
950 .compaction
951 .in_progress
952 .lock()
953 .unwrap_or_else(|poisoned| poisoned.into_inner())
954 .remove(&session);
955 let deferred = self
956 .inner
957 .compaction
958 .deferred
959 .lock()
960 .unwrap_or_else(|poisoned| poisoned.into_inner())
961 .remove(&session)
962 .unwrap_or_default();
963 for command in deferred {
964 Box::pin(self.handle_actor_command(session, command)).await;
965 }
966 }
967
968 pub(super) async fn handle_actor_command(&self, session: SessionId, command: SessionCommand) {
969 if matches!(&command, SessionCommand::Start { .. }) {
970 {
971 let mut registry = self
972 .inner
973 .sessions
974 .producers
975 .lock()
976 .unwrap_or_else(|e| e.into_inner());
977 let state = registry.entry(session).or_default();
978 if state.starting {
979 state.preempted = true;
980 }
981 }
982 }
983 if let Some(kind) = command.compaction_deferred_kind()
984 && self
985 .inner
986 .compaction
987 .in_progress
988 .lock()
989 .unwrap_or_else(|poisoned| poisoned.into_inner())
990 .contains(&session)
991 {
992 let rejected = {
993 let mut deferred = self
994 .inner
995 .compaction
996 .deferred
997 .lock()
998 .unwrap_or_else(|poisoned| poisoned.into_inner());
999 let queue = deferred.entry(session).or_default();
1000 let duplicate = queue
1001 .iter()
1002 .any(|pending| pending.compaction_deferred_kind() == Some(kind));
1003 let rejected = if duplicate {
1004 Some(command)
1005 } else {
1006 queue.push_back(command);
1007 None
1008 };
1009 debug_assert!(queue.len() <= MAX_COMPACTION_DEFERRED_COMMANDS);
1010 rejected
1011 };
1012 if let Some(rejected) = rejected {
1013 rejected.reject_duplicate_during_compaction(session);
1014 }
1015 return;
1016 }
1017 let reconcile = match &command {
1018 SessionCommand::EvictionBarrier { .. } => false,
1019 SessionCommand::Append { event, .. } => matches!(
1020 &**event,
1021 Event::ModelTurnCommitted { .. }
1022 | Event::RunCompleted { .. }
1023 | Event::RunFailed { .. }
1024 | Event::RunCancelled { .. }
1025 | Event::RunInterrupted { .. }
1026 | Event::GoalActivated { .. }
1027 | Event::GoalChecklistRevised { .. }
1028 | Event::GoalLifecycleChanged { .. }
1029 | Event::ProducerMessageAccepted { .. }
1030 | Event::ProducerMessageConsumed { .. }
1031 | Event::ProducerMessageDiscarded { .. }
1032 | Event::UserInputAdmitted { .. }
1033 | Event::UserInputSubmitted { .. }
1034 | Event::UserInputRecalled { .. }
1035 | Event::UserInputRecalledV2 { .. }
1036 | Event::SessionReverted { .. }
1037 ),
1038 SessionCommand::Producer(
1039 super::producers::ProducerCommand::WakeFinished { .. }
1040 | super::producers::ProducerCommand::Reconcile { .. }
1041 | super::producers::ProducerCommand::CommitStart { .. },
1042 ) => false,
1043 _ => true,
1044 };
1045 match command {
1046 SessionCommand::Producer(command) => {
1047 self.handle_producer_command(session, command).await
1048 }
1049 SessionCommand::EvictionBarrier { reply } => {
1050 let _ = reply.send(Ok(()));
1051 }
1052 SessionCommand::Append {
1053 run,
1054 origin,
1055 event,
1056 reply,
1057 } => {
1058 #[cfg(test)]
1059 if matches!(
1060 &*event,
1061 Event::MessageInjected { .. }
1062 | Event::UserInputTransformed { .. }
1063 | Event::UserInputSubmitted { .. }
1064 | Event::RunFailed { .. }
1065 ) && self
1066 .inner
1067 .test_hooks
1068 .run_setup_append_failures
1069 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |remaining| {
1070 remaining.checked_sub(1)
1071 })
1072 .is_ok()
1073 {
1074 let event_name = match &*event {
1075 Event::MessageInjected { .. } => "message injected",
1076 Event::UserInputTransformed { .. } => "user input transformed",
1077 Event::UserInputSubmitted { .. } => "user input submitted",
1078 Event::RunFailed { .. } => "run failed",
1079 _ => unreachable!("run setup failure filter checked"),
1080 };
1081 let _ = reply.send(Err(EngineError::Mcp(format!(
1082 "injected {event_name} append failure"
1083 ))));
1084 return;
1085 }
1086 let reverted = matches!(&*event, Event::SessionReverted { .. });
1087 let result = self
1088 .append_direct(session, run, origin, *event)
1089 .and_then(|result| {
1090 if reverted {
1091 self.inner.delegation_events.reconcile_parent(session)?;
1092 self.reconcile_reverted_producers_direct(session)?;
1093 }
1094 Ok(result)
1095 });
1096 let _ = reply.send(result);
1097 }
1098 SessionCommand::EnsureToolCallLinked {
1099 run,
1100 tool_call_id,
1101 child_session_id,
1102 reply,
1103 } => {
1104 let result = (|| {
1105 let linked = self
1106 .inner
1107 .store
1108 .get(session)?
1109 .log
1110 .event_snapshot()
1111 .iter()
1112 .any(|event| {
1113 matches!(event.payload, Event::ToolCallLinked { tool_call_id: linked_call, child_session_id: linked_child }
1114 if linked_call == tool_call_id && linked_child == child_session_id)
1115 });
1116 if !linked {
1117 self.append_direct(
1118 session,
1119 Some(run),
1120 super::event_origin("engine:delegation"),
1121 Event::ToolCallLinked {
1122 tool_call_id,
1123 child_session_id,
1124 },
1125 )?;
1126 }
1127 Ok(())
1128 })();
1129 let _ = reply.send(result);
1130 }
1131 SessionCommand::Start {
1132 params,
1133 origin,
1134 admission,
1135 reply,
1136 } => {
1137 if !self.reserve_compaction(session) {
1138 let _ = reply.send(Err(EngineError::SessionRunning(session)));
1139 } else {
1140 let engine = self.clone();
1141 tokio::spawn(async move {
1142 let child_session_id = params.session_id;
1143 let mut result = engine
1144 .start_run_direct(params, origin, admission, false)
1145 .await;
1146 if let (Some((invocation_id, generation)), Ok(started)) =
1147 (admission, result.as_ref())
1148 && let Err(error) = engine.publish_admission_run(
1149 invocation_id,
1150 generation,
1151 child_session_id,
1152 started.run_id,
1153 )
1154 {
1155 let _ = engine.cancel_run_durably(
1156 started.run_id,
1157 Some("delegate admission could not be published".into()),
1158 );
1159 result = Err(error);
1160 }
1161 if let Err(error) = engine.finish_compaction(session).await
1162 && result.is_ok()
1163 {
1164 result = Err(error);
1165 }
1166 let started = result.as_ref().ok().map(|result| result.run_id);
1167 if reply.send(result).is_err()
1168 && admission.is_some()
1169 && let Some(run_id) = started
1170 {
1171 let _ = engine.cancel_run_durably(
1172 run_id,
1173 Some("delegate admission reply abandoned".into()),
1174 );
1175 }
1176 });
1177 }
1178 }
1179 SessionCommand::Steer {
1180 run,
1181 origin,
1182 input,
1183 original_input,
1184 reply,
1185 } => {
1186 let active = self
1187 .inner
1188 .sessions
1189 .active
1190 .lock()
1191 .unwrap_or_else(|poisoned| poisoned.into_inner())
1192 .get(&run)
1193 .cloned()
1194 .filter(|active| active.session == session)
1195 .ok_or(EngineError::MissingRun(run));
1196 let result = match active {
1197 Err(error) => Err(error),
1198 Ok(_) => {
1199 let projection = self.inner.store.get(session);
1200 match projection {
1201 Err(error) => Err(error.into()),
1202 Ok(projection)
1203 if !projection
1204 .runs
1205 .get(&run)
1206 .is_some_and(|run| run.status == SessionStatus::Running) =>
1207 {
1208 Ok(RunSteerResult {
1209 accepted: false,
1210 handled_reason: None,
1211 })
1212 }
1213 Ok(_) => (|| {
1214 if let Some(original_input) = original_input {
1215 self.append_direct(
1216 session,
1217 Some(run),
1218 origin.clone(),
1219 Event::UserInputTransformed {
1220 original_input,
1221 input: input.clone(),
1222 },
1223 )?;
1224 }
1225 self.append_direct(
1226 session,
1227 Some(run),
1228 origin,
1229 Event::UserInputAdmitted { input },
1230 )?;
1231 self.clear_skill_turn_state(session);
1232 Ok(RunSteerResult {
1233 accepted: true,
1234 handled_reason: None,
1235 })
1236 })(),
1237 }
1238 }
1239 };
1240 let _ = reply.send(result);
1241 }
1242 SessionCommand::AdmitDelegatedResume { run, input, reply } => {
1243 let active = self
1244 .inner
1245 .sessions
1246 .active
1247 .lock()
1248 .unwrap_or_else(|poisoned| poisoned.into_inner())
1249 .get(&run)
1250 .cloned()
1251 .filter(|active| active.session == session)
1252 .ok_or(EngineError::MissingRun(run));
1253 let result = match active {
1254 Err(error) => Err(error),
1255 Ok(_) => {
1256 let projection = self.inner.store.get(session);
1257 match projection {
1258 Err(error) => Err(error.into()),
1259 Ok(projection)
1260 if !projection
1261 .runs
1262 .get(&run)
1263 .is_some_and(|run| run.status == SessionStatus::Running) =>
1264 {
1265 Ok(super::DelegatedResumeAdmission {
1266 accepted: false,
1267 admission_seq: None,
1268 })
1269 }
1270 Ok(_) => self
1271 .append_direct(
1272 session,
1273 Some(run),
1274 super::event_origin("engine:delegation"),
1275 Event::UserInputAdmitted { input },
1276 )
1277 .and_then(|()| {
1278 self.clear_skill_turn_state(session);
1279 let admission_seq =
1280 self.inner.store.get(session)?.meta.last_event_seq;
1281 Ok(super::DelegatedResumeAdmission {
1282 accepted: true,
1283 admission_seq: Some(admission_seq),
1284 })
1285 }),
1286 }
1287 }
1288 };
1289 let _ = reply.send(result);
1290 }
1291 SessionCommand::RecallDelegatedResume {
1292 run,
1293 admission_seq,
1294 reply,
1295 } => {
1296 let result = (|| {
1297 let projection = self.inner.store.get(session)?;
1298 if !projection.runs.contains_key(&run) {
1299 return Err(EngineError::MissingRun(run));
1300 }
1301 let recalled = pending_inputs(&projection.log.event_snapshot(), run)
1302 .into_iter()
1303 .find(|pending| pending.admission_seq == admission_seq);
1304 if let Some(pending) = recalled {
1305 self.append_direct(
1306 session,
1307 Some(run),
1308 super::event_origin("engine:delegation"),
1309 Event::UserInputRecalledV2 {
1310 user_input_seq: admission_seq,
1311 input: pending.input,
1312 },
1313 )?;
1314 Ok(true)
1315 } else {
1316 Ok(false)
1317 }
1318 })();
1319 let _ = reply.send(result);
1320 }
1321 SessionCommand::RecallSteer { run, reply } => {
1322 let result = (|| {
1323 self.inner
1324 .sessions
1325 .active
1326 .lock()
1327 .unwrap_or_else(|poisoned| poisoned.into_inner())
1328 .get(&run)
1329 .cloned()
1330 .filter(|active| active.session == session)
1331 .ok_or(EngineError::MissingRun(run))?;
1332 let projection = self.inner.store.get(session)?;
1333 if !projection
1334 .runs
1335 .get(&run)
1336 .is_some_and(|run| run.status == SessionStatus::Running)
1337 {
1338 return Ok(RunRecallSteerResult { recalled: None });
1339 }
1340 let recalled = pending_inputs(&projection.log.event_snapshot(), run)
1341 .pop()
1342 .map(|pending| pending.input);
1343 if let Some(input) = &recalled {
1344 self.append_direct(
1345 session,
1346 Some(run),
1347 super::event_origin("user"),
1348 Event::UserInputRecalled {
1349 input: input.clone(),
1350 },
1351 )?;
1352 }
1353 Ok(RunRecallSteerResult { recalled })
1354 })();
1355 let _ = reply.send(result);
1356 }
1357 SessionCommand::CommitPendingPromotion {
1358 run,
1359 through_admission_seq,
1360 final_text,
1361 complete_if_empty,
1362 already_promoted,
1363 reply,
1364 } => {
1365 let result = (|| {
1366 let projection = self.inner.store.get(session)?;
1367 if !projection
1368 .runs
1369 .get(&run)
1370 .is_some_and(|run| run.status == SessionStatus::Running)
1371 {
1372 return Ok(PendingPromotionState {
1373 promoted: already_promoted,
1374 pending: Vec::new(),
1375 continue_run: false,
1376 });
1377 }
1378 let eligible = pending_inputs(&projection.log.event_snapshot(), run)
1379 .into_iter()
1380 .take_while(|pending| pending.admission_seq <= through_admission_seq)
1381 .collect::<Vec<_>>();
1382 for pending in &eligible {
1383 self.append_direct(
1384 session,
1385 Some(run),
1386 pending.origin.clone(),
1387 Event::UserInputSubmitted {
1388 input: pending.input.clone(),
1389 },
1390 )?;
1391 }
1392 let producer_promoted =
1393 self.promote_producer_inputs_direct(session, run, false)?;
1394 let promoted = already_promoted || !eligible.is_empty() || producer_promoted;
1395 let pending =
1396 pending_inputs(&self.inner.store.get(session)?.log.event_snapshot(), run);
1397 if pending.is_empty() && !promoted && complete_if_empty {
1398 self.append_direct(
1399 session,
1400 Some(run),
1401 super::event_origin("engine:model-loop"),
1402 Event::RunCompleted { final_text },
1403 )?;
1404 }
1405 Ok(PendingPromotionState {
1406 promoted,
1407 continue_run: promoted,
1408 pending,
1409 })
1410 })();
1411 if result.as_ref().is_ok_and(|state| state.pending.is_empty()) {
1412 self.release_compaction_direct(session).await;
1413 }
1414 let _ = reply.send(result);
1415 }
1416 SessionCommand::Compact {
1417 focus,
1418 origin,
1419 reply,
1420 } => {
1421 if !self.reserve_compaction(session) {
1422 let _ = reply.send(Err(EngineError::SessionRunning(session)));
1423 } else {
1424 let engine = self.clone();
1425 tokio::spawn(async move {
1426 #[cfg(test)]
1427 let hook = engine
1428 .inner
1429 .test_hooks
1430 .compaction_execution_hook
1431 .lock()
1432 .expect("compaction execution hook lock poisoned")
1433 .take();
1434 #[cfg(test)]
1435 if let Some(hook) = hook {
1436 if let Some(reached) = hook
1437 .reached
1438 .lock()
1439 .expect("compaction execution reached lock poisoned")
1440 .take()
1441 {
1442 let _ = reached.send(());
1443 }
1444 hook.release.notified().await;
1445 }
1446 let mut result = engine
1447 .compact_session_direct(session, focus.as_deref(), origin)
1448 .await;
1449 if let Err(error) = engine.finish_compaction(session).await
1450 && result.is_ok()
1451 {
1452 result = Err(error);
1453 }
1454 let _ = reply.send(result);
1455 });
1456 }
1457 }
1458 SessionCommand::Revert {
1459 through_seq,
1460 origin,
1461 instructions_override,
1462 reply,
1463 } => {
1464 if !self.reserve_compaction(session) {
1465 let _ = reply.send(Err(EngineError::SessionRunning(session)));
1466 } else {
1467 let result = (|| {
1468 let projection = self.inner.store.get(session)?;
1469 if projection.status == SessionStatus::Running {
1470 return Err(EngineError::SessionRunning(session));
1471 }
1472 let tip = projection
1473 .log
1474 .all_events()
1475 .last()
1476 .map_or(0, |event| event.seq);
1477 if through_seq == 0 || through_seq > tip {
1478 return Err(SessionError::InvalidSequence {
1479 session_id: session,
1480 through_seq,
1481 }
1482 .into());
1483 }
1484 self.append_direct(
1485 session,
1486 None,
1487 origin,
1488 Event::SessionReverted { through_seq },
1489 )?;
1490 self.inner
1491 .compaction
1492 .context_token_estimators
1493 .lock()
1494 .unwrap_or_else(|poisoned| poisoned.into_inner())
1495 .remove(&session);
1496 self.rebuild_visible_tree_grants();
1497 self.inner.delegation_events.reconcile_parent(session)?;
1498 self.reconcile_reverted_producers_direct(session)?;
1499 Ok(SessionRevertResult {
1500 session: self.inner.store.get(session)?.metadata(),
1501 instructions_override,
1502 })
1503 })();
1504 self.release_compaction_direct(session).await;
1505 let _ = reply.send(result);
1506 }
1507 }
1508 SessionCommand::CompactionFinished { reply } => {
1509 self.release_compaction_direct(session).await;
1510 let _ = reply.send(Ok(()));
1511 }
1512 SessionCommand::Cancel { run, reply } => {
1513 let result = (|| {
1514 let active = self
1515 .inner
1516 .sessions
1517 .active
1518 .lock()
1519 .unwrap_or_else(|poisoned| poisoned.into_inner())
1520 .get(&run)
1521 .cloned()
1522 .filter(|active| active.session == session)
1523 .ok_or(EngineError::MissingRun(run))?;
1524 active.cancellation.cancel();
1525 active
1526 .stdin
1527 .lock()
1528 .unwrap_or_else(|poisoned| poisoned.into_inner())
1529 .clear();
1530 let events = self.inner.store.get(session)?.log.event_snapshot();
1531 let pending = approval_records(session, &events)
1532 .into_values()
1533 .filter(|record| {
1534 matches!(
1535 record.status,
1536 ApprovalStatus::Pending | ApprovalStatus::Escalated
1537 ) && approval_run_id(&events, record.request.approval_id()) == Some(run)
1538 })
1539 .map(|record| record.request.approval_id())
1540 .collect::<Vec<_>>();
1541 for approval_id in pending {
1542 self.approval_terminal_direct(
1543 session,
1544 run,
1545 approval_id,
1546 ApprovalTerminal::Cancelled,
1547 )?;
1548 }
1549 Ok(RunCancelResult { cancelled: true })
1550 })();
1551 let _ = reply.send(result);
1552 }
1553 SessionCommand::Stdin { params, reply } => {
1554 let result = (|| {
1555 let active = self
1556 .inner
1557 .sessions
1558 .active
1559 .lock()
1560 .unwrap_or_else(|poisoned| poisoned.into_inner())
1561 .get(¶ms.run_id)
1562 .cloned()
1563 .filter(|active| active.session == session)
1564 .ok_or(EngineError::MissingRun(params.run_id))?;
1565 let data = params
1566 .data
1567 .map(|encoded| STANDARD.decode(encoded))
1568 .transpose()?
1569 .unwrap_or_default();
1570 let sender = active
1571 .stdin
1572 .lock()
1573 .unwrap_or_else(|poisoned| poisoned.into_inner())
1574 .get(¶ms.call_id)
1575 .cloned()
1576 .ok_or(EngineError::StdinUnavailable)?;
1577 sender
1578 .try_send(StdinWrite {
1579 data: data.clone(),
1580 eof: params.eof,
1581 })
1582 .map_err(|_| EngineError::StdinUnavailable)?;
1583 if params.eof {
1584 active
1585 .stdin
1586 .lock()
1587 .unwrap_or_else(|poisoned| poisoned.into_inner())
1588 .remove(¶ms.call_id);
1589 }
1590 self.append_direct(
1591 session,
1592 Some(params.run_id),
1593 super::event_origin("engine:tool-execution"),
1594 Event::ToolStdinSubmitted {
1595 tool_call_id: params.call_id,
1596 byte_count: data.len() as u64,
1597 },
1598 )?;
1599 Ok(RunToolStdinResult { accepted: true })
1600 })();
1601 let _ = reply.send(result);
1602 }
1603 SessionCommand::Subscribe { cursor, reply } => {
1604 let result = self.inner.store.subscribe_events(session, cursor);
1605 let _ = reply.send(result.map_err(EngineError::from));
1606 }
1607 SessionCommand::Resume { reply } => {
1608 let result = self
1609 .resolve_interrupted_direct(session)
1610 .await
1611 .and_then(|()| Ok(self.inner.store.get(session)?.metadata()));
1612 let _ = reply.send(result);
1613 }
1614 SessionCommand::Rename {
1615 params,
1616 origin: _request_origin,
1617 reply,
1618 } => {
1619 let result = (|| {
1620 let projection = self.inner.store.get(session)?;
1621 if let Some(record) = projection.rename_records.get(¶ms.client_rename_id) {
1622 if record.conflicts_with(¶ms) {
1623 return Err(EngineError::RenameConflict);
1624 }
1625 return Ok(SessionRenameResult {
1626 client_rename_id: params.client_rename_id,
1627 session: projection.metadata(),
1628 });
1629 }
1630 let commit = match params.change {
1631 SessionRenameChange::Set { title } => SessionTitleChange::UserSet {
1632 title,
1633 client_rename_id: params.client_rename_id.clone(),
1634 },
1635 SessionRenameChange::Clear => SessionTitleChange::UserClear {
1636 client_rename_id: params.client_rename_id.clone(),
1637 },
1638 SessionRenameChange::Reset => SessionTitleChange::UserReset {
1639 client_rename_id: params.client_rename_id.clone(),
1640 },
1641 };
1642 let input_through_seq = projection
1643 .log
1644 .all_events()
1645 .last()
1646 .map_or(0, |event| event.seq);
1647 self.append_direct(
1648 session,
1649 None,
1650 super::event_origin("user"),
1651 Event::SessionTitleCommitted {
1652 input_through_seq,
1653 change: commit,
1654 },
1655 )?;
1656 Ok(SessionRenameResult {
1657 client_rename_id: params.client_rename_id,
1658 session: self.inner.store.get(session)?.metadata(),
1659 })
1660 })();
1661 let _ = reply.send(result);
1662 }
1663 SessionCommand::ApprovalRespond {
1664 params,
1665 origin: _request_origin,
1666 reply,
1667 } => {
1668 let _ = reply.send(self.approval_respond_direct(params));
1669 }
1670 SessionCommand::ApprovalCapabilityInvalid {
1671 params,
1672 invalidation,
1673 reply,
1674 } => {
1675 let _ = reply.send(self.approval_capability_invalid_direct(params, invalidation));
1676 }
1677 SessionCommand::ApprovalEvaluationComplete {
1678 run,
1679 request,
1680 executor,
1681 decision,
1682 permission_mode,
1683 cancelled,
1684 reply,
1685 } => {
1686 let _ = reply.send(self.approval_evaluation_complete_direct(
1687 session,
1688 run,
1689 request,
1690 executor,
1691 (permission_mode, decision),
1692 cancelled,
1693 ));
1694 }
1695 SessionCommand::ApprovalTerminal {
1696 run,
1697 approval_id,
1698 terminal,
1699 reply,
1700 } => {
1701 let _ =
1702 reply.send(self.approval_terminal_direct(session, run, approval_id, terminal));
1703 }
1704 SessionCommand::ToolResult {
1705 run,
1706 tool_call_id,
1707 result,
1708 cancelled,
1709 reply,
1710 } => {
1711 let pending = self
1712 .inner
1713 .store
1714 .get(session)
1715 .ok()
1716 .and_then(|projection| projection.runs.get(&run).cloned())
1717 .is_some_and(|run| run.pending_calls.contains_key(&tool_call_id));
1718 let response = if !pending {
1719 Ok(false)
1720 } else {
1721 (|| {
1722 let owner = self.tool_call_owner(session, run, tool_call_id)?;
1723 let event = match result {
1724 Ok(result) => Event::ToolCallTerminated {
1725 termination: ToolCallTermination {
1726 tool_call_id,
1727 owner,
1728 outcome: if cancelled {
1729 ToolTerminationOutcome::Cancelled
1730 } else {
1731 ToolTerminationOutcome::Completed
1732 },
1733 result: Some(result),
1734 error: cancelled.then(|| SafeToolError {
1735 code: super::safe_code(super::CANCELLED_AFTER_COMPLETION),
1736 message: safe_error("tool call cancelled after it started"),
1737 }),
1738 },
1739 },
1740 Err(failure) => Event::ToolCallTerminated {
1741 termination: ToolCallTermination {
1742 tool_call_id,
1743 owner,
1744 outcome: if cancelled {
1745 ToolTerminationOutcome::Cancelled
1746 } else {
1747 ToolTerminationOutcome::Failed
1748 },
1749 result: failure.partial_output.map(|result| *result),
1750 error: Some(SafeToolError {
1751 code: failure.code.safe_code(),
1752 message: safe_error(&failure.message),
1753 }),
1754 },
1755 },
1756 };
1757 self.append_direct(
1758 session,
1759 Some(run),
1760 super::event_origin("engine:tool-result"),
1761 event,
1762 )?;
1763 self.inner.store.get(session)?.log.flush()?;
1764 Ok(true)
1765 })()
1766 };
1767 if let Some(capture) = self
1768 .inner
1769 .output
1770 .captures
1771 .lock()
1772 .unwrap_or_else(|p| p.into_inner())
1773 .remove(&tool_call_id)
1774 {
1775 capture.release_publication();
1776 }
1777 let _ = reply.send(response);
1778 }
1779 SessionCommand::ResolveDelegateFailureIfPending {
1780 run,
1781 tool_call_id,
1782 result,
1783 reply,
1784 } => {
1785 let result = self.resolve_delegate_failure_if_pending_direct(
1786 session,
1787 run,
1788 tool_call_id,
1789 result,
1790 );
1791 let _ = reply.send(result);
1792 }
1793 SessionCommand::ResolveAbandonedDelegateFailureIfPending {
1794 invocation_id,
1795 generation,
1796 run,
1797 tool_call_id,
1798 result,
1799 reply,
1800 } => {
1801 let result = self.resolve_abandoned_delegate_failure_if_pending_direct(
1802 invocation_id,
1803 generation,
1804 session,
1805 run,
1806 tool_call_id,
1807 result,
1808 );
1809 let _ = reply.send(result);
1810 }
1811 SessionCommand::PromotePendingOrComplete {
1812 run,
1813 final_text,
1814 complete_if_empty,
1815 reply,
1816 } => {
1817 let producer_promoted =
1818 match self.promote_producer_inputs_direct(session, run, false) {
1819 Ok(promoted) => promoted,
1820 Err(error) => {
1821 let _ = reply.send(Err(error));
1822 return;
1823 }
1824 };
1825 let active = self
1826 .inner
1827 .sessions
1828 .active
1829 .lock()
1830 .unwrap_or_else(|poisoned| poisoned.into_inner())
1831 .get(&run)
1832 .cloned()
1833 .filter(|active| active.session == session)
1834 .ok_or(EngineError::MissingRun(run));
1835 let active = match active {
1836 Ok(active) => active,
1837 Err(error) => {
1838 let _ = reply.send(Err(error));
1839 return;
1840 }
1841 };
1842 let pending = match self.inner.store.get(session) {
1843 Ok(projection) => pending_inputs(&projection.log.event_snapshot(), run),
1844 Err(error) => {
1845 let _ = reply.send(Err(error.into()));
1846 return;
1847 }
1848 };
1849 if pending.is_empty() {
1850 let result = if complete_if_empty && !producer_promoted {
1851 self.append_direct(
1852 session,
1853 Some(run),
1854 super::event_origin("engine:model-loop"),
1855 Event::RunCompleted { final_text },
1856 )
1857 } else {
1858 Ok(())
1859 }
1860 .map(|()| producer_promoted);
1861 let _ = reply.send(result);
1862 } else if !self.reserve_compaction(session) {
1863 let _ = reply.send(Err(EngineError::SessionRunning(session)));
1864 } else {
1865 let engine = self.clone();
1866 tokio::spawn(async move {
1867 let mut pending = pending;
1868 let mut promoted = false;
1869 let result = loop {
1870 let fallback_index =
1871 active.fallback_index.load(Ordering::Acquire) as usize;
1872 let through_admission_seq = pending
1873 .last()
1874 .expect("pending batch is nonempty")
1875 .admission_seq;
1876 let mut failed = None;
1877 let mut serialized_message_bytes = 0_usize;
1878 for pending_input in &pending {
1879 let input_bytes = match model_loop::serialized_input_bytes(
1880 &pending_input.input,
1881 ) {
1882 Ok(input_bytes) => input_bytes,
1883 Err(error) => {
1884 failed = Some(error);
1885 break;
1886 }
1887 };
1888 serialized_message_bytes =
1889 serialized_message_bytes.saturating_add(input_bytes);
1890 match engine
1891 .maybe_predictive_compact_before_input(
1892 PredictiveCompactionInput {
1893 session,
1894 run,
1895 serialized_message_bytes,
1896 policy: &active.policy,
1897 fallback_index,
1898 cancellation: &active.cancellation,
1899 actor_direct: false,
1900 },
1901 )
1902 .await
1903 {
1904 Ok(true) => break,
1905 Ok(false) => {}
1906 Err(error) => {
1907 failed = Some(error);
1908 break;
1909 }
1910 }
1911 }
1912 if let Some(error) = failed {
1913 break Err(error);
1914 }
1915 match engine
1916 .request(session, |reply| SessionCommand::CommitPendingPromotion {
1917 run,
1918 through_admission_seq,
1919 final_text: final_text.clone(),
1920 complete_if_empty,
1921 already_promoted: promoted,
1922 reply,
1923 })
1924 .await
1925 {
1926 Ok(state) if state.pending.is_empty() => {
1927 break Ok(state.continue_run);
1928 }
1929 Ok(state) => {
1930 promoted = state.promoted;
1931 pending = state.pending;
1932 }
1933 Err(error) => break Err(error),
1934 }
1935 };
1936 let mut result = result;
1937 if result.is_err()
1938 && let Err(error) = engine.finish_compaction(session).await
1939 {
1940 result = Err(error);
1941 }
1942 let _ = reply.send(result);
1943 });
1944 }
1945 }
1946 SessionCommand::PromotePendingInputs { run, reply } => {
1947 let result = self
1948 .inner
1949 .sessions
1950 .active
1951 .lock()
1952 .unwrap_or_else(|poisoned| poisoned.into_inner())
1953 .get(&run)
1954 .cloned()
1955 .filter(|active| active.session == session)
1956 .ok_or(EngineError::MissingRun(run))
1957 .and_then(|_| {
1958 self.promote_producer_inputs_direct(session, run, false)?;
1959 let events = self.inner.store.get(session)?.log.event_snapshot();
1960 let applied: HashSet<u64> = events
1961 .iter()
1962 .filter_map(|event| match &event.payload {
1963 Event::UserInputApplied { user_input_seq }
1964 if event.run_id == Some(run) =>
1965 {
1966 Some(*user_input_seq)
1967 }
1968 _ => None,
1969 })
1970 .collect();
1971 for user_input_seq in
1972 events.iter().filter_map(|event| match &event.payload {
1973 Event::UserInputSubmitted { .. }
1974 if event.run_id == Some(run)
1975 && !applied.contains(&event.seq) =>
1976 {
1977 Some(event.seq)
1978 }
1979 _ => None,
1980 })
1981 {
1982 self.append_direct(
1983 session,
1984 Some(run),
1985 super::event_origin("engine:model-loop"),
1986 Event::UserInputApplied { user_input_seq },
1987 )?;
1988 }
1989 self.claim_producer_snapshot_direct(session, run)
1990 });
1991 let _ = reply.send(result);
1992 }
1993 }
1994 if reconcile && let Err(error) = self.reconcile_producers_direct(session, None) {
1995 eprintln!("session {session} producer reconciliation failed: {error}");
1996 }
1997 }
1998}
1999
2000fn pending_inputs(events: &[StoredEvent], run: RunId) -> Vec<PendingInput> {
2001 let mut pending = VecDeque::new();
2002 let mut initial_input_submitted = false;
2003 let run_start_seq = events.iter().find_map(|event| {
2004 (event.run_id == Some(run) && matches!(event.payload, Event::RunStarted { .. }))
2005 .then_some(event.seq)
2006 });
2007 let previous_terminal_seq = run_start_seq
2008 .and_then(|run_start_seq| {
2009 events
2010 .iter()
2011 .rev()
2012 .find(|event| {
2013 event.seq < run_start_seq
2014 && matches!(
2015 event.payload,
2016 Event::RunCompleted { .. }
2017 | Event::RunFailed { .. }
2018 | Event::RunCancelled { .. }
2019 | Event::RunInterrupted { .. }
2020 )
2021 })
2022 .map(|event| event.seq)
2023 })
2024 .unwrap_or(0);
2025 for event in events.iter().filter(|event| {
2026 event.run_id == Some(run)
2027 || (run_start_seq.is_some_and(|run_start_seq| event.seq < run_start_seq)
2028 && event.seq > previous_terminal_seq
2029 && event.run_id.is_none()
2030 && matches!(
2031 event.payload,
2032 Event::UserInputAdmitted { .. } | Event::UserInputRecalled { .. }
2033 ))
2034 }) {
2035 match &event.payload {
2036 Event::ProducerMessageAdmitted { .. } => initial_input_submitted = true,
2037 Event::UserInputAdmitted { input } => pending.push_back(PendingInput {
2038 admission_seq: event.seq,
2039 origin: event
2040 .origin
2041 .clone()
2042 .unwrap_or_else(|| super::event_origin("user")),
2043 input: input.clone(),
2044 }),
2045 Event::UserInputRecalled { .. } => {
2046 pending.pop_back();
2047 }
2048 Event::UserInputRecalledV2 { user_input_seq, .. } => {
2049 if let Some(position) = pending
2050 .iter()
2051 .position(|pending| pending.admission_seq == *user_input_seq)
2052 {
2053 pending.remove(position);
2054 }
2055 }
2056 Event::UserInputSubmitted { .. } => {
2057 if initial_input_submitted {
2058 pending.pop_front();
2059 } else {
2060 initial_input_submitted = true;
2061 }
2062 }
2063 Event::RunCompleted { .. }
2064 | Event::RunFailed { .. }
2065 | Event::RunCancelled { .. }
2066 | Event::RunInterrupted { .. } => pending.clear(),
2067 _ => {}
2068 }
2069 }
2070 pending.into_iter().collect()
2071}
2072
2073#[cfg(test)]
2074mod tests {
2075 use cookie_agent_protocol::{RunId, SessionId, StoredEvent};
2076
2077 use super::{Event, PendingInput, pending_inputs};
2078
2079 fn event(run: RunId, seq: u64, payload: Event) -> StoredEvent {
2080 StoredEvent {
2081 engine_version: None,
2082 origin: None,
2083 session_id: SessionId::new_v7(),
2084 run_id: Some(run),
2085 seq,
2086 timestamp: jiff::Timestamp::now(),
2087 payload,
2088 }
2089 }
2090
2091 #[test]
2092 fn pending_lane_replays_fifo_promotion_lifo_recall_and_terminal_voiding() {
2093 let run = RunId::new_v7();
2094 let events = vec![
2095 event(
2096 run,
2097 1,
2098 Event::UserInputSubmitted {
2099 input: "initial".into(),
2100 },
2101 ),
2102 event(
2103 run,
2104 2,
2105 Event::UserInputAdmitted {
2106 input: "one".into(),
2107 },
2108 ),
2109 event(
2110 run,
2111 3,
2112 Event::UserInputAdmitted {
2113 input: "two".into(),
2114 },
2115 ),
2116 event(
2117 run,
2118 4,
2119 Event::UserInputRecalled {
2120 input: "two".into(),
2121 },
2122 ),
2123 event(
2124 run,
2125 5,
2126 Event::UserInputAdmitted {
2127 input: "three".into(),
2128 },
2129 ),
2130 event(
2131 run,
2132 6,
2133 Event::UserInputSubmitted {
2134 input: "one".into(),
2135 },
2136 ),
2137 ];
2138 assert_eq!(
2139 pending_inputs(&events, run),
2140 vec![PendingInput {
2141 admission_seq: 5,
2142 origin: cookie_agent_protocol::EventOrigin::new("user").unwrap(),
2143 input: "three".into(),
2144 }]
2145 );
2146 let replayed = serde_json::from_slice::<Vec<StoredEvent>>(
2147 &serde_json::to_vec(&events).expect("serialize replay events"),
2148 )
2149 .expect("deserialize replay events");
2150 assert_eq!(pending_inputs(&replayed, run), pending_inputs(&events, run));
2151
2152 let mut terminal = replayed;
2153 terminal.push(event(run, 7, Event::RunCancelled { reason: None }));
2154 assert!(pending_inputs(&terminal, run).is_empty());
2155 }
2156
2157 #[test]
2158 fn targeted_recall_removes_only_its_admission_sequence() {
2159 let run = RunId::new_v7();
2160 let events = vec![
2161 event(
2162 run,
2163 1,
2164 Event::UserInputSubmitted {
2165 input: "initial".into(),
2166 },
2167 ),
2168 event(
2169 run,
2170 2,
2171 Event::UserInputAdmitted {
2172 input: "resume".into(),
2173 },
2174 ),
2175 event(
2176 run,
2177 3,
2178 Event::UserInputAdmitted {
2179 input: "direct steer".into(),
2180 },
2181 ),
2182 event(
2183 run,
2184 4,
2185 Event::UserInputRecalledV2 {
2186 user_input_seq: 2,
2187 input: "resume".into(),
2188 },
2189 ),
2190 ];
2191 assert_eq!(
2192 pending_inputs(&events, run),
2193 vec![PendingInput {
2194 admission_seq: 3,
2195 origin: cookie_agent_protocol::EventOrigin::new("user").unwrap(),
2196 input: "direct steer".into(),
2197 }]
2198 );
2199 }
2200}