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