cookie_agent_engine/runtime/
runs.rs1use std::collections::HashSet;
2
3use cookie_agent_protocol::{
4 EventOrigin, RunCancelResult, RunId, RunRecallSteerResult, RunStartParams, RunStartResult,
5 RunSteerResult, RunToolStdinParams, RunToolStdinResult, SessionId, SessionStatus,
6};
7
8use super::{
9 ActiveRun, Engine, EngineError, Event, SessionCommand, UserInputInterception,
10 helpers::safe_error,
11};
12
13impl Engine {
14 pub async fn start_run(
15 &self,
16 params: RunStartParams,
17 origin: EventOrigin,
18 ) -> Result<RunStartResult, EngineError> {
19 let session = params.session_id;
20 self.request(session, |reply| SessionCommand::Start {
21 params,
22 origin,
23 admission: None,
24 reply,
25 })
26 .await
27 }
28
29 pub fn start_run_blocking(
31 &self,
32 params: RunStartParams,
33 origin: EventOrigin,
34 ) -> Result<RunStartResult, EngineError> {
35 let session = params.session_id;
36 self.request_blocking(session, |reply| SessionCommand::Start {
37 params,
38 origin,
39 admission: None,
40 reply,
41 })
42 }
43
44 pub async fn steer(
45 &self,
46 run_id: RunId,
47 input: String,
48 origin: EventOrigin,
49 ) -> Result<RunSteerResult, EngineError> {
50 let active = self
51 .inner
52 .sessions
53 .active
54 .lock()
55 .unwrap_or_else(|poisoned| poisoned.into_inner())
56 .get(&run_id)
57 .cloned()
58 .ok_or(EngineError::MissingRun(run_id))?;
59 match self.intercept_user_input(active.session, input).await? {
60 UserInputInterception::Accepted {
61 input,
62 original_input,
63 } => {
64 self.request(active.session, |reply| SessionCommand::Steer {
65 run: run_id,
66 origin,
67 input,
68 original_input,
69 reply,
70 })
71 .await
72 }
73 UserInputInterception::Handled { reason } => Ok(RunSteerResult {
74 accepted: false,
75 handled_reason: Some(reason),
76 }),
77 }
78 }
79
80 pub fn steer_blocking(
82 &self,
83 run_id: RunId,
84 input: String,
85 origin: EventOrigin,
86 ) -> Result<RunSteerResult, EngineError> {
87 tokio::runtime::Builder::new_current_thread()
88 .enable_all()
89 .build()
90 .map_err(|error| {
91 EngineError::Mcp(format!("create blocking steering runtime: {error}"))
92 })?
93 .block_on(self.steer(run_id, input, origin))
94 }
95
96 pub async fn recall_steer(&self, run_id: RunId) -> Result<RunRecallSteerResult, EngineError> {
97 let active = self
98 .inner
99 .sessions
100 .active
101 .lock()
102 .unwrap_or_else(|poisoned| poisoned.into_inner())
103 .get(&run_id)
104 .cloned()
105 .ok_or(EngineError::MissingRun(run_id))?;
106 self.request(active.session, |reply| SessionCommand::RecallSteer {
107 run: run_id,
108 reply,
109 })
110 .await
111 }
112
113 pub async fn cancel_run(&self, run_id: RunId) -> Result<RunCancelResult, EngineError> {
114 let active = self
115 .inner
116 .sessions
117 .active
118 .lock()
119 .unwrap_or_else(|poisoned| poisoned.into_inner())
120 .get(&run_id)
121 .cloned()
122 .ok_or(EngineError::MissingRun(run_id))?;
123 let result = self
124 .request(active.session, |reply| SessionCommand::Cancel {
125 run: run_id,
126 reply,
127 })
128 .await?;
129 let inflight_runs: Vec<_> = {
130 let mut inflight = self
131 .inner
132 .delegation
133 .inflight
134 .lock()
135 .unwrap_or_else(|poisoned| poisoned.into_inner());
136 inflight
137 .values_mut()
138 .flat_map(|entries| entries.values_mut())
139 .filter(|delegate| delegate.parent_run_id == run_id)
140 .filter_map(|delegate| {
141 delegate.cancelled = true;
142 delegate.child_run_id
143 })
144 .collect()
145 };
146 let delegation_events = self.inner.delegation_events.clone();
147 let children = self
148 .spawn_admission_blocking(move || Ok::<_, EngineError>(delegation_events.entries()))
149 .await?;
150 let mut pending = vec![run_id];
151 pending.extend(inflight_runs);
152 let mut visited = HashSet::new();
153 while let Some(parent_run_id) = pending.pop() {
154 if !visited.insert(parent_run_id) {
155 continue;
156 }
157 let inflight_children: Vec<_> = {
158 let mut inflight = self
159 .inner
160 .delegation
161 .inflight
162 .lock()
163 .unwrap_or_else(|poisoned| poisoned.into_inner());
164 inflight
165 .values_mut()
166 .flat_map(|entries| entries.values_mut())
167 .filter(|delegate| delegate.parent_run_id == parent_run_id)
168 .filter_map(|delegate| {
169 delegate.cancelled = true;
170 delegate.child_run_id
171 })
172 .collect()
173 };
174 pending.extend(inflight_children);
175 for child_run_id in children
176 .iter()
177 .filter(|entry| entry.reservation.parent_run_id == parent_run_id)
178 .filter_map(|entry| entry.child_run_id)
179 {
180 pending.push(child_run_id);
181 if child_run_id == run_id {
182 continue;
183 }
184 let child_active = {
185 self.inner
186 .sessions
187 .active
188 .lock()
189 .unwrap_or_else(|poisoned| poisoned.into_inner())
190 .get(&child_run_id)
191 .cloned()
192 };
193 if let Some(child_active) = child_active {
194 child_active.cancellation.cancel();
195 let _ = self
196 .request(child_active.session, |reply| SessionCommand::Cancel {
197 run: child_run_id,
198 reply,
199 })
200 .await;
201 }
202 }
203 }
204 Ok(result)
205 }
206
207 pub(super) fn cancel_run_durably(
211 &self,
212 run_id: RunId,
213 reason: Option<String>,
214 ) -> Result<bool, EngineError> {
215 let active = self
216 .inner
217 .sessions
218 .active
219 .lock()
220 .unwrap_or_else(|poisoned| poisoned.into_inner())
221 .get(&run_id)
222 .cloned();
223 let Some(active) = active else {
224 let session = self
225 .inner
226 .store
227 .all()
228 .into_iter()
229 .find(|session| session.runs.contains_key(&run_id))
230 .ok_or(EngineError::MissingRun(run_id))?;
231 let mut committed = false;
232 return self.commit_run_cancelled_with_retry(
233 session.meta.session_id,
234 run_id,
235 reason,
236 &mut committed,
237 );
238 };
239 active.cancellation.cancel();
240 active
241 .stdin
242 .lock()
243 .unwrap_or_else(|poisoned| poisoned.into_inner())
244 .clear();
245 let mut committed = active
246 .cancelled_committed
247 .lock()
248 .unwrap_or_else(|poisoned| poisoned.into_inner());
249 self.commit_run_cancelled_with_retry(active.session, run_id, reason, &mut committed)
250 }
251
252 pub(super) fn append_run_cancelled_once(
253 &self,
254 active: &ActiveRun,
255 run_id: RunId,
256 reason: Option<String>,
257 ) -> Result<bool, EngineError> {
258 let mut committed = active
259 .cancelled_committed
260 .lock()
261 .unwrap_or_else(|poisoned| poisoned.into_inner());
262 self.commit_run_cancelled_with_retry(active.session, run_id, reason, &mut committed)
263 }
264
265 pub(super) fn commit_run_cancelled_with_retry(
266 &self,
267 session: SessionId,
268 run_id: RunId,
269 reason: Option<String>,
270 committed: &mut bool,
271 ) -> Result<bool, EngineError> {
272 let mut last_error = None;
273 for _ in 0..3 {
274 match self.commit_run_cancelled_once(session, run_id, reason.clone(), committed) {
275 Ok(result) => return Ok(result),
276 Err(error) => last_error = Some(error),
277 }
278 }
279 Err(last_error.expect("cancellation retry attempts are nonempty"))
280 }
281
282 pub(super) fn commit_run_cancelled_once(
283 &self,
284 session: SessionId,
285 run_id: RunId,
286 reason: Option<String>,
287 committed: &mut bool,
288 ) -> Result<bool, EngineError> {
289 if *committed {
290 return Ok(false);
291 }
292 if self.run_cancelled_recorded(session, run_id)? {
295 *committed = true;
296 return Ok(false);
297 }
298 if self
299 .inner
300 .store
301 .get(session)?
302 .runs
303 .get(&run_id)
304 .is_none_or(|run| run.status != SessionStatus::Running)
305 {
306 return Ok(false);
307 }
308 match self.append_direct(
309 session,
310 Some(run_id),
311 super::event_origin("engine:model-loop"),
312 Event::RunCancelled {
313 reason: reason.as_deref().map(safe_error),
314 },
315 ) {
316 Ok(()) => {
317 *committed = true;
318 Ok(true)
319 }
320 Err(error) => {
321 if self.run_cancelled_recorded(session, run_id)? {
322 *committed = true;
323 Ok(true)
324 } else {
325 Err(error)
326 }
327 }
328 }
329 }
330
331 pub(super) fn run_cancelled_recorded(
332 &self,
333 session: SessionId,
334 run_id: RunId,
335 ) -> Result<bool, EngineError> {
336 Ok(self
337 .inner
338 .store
339 .get(session)?
340 .log
341 .event_snapshot()
342 .iter()
343 .any(|event| {
344 event.run_id == Some(run_id) && matches!(event.payload, Event::RunCancelled { .. })
345 }))
346 }
347
348 pub async fn tool_stdin(
349 &self,
350 params: RunToolStdinParams,
351 ) -> Result<RunToolStdinResult, EngineError> {
352 let active = self
353 .inner
354 .sessions
355 .active
356 .lock()
357 .unwrap_or_else(|poisoned| poisoned.into_inner())
358 .get(¶ms.run_id)
359 .cloned()
360 .ok_or(EngineError::MissingRun(params.run_id))?;
361 self.request(active.session, |reply| SessionCommand::Stdin {
362 params,
363 reply,
364 })
365 .await
366 }
367}