Skip to main content

cookie_agent_engine/runtime/
mailbox.rs

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/// Per-session mailbox and run state owned by [`super::Inner`].
33#[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    /// The task driving each in-flight run loop.
39    ///
40    /// Shutdown joins these after cancelling their runs, so a run task gets to
41    /// write its `RunCancelled` while the session actors and the store are
42    /// still up. A task drops its own entry when it finishes, and a new
43    /// registration sweeps whatever finished without being noticed.
44    pub(super) run_tasks: Mutex<HashMap<RunId, tokio::task::JoinHandle<()>>>,
45    pub(crate) residency_mutation: tokio::sync::Mutex<()>,
46}
47
48/// Removes a run task's join handle when its task ends, however it ends.
49pub(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    /// Records the task driving `run_id` so shutdown can wait for it.
64    ///
65    /// A task that finished before its handle arrived here has already dropped
66    /// its slot, so the sweep is what retires the handle it left behind.
67    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    /// Waits for the in-flight run tasks, then aborts whatever is left.
79    ///
80    /// Called from shutdown while the session actors and the store are still
81    /// up, so a cancelled run's terminal event lands in its log instead of
82    /// being repaired as a restart interruption on the next startup.
83    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    /// Subscribes to a currently running call's retained output and live tail.
140    /// Output is ephemeral and intentionally separate from event cursors.
141    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    /// Synchronous setup/CLI wrapper. Do not call from a Tokio runtime.
242    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    /// Commits a completed tool invocation through its session actor.
258    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            // Finalization and after-result hooks can await after the executor succeeds.
339            // Freeze status and retained-output state together at this append boundary.
340            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(&params.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(&params.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(&params.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(&params.client_rename_id) {
1622                        if record.conflicts_with(&params) {
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}