1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use incurs::command::RequestContext;
6use incurs::tool::{ToolCallControl, ToolEvent, ToolEventSink};
7use serde_json::Value;
8use tokio::sync::{Mutex, Semaphore};
9use tokio_util::sync::CancellationToken;
10
11use crate::{
12 ArtifactStore, CapabilitySnapshot, Clock, CodeExecutor, CodeModeRuntime, Connector,
13 DispatchRequest, DispatchSession, ExecutionEvent, ExecutionHost, ExecutionState,
14 ExecutionStatus, RuntimeStore, SearchOutput, SystemClock, ToolContext,
15};
16
17#[derive(Clone, Default)]
19pub struct CodeModeRunOptions {
20 pub cancellation: CancellationToken,
22 pub request: Option<RequestContext>,
24}
25
26struct RuntimeEventSink {
27 runtime: Arc<CodeModeRuntime>,
28 execution_id: String,
29 clock: Arc<dyn Clock>,
30}
31
32#[async_trait]
33impl ToolEventSink for RuntimeEventSink {
34 async fn emit(&self, event: ToolEvent) {
35 let at = self.clock.now_ms();
36 let event = match event {
37 ToolEvent::Progress { message, fraction } => ExecutionEvent::Progress {
38 message,
39 fraction,
40 at,
41 },
42 ToolEvent::Log { level, message } => ExecutionEvent::Log { level, message, at },
43 ToolEvent::Chunk { data } => ExecutionEvent::Chunk { data, at },
44 };
45 let _ = self.runtime.event(&self.execution_id, event).await;
46 }
47}
48
49pub struct CodeMode {
51 runtime: Arc<CodeModeRuntime>,
52 executor: Box<dyn CodeExecutor>,
53 connectors: Vec<Arc<dyn Connector>>,
54 clock: Arc<dyn Clock>,
55 contexts: Mutex<HashMap<String, ToolContext>>,
56 pass_gates: Mutex<HashMap<String, Arc<Semaphore>>>,
57 active_rollbacks: Mutex<HashSet<String>>,
58}
59
60impl CodeMode {
61 pub fn new(
63 store: Arc<dyn RuntimeStore>,
64 executor: impl CodeExecutor + 'static,
65 connectors: Vec<Arc<dyn Connector>>,
66 ) -> Self {
67 Self::with_clock(store, executor, connectors, SystemClock)
68 }
69
70 pub fn with_artifact_store(
72 store: Arc<dyn RuntimeStore>,
73 artifacts: Arc<dyn ArtifactStore>,
74 executor: impl CodeExecutor + 'static,
75 connectors: Vec<Arc<dyn Connector>>,
76 ) -> Self {
77 Self {
78 runtime: Arc::new(CodeModeRuntime::with_artifacts(store, artifacts)),
79 executor: Box::new(executor),
80 connectors,
81 clock: Arc::new(SystemClock),
82 contexts: Mutex::new(HashMap::new()),
83 pass_gates: Mutex::new(HashMap::new()),
84 active_rollbacks: Mutex::new(HashSet::new()),
85 }
86 }
87
88 pub fn with_clock(
90 store: Arc<dyn RuntimeStore>,
91 executor: impl CodeExecutor + 'static,
92 connectors: Vec<Arc<dyn Connector>>,
93 clock: impl Clock + 'static,
94 ) -> Self {
95 Self {
96 runtime: Arc::new(CodeModeRuntime::new(store)),
97 executor: Box::new(executor),
98 connectors,
99 clock: Arc::new(clock),
100 contexts: Mutex::new(HashMap::new()),
101 pass_gates: Mutex::new(HashMap::new()),
102 active_rollbacks: Mutex::new(HashSet::new()),
103 }
104 }
105
106 pub fn with_clock_and_artifact_store(
108 store: Arc<dyn RuntimeStore>,
109 artifacts: Arc<dyn ArtifactStore>,
110 executor: impl CodeExecutor + 'static,
111 connectors: Vec<Arc<dyn Connector>>,
112 clock: impl Clock + 'static,
113 ) -> Self {
114 Self {
115 runtime: Arc::new(CodeModeRuntime::with_artifacts(store, artifacts)),
116 executor: Box::new(executor),
117 connectors,
118 clock: Arc::new(clock),
119 contexts: Mutex::new(HashMap::new()),
120 pass_gates: Mutex::new(HashMap::new()),
121 active_rollbacks: Mutex::new(HashSet::new()),
122 }
123 }
124
125 pub fn runtime(&self) -> Arc<CodeModeRuntime> {
127 Arc::clone(&self.runtime)
128 }
129
130 pub async fn instructions(&self) -> Result<String, String> {
132 let mut descriptions = Vec::new();
133 for connector in &self.connectors {
134 descriptions.push(connector.describe().await?);
135 }
136 let mut sections = descriptions
137 .iter()
138 .filter_map(|connector| {
139 connector
140 .instructions
141 .as_ref()
142 .map(|instructions| format!("## {}\n\n{instructions}", connector.name))
143 })
144 .collect::<Vec<_>>();
145 sections.extend(descriptions.iter().map(crate::generate_types));
146 Ok(sections.join("\n\n"))
147 }
148
149 pub async fn search(&self, query: &str) -> Result<SearchOutput, String> {
151 let mut descriptions = Vec::new();
152 for connector in &self.connectors {
153 descriptions.push(connector.describe().await?);
154 }
155 let snippets = self
156 .runtime
157 .snippets()
158 .await
159 .map_err(|error| error.to_string())?;
160 Ok(crate::search(query, &descriptions, &snippets))
161 }
162
163 pub async fn execution(&self, execution_id: &str) -> Result<ExecutionState, String> {
165 self.require(execution_id).await
166 }
167
168 pub async fn execution_snapshot(&self, execution_id: &str) -> Result<ExecutionState, String> {
170 self.runtime
171 .execution_snapshot(execution_id)
172 .await
173 .map_err(|error| error.to_string())?
174 .ok_or_else(|| format!("Execution \"{execution_id}\" not found"))
175 }
176
177 pub async fn artifact(&self, execution_id: &str, artifact_id: &str) -> Result<Value, String> {
179 self.runtime
180 .artifact(execution_id, artifact_id)
181 .await
182 .map_err(|error| error.to_string())?
183 .ok_or_else(|| {
184 format!("Artifact \"{artifact_id}\" not found for execution \"{execution_id}\"")
185 })
186 }
187
188 pub async fn events(&self, execution_id: &str) -> Result<Vec<ExecutionEvent>, String> {
190 Ok(self.require(execution_id).await?.events)
191 }
192
193 pub async fn cancel(&self, execution_id: &str) -> Result<ExecutionState, String> {
195 let pass_active = self.contexts.lock().await.contains_key(execution_id);
196 if let Some(context) = self.contexts.lock().await.get(execution_id) {
197 context.control.cancellation.cancel();
198 }
199 let changed = self
200 .runtime
201 .cancel(execution_id, self.clock.now_ms())
202 .await
203 .map_err(|error| error.to_string())?;
204 if changed && !pass_active {
205 self.notify_execution_end(execution_id, "cancelled").await;
206 }
207 self.require(execution_id).await
208 }
209
210 pub async fn start(&self, code: &str) -> Result<ExecutionState, String> {
212 let mut descriptions = Vec::new();
213 for connector in &self.connectors {
214 descriptions.push(connector.describe().await?);
215 }
216 let capabilities = CapabilitySnapshot::new(descriptions)?;
217 let id = self
218 .runtime
219 .begin_with_capabilities(code, capabilities, self.clock.now_ms())
220 .await
221 .map_err(|error| error.to_string())?;
222 self.require(&id).await
223 }
224
225 pub async fn execute(&self, code: &str) -> Result<ExecutionState, String> {
227 self.execute_with(code, CodeModeRunOptions::default()).await
228 }
229
230 pub async fn execute_with(
232 &self,
233 code: &str,
234 options: CodeModeRunOptions,
235 ) -> Result<ExecutionState, String> {
236 let state = self.start(code).await?;
237 self.drive_with(&state.id, options).await
238 }
239
240 pub async fn resume(&self, execution_id: &str) -> Result<ExecutionState, String> {
242 self.resume_with(execution_id, CodeModeRunOptions::default())
243 .await
244 }
245
246 pub async fn resume_with(
248 &self,
249 execution_id: &str,
250 options: CodeModeRunOptions,
251 ) -> Result<ExecutionState, String> {
252 self.runtime
253 .resume(execution_id, self.clock.now_ms())
254 .await
255 .map_err(|error| error.to_string())?;
256 self.drive_with(execution_id, options).await
257 }
258
259 pub async fn approve(&self, execution_id: &str, seq: u64) -> Result<ExecutionState, String> {
261 self.approve_with(execution_id, seq, CodeModeRunOptions::default())
262 .await
263 }
264
265 pub async fn approve_with(
267 &self,
268 execution_id: &str,
269 seq: u64,
270 options: CodeModeRunOptions,
271 ) -> Result<ExecutionState, String> {
272 if !self
273 .runtime
274 .approve(execution_id, seq, self.clock.now_ms())
275 .await
276 .map_err(|error| error.to_string())?
277 {
278 return self.require(execution_id).await;
279 }
280 self.drive_with(execution_id, options).await
281 }
282
283 pub async fn reject(&self, execution_id: &str, seq: u64) -> Result<ExecutionState, String> {
285 if self
286 .runtime
287 .reject(execution_id, seq, self.clock.now_ms())
288 .await
289 .map_err(|error| error.to_string())?
290 {
291 self.notify_execution_end(execution_id, "rejected").await;
292 }
293 self.require(execution_id).await
294 }
295
296 pub async fn rollback(&self, execution_id: &str) -> Result<ExecutionState, String> {
298 {
299 let mut active = self.active_rollbacks.lock().await;
300 if !active.insert(execution_id.to_string()) {
301 return Err(format!(
302 "Execution \"{execution_id}\" is already rolling back"
303 ));
304 }
305 }
306 let result = self.rollback_inner(execution_id).await;
307 self.active_rollbacks.lock().await.remove(execution_id);
308 result
309 }
310
311 async fn rollback_inner(&self, execution_id: &str) -> Result<ExecutionState, String> {
312 for action in self
313 .runtime
314 .actions_to_revert(execution_id)
315 .await
316 .map_err(|error| error.to_string())?
317 {
318 let connector = self
319 .connector(&action.connector)
320 .await?
321 .ok_or_else(|| format!("Connector \"{}\" not found", action.connector))?;
322 if !connector
323 .revert(
324 &action.method,
325 action.arguments,
326 action.result.unwrap_or(Value::Null),
327 &ToolContext {
328 execution_id: execution_id.to_string(),
329 control: Default::default(),
330 request: None,
331 },
332 )
333 .await?
334 {
335 return Err(format!(
336 "{}.{} did not compensate step {}",
337 action.connector, action.method, action.seq
338 ));
339 }
340 self.runtime
341 .mark_reverted(execution_id, action.seq, self.clock.now_ms())
342 .await
343 .map_err(|error| error.to_string())?;
344 }
345 self.runtime
346 .finish_rollback(execution_id, self.clock.now_ms())
347 .await
348 .map_err(|error| error.to_string())?;
349 self.notify_execution_end(execution_id, "rolled_back").await;
350 self.require(execution_id).await
351 }
352
353 pub async fn expire(&self, max_age_ms: u64) -> Result<Vec<String>, String> {
355 let ids = self
356 .runtime
357 .expire(self.clock.now_ms(), max_age_ms)
358 .await
359 .map_err(|error| error.to_string())?;
360 for id in &ids {
361 let status = self.require(id).await?.status;
362 self.notify_execution_end(
363 id,
364 if status == ExecutionStatus::Rejected {
365 "rejected"
366 } else {
367 "error"
368 },
369 )
370 .await;
371 }
372 Ok(ids)
373 }
374
375 pub async fn dispatch(&self, request: DispatchRequest) -> Result<Value, String> {
377 match request {
378 DispatchRequest::Call {
379 execution_id,
380 seq,
381 connector,
382 method,
383 arguments,
384 } => {
385 let session = self.session(&execution_id).await?;
386 serde_json::to_value(
387 session
388 .call_at(seq, &connector, &method, arguments, self.clock.now_ms())
389 .await,
390 )
391 .map_err(|error| error.to_string())
392 }
393 DispatchRequest::BeginStep {
394 execution_id,
395 seq,
396 name,
397 } => {
398 let session = self.session(&execution_id).await?;
399 serde_json::to_value(
400 session
401 .begin_step_at(seq, &name, self.clock.now_ms())
402 .await
403 .map_err(|error| error.to_string())?,
404 )
405 .map_err(|error| error.to_string())
406 }
407 DispatchRequest::RecordStep {
408 execution_id,
409 seq,
410 result,
411 } => {
412 let _ = self.active_context(&execution_id).await?;
413 self.runtime
414 .record_result(&execution_id, seq, result, self.clock.now_ms())
415 .await
416 .map_err(|error| error.to_string())?;
417 Ok(serde_json::json!({ "ok": true }))
418 }
419 }
420 }
421
422 pub async fn drive_with(
424 &self,
425 execution_id: &str,
426 options: CodeModeRunOptions,
427 ) -> Result<ExecutionState, String> {
428 let gate = {
429 let mut gates = self.pass_gates.lock().await;
430 Arc::clone(
431 gates
432 .entry(execution_id.to_string())
433 .or_insert_with(|| Arc::new(Semaphore::new(1))),
434 )
435 };
436 let _permit = gate
437 .acquire_owned()
438 .await
439 .map_err(|_| "Code Mode execution gate closed".to_string())?;
440 self.drive_pass(execution_id, options).await
441 }
442
443 async fn drive_pass(
444 &self,
445 execution_id: &str,
446 options: CodeModeRunOptions,
447 ) -> Result<ExecutionState, String> {
448 let state = self.require(execution_id).await?;
449 if matches!(
450 state.status,
451 ExecutionStatus::Completed
452 | ExecutionStatus::Error
453 | ExecutionStatus::Rejected
454 | ExecutionStatus::RolledBack
455 | ExecutionStatus::Cancelled
456 ) {
457 return Ok(state);
458 }
459 let context = self.context(execution_id, options);
460 let cancellation = context.control.cancellation.clone();
461 self.contexts
462 .lock()
463 .await
464 .insert(execution_id.to_string(), context.clone());
465 let session = match self.session_with_context(execution_id, context).await {
466 Ok(session) => session,
467 Err(error) => {
468 self.contexts.lock().await.remove(execution_id);
469 return Err(error);
470 }
471 };
472 let descriptions = session.descriptions();
473 let execution = self.executor.execute(
474 &state.code,
475 &descriptions,
476 execution_id,
477 Arc::new(ExecutionHost::new(
478 Arc::clone(&session),
479 Arc::clone(&self.clock),
480 )),
481 );
482 tokio::pin!(execution);
483 let response = tokio::select! {
484 _ = cancellation.cancelled() => None,
485 response = &mut execution => Some(response),
486 };
487 self.contexts.lock().await.remove(execution_id);
488 let Some(response) = response else {
489 self.runtime
490 .cancel(execution_id, self.clock.now_ms())
491 .await
492 .map_err(|error| error.to_string())?;
493 session.pass_ended("cancelled").await;
494 session.execution_ended("cancelled").await;
495 return self.require(execution_id).await;
496 };
497 let response = match response {
498 Ok(response) => response,
499 Err(error) => {
500 self.runtime
501 .fail(execution_id, error, Vec::new(), self.clock.now_ms())
502 .await
503 .map_err(|error| error.to_string())?;
504 session.pass_ended("error").await;
505 session.execution_ended("error").await;
506 return self.require(execution_id).await;
507 }
508 };
509 let current = self.require(execution_id).await?;
510 if current.status == ExecutionStatus::Paused {
511 session.pass_ended("paused").await;
512 return Ok(current);
513 }
514 if current.status == ExecutionStatus::Error {
515 session.pass_ended("error").await;
516 session.execution_ended("error").await;
517 return Ok(current);
518 }
519 if current.status == ExecutionStatus::Cancelled {
520 session.pass_ended("cancelled").await;
521 session.execution_ended("cancelled").await;
522 return Ok(current);
523 }
524 if let Some(error) = response.error {
525 self.runtime
526 .fail(execution_id, error, response.logs, self.clock.now_ms())
527 .await
528 .map_err(|error| error.to_string())?;
529 session.pass_ended("error").await;
530 session.execution_ended("error").await;
531 } else {
532 self.runtime
533 .complete(
534 execution_id,
535 response.result.unwrap_or(Value::Null),
536 response.logs,
537 self.clock.now_ms(),
538 )
539 .await
540 .map_err(|error| error.to_string())?;
541 session.pass_ended("completed").await;
542 session.execution_ended("completed").await;
543 }
544 self.require(execution_id).await
545 }
546
547 async fn session(&self, execution_id: &str) -> Result<Arc<DispatchSession>, String> {
548 let context = self.active_context(execution_id).await?;
549 self.session_with_context(execution_id, context).await
550 }
551
552 async fn active_context(&self, execution_id: &str) -> Result<ToolContext, String> {
553 self.contexts
554 .lock()
555 .await
556 .get(execution_id)
557 .cloned()
558 .ok_or_else(|| format!("Execution \"{execution_id}\" does not have an active pass"))
559 }
560
561 async fn session_with_context(
562 &self,
563 execution_id: &str,
564 context: ToolContext,
565 ) -> Result<Arc<DispatchSession>, String> {
566 let state = self.require(execution_id).await?;
567 let descriptions = if let Some(capabilities) = state.capabilities {
568 capabilities.connectors
569 } else {
570 let mut descriptions = Vec::new();
571 for connector in &self.connectors {
572 descriptions.push(connector.describe().await?);
573 }
574 descriptions
575 };
576 Ok(Arc::new(
577 DispatchSession::new_with_descriptions_and_context(
578 Arc::clone(&self.runtime),
579 context,
580 self.connectors.clone(),
581 descriptions,
582 )
583 .await?,
584 ))
585 }
586
587 fn context(&self, execution_id: &str, options: CodeModeRunOptions) -> ToolContext {
588 ToolContext {
589 execution_id: execution_id.to_string(),
590 control: ToolCallControl {
591 cancellation: options.cancellation,
592 events: Some(Arc::new(RuntimeEventSink {
593 runtime: Arc::clone(&self.runtime),
594 execution_id: execution_id.to_string(),
595 clock: Arc::clone(&self.clock),
596 })),
597 },
598 request: options.request,
599 }
600 }
601
602 async fn require(&self, execution_id: &str) -> Result<ExecutionState, String> {
603 self.runtime
604 .execution(execution_id)
605 .await
606 .map_err(|error| error.to_string())?
607 .ok_or_else(|| format!("Execution \"{execution_id}\" not found"))
608 }
609
610 async fn connector(&self, name: &str) -> Result<Option<&Arc<dyn Connector>>, String> {
611 for connector in &self.connectors {
612 if connector.describe().await?.name == name {
613 return Ok(Some(connector));
614 }
615 }
616 Ok(None)
617 }
618
619 async fn notify_execution_end(&self, execution_id: &str, status: &str) {
620 for connector in &self.connectors {
621 connector.execution_ended(execution_id, status).await;
622 }
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use std::sync::atomic::{AtomicUsize, Ordering};
629
630 use serde_json::json;
631 use tokio::sync::Notify;
632
633 use super::*;
634 use crate::{
635 ConnectorDescription, ConnectorTool, ExecuteResult, MemoryStore, ReplayPolicy,
636 ToolAnnotations, ToolPolicy,
637 };
638
639 struct TestConnector {
640 calls: AtomicUsize,
641 ended: Mutex<Vec<String>>,
642 started: Notify,
643 release: Notify,
644 blocked: bool,
645 }
646
647 impl TestConnector {
648 fn new(blocked: bool) -> Self {
649 Self {
650 calls: AtomicUsize::new(0),
651 ended: Mutex::new(Vec::new()),
652 started: Notify::new(),
653 release: Notify::new(),
654 blocked,
655 }
656 }
657 }
658
659 #[async_trait]
660 impl Connector for TestConnector {
661 async fn describe(&self) -> Result<ConnectorDescription, String> {
662 Ok(ConnectorDescription {
663 name: "test".to_string(),
664 instructions: None,
665 tools: vec![ConnectorTool {
666 name: "run".to_string(),
667 description: None,
668 input_schema: json!({"type": "object"}),
669 output_schema: Some(json!({"type": "integer"})),
670 instructions: None,
671 examples: Vec::new(),
672 annotations: ToolAnnotations {
673 read_only: Some(true),
674 ..ToolAnnotations::default()
675 },
676 policy: ToolPolicy {
677 requires_approval: false,
678 replay: ReplayPolicy::Log,
679 },
680 }],
681 })
682 }
683
684 async fn execute(
685 &self,
686 _method: &str,
687 _arguments: Value,
688 _context: &ToolContext,
689 ) -> Result<Value, String> {
690 self.calls.fetch_add(1, Ordering::SeqCst);
691 self.started.notify_one();
692 if self.blocked {
693 self.release.notified().await;
694 }
695 Ok(json!(42))
696 }
697
698 async fn execution_ended(&self, _execution_id: &str, status: &str) {
699 self.ended.lock().await.push(status.to_string());
700 }
701 }
702
703 struct HostExecutor;
704
705 #[async_trait(?Send)]
706 impl CodeExecutor for HostExecutor {
707 async fn execute(
708 &self,
709 _code: &str,
710 _connectors: &[ConnectorDescription],
711 _execution_id: &str,
712 host: Arc<ExecutionHost>,
713 ) -> Result<ExecuteResult, String> {
714 let response = host.call(0, "test", "run", json!({})).await;
715 Ok(ExecuteResult {
716 result: response.result,
717 error: response.message,
718 logs: Vec::new(),
719 })
720 }
721 }
722
723 #[tokio::test(flavor = "current_thread")]
724 async fn dispatch_requires_an_active_pass() {
725 let connector = Arc::new(TestConnector::new(false));
726 let code_mode = CodeMode::new(
727 Arc::new(MemoryStore::default()),
728 HostExecutor,
729 vec![connector.clone()],
730 );
731 let state = code_mode.start("ignored").await.unwrap();
732
733 let error = code_mode
734 .dispatch(DispatchRequest::Call {
735 execution_id: state.id.clone(),
736 seq: 0,
737 connector: "test".to_string(),
738 method: "run".to_string(),
739 arguments: json!({}),
740 })
741 .await
742 .unwrap_err();
743
744 assert!(error.contains("active pass"));
745 assert_eq!(connector.calls.load(Ordering::SeqCst), 0);
746 assert_eq!(
747 code_mode.execution(&state.id).await.unwrap().status,
748 ExecutionStatus::Running
749 );
750 }
751
752 #[tokio::test(flavor = "current_thread")]
753 async fn cancelling_an_idle_execution_ends_connector_lifecycle_once() {
754 let connector = Arc::new(TestConnector::new(false));
755 let code_mode = CodeMode::new(
756 Arc::new(MemoryStore::default()),
757 HostExecutor,
758 vec![connector.clone()],
759 );
760 let state = code_mode.start("ignored").await.unwrap();
761
762 code_mode.cancel(&state.id).await.unwrap();
763 code_mode.cancel(&state.id).await.unwrap();
764
765 assert_eq!(&*connector.ended.lock().await, &["cancelled"]);
766 }
767
768 #[tokio::test(flavor = "current_thread")]
769 async fn concurrent_drive_calls_execute_one_pass() {
770 let connector = Arc::new(TestConnector::new(true));
771 let code_mode = CodeMode::new(
772 Arc::new(MemoryStore::default()),
773 HostExecutor,
774 vec![connector.clone()],
775 );
776 let state = code_mode.start("ignored").await.unwrap();
777 let first = code_mode.drive_with(&state.id, CodeModeRunOptions::default());
778 let second = code_mode.drive_with(&state.id, CodeModeRunOptions::default());
779 let release = async {
780 connector.started.notified().await;
781 tokio::task::yield_now().await;
782 connector.release.notify_waiters();
783 };
784
785 let (first, second, ()) = tokio::join!(first, second, release);
786
787 assert_eq!(first.unwrap().status, ExecutionStatus::Completed);
788 assert_eq!(second.unwrap().status, ExecutionStatus::Completed);
789 assert_eq!(connector.calls.load(Ordering::SeqCst), 1);
790 }
791
792 #[tokio::test(flavor = "current_thread")]
793 async fn rollback_rejects_live_executions() {
794 let code_mode = CodeMode::new(
795 Arc::new(MemoryStore::default()),
796 HostExecutor,
797 vec![Arc::new(TestConnector::new(false))],
798 );
799 let state = code_mode.start("ignored").await.unwrap();
800
801 let error = code_mode.rollback(&state.id).await.unwrap_err();
802
803 assert!(error.contains("not rollback eligible"));
804 assert_eq!(
805 code_mode.execution(&state.id).await.unwrap().status,
806 ExecutionStatus::Running
807 );
808 }
809}