1use std::collections::HashMap;
2#[cfg(any(test, feature = "testing"))]
3use std::pin::Pin;
4use std::sync::Arc;
5use std::time::Instant;
6
7use tokio::sync::mpsc;
8use tokio::sync::oneshot;
9use tokio_util::sync::CancellationToken;
10
11mod control;
12mod controller_error;
13mod scoped;
14mod trigger;
15
16pub use control::{
17 AwaitEventKey, AwaitEventResolver, AwaitEventWaitIdentity, BoundaryReason, EffectHost,
18 ExecutionScope, ExternalCompletionError, Resolution, ResolveOutcome, RuntimeEffectController,
19 ScopedEffectController, SegmentProgress,
20};
21pub(crate) use control::{
22 EffectTaskController, RuntimeEffectControllerHandle, drive_effect_controller_task,
23};
24pub use controller_error::RuntimeEffectControllerError;
25pub use trigger::TriggerLocalExecution;
26
27use crate::LlmRequest as CoreLlmRequest;
28use crate::ProcessRecord;
29use crate::ProcessRegistry;
30use crate::provider::ProviderHandle;
31use crate::runtime::{RuntimeStreamEvent, RuntimeTurnDriver};
32use crate::sansio::LlmCallError;
33use crate::{PluginError, RuntimeError};
34#[cfg(test)]
35use control::EffectControllerTaskRequest;
36use control::{RemoteLocalExecutionRequest, ScopedEffectControllerInner};
37
38use super::envelope::{
39 ProcessCommand, ProcessEffectOutcome, RuntimeDirectLlmOutcome, RuntimeEffectCommand,
40 RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectOutcome,
41};
42use super::outcome::llm_call_error_from_transport;
43
44pub struct RuntimeAwaitEventOptions {
49 pub cancellation: CancellationToken,
50 pub deadline: Option<Instant>,
51 pub clock: Arc<dyn crate::Clock>,
52 pub observe_turn_cancel: bool,
53}
54
55pub struct RuntimeSleepOptions {
57 pub cancellation: CancellationToken,
58 pub observe_turn_cancel: bool,
59}
60
61use super::await_events::AwaitEventRegistry;
62
63#[async_trait::async_trait]
68pub(crate) trait ProcessRunner: Send + Sync {
69 async fn run_process(
70 &self,
71 registration: crate::ProcessRegistration,
72 execution_context: crate::ProcessExecutionContext,
73 registry: Arc<dyn ProcessRegistry>,
74 scoped_effect_controller: crate::ScopedEffectController<'_>,
75 cancellation: CancellationToken,
76 handover: Option<crate::SegmentHandover>,
77 ) -> crate::ProcessRunOutcome;
78}
79
80pub struct ProcessLocalExecution {
81 pub registry: Arc<dyn ProcessRegistry>,
82 pub process_work_driver: Option<crate::ProcessWorkDriver>,
83}
84
85impl ProcessLocalExecution {
86 pub async fn execute(
87 self,
88 command: ProcessCommand,
89 ) -> Result<ProcessEffectOutcome, RuntimeEffectControllerError> {
90 let Self {
91 registry,
92 process_work_driver,
93 } = self;
94 match command {
95 ProcessCommand::Start {
96 registration,
97 grant,
98 execution_context: _,
99 } => {
100 let record =
101 InlineRuntimeEffectController::start_process(registry, registration, grant)
102 .await?;
103 if let Some(driver) = process_work_driver.as_ref() {
104 driver.claim_and_run_pending("process_start").await?;
105 }
106 Ok(ProcessEffectOutcome::Start {
107 record: Box::new(record),
108 })
109 }
110 ProcessCommand::List {
111 session_scope,
112 mode,
113 } => {
114 let entries = match mode {
115 crate::ProcessListMode::Live => {
116 registry.list_live_handle_grants(&session_scope).await?
117 }
118 crate::ProcessListMode::All => {
119 registry.list_handle_grants(&session_scope).await?
120 }
121 };
122 Ok(ProcessEffectOutcome::List { entries })
123 }
124 ProcessCommand::Transfer {
125 from_scope,
126 to_scope,
127 process_ids,
128 } => {
129 registry
130 .transfer_handle_grants(&from_scope, &to_scope, &process_ids)
131 .await?;
132 Ok(ProcessEffectOutcome::Transfer)
133 }
134 ProcessCommand::DeleteSession { session_id } => {
135 let report = registry.delete_session_process_state(&session_id).await?;
136 Ok(ProcessEffectOutcome::DeleteSession { report })
137 }
138 ProcessCommand::Await { process_id } => {
139 let output = if let Some(driver) = process_work_driver.as_ref() {
140 driver.await_terminal(&process_id).await?
141 } else {
142 crate::ProcessAwaiter::polling(registry)
143 .await_terminal(&process_id)
144 .await?
145 };
146 Ok(ProcessEffectOutcome::Await {
147 output: Box::new(output),
148 })
149 }
150 ProcessCommand::Cancel { process_id, reason } => {
151 let record = InlineRuntimeEffectController::request_process_cancel(
152 registry,
153 &process_id,
154 reason,
155 )
156 .await?;
157 Ok(ProcessEffectOutcome::Cancel {
158 record: Box::new(record),
159 })
160 }
161 ProcessCommand::Signal {
162 process_id,
163 request,
164 ..
165 } => {
166 let result = registry.append_event(&process_id, request).await?;
167 Ok(ProcessEffectOutcome::Signal {
168 event: Box::new(result.event),
169 })
170 }
171 }
172 }
173}
174
175pub(crate) struct TurnEffectStateUpdate {
176 pub(crate) policy: crate::RuntimeSessionPolicy,
177 pub(crate) llm_stream_summaries: HashMap<usize, crate::runtime::LlmStreamSummary>,
178 pub(crate) next_llm_ordinal: usize,
179 pub(crate) pending_queue_claims: Vec<crate::QueuedWorkClaim>,
180 pub(crate) pending_turn_input_claims: Vec<crate::TurnInputClaim>,
181}
182
183pub(super) struct LocalTurnEffectRunner {
184 driver: RuntimeTurnDriver<'static>,
185 protocol_iteration: usize,
186 messages: crate::MessageSequence,
187 event_tx: mpsc::Sender<RuntimeStreamEvent>,
188 cancellation: CancellationToken,
189 update: Arc<std::sync::Mutex<Option<TurnEffectStateUpdate>>>,
190}
191
192pub(super) struct LocalDirectEffectRunner {
193 provider: ProviderHandle,
194 attachment_store: Arc<crate::SessionAttachmentStore>,
195}
196
197struct LocalToolBatchEffectRunner<'run> {
198 context: crate::RuntimeExecutionContext<'run>,
199 child_trace_hooks: HashMap<String, crate::ToolChildExecutionTraceHook>,
200}
201
202struct LocalPreparedToolAttemptEffectRunner<'run> {
203 dispatch: Arc<crate::tool_dispatch::ToolDispatchContext<'run>>,
204 tool_context: crate::ToolContext<'run>,
205}
206
207struct RemoteEffectRunner {
208 requests: mpsc::UnboundedSender<RemoteLocalExecutionRequest>,
209}
210
211#[async_trait::async_trait]
212trait RuntimeEffectLocalRunner: Send {
213 fn uses_task_boundary(&self, _command: &RuntimeEffectCommand) -> bool {
214 false
215 }
216
217 async fn execute(
218 self: Box<Self>,
219 envelope: RuntimeEffectEnvelope,
220 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError>;
221}
222
223#[cfg(any(test, feature = "testing"))]
224type TestingRuntimeEffectLocalRunnerFn<'run> = dyn FnOnce(
225 RuntimeEffectEnvelope,
226 ) -> Pin<
227 Box<
228 dyn Future<Output = Result<RuntimeEffectOutcome, RuntimeEffectControllerError>>
229 + Send
230 + 'run,
231 >,
232 > + Send
233 + 'run;
234
235#[cfg(any(test, feature = "testing"))]
236struct TestingRuntimeEffectLocalRunner<'run> {
237 run: Box<TestingRuntimeEffectLocalRunnerFn<'run>>,
238}
239
240enum RuntimeEffectLocalExecutorState<'run> {
241 Unavailable,
242 SleepOnly {
243 cancellation: CancellationToken,
244 clock: Arc<dyn crate::Clock>,
245 observe_turn_cancel: bool,
246 },
247 ExternalWaitOptions {
248 cancellation: CancellationToken,
249 deadline: Option<Instant>,
250 clock: Arc<dyn crate::Clock>,
251 observe_turn_cancel: bool,
252 },
253 Process(ProcessLocalExecution),
254 Trigger(TriggerLocalExecution),
255 Runner(Box<dyn RuntimeEffectLocalRunner + Send + 'run>),
256 OwnedRunner(Box<dyn RuntimeEffectLocalRunner + Send + 'static>),
257}
258
259pub struct RuntimeEffectLocalExecutor<'run> {
265 state: RuntimeEffectLocalExecutorState<'run>,
266 replay_trace: Option<super::RuntimeEffectReplayTrace>,
267}
268
269struct AbortEffectTaskOnDrop {
270 handle: tokio::task::AbortHandle,
271 armed: bool,
272}
273
274impl AbortEffectTaskOnDrop {
275 fn new(handle: tokio::task::AbortHandle) -> Self {
276 Self {
277 handle,
278 armed: true,
279 }
280 }
281
282 fn disarm(&mut self) {
283 self.armed = false;
284 }
285}
286
287impl Drop for AbortEffectTaskOnDrop {
288 fn drop(&mut self) {
289 if self.armed {
290 self.handle.abort();
291 }
292 }
293}
294
295impl<'run> RuntimeEffectLocalExecutor<'run> {
296 pub fn unavailable() -> Self {
297 Self {
298 state: RuntimeEffectLocalExecutorState::Unavailable,
299 replay_trace: None,
300 }
301 }
302
303 pub fn sleep(cancellation: CancellationToken) -> Self {
304 Self::sleep_with_clock(cancellation, Arc::new(crate::SystemClock))
305 }
306
307 pub fn sleep_with_clock(cancellation: CancellationToken, clock: Arc<dyn crate::Clock>) -> Self {
308 Self {
309 state: RuntimeEffectLocalExecutorState::SleepOnly {
310 cancellation,
311 clock,
312 observe_turn_cancel: true,
313 },
314 replay_trace: None,
315 }
316 }
317
318 pub fn await_event(cancellation: CancellationToken, deadline: Option<Instant>) -> Self {
319 Self::await_event_with_clock(cancellation, deadline, Arc::new(crate::SystemClock))
320 }
321
322 pub fn await_event_with_clock(
323 cancellation: CancellationToken,
324 deadline: Option<Instant>,
325 clock: Arc<dyn crate::Clock>,
326 ) -> Self {
327 Self {
328 state: RuntimeEffectLocalExecutorState::ExternalWaitOptions {
329 cancellation,
330 deadline,
331 clock,
332 observe_turn_cancel: true,
333 },
334 replay_trace: None,
335 }
336 }
337
338 #[doc(hidden)]
339 pub fn with_turn_cancel_observation(mut self, observe_turn_cancel: bool) -> Self {
340 match &mut self.state {
341 RuntimeEffectLocalExecutorState::SleepOnly {
342 observe_turn_cancel: current,
343 ..
344 }
345 | RuntimeEffectLocalExecutorState::ExternalWaitOptions {
346 observe_turn_cancel: current,
347 ..
348 } => *current = observe_turn_cancel,
349 _ => {}
350 }
351 self
352 }
353
354 pub fn processes(
355 registry: Arc<dyn ProcessRegistry>,
356 process_work_driver: Option<crate::ProcessWorkDriver>,
357 ) -> Self {
358 Self {
359 state: RuntimeEffectLocalExecutorState::Process(ProcessLocalExecution {
360 registry,
361 process_work_driver,
362 }),
363 replay_trace: None,
364 }
365 }
366
367 pub fn triggers(store: Arc<dyn crate::TriggerStore>) -> Self {
368 Self {
369 state: RuntimeEffectLocalExecutorState::Trigger(TriggerLocalExecution { store }),
370 replay_trace: None,
371 }
372 }
373
374 #[cfg(any(test, feature = "testing"))]
375 pub fn testing<F, Fut>(run: F) -> Self
376 where
377 F: FnOnce(RuntimeEffectEnvelope) -> Fut + Send + 'run,
378 Fut: Future<Output = Result<RuntimeEffectOutcome, RuntimeEffectControllerError>>
379 + Send
380 + 'run,
381 {
382 Self {
383 state: RuntimeEffectLocalExecutorState::Runner(Box::new(
384 TestingRuntimeEffectLocalRunner {
385 run: Box::new(move |envelope| Box::pin(run(envelope))),
386 },
387 )),
388 replay_trace: None,
389 }
390 }
391
392 pub(in crate::runtime) fn turn(
393 driver: &mut RuntimeTurnDriver<'_>,
394 machine: &crate::TurnMachine,
395 event_tx: mpsc::Sender<RuntimeStreamEvent>,
396 cancellation: CancellationToken,
397 scoped_effect_controller: ScopedEffectController<'static>,
398 ) -> (
399 RuntimeEffectLocalExecutor<'static>,
400 Arc<std::sync::Mutex<Option<TurnEffectStateUpdate>>>,
401 ) {
402 let replay_trace = super::RuntimeEffectReplayTrace::gated(
403 driver.host.core.tracing.trace_level,
404 driver.host.core.tracing.trace_sink.as_ref(),
405 driver.host.core.tracing.trace_context.clone(),
406 driver.trace_context(machine.protocol_iteration()),
407 Arc::clone(&driver.host.core.clock),
408 );
409 let update = Arc::new(std::sync::Mutex::new(None));
410 let owned_driver = RuntimeTurnDriver {
411 session: driver.session.clone_for_effect(),
412 policy: driver.policy.clone(),
413 host: driver.host.clone(),
414 scoped_effect_controller,
415 session_id: driver.session_id.clone(),
416 turn_id: driver.turn_id.clone(),
417 turn_index: driver.turn_index,
418 turn_pipeline: crate::runtime::TurnBoundary::from_state_with_clock(
419 driver.turn_pipeline.state().clone(),
420 Arc::clone(&driver.host.core.clock),
421 )
422 .with_session_execution_lease(driver.session_execution_lease.clone()),
423 llm_stream_summaries: driver.llm_stream_summaries.clone(),
424 llm_calls: Vec::new(),
425 next_llm_ordinal: driver.next_llm_ordinal,
426 session_services: Arc::clone(&driver.session_services),
427 protocol_turn_options: driver.protocol_turn_options.clone(),
428 protocol_extension: driver.protocol_extension.clone(),
429 turn_context: driver.turn_context.clone(),
430 turn_causes: driver.turn_causes.clone(),
431 pending_queue_claims: driver.pending_queue_claims.clone(),
432 pending_turn_input_claims: driver.pending_turn_input_claims.clone(),
433 checkpoint_messages: driver.checkpoint_messages.clone(),
434 session_execution_lease: driver.session_execution_lease.clone(),
435 runtime_lease_owner: driver.runtime_lease_owner.clone(),
436 turn_phase_probe: driver.turn_phase_probe.clone(),
437 };
438 (
439 RuntimeEffectLocalExecutor {
440 state: RuntimeEffectLocalExecutorState::OwnedRunner(Box::new(
441 LocalTurnEffectRunner {
442 driver: owned_driver,
443 protocol_iteration: machine.protocol_iteration(),
444 messages: machine.message_sequence(),
445 event_tx,
446 cancellation,
447 update: Arc::clone(&update),
448 },
449 )),
450 replay_trace,
451 },
452 update,
453 )
454 }
455
456 pub(in crate::runtime) fn direct(
457 provider: ProviderHandle,
458 attachment_store: Arc<crate::SessionAttachmentStore>,
459 replay_trace: Option<super::RuntimeEffectReplayTrace>,
460 ) -> Self {
461 Self {
462 state: RuntimeEffectLocalExecutorState::OwnedRunner(Box::new(
463 LocalDirectEffectRunner {
464 provider,
465 attachment_store,
466 },
467 )),
468 replay_trace,
469 }
470 }
471
472 pub(crate) fn tool_batch(
473 context: crate::RuntimeExecutionContext<'run>,
474 child_trace_hooks: HashMap<String, crate::ToolChildExecutionTraceHook>,
475 ) -> Self {
476 let replay_trace = context.replay_validation_trace();
477 if let Some(context) = context.to_static() {
478 return Self {
479 state: RuntimeEffectLocalExecutorState::OwnedRunner(Box::new(
480 LocalToolBatchEffectRunner {
481 context,
482 child_trace_hooks,
483 },
484 )),
485 replay_trace,
486 };
487 }
488 Self {
489 state: RuntimeEffectLocalExecutorState::Runner(Box::new(LocalToolBatchEffectRunner {
490 context,
491 child_trace_hooks,
492 })),
493 replay_trace,
494 }
495 }
496
497 pub(crate) fn prepared_tool_attempt(
498 dispatch: Arc<crate::tool_dispatch::ToolDispatchContext<'run>>,
499 tool_context: crate::ToolContext<'run>,
500 ) -> Self {
501 let replay_trace = tool_context.replay_validation_trace();
502 if let (Some(dispatch), Some(tool_context)) =
503 (dispatch.to_static(), tool_context.to_static())
504 {
505 return Self {
506 state: RuntimeEffectLocalExecutorState::OwnedRunner(Box::new(
507 LocalPreparedToolAttemptEffectRunner {
508 dispatch: Arc::new(dispatch),
509 tool_context,
510 },
511 )),
512 replay_trace,
513 };
514 }
515 Self {
516 state: RuntimeEffectLocalExecutorState::Runner(Box::new(
517 LocalPreparedToolAttemptEffectRunner {
518 dispatch,
519 tool_context,
520 },
521 )),
522 replay_trace,
523 }
524 }
525
526 pub fn replay_validation_trace(&self) -> Option<&super::RuntimeEffectReplayTrace> {
527 self.replay_trace.as_ref()
528 }
529
530 pub async fn execute(
531 self,
532 envelope: RuntimeEffectEnvelope,
533 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
534 match self.state {
535 RuntimeEffectLocalExecutorState::Runner(runner) => runner.execute(envelope).await,
536 RuntimeEffectLocalExecutorState::OwnedRunner(runner) => {
537 if !runner.uses_task_boundary(&envelope.command) {
538 return runner.execute(envelope).await;
539 }
540 let task = crate::task::spawn(
541 crate::runtime::process_worker::inherit_process_execution_permit(
542 runner.execute(envelope),
543 ),
544 );
545 let mut abort = AbortEffectTaskOnDrop::new(task.abort_handle());
546 let result = task.await.map_err(|err| {
547 RuntimeEffectControllerError::new(
548 "runtime_effect_task_join",
549 format!("spawned local effect task failed: {err}"),
550 )
551 })?;
552 abort.disarm();
553 result
554 }
555 RuntimeEffectLocalExecutorState::SleepOnly {
556 cancellation,
557 clock,
558 ..
559 } => execute_local_sleep(envelope, cancellation, clock.as_ref()).await,
560 RuntimeEffectLocalExecutorState::ExternalWaitOptions { .. } => {
561 Err(RuntimeEffectControllerError::new(
562 "runtime_effect_local_executor_mismatch",
563 format!(
564 "local await-event options cannot execute {} command directly",
565 envelope.command.kind().as_str()
566 ),
567 ))
568 }
569 RuntimeEffectLocalExecutorState::Unavailable => Err(RuntimeEffectControllerError::new(
570 "runtime_effect_local_executor_unavailable",
571 format!(
572 "no local executor is available for {}",
573 envelope.command.kind().as_str()
574 ),
575 )),
576 RuntimeEffectLocalExecutorState::Process(_) => Err(RuntimeEffectControllerError::new(
577 "runtime_effect_local_executor_mismatch",
578 format!(
579 "process executor cannot execute {} command directly",
580 envelope.command.kind().as_str()
581 ),
582 )),
583 RuntimeEffectLocalExecutorState::Trigger(_) => Err(RuntimeEffectControllerError::new(
584 "runtime_effect_local_executor_mismatch",
585 format!(
586 "trigger executor cannot execute {} command directly",
587 envelope.command.kind().as_str()
588 ),
589 )),
590 }
591 }
592
593 pub fn into_process(self) -> Result<ProcessLocalExecution, RuntimeEffectControllerError> {
594 match self.state {
595 RuntimeEffectLocalExecutorState::Process(execution) => Ok(execution),
596 _ => Err(RuntimeEffectControllerError::new(
597 "runtime_effect_local_executor_unavailable",
598 "no process executor is available for process command",
599 )),
600 }
601 }
602
603 fn into_remote_execution(
604 self,
605 ) -> (
606 RuntimeEffectLocalExecutor<'static>,
607 Option<(
608 RuntimeEffectLocalExecutor<'run>,
609 mpsc::UnboundedReceiver<RemoteLocalExecutionRequest>,
610 )>,
611 ) {
612 let RuntimeEffectLocalExecutor {
613 state,
614 replay_trace,
615 } = self;
616 match state {
617 RuntimeEffectLocalExecutorState::Runner(runner) => {
618 let (requests, request_rx) = mpsc::unbounded_channel();
619 (
620 RuntimeEffectLocalExecutor {
621 state: RuntimeEffectLocalExecutorState::OwnedRunner(Box::new(
622 RemoteEffectRunner { requests },
623 )),
624 replay_trace: replay_trace.clone(),
625 },
626 Some((
627 RuntimeEffectLocalExecutor {
628 state: RuntimeEffectLocalExecutorState::Runner(runner),
629 replay_trace,
630 },
631 request_rx,
632 )),
633 )
634 }
635 RuntimeEffectLocalExecutorState::OwnedRunner(runner) => {
636 let (requests, request_rx) = mpsc::unbounded_channel();
637 (
638 RuntimeEffectLocalExecutor {
639 state: RuntimeEffectLocalExecutorState::OwnedRunner(Box::new(
640 RemoteEffectRunner { requests },
641 )),
642 replay_trace: replay_trace.clone(),
643 },
644 Some((
645 RuntimeEffectLocalExecutor {
646 state: RuntimeEffectLocalExecutorState::OwnedRunner(runner),
647 replay_trace,
648 },
649 request_rx,
650 )),
651 )
652 }
653 state => (
654 RuntimeEffectLocalExecutor {
655 state: match state {
656 RuntimeEffectLocalExecutorState::Unavailable => {
657 RuntimeEffectLocalExecutorState::Unavailable
658 }
659 RuntimeEffectLocalExecutorState::SleepOnly {
660 cancellation,
661 clock,
662 observe_turn_cancel,
663 } => RuntimeEffectLocalExecutorState::SleepOnly {
664 cancellation,
665 clock,
666 observe_turn_cancel,
667 },
668 RuntimeEffectLocalExecutorState::ExternalWaitOptions {
669 cancellation,
670 deadline,
671 clock,
672 observe_turn_cancel,
673 } => RuntimeEffectLocalExecutorState::ExternalWaitOptions {
674 cancellation,
675 deadline,
676 clock,
677 observe_turn_cancel,
678 },
679 RuntimeEffectLocalExecutorState::Process(execution) => {
680 RuntimeEffectLocalExecutorState::Process(execution)
681 }
682 RuntimeEffectLocalExecutorState::Trigger(execution) => {
683 RuntimeEffectLocalExecutorState::Trigger(execution)
684 }
685 RuntimeEffectLocalExecutorState::Runner(_)
686 | RuntimeEffectLocalExecutorState::OwnedRunner(_) => {
687 unreachable!("runner states are handled above")
688 }
689 },
690 replay_trace,
691 },
692 None,
693 ),
694 }
695 }
696
697 async fn execute_forwarded(
698 self,
699 envelope: RuntimeEffectEnvelope,
700 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
701 let RuntimeEffectEnvelope {
702 invocation,
703 command,
704 } = envelope;
705 match command {
706 RuntimeEffectCommand::Trigger { command } => {
707 self.execute_trigger(invocation, *command).await
708 }
709 command => {
710 self.execute(RuntimeEffectEnvelope {
711 invocation,
712 command,
713 })
714 .await
715 }
716 }
717 }
718
719 pub fn into_trigger(self) -> Result<TriggerLocalExecution, RuntimeEffectControllerError> {
720 match self.state {
721 RuntimeEffectLocalExecutorState::Trigger(execution) => Ok(execution),
722 _ => Err(RuntimeEffectControllerError::new(
723 "runtime_effect_local_executor_unavailable",
724 "no trigger executor is available for trigger command",
725 )),
726 }
727 }
728
729 pub async fn execute_trigger(
730 self,
731 invocation: crate::RuntimeInvocation,
732 command: crate::TriggerCommand,
733 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
734 let operation_id = invocation
735 .effect_id()
736 .ok_or_else(|| {
737 RuntimeEffectControllerError::new(
738 "runtime_effect_invocation_subject",
739 "trigger effect requires an effect id",
740 )
741 })?
742 .to_string();
743 match self.state {
744 RuntimeEffectLocalExecutorState::Trigger(execution) => {
745 let result = execution.execute(&operation_id, command).await?;
746 Ok(RuntimeEffectOutcome::Trigger {
747 result: Box::new(result),
748 })
749 }
750 RuntimeEffectLocalExecutorState::Runner(runner)
751 | RuntimeEffectLocalExecutorState::OwnedRunner(runner) => {
752 runner
753 .execute(RuntimeEffectEnvelope::new(
754 invocation,
755 RuntimeEffectCommand::Trigger {
756 command: Box::new(command),
757 },
758 ))
759 .await
760 }
761 _ => Err(RuntimeEffectControllerError::new(
762 "runtime_effect_local_executor_unavailable",
763 "no trigger executor is available for trigger command",
764 )),
765 }
766 }
767
768 pub fn into_await_event_options(
769 self,
770 ) -> Result<RuntimeAwaitEventOptions, RuntimeEffectControllerError> {
771 match self.state {
772 RuntimeEffectLocalExecutorState::ExternalWaitOptions {
773 cancellation,
774 deadline,
775 clock,
776 observe_turn_cancel,
777 } => Ok(RuntimeAwaitEventOptions {
778 cancellation,
779 deadline,
780 clock,
781 observe_turn_cancel,
782 }),
783 _ => Ok(RuntimeAwaitEventOptions {
784 cancellation: CancellationToken::new(),
785 deadline: None,
786 clock: Arc::new(crate::SystemClock),
787 observe_turn_cancel: false,
788 }),
789 }
790 }
791
792 pub fn into_sleep_options(self) -> RuntimeSleepOptions {
793 match self.state {
794 RuntimeEffectLocalExecutorState::SleepOnly {
795 cancellation,
796 observe_turn_cancel,
797 ..
798 } => RuntimeSleepOptions {
799 cancellation,
800 observe_turn_cancel,
801 },
802 _ => RuntimeSleepOptions {
803 cancellation: CancellationToken::new(),
804 observe_turn_cancel: false,
805 },
806 }
807 }
808}
809
810#[cfg(any(test, feature = "testing"))]
811#[async_trait::async_trait]
812impl RuntimeEffectLocalRunner for TestingRuntimeEffectLocalRunner<'_> {
813 async fn execute(
814 self: Box<Self>,
815 envelope: RuntimeEffectEnvelope,
816 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
817 (self.run)(envelope).await
818 }
819}
820
821#[async_trait::async_trait]
822impl RuntimeEffectLocalRunner for LocalToolBatchEffectRunner<'_> {
823 fn uses_task_boundary(&self, command: &RuntimeEffectCommand) -> bool {
824 matches!(
825 command,
826 RuntimeEffectCommand::ToolBatch { .. } | RuntimeEffectCommand::ToolAttempt { .. }
827 )
828 }
829
830 async fn execute(
831 self: Box<Self>,
832 envelope: RuntimeEffectEnvelope,
833 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
834 match envelope.command {
835 RuntimeEffectCommand::ToolBatch { batch } => {
836 let outcome = self
837 .context
838 .execute_prepared_tool_batch_launches(
839 batch,
840 envelope.invocation,
841 self.child_trace_hooks,
842 )
843 .await?;
844 Ok(RuntimeEffectOutcome::ToolBatch {
845 launches: outcome.launches,
846 triggers: outcome.triggers,
847 })
848 }
849 RuntimeEffectCommand::ToolAttempt {
850 call,
851 execution_grant,
852 attempt,
853 max_attempts,
854 } => {
855 let child_execution_trace_hook = self.child_trace_hooks.get(&call.call_id).cloned();
856 let outcome = Box::pin(self.context.execute_prepared_tool_attempt_effect(
857 call,
858 execution_grant,
859 attempt,
860 max_attempts,
861 envelope.invocation,
862 child_execution_trace_hook,
863 ))
864 .await?;
865 Ok(RuntimeEffectOutcome::ToolAttempt {
866 launch: Box::new(outcome.launch),
867 triggers: outcome.triggers,
868 })
869 }
870 command => Err(RuntimeEffectControllerError::new(
871 "runtime_effect_local_executor_mismatch",
872 format!(
873 "local tool executor cannot execute {} command",
874 command.kind().as_str()
875 ),
876 )),
877 }
878 }
879}
880
881#[async_trait::async_trait]
882impl RuntimeEffectLocalRunner for LocalPreparedToolAttemptEffectRunner<'_> {
883 fn uses_task_boundary(&self, command: &RuntimeEffectCommand) -> bool {
884 matches!(command, RuntimeEffectCommand::ToolAttempt { .. })
885 }
886
887 async fn execute(
888 self: Box<Self>,
889 envelope: RuntimeEffectEnvelope,
890 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
891 let RuntimeEffectCommand::ToolAttempt {
892 call,
893 execution_grant,
894 attempt,
895 max_attempts,
896 } = envelope.command
897 else {
898 return Err(RuntimeEffectControllerError::new(
899 "runtime_effect_local_executor_mismatch",
900 "prepared tool attempt executor requires a tool_attempt command",
901 ));
902 };
903 let mut dispatch = (*self.dispatch).clone();
904 dispatch.parent_invocation = Some(envelope.invocation.clone());
905 dispatch.trigger_outcomes = crate::tool_dispatch::ToolTriggerOutcomeBuffer::default();
906 let dispatch = Arc::new(dispatch);
907 let tool_context = self
908 .tool_context
909 .with_attempt_dispatch(Arc::clone(&dispatch), envelope.invocation);
910 let outcome = Box::pin(crate::tool_dispatch::execute_prepared_tool_attempt_effect(
911 dispatch.as_ref(),
912 call,
913 execution_grant,
914 attempt,
915 max_attempts,
916 tool_context,
917 ))
918 .await?;
919 Ok(RuntimeEffectOutcome::ToolAttempt {
920 launch: Box::new(outcome.launch),
921 triggers: outcome.triggers,
922 })
923 }
924}
925
926#[async_trait::async_trait]
927impl RuntimeEffectLocalRunner for LocalTurnEffectRunner {
928 fn uses_task_boundary(&self, command: &RuntimeEffectCommand) -> bool {
929 matches!(
930 command,
931 RuntimeEffectCommand::LlmCall { .. }
932 | RuntimeEffectCommand::ToolBatch { .. }
933 | RuntimeEffectCommand::ExecCode { .. }
934 )
935 }
936
937 async fn execute(
938 self: Box<Self>,
939 envelope: RuntimeEffectEnvelope,
940 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
941 let mut runner = *self;
942 let result = match envelope.command {
943 RuntimeEffectCommand::LlmCall { request } => {
944 let (result, text_streamed, call_record) = runner
945 .driver
946 .run_llm_call(
947 Arc::new((*request).into_request(None, None)),
948 runner.protocol_iteration,
949 envelope.invocation,
950 &runner.event_tx,
951 &runner.cancellation,
952 )
953 .await;
954 Ok(RuntimeEffectOutcome::LlmCall {
955 result: Box::new(result),
956 text_streamed,
957 call_record,
958 })
959 }
960 RuntimeEffectCommand::ToolBatch { batch } => runner
961 .driver
962 .run_tool_batch(
963 batch,
964 envelope.invocation,
965 &runner.event_tx,
966 &runner.cancellation,
967 )
968 .await
969 .map(|outcome| RuntimeEffectOutcome::ToolBatch {
970 launches: outcome.launches,
971 triggers: outcome.triggers,
972 }),
973 RuntimeEffectCommand::ExecCode { language, code } => {
974 Ok(RuntimeEffectOutcome::ExecCode {
975 result: Box::new(
976 runner
977 .driver
978 .run_exec_code(
979 language,
980 &code,
981 runner.messages.clone(),
982 runner.protocol_iteration,
983 envelope.invocation,
984 &runner.event_tx,
985 &runner.cancellation,
986 )
987 .await,
988 ),
989 })
990 }
991 RuntimeEffectCommand::Checkpoint { checkpoint } => {
992 Ok(RuntimeEffectOutcome::Checkpoint {
993 result: runner
994 .driver
995 .run_checkpoint(
996 runner.messages.clone(),
997 runner.protocol_iteration,
998 checkpoint,
999 &runner.event_tx,
1000 )
1001 .await
1002 .map_err(RuntimeEffectControllerError::from),
1003 })
1004 }
1005 RuntimeEffectCommand::SyncExecutionEnvironment {
1006 update_machine_config,
1007 } => Ok(RuntimeEffectOutcome::SyncExecutionEnvironment {
1008 result: runner
1009 .driver
1010 .refresh_execution_environment(runner.messages.clone(), update_machine_config)
1011 .await
1012 .map_err(|err| err.to_string()),
1013 }),
1014 RuntimeEffectCommand::Sleep { duration_ms } => {
1015 sleep_with_cancellation(
1016 duration_ms,
1017 &runner.cancellation,
1018 runner.driver.host.core.clock.as_ref(),
1019 )
1020 .await?;
1021 Ok(RuntimeEffectOutcome::Sleep)
1022 }
1023 command => Err(RuntimeEffectControllerError::new(
1024 "runtime_effect_local_executor_mismatch",
1025 format!(
1026 "local turn executor cannot execute {} command",
1027 command.kind().as_str()
1028 ),
1029 )),
1030 };
1031 *runner.update.lock().expect("turn effect state update lock") =
1032 Some(TurnEffectStateUpdate {
1033 policy: runner.driver.policy,
1034 llm_stream_summaries: runner.driver.llm_stream_summaries,
1035 next_llm_ordinal: runner.driver.next_llm_ordinal,
1036 pending_queue_claims: runner.driver.pending_queue_claims,
1037 pending_turn_input_claims: runner.driver.pending_turn_input_claims,
1038 });
1039 result
1040 }
1041}
1042
1043#[async_trait::async_trait]
1044impl RuntimeEffectLocalRunner for LocalDirectEffectRunner {
1045 fn uses_task_boundary(&self, command: &RuntimeEffectCommand) -> bool {
1046 matches!(command, RuntimeEffectCommand::Direct { .. })
1047 }
1048
1049 async fn execute(
1050 mut self: Box<Self>,
1051 envelope: RuntimeEffectEnvelope,
1052 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
1053 match envelope.command {
1054 RuntimeEffectCommand::Direct { request, .. } => {
1055 let (result, call_record) = self
1056 .run_direct_llm_request((*request).into_request(
1057 crate::session_model::transport_stream_events(&self.provider, None),
1058 None,
1059 ))
1060 .await;
1061 Ok(RuntimeEffectOutcome::Direct {
1062 result: Box::new(result),
1063 call_record,
1064 })
1065 }
1066 RuntimeEffectCommand::Sleep { duration_ms } => {
1067 sleep_with_cancellation(
1068 duration_ms,
1069 &CancellationToken::new(),
1070 &crate::SystemClock,
1071 )
1072 .await?;
1073 Ok(RuntimeEffectOutcome::Sleep)
1074 }
1075 command => Err(RuntimeEffectControllerError::new(
1076 "runtime_effect_local_executor_mismatch",
1077 format!(
1078 "local direct executor cannot execute {} command",
1079 command.kind().as_str()
1080 ),
1081 )),
1082 }
1083 }
1084}
1085
1086#[async_trait::async_trait]
1087impl RuntimeEffectLocalRunner for RemoteEffectRunner {
1088 async fn execute(
1089 self: Box<Self>,
1090 envelope: RuntimeEffectEnvelope,
1091 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
1092 let (response, response_rx) = oneshot::channel();
1093 self.requests
1094 .send(RemoteLocalExecutionRequest { envelope, response })
1095 .map_err(|_| {
1096 RuntimeEffectControllerError::new(
1097 "runtime_effect_local_task_closed",
1098 "spawned effect local executor is no longer running",
1099 )
1100 })?;
1101 response_rx.await.map_err(|_| {
1102 RuntimeEffectControllerError::new(
1103 "runtime_effect_local_task_closed",
1104 "spawned effect local executor response was dropped",
1105 )
1106 })?
1107 }
1108}
1109
1110impl LocalDirectEffectRunner {
1111 async fn run_direct_llm_request(&mut self, request: CoreLlmRequest) -> RuntimeDirectLlmOutcome {
1112 let request = match crate::attachments::resolve_llm_request_attachments(
1113 request,
1114 self.attachment_store.as_ref(),
1115 )
1116 .await
1117 {
1118 Ok(request) => request,
1119 Err(err) => {
1120 return (
1121 Err(LlmCallError {
1122 message: err.to_string(),
1123 retryable: false,
1124 kind: crate::ProviderFailureKind::Unknown,
1125 raw: None,
1126 code: Some("attachment_resolution_failed".to_string()),
1127 terminal_reason: crate::LlmTerminalReason::ProviderError,
1128 request_body: None,
1129 partial_response: None,
1130 }),
1131 None,
1132 );
1133 }
1134 };
1135 match self.provider.complete(request).await {
1136 Ok(completion) => (Ok(completion.response), Some(completion.call_record)),
1137 Err(failure) => (
1138 Err(llm_call_error_from_transport(failure.error)),
1139 Some(failure.call_record),
1140 ),
1141 }
1142 }
1143}
1144
1145async fn execute_local_sleep(
1146 envelope: RuntimeEffectEnvelope,
1147 cancellation: CancellationToken,
1148 clock: &dyn crate::Clock,
1149) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
1150 match envelope.command {
1151 RuntimeEffectCommand::Sleep { duration_ms } => {
1152 sleep_with_cancellation(duration_ms, &cancellation, clock).await?;
1153 Ok(RuntimeEffectOutcome::Sleep)
1154 }
1155 command => Err(RuntimeEffectControllerError::new(
1156 "runtime_effect_local_executor_mismatch",
1157 format!(
1158 "local sleep executor cannot execute {} command",
1159 command.kind().as_str()
1160 ),
1161 )),
1162 }
1163}
1164
1165async fn sleep_with_cancellation(
1166 duration_ms: u64,
1167 cancellation: &CancellationToken,
1168 clock: &dyn crate::Clock,
1169) -> Result<(), RuntimeEffectControllerError> {
1170 let sleep = clock.sleep(std::time::Duration::from_millis(duration_ms));
1171 tokio::pin!(sleep);
1172 tokio::select! {
1173 _ = cancellation.cancelled() => Err(RuntimeEffectControllerError::new(
1174 "runtime_effect_sleep_cancelled",
1175 "runtime effect sleep was cancelled",
1176 )),
1177 _ = &mut sleep => Ok(()),
1178 }
1179}
1180
1181#[derive(Clone)]
1191pub struct InlineRuntimeEffectController {
1192 await_events: Arc<AwaitEventRegistry>,
1193 allow_process_lifetime_completion_keys: bool,
1194}
1195
1196impl Default for InlineRuntimeEffectController {
1197 fn default() -> Self {
1198 Self {
1199 await_events: Arc::new(AwaitEventRegistry::new()),
1200 allow_process_lifetime_completion_keys: false,
1201 }
1202 }
1203}
1204
1205#[async_trait::async_trait]
1206impl AwaitEventResolver for InlineRuntimeEffectController {
1207 fn allows_process_lifetime_completion_keys(&self) -> bool {
1208 self.allow_process_lifetime_completion_keys
1209 }
1210
1211 async fn await_event_key(
1212 &self,
1213 scope: &ExecutionScope,
1214 wait: AwaitEventWaitIdentity,
1215 ) -> Result<AwaitEventKey, RuntimeError> {
1216 self.await_events.key_for(scope, wait)
1217 }
1218
1219 async fn resolve_await_event(
1220 &self,
1221 key: &AwaitEventKey,
1222 resolution: Resolution,
1223 ) -> Result<ResolveOutcome, RuntimeError> {
1224 self.await_events.resolve(key, resolution)
1225 }
1226
1227 async fn peek_await_event(
1228 &self,
1229 key: &AwaitEventKey,
1230 ) -> Result<Option<Resolution>, RuntimeError> {
1231 self.await_events.peek_resolution(key)
1232 }
1233
1234 async fn await_await_event(
1235 &self,
1236 key: &AwaitEventKey,
1237 cancel: CancellationToken,
1238 deadline: Option<Instant>,
1239 ) -> Result<Resolution, RuntimeError> {
1240 self.await_events
1241 .await_resolution(key, cancel, deadline, &crate::SystemClock)
1242 .await
1243 }
1244
1245 async fn revoke_await_events_for_session(&self, session_id: &str) -> Result<(), RuntimeError> {
1246 self.await_events.revoke_session(session_id)
1247 }
1248
1249 async fn cancel_await_events_for_session(&self, session_id: &str) -> Result<(), RuntimeError> {
1250 self.await_events.cancel_session(session_id)
1251 }
1252}
1253
1254#[async_trait::async_trait]
1255impl RuntimeEffectController for InlineRuntimeEffectController {
1256 async fn execute_effect(
1257 &self,
1258 envelope: RuntimeEffectEnvelope,
1259 local_executor: RuntimeEffectLocalExecutor<'_>,
1260 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
1261 match envelope.command {
1262 RuntimeEffectCommand::PeekAwaitEvent { key } => {
1263 let resolution = self
1264 .await_events
1265 .peek_resolution(&key)
1266 .map_err(RuntimeEffectControllerError::from)?;
1267 Ok(RuntimeEffectOutcome::PeekAwaitEvent { resolution })
1268 }
1269 RuntimeEffectCommand::AwaitEvent { key } => {
1270 let RuntimeAwaitEventOptions {
1271 cancellation,
1272 deadline,
1273 clock,
1274 ..
1275 } = local_executor.into_await_event_options()?;
1276 let resolution = self
1277 .await_events
1278 .await_resolution(&key, cancellation, deadline, clock.as_ref())
1279 .await
1280 .map_err(RuntimeEffectControllerError::from)?;
1281 Ok(RuntimeEffectOutcome::AwaitEvent { resolution })
1282 }
1283 RuntimeEffectCommand::Process { command } => {
1284 let execution = local_executor.into_process()?;
1285 if matches!(command.as_ref(), ProcessCommand::Await { .. }) {
1286 let result = execution.execute(*command).await?;
1287 return Ok(RuntimeEffectOutcome::Process { result });
1288 }
1289 let result = crate::task::spawn(
1290 crate::runtime::process_worker::inherit_process_execution_permit(async move {
1291 execution.execute(*command).await
1292 }),
1293 )
1294 .await
1295 .map_err(|err| {
1296 RuntimeEffectControllerError::new(
1297 "runtime_effect_process_task_join",
1298 format!("inline process effect task failed: {err}"),
1299 )
1300 })??;
1301 Ok(RuntimeEffectOutcome::Process { result })
1302 }
1303 RuntimeEffectCommand::Trigger { command } => {
1304 local_executor
1305 .execute_trigger(envelope.invocation, *command)
1306 .await
1307 }
1308 _ => local_executor.execute(envelope).await,
1309 }
1310 }
1311}
1312
1313impl InlineRuntimeEffectController {
1314 pub fn allow_process_lifetime_completion_keys(mut self) -> Self {
1317 self.allow_process_lifetime_completion_keys = true;
1318 self
1319 }
1320 pub(crate) async fn start_process(
1328 registry: Arc<dyn crate::ProcessRegistry>,
1329 registration: crate::ProcessRegistration,
1330 grant: Option<crate::ProcessStartGrant>,
1331 ) -> Result<ProcessRecord, PluginError> {
1332 let registration_for_record = registration.clone();
1333 let record = registry.register_process(registration_for_record).await?;
1334 if let Some(grant) = grant {
1335 registry
1336 .grant_handle(&grant.session_scope, ®istration.id, grant.descriptor)
1337 .await?;
1338 }
1339 Ok(record)
1340 }
1341
1342 pub(crate) async fn request_process_cancel(
1343 registry: Arc<dyn crate::ProcessRegistry>,
1344 process_id: &str,
1345 reason: Option<String>,
1346 ) -> Result<ProcessRecord, PluginError> {
1347 registry
1351 .append_event(
1352 process_id,
1353 crate::ProcessEventAppendRequest::cancel_requested(process_id, reason.clone()),
1354 )
1355 .await?;
1356 registry
1357 .get_process(process_id)
1358 .await
1359 .ok_or_else(|| PluginError::Session(format!("unknown process `{process_id}`")))
1360 }
1361}
1362
1363impl std::fmt::Debug for InlineRuntimeEffectController {
1364 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1365 f.debug_struct("InlineRuntimeEffectController").finish()
1366 }
1367}
1368
1369#[cfg(test)]
1370mod task_boundary_tests {
1371 use super::*;
1372 use crate::RuntimeInvocation;
1373 use std::sync::atomic::{AtomicBool, Ordering};
1374
1375 struct TaskIdentityRunner {
1376 observed: oneshot::Sender<tokio::task::Id>,
1377 }
1378
1379 #[async_trait::async_trait]
1380 impl RuntimeEffectLocalRunner for TaskIdentityRunner {
1381 fn uses_task_boundary(&self, command: &RuntimeEffectCommand) -> bool {
1382 matches!(command, RuntimeEffectCommand::ExecCode { .. })
1383 }
1384
1385 async fn execute(
1386 self: Box<Self>,
1387 _envelope: RuntimeEffectEnvelope,
1388 ) -> Result<RuntimeEffectOutcome, RuntimeEffectControllerError> {
1389 let _ = self.observed.send(tokio::task::id());
1390 Ok(RuntimeEffectOutcome::Sleep)
1391 }
1392 }
1393
1394 #[tokio::test]
1395 async fn owned_heavy_effect_runs_on_a_fresh_task() {
1396 let (observed_tx, observed_rx) = oneshot::channel();
1397 let executor = RuntimeEffectLocalExecutor {
1398 state: RuntimeEffectLocalExecutorState::OwnedRunner(Box::new(TaskIdentityRunner {
1399 observed: observed_tx,
1400 })),
1401 replay_trace: None,
1402 };
1403 let parent = crate::task::spawn(async move {
1404 let parent_id = tokio::task::id();
1405 let outcome = executor
1406 .execute(RuntimeEffectEnvelope::new(
1407 RuntimeInvocation::effect(
1408 crate::RuntimeScope::new("task-boundary"),
1409 "exec",
1410 RuntimeEffectKind::ExecCode,
1411 "task-boundary:exec",
1412 ),
1413 RuntimeEffectCommand::ExecCode {
1414 language: "text".to_string(),
1415 code: String::new(),
1416 },
1417 ))
1418 .await
1419 .expect("spawned effect");
1420 assert!(matches!(outcome, RuntimeEffectOutcome::Sleep));
1421 parent_id
1422 });
1423 let child_id = observed_rx.await.expect("effect task id");
1424 let parent_id = parent.await.expect("parent task");
1425 assert_ne!(child_id, parent_id);
1426 }
1427
1428 #[tokio::test]
1429 async fn replayed_effect_may_skip_remote_local_execution() {
1430 let executed = Arc::new(AtomicBool::new(false));
1431 let local_executed = Arc::clone(&executed);
1432 let local_executor = RuntimeEffectLocalExecutor::testing(move |_| async move {
1433 local_executed.store(true, Ordering::SeqCst);
1434 Ok(RuntimeEffectOutcome::Sleep)
1435 });
1436 let controller = InlineRuntimeEffectController::default();
1437 let (proxy, mut requests) = EffectTaskController::scoped(
1438 &controller,
1439 ExecutionScope::runtime_operation("replay-skips-local"),
1440 )
1441 .expect("task controller");
1442 let envelope = RuntimeEffectEnvelope::new(
1443 RuntimeInvocation::effect(
1444 crate::RuntimeScope::new("replay-skips-local"),
1445 "sleep",
1446 RuntimeEffectKind::Sleep,
1447 "replay-skips-local:sleep",
1448 ),
1449 RuntimeEffectCommand::Sleep { duration_ms: 0 },
1450 );
1451 let invoke = proxy.controller().execute_effect(envelope, local_executor);
1452 let service = async {
1453 let Some(EffectControllerTaskRequest::Execute {
1454 local_executor,
1455 response,
1456 ..
1457 }) = requests.recv().await
1458 else {
1459 panic!("expected proxied execute request");
1460 };
1461 drop(local_executor);
1462 tokio::task::yield_now().await;
1463 response
1464 .send(Ok(RuntimeEffectOutcome::Sleep))
1465 .expect("proxy response receiver");
1466 };
1467 let (outcome, ()) = tokio::join!(invoke, service);
1468 assert!(matches!(outcome, Ok(RuntimeEffectOutcome::Sleep)));
1469 assert!(!executed.load(Ordering::SeqCst));
1470 }
1471}