1use std::{
7 collections::BTreeMap,
8 sync::{
9 Arc, Mutex,
10 atomic::{AtomicBool, AtomicU64, Ordering},
11 },
12};
13
14use crate::{
15 approval_ports::ApprovalDispatcher,
16 capability::ProjectionMode,
17 content_ports::ContentResolver,
18 domain::{
19 AgentError, AgentErrorKind, AgentId, RetryClassification, RunId, RuntimePackageId,
20 SessionId, SourceRef, TurnId,
21 },
22 error::CausalIds,
23 event::{CompiledEventFilter, EventCursor, EventKind},
24 event_bus::{AgentEventBus, AgentEventStream},
25 hook_ports::{HookExecutorRegistry, InMemoryHookExecutorRegistry},
26 journal::{JournalCursor, JournalRecord},
27 journal_ports::RunJournal,
28 package::{RuntimePackage, RuntimePackageFingerprint},
29 policy::PolicyDecision,
30 ports::{
31 InMemoryRuntimePackageResolver, OutputSinkPort, OutputSinkRegistry, ProviderAdapter,
32 ProviderRegistry, RuntimePackageResolver, RuntimePolicyPort,
33 },
34 provider::ProviderToolSpec,
35 run::{RunRequest, RunResult, RunStatus},
36 run_handle::{InMemoryRunControlStore, RunControlStore, RunHandle},
37 subscription::RunSubscriptionSource,
38 tool_execution::ToolExecutionCoordinator,
39 tool_ports::{
40 ToolExecutor, ToolExecutorRegistry, ToolPolicyPort, ToolRegistrySnapshot, ToolRoute,
41 ToolRouter,
42 },
43};
44
45#[derive(Clone)]
46pub struct AgentRuntime {
49 inner: Arc<RuntimeInner>,
50}
51
52impl AgentRuntime {
53 pub fn builder() -> AgentRuntimeBuilder {
57 AgentRuntimeBuilder::default()
58 }
59
60 pub fn start_run(&self, request: RunRequest) -> Result<RunHandle, AgentError> {
66 let _journal = self.journal_port(&request.run_id)?;
67 let _events = self.event_bus_port(&request.run_id)?;
68 let _content = self.content_port(&request.run_id)?;
69 let policy = self.policy_port(&request.run_id)?;
70
71 let effective = self.resolve_effective_package(&request)?;
72 self.provider_for(&effective.package, &request.run_id)?;
73 crate::hooks::validate_package_hooks(
74 &effective.package.hooks,
75 self.inner.hook_registry.as_ref(),
76 )
77 .map_err(|error| {
78 error.with_causal_ids(CausalIds {
79 run_id: Some(request.run_id.clone()),
80 ..CausalIds::default()
81 })
82 })?;
83 self.evaluate_run_start_policy(policy.as_ref(), &request, &effective.package)?;
84
85 let cancellation = CancellationHandle::new();
86 let entry = RegisteredRun {
87 run_id: request.run_id.clone(),
88 session_id: request.session_id.clone(),
89 turn_id: request.turn_id.clone(),
90 agent_id: request.agent_id.clone(),
91 source: request.source.clone(),
92 status: RunRegistryStatus::Registered,
93 runtime_package_id: effective.package.package_id.clone(),
94 runtime_package_fingerprint: effective.fingerprint,
95 provider_route_id: effective.package.provider_route.route_id,
96 provider_model_id: effective.package.provider_route.model_id,
97 cancellation: cancellation.clone(),
98 };
99
100 self.insert_run(entry, &request.run_id)?;
101 self.inner
102 .run_control
103 .register_run(request.run_id.clone(), request.agent_id.clone())?;
104 Ok(RunHandle::new(
105 request.run_id,
106 Arc::new(RuntimeRunControlStore {
107 runtime: self.clone(),
108 }),
109 Arc::new(RuntimeRunSubscriptionSource {
110 runtime: self.clone(),
111 }),
112 ))
113 }
114
115 pub fn run_text(&self, request: RunRequest) -> Result<RunResult, AgentError> {
120 let handle = self.start_run(request.clone())?;
121 crate::loop_driver::run_p0_text(self, request, handle)
122 }
123
124 pub fn run_typed<T: crate::typed_output_ports::TypedOutputModel>(
129 &self,
130 request: RunRequest,
131 ) -> Result<RunResult, AgentError> {
132 self.run_text(request.with_output_contract(crate::output::OutputContract::for_type::<T>()))
133 }
134
135 pub fn resolve_effective_package(
139 &self,
140 request: &RunRequest,
141 ) -> Result<EffectiveRuntimePackage, AgentError> {
142 let package_id = self
143 .inner
144 .default_package_id
145 .as_ref()
146 .ok_or_else(|| missing_port_error("default runtime package", &request.run_id))?;
147 let resolver = self.package_resolver_port(&request.run_id)?;
148 let mut package = resolver.resolve(package_id).map_err(|error| {
149 error.with_causal_ids(CausalIds {
150 run_id: Some(request.run_id.clone()),
151 ..CausalIds::default()
152 })
153 })?;
154 if let Some(output_contract) = &request.output_contract {
155 package = package
156 .with_output_contract(output_contract)
157 .map_err(|error| {
158 error.with_causal_ids(CausalIds {
159 run_id: Some(request.run_id.clone()),
160 ..CausalIds::default()
161 })
162 })?;
163 }
164
165 package.validate().map_err(|error| {
166 error.with_causal_ids(CausalIds {
167 run_id: Some(request.run_id.clone()),
168 ..CausalIds::default()
169 })
170 })?;
171 if package.agent.agent_id != request.agent_id {
172 return Err(AgentError::new(
173 AgentErrorKind::InvalidPackage,
174 RetryClassification::HostConfigurationNeeded,
175 "runtime package agent snapshot must match the run request agent_id",
176 )
177 .with_causal_ids(CausalIds {
178 run_id: Some(request.run_id.clone()),
179 ..CausalIds::default()
180 }));
181 }
182
183 let fingerprint = package.fingerprint().map_err(|error| {
184 error.with_causal_ids(CausalIds {
185 run_id: Some(request.run_id.clone()),
186 ..CausalIds::default()
187 })
188 })?;
189 Ok(EffectiveRuntimePackage {
190 package,
191 fingerprint,
192 })
193 }
194
195 pub fn cancel_run(&self, run_id: &RunId) -> Result<(), AgentError> {
199 let mut runs = self
200 .inner
201 .runs
202 .lock()
203 .map_err(|_| AgentError::contract_violation("run registry lock poisoned"))?;
204 let entry = runs.get_mut(run_id).ok_or_else(|| {
205 AgentError::new(
206 AgentErrorKind::InvalidStateTransition,
207 RetryClassification::RepairNeeded,
208 "run is not registered with this runtime",
209 )
210 .with_causal_ids(CausalIds {
211 run_id: Some(run_id.clone()),
212 ..CausalIds::default()
213 })
214 })?;
215 entry.cancellation.cancel();
216 entry.status = RunRegistryStatus::CancellationRequested;
217 self.inner.run_control.request_cancel(run_id)?;
218 Ok(())
219 }
220
221 pub fn run_snapshot(&self, run_id: &RunId) -> Result<RunSnapshot, AgentError> {
224 let runs = self
225 .inner
226 .runs
227 .lock()
228 .map_err(|_| AgentError::contract_violation("run registry lock poisoned"))?;
229 runs.get(run_id)
230 .map(RegisteredRun::snapshot)
231 .ok_or_else(|| {
232 AgentError::new(
233 AgentErrorKind::InvalidStateTransition,
234 RetryClassification::RepairNeeded,
235 "run is not registered with this runtime",
236 )
237 .with_causal_ids(CausalIds {
238 run_id: Some(run_id.clone()),
239 ..CausalIds::default()
240 })
241 })
242 }
243
244 pub fn registered_run_count(&self) -> Result<usize, AgentError> {
248 Ok(self
249 .inner
250 .runs
251 .lock()
252 .map_err(|_| AgentError::contract_violation("run registry lock poisoned"))?
253 .len())
254 }
255
256 pub fn events(&self) -> Result<Arc<dyn AgentEventBus>, AgentError> {
260 self.event_bus_subscription_port()
261 }
262
263 pub fn subscribe_all(
267 &self,
268 cursor: Option<EventCursor>,
269 ) -> Result<AgentEventStream, AgentError> {
270 self.event_bus_subscription_port()?.subscribe_all(cursor)
271 }
272
273 pub fn subscribe_run(
277 &self,
278 run_id: RunId,
279 cursor: Option<EventCursor>,
280 ) -> Result<AgentEventStream, AgentError> {
281 self.event_bus_subscription_port()?
282 .subscribe_run(run_id, cursor)
283 }
284
285 pub fn subscribe_agent(
289 &self,
290 agent_id: AgentId,
291 cursor: Option<EventCursor>,
292 ) -> Result<AgentEventStream, AgentError> {
293 self.event_bus_subscription_port()?
294 .subscribe_agent(agent_id, cursor)
295 }
296
297 pub fn subscribe_events(
300 &self,
301 filter: CompiledEventFilter,
302 cursor: Option<EventCursor>,
303 ) -> Result<AgentEventStream, AgentError> {
304 self.event_bus_subscription_port()?
305 .subscribe_filtered(filter, cursor)
306 }
307
308 pub fn provider_registry(&self) -> &ProviderRegistry {
312 &self.inner.providers
313 }
314
315 pub fn output_sinks(&self) -> &OutputSinkRegistry {
319 &self.inner.output_sinks
320 }
321
322 pub(crate) fn tool_execution_coordinator(
327 &self,
328 package: &RuntimePackage,
329 run_id: &RunId,
330 ) -> Result<ToolExecutionCoordinator, AgentError> {
331 if self.inner.tool_routes.is_empty() {
332 return Err(missing_port_error("tool routes", run_id));
333 }
334 let snapshot =
335 ToolRegistrySnapshot::from_runtime_package(package, self.inner.tool_routes.clone())
336 .map_err(|error| {
337 error.with_causal_ids(CausalIds {
338 run_id: Some(run_id.clone()),
339 ..CausalIds::default()
340 })
341 })?;
342 let coordinator = ToolExecutionCoordinator::new(
343 ToolRouter::new(snapshot),
344 self.inner.tool_executors.clone(),
345 );
346 let coordinator = match self.inner.tool_policy.clone() {
347 Some(policy) => coordinator.with_policy(policy),
348 None => coordinator,
349 };
350 Ok(match self.inner.approval_dispatcher.clone() {
351 Some(dispatcher) => coordinator.with_approval_dispatcher(dispatcher),
352 None => coordinator,
353 })
354 }
355
356 pub(crate) fn provider_tool_specs(
360 &self,
361 package: &RuntimePackage,
362 run_id: &RunId,
363 ) -> Result<Vec<ProviderToolSpec>, AgentError> {
364 let projected_tools = package.provider_tool_specs()?;
365 if projected_tools.is_empty() {
366 return Ok(Vec::new());
367 }
368 if self.inner.tool_routes.is_empty() {
369 return Err(missing_port_error("tool routes", run_id));
370 }
371 let snapshot =
372 ToolRegistrySnapshot::from_runtime_package(package, self.inner.tool_routes.clone())
373 .map_err(|error| {
374 error.with_causal_ids(CausalIds {
375 run_id: Some(run_id.clone()),
376 ..CausalIds::default()
377 })
378 })?;
379 let projected_schema_refs = projected_tools
380 .into_iter()
381 .filter_map(|projection| match projection.projection {
382 ProjectionMode::ProviderToolSchema { schema_ref } => {
383 Some((projection.capability_id, schema_ref))
384 }
385 _ => None,
386 })
387 .collect::<BTreeMap<_, _>>();
388 let mut specs = Vec::new();
389 for route in snapshot.routes {
390 let Some(schema_ref) = projected_schema_refs.get(&route.capability_id).cloned() else {
391 return Err(
392 AgentError::missing_required_field("provider_tool_spec.schema_ref")
393 .with_causal_ids(CausalIds {
394 run_id: Some(run_id.clone()),
395 ..CausalIds::default()
396 }),
397 );
398 };
399 let mut spec = ProviderToolSpec::new(
400 route.canonical_tool_name.as_str(),
401 route.capability_id,
402 route.namespace,
403 schema_ref.clone(),
404 route.policy_refs,
405 );
406 if let Some(description) = route.description {
407 spec = spec.with_description(description);
408 }
409 if let Some(schema) = provider_schema_from_sidecars(package, &schema_ref) {
410 spec = spec.with_redacted_schema(schema);
411 }
412 specs.push(spec);
413 }
414 Ok(specs)
415 }
416
417 pub(crate) fn hook_registry_port(&self) -> Arc<dyn HookExecutorRegistry> {
420 self.inner.hook_registry.clone()
421 }
422
423 fn insert_run(&self, entry: RegisteredRun, run_id: &RunId) -> Result<(), AgentError> {
424 let mut runs = self
425 .inner
426 .runs
427 .lock()
428 .map_err(|_| AgentError::contract_violation("run registry lock poisoned"))?;
429 if runs.contains_key(run_id) {
430 return Err(AgentError::new(
431 AgentErrorKind::InvalidStateTransition,
432 RetryClassification::NotRetryable,
433 "run_id is already registered with this runtime",
434 )
435 .with_causal_ids(CausalIds {
436 run_id: Some(run_id.clone()),
437 ..CausalIds::default()
438 }));
439 }
440 runs.insert(run_id.clone(), entry);
441 Ok(())
442 }
443
444 pub(crate) fn provider_for(
447 &self,
448 package: &RuntimePackage,
449 run_id: &RunId,
450 ) -> Result<Arc<dyn ProviderAdapter>, AgentError> {
451 self.inner
452 .providers
453 .get(&package.provider_route.route_id)
454 .ok_or_else(|| {
455 AgentError::new(
456 AgentErrorKind::ProviderFailure,
457 RetryClassification::HostConfigurationNeeded,
458 format!(
459 "missing provider adapter for package route {}",
460 package.provider_route.route_id
461 ),
462 )
463 .with_causal_ids(CausalIds {
464 run_id: Some(run_id.clone()),
465 ..CausalIds::default()
466 })
467 })
468 }
469
470 pub(crate) fn provider_for_route(
473 &self,
474 route_id: &str,
475 run_id: &RunId,
476 ) -> Result<Arc<dyn ProviderAdapter>, AgentError> {
477 self.inner.providers.get(route_id).ok_or_else(|| {
478 AgentError::new(
479 AgentErrorKind::ProviderFailure,
480 RetryClassification::HostConfigurationNeeded,
481 format!("missing provider adapter for package route {route_id}"),
482 )
483 .with_causal_ids(CausalIds {
484 run_id: Some(run_id.clone()),
485 ..CausalIds::default()
486 })
487 })
488 }
489
490 fn evaluate_run_start_policy(
491 &self,
492 policy: &dyn RuntimePolicyPort,
493 request: &RunRequest,
494 package: &RuntimePackage,
495 ) -> Result<(), AgentError> {
496 let outcome = policy.evaluate_run_start(request, package)?;
497 if outcome.is_allowed() {
498 return Ok(());
499 }
500
501 let mut error = AgentError::new(
502 AgentErrorKind::PolicyDenial,
503 RetryClassification::UserActionNeeded,
504 policy_denial_message(&outcome.decision),
505 )
506 .with_causal_ids(CausalIds {
507 run_id: Some(request.run_id.clone()),
508 ..CausalIds::default()
509 })
510 .with_source(request.source.clone());
511 for policy_ref in outcome.policy_refs {
512 error = error.with_policy_ref(policy_ref);
513 }
514 Err(error)
515 }
516
517 fn package_resolver_port(
518 &self,
519 run_id: &RunId,
520 ) -> Result<Arc<dyn RuntimePackageResolver>, AgentError> {
521 self.inner
522 .package_resolver
523 .clone()
524 .ok_or_else(|| missing_port_error("runtime package resolver", run_id))
525 }
526
527 pub(crate) fn journal_port(&self, run_id: &RunId) -> Result<Arc<dyn RunJournal>, AgentError> {
530 self.inner
531 .journal
532 .clone()
533 .ok_or_else(|| missing_port_error("run journal", run_id))
534 }
535
536 pub(crate) fn event_bus_port(
539 &self,
540 run_id: &RunId,
541 ) -> Result<Arc<dyn AgentEventBus>, AgentError> {
542 self.inner
543 .events
544 .clone()
545 .ok_or_else(|| missing_port_error("agent event bus", run_id))
546 }
547
548 fn event_bus_subscription_port(&self) -> Result<Arc<dyn AgentEventBus>, AgentError> {
549 self.inner
550 .events
551 .clone()
552 .ok_or_else(|| missing_runtime_port_error("agent event bus"))
553 }
554
555 pub(crate) fn content_port(
558 &self,
559 run_id: &RunId,
560 ) -> Result<Arc<dyn ContentResolver + Send + Sync>, AgentError> {
561 self.inner
562 .content
563 .clone()
564 .ok_or_else(|| missing_port_error("content resolver", run_id))
565 }
566
567 fn policy_port(&self, run_id: &RunId) -> Result<Arc<dyn RuntimePolicyPort>, AgentError> {
568 self.inner
569 .policy
570 .clone()
571 .ok_or_else(|| missing_port_error("runtime policy port", run_id))
572 }
573
574 pub(crate) fn seal_terminal_result_from_journal(
578 &self,
579 record: &JournalRecord,
580 output: impl Into<String>,
581 ) -> Result<RunResult, AgentError> {
582 self.inner
583 .run_control
584 .seal_terminal_result_from_journal(record, output)
585 }
586
587 pub(crate) fn next_journal_seq(&self) -> u64 {
590 let _guard = self
591 .inner
592 .journal_sequence_lock
593 .lock()
594 .unwrap_or_else(|poisoned| poisoned.into_inner());
595 self.inner.next_journal_seq.fetch_add(1, Ordering::SeqCst) + 1
596 }
597
598 pub(crate) fn reserve_journal_seq_block(&self, width: u64) -> u64 {
601 debug_assert!(width > 0);
602 let _guard = self
603 .inner
604 .journal_sequence_lock
605 .lock()
606 .unwrap_or_else(|poisoned| poisoned.into_inner());
607 self.inner
608 .next_journal_seq
609 .fetch_add(width, Ordering::SeqCst)
610 + 1
611 }
612
613 pub(crate) fn next_journal_seq_hint(&self) -> u64 {
616 self.inner.next_journal_seq.load(Ordering::SeqCst) + 1
617 }
618}
619
620impl Default for AgentRuntime {
621 fn default() -> Self {
622 AgentRuntimeBuilder::default()
623 .build()
624 .expect("empty runtime builder is infallible")
625 }
626}
627
628#[derive(Default)]
629pub struct AgentRuntimeBuilder {
632 providers: ProviderRegistry,
633 package_resolver: Option<Arc<dyn RuntimePackageResolver>>,
634 local_packages: Vec<RuntimePackage>,
635 default_package_id: Option<RuntimePackageId>,
636 journal: Option<Arc<dyn RunJournal>>,
637 events: Option<Arc<dyn AgentEventBus>>,
638 content: Option<Arc<dyn ContentResolver + Send + Sync>>,
639 policy: Option<Arc<dyn RuntimePolicyPort>>,
640 output_sinks: OutputSinkRegistry,
641 hook_registry: Option<Arc<dyn HookExecutorRegistry>>,
642 tool_routes: Vec<ToolRoute>,
643 tool_executors: ToolExecutorRegistry,
644 tool_policy: Option<Arc<dyn ToolPolicyPort>>,
645 approval_dispatcher: Option<Arc<dyn ApprovalDispatcher>>,
646}
647
648impl AgentRuntimeBuilder {
649 pub fn providers(mut self, providers: ProviderRegistry) -> Self {
652 self.providers = providers;
653 self
654 }
655
656 pub fn provider<P>(
659 mut self,
660 route_id: impl Into<String>,
661 provider: P,
662 ) -> Result<Self, AgentError>
663 where
664 P: ProviderAdapter + 'static,
665 {
666 self.providers.register(route_id, Arc::new(provider))?;
667 Ok(self)
668 }
669
670 pub fn package_resolver<R>(mut self, resolver: R) -> Self
674 where
675 R: RuntimePackageResolver + 'static,
676 {
677 self.package_resolver = Some(Arc::new(resolver));
678 self
679 }
680
681 pub fn default_package_id(mut self, package_id: RuntimePackageId) -> Self {
685 self.default_package_id = Some(package_id);
686 self
687 }
688
689 pub fn package(mut self, package: RuntimePackage) -> Self {
692 self.local_packages.push(package);
693 self
694 }
695
696 pub fn default_package(mut self, package: RuntimePackage) -> Self {
700 self.default_package_id = Some(package.package_id.clone());
701 self.local_packages.push(package);
702 self
703 }
704
705 pub fn journal<J>(mut self, journal: J) -> Self
708 where
709 J: RunJournal + 'static,
710 {
711 self.journal = Some(Arc::new(journal));
712 self
713 }
714
715 pub fn event_bus<E>(mut self, event_bus: E) -> Self
718 where
719 E: AgentEventBus + 'static,
720 {
721 self.events = Some(Arc::new(event_bus));
722 self
723 }
724
725 pub fn content<C>(mut self, content: C) -> Self
728 where
729 C: ContentResolver + Send + Sync + 'static,
730 {
731 self.content = Some(Arc::new(content));
732 self
733 }
734
735 pub fn policy<P>(mut self, policy: P) -> Self
739 where
740 P: RuntimePolicyPort + 'static,
741 {
742 self.policy = Some(Arc::new(policy));
743 self
744 }
745
746 pub fn output_sink<S>(mut self, sink: S) -> Result<Self, AgentError>
750 where
751 S: OutputSinkPort + 'static,
752 {
753 self.output_sinks.register(Arc::new(sink))?;
754 Ok(self)
755 }
756
757 pub fn tool_route(mut self, route: ToolRoute) -> Self {
761 self.tool_routes.push(route);
762 self
763 }
764
765 pub fn tool_routes(mut self, routes: impl IntoIterator<Item = ToolRoute>) -> Self {
768 self.tool_routes = routes.into_iter().collect();
769 self
770 }
771
772 pub fn tool_executors(mut self, executors: ToolExecutorRegistry) -> Self {
775 self.tool_executors = executors;
776 self
777 }
778
779 pub fn tool_executor(mut self, executor: Arc<dyn ToolExecutor>) -> Result<Self, AgentError> {
783 self.tool_executors.register(executor)?;
784 Ok(self)
785 }
786
787 pub fn tool_policy<P>(mut self, policy: P) -> Self
790 where
791 P: ToolPolicyPort + 'static,
792 {
793 self.tool_policy = Some(Arc::new(policy));
794 self
795 }
796
797 pub fn approval_dispatcher<D>(mut self, dispatcher: D) -> Self
800 where
801 D: ApprovalDispatcher + 'static,
802 {
803 self.approval_dispatcher = Some(Arc::new(dispatcher));
804 self
805 }
806
807 pub fn shared_approval_dispatcher(mut self, dispatcher: Arc<dyn ApprovalDispatcher>) -> Self {
809 self.approval_dispatcher = Some(dispatcher);
810 self
811 }
812
813 pub fn hook_executor_registry<R>(mut self, registry: R) -> Self
817 where
818 R: HookExecutorRegistry + 'static,
819 {
820 self.hook_registry = Some(Arc::new(registry));
821 self
822 }
823
824 pub fn build(self) -> Result<AgentRuntime, AgentError> {
828 let package_resolver = match (self.package_resolver, self.local_packages.is_empty()) {
829 (Some(resolver), _) => Some(resolver),
830 (None, true) => None,
831 (None, false) => Some(Arc::new(InMemoryRuntimePackageResolver::from_packages(
832 self.local_packages,
833 )?) as Arc<dyn RuntimePackageResolver>),
834 };
835
836 Ok(AgentRuntime {
837 inner: Arc::new(RuntimeInner {
838 providers: self.providers,
839 package_resolver,
840 default_package_id: self.default_package_id,
841 journal: self.journal,
842 events: self.events,
843 content: self.content,
844 policy: self.policy,
845 output_sinks: self.output_sinks,
846 hook_registry: self
847 .hook_registry
848 .unwrap_or_else(|| Arc::new(InMemoryHookExecutorRegistry::default())),
849 tool_routes: self.tool_routes,
850 tool_executors: self.tool_executors,
851 tool_policy: self.tool_policy,
852 approval_dispatcher: self.approval_dispatcher,
853 run_control: InMemoryRunControlStore::default(),
854 next_journal_seq: AtomicU64::new(0),
855 journal_sequence_lock: Mutex::new(()),
856 runs: Mutex::new(BTreeMap::new()),
857 }),
858 })
859 }
860}
861
862struct RuntimeInner {
863 providers: ProviderRegistry,
864 package_resolver: Option<Arc<dyn RuntimePackageResolver>>,
865 default_package_id: Option<RuntimePackageId>,
866 journal: Option<Arc<dyn RunJournal>>,
867 events: Option<Arc<dyn AgentEventBus>>,
868 content: Option<Arc<dyn ContentResolver + Send + Sync>>,
869 policy: Option<Arc<dyn RuntimePolicyPort>>,
870 output_sinks: OutputSinkRegistry,
871 hook_registry: Arc<dyn HookExecutorRegistry>,
872 tool_routes: Vec<ToolRoute>,
873 tool_executors: ToolExecutorRegistry,
874 tool_policy: Option<Arc<dyn ToolPolicyPort>>,
875 approval_dispatcher: Option<Arc<dyn ApprovalDispatcher>>,
876 run_control: InMemoryRunControlStore,
877 next_journal_seq: AtomicU64,
878 journal_sequence_lock: Mutex<()>,
879 runs: Mutex<BTreeMap<RunId, RegisteredRun>>,
880}
881
882fn provider_schema_from_sidecars(
883 package: &RuntimePackage,
884 schema_ref: &crate::capability::PackageSidecarRef,
885) -> Option<serde_json::Value> {
886 package
887 .sidecars
888 .iter()
889 .filter_map(|sidecar| sidecar.redacted_payload.as_ref())
890 .find_map(|payload| {
891 payload.get("tools")?.as_array()?.iter().find_map(|tool| {
892 let tool_schema_ref = tool.get("schema_ref")?;
893 if tool_schema_ref.get("sidecar_id")?.as_str()? == schema_ref.sidecar_id {
894 tool.get("redacted_schema").cloned()
895 } else {
896 None
897 }
898 })
899 })
900}
901
902#[derive(Clone, Debug, Eq, PartialEq)]
903pub struct EffectiveRuntimePackage {
906 pub package: RuntimePackage,
908 pub fingerprint: RuntimePackageFingerprint,
911}
912
913#[derive(Clone, Debug, Eq, PartialEq)]
914pub struct RunSnapshot {
917 pub run_id: RunId,
919 pub session_id: Option<SessionId>,
921 pub turn_id: Option<TurnId>,
923 pub agent_id: AgentId,
925 pub source: SourceRef,
928 pub status: RunRegistryStatus,
930 pub runtime_package_id: RuntimePackageId,
932 pub runtime_package_fingerprint: RuntimePackageFingerprint,
936 pub provider_route_id: String,
938 pub provider_model_id: String,
940 pub cancellation_requested: bool,
943}
944
945#[derive(Clone, Debug, Eq, PartialEq)]
946pub enum RunRegistryStatus {
949 Registered,
951 CancellationRequested,
953}
954
955#[derive(Clone, Default)]
956pub struct CancellationHandle {
959 cancelled: Arc<AtomicBool>,
960}
961
962impl CancellationHandle {
963 pub fn new() -> Self {
967 Self::default()
968 }
969
970 pub fn cancel(&self) {
974 self.cancelled.store(true, Ordering::SeqCst);
975 }
976
977 pub fn is_cancelled(&self) -> bool {
980 self.cancelled.load(Ordering::SeqCst)
981 }
982}
983
984impl std::fmt::Debug for CancellationHandle {
985 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
986 formatter
987 .debug_struct("CancellationHandle")
988 .field("cancelled", &self.is_cancelled())
989 .finish()
990 }
991}
992
993#[derive(Clone, Debug)]
994struct RegisteredRun {
995 run_id: RunId,
996 session_id: Option<SessionId>,
997 turn_id: Option<TurnId>,
998 agent_id: AgentId,
999 source: SourceRef,
1000 status: RunRegistryStatus,
1001 runtime_package_id: RuntimePackageId,
1002 runtime_package_fingerprint: RuntimePackageFingerprint,
1003 provider_route_id: String,
1004 provider_model_id: String,
1005 cancellation: CancellationHandle,
1006}
1007
1008impl RegisteredRun {
1009 fn snapshot(&self) -> RunSnapshot {
1010 RunSnapshot {
1011 run_id: self.run_id.clone(),
1012 session_id: self.session_id.clone(),
1013 turn_id: self.turn_id.clone(),
1014 agent_id: self.agent_id.clone(),
1015 source: self.source.clone(),
1016 status: self.status.clone(),
1017 runtime_package_id: self.runtime_package_id.clone(),
1018 runtime_package_fingerprint: self.runtime_package_fingerprint.clone(),
1019 provider_route_id: self.provider_route_id.clone(),
1020 provider_model_id: self.provider_model_id.clone(),
1021 cancellation_requested: self.cancellation.is_cancelled(),
1022 }
1023 }
1024}
1025
1026fn missing_port_error(port_name: &str, run_id: &RunId) -> AgentError {
1027 AgentError::new(
1028 AgentErrorKind::HostConfigurationNeeded,
1029 RetryClassification::HostConfigurationNeeded,
1030 format!("missing required runtime port: {port_name}"),
1031 )
1032 .with_causal_ids(CausalIds {
1033 run_id: Some(run_id.clone()),
1034 ..CausalIds::default()
1035 })
1036}
1037
1038fn missing_runtime_port_error(port_name: &str) -> AgentError {
1039 AgentError::new(
1040 AgentErrorKind::HostConfigurationNeeded,
1041 RetryClassification::HostConfigurationNeeded,
1042 format!("missing required runtime port: {port_name}"),
1043 )
1044}
1045
1046fn policy_denial_message(decision: &PolicyDecision) -> String {
1047 match decision {
1048 PolicyDecision::Deny { reason }
1049 | PolicyDecision::Interrupt { reason }
1050 | PolicyDecision::Defer {
1051 resume_policy: crate::policy::ResumePolicy { reason, .. },
1052 } => reason.code.clone(),
1053 PolicyDecision::Ask { .. } => "policy requested host approval before run start".to_string(),
1054 PolicyDecision::Modify { .. } => {
1055 "policy modification is not valid for runtime start".to_string()
1056 }
1057 PolicyDecision::Allow { .. } => "policy allowed run start".to_string(),
1058 }
1059}
1060
1061#[derive(Clone)]
1062struct RuntimeRunControlStore {
1063 runtime: AgentRuntime,
1064}
1065
1066impl RunControlStore for RuntimeRunControlStore {
1067 fn status(&self, run_id: &RunId) -> Result<RunStatus, AgentError> {
1068 self.runtime.inner.run_control.status(run_id)
1069 }
1070
1071 fn terminal_result(&self, run_id: &RunId) -> Result<Option<RunResult>, AgentError> {
1072 self.runtime.inner.run_control.terminal_result(run_id)
1073 }
1074
1075 fn request_cancel(&self, run_id: &RunId) -> Result<(), AgentError> {
1076 self.runtime.cancel_run(run_id)
1077 }
1078}
1079
1080#[derive(Clone)]
1081struct RuntimeRunSubscriptionSource {
1082 runtime: AgentRuntime,
1083}
1084
1085impl RunSubscriptionSource for RuntimeRunSubscriptionSource {
1086 fn subscribe_all(&self, cursor: Option<EventCursor>) -> Result<AgentEventStream, AgentError> {
1087 self.runtime.subscribe_all(cursor)
1088 }
1089
1090 fn subscribe_run(
1091 &self,
1092 run_id: RunId,
1093 cursor: Option<EventCursor>,
1094 ) -> Result<AgentEventStream, AgentError> {
1095 self.runtime.subscribe_run(run_id, cursor)
1096 }
1097
1098 fn subscribe_agent(
1099 &self,
1100 agent_id: AgentId,
1101 cursor: Option<EventCursor>,
1102 ) -> Result<AgentEventStream, AgentError> {
1103 self.runtime.subscribe_agent(agent_id, cursor)
1104 }
1105
1106 fn subscribe_events(
1107 &self,
1108 filter: CompiledEventFilter,
1109 cursor: Option<EventCursor>,
1110 ) -> Result<AgentEventStream, AgentError> {
1111 self.runtime.subscribe_events(filter, cursor)
1112 }
1113
1114 fn replay_run_from_cursor(
1115 &self,
1116 _run_id: RunId,
1117 _cursor: JournalCursor,
1118 ) -> Result<AgentEventStream, AgentError> {
1119 Err(AgentError::host_configuration_needed(
1120 "run journal replay subscription requires an archive-backed subscription source",
1121 ))
1122 }
1123
1124 fn latest_terminal_event(
1125 &self,
1126 run_id: &RunId,
1127 ) -> Result<Option<crate::event::EventFrame>, AgentError> {
1128 Ok(self
1129 .runtime
1130 .subscribe_run(run_id.clone(), None)?
1131 .filter(|frame| {
1132 matches!(
1133 frame.event.envelope.event_kind,
1134 EventKind::RunCompleted | EventKind::RunFailed | EventKind::RunCancelled
1135 )
1136 })
1137 .last())
1138 }
1139}