1use crate::HostComposition;
6use crate::InProcessExecution;
7use crate::SessionFileSystemFactoryContext;
8use crate::SessionMutator;
9use crate::backends::{
10 HostBackends, RuntimeAgentStore, RuntimeHarnessStore, RuntimeProviderStore, RuntimeSessionStore,
11};
12use crate::builders::SingleSessionBuilder;
13use crate::events::{EventHistory, EventLog, EventReadLimit, EventReadRequest, HostEventEmitter};
14use crate::host::{
15 ResolvedTurnInputs, RuntimeHostAdapter, execute_act_activity, execute_input_activity,
16 execute_reason_activity_with_prompt_messages, run_user_prompt_submit_for_message,
17};
18use crate::in_memory::{InMemorySessionFileStore, InMemorySessionFileSystemFactory};
19use async_trait::async_trait;
20use chrono::Utc;
21use everruns_capability::plugin_capability_id;
22use everruns_core::ExecutionContext;
23use everruns_core::agent_definition::AgentDefinition;
24#[cfg(feature = "mcp")]
25use everruns_core::capabilities::collect_capability_mcp_servers;
26use everruns_core::capabilities::{
27 Capability, CapabilityRegistry, CapabilityStatus, resolve_capability_configs,
28};
29use everruns_core::config_layer::AgentConfigOverlay;
30use everruns_core::events::{
31 Event, EventContext, EventRequest, InputMessageData, SessionStartedData,
32};
33use everruns_core::harness_definition::HarnessDefinition;
34use everruns_core::lifecycle_hooks::UserPromptDecision;
35use everruns_core::message::{ContentPart, Message};
36use everruns_core::plugins::{PluginFileSet, compile_plugin};
37#[cfg(feature = "mcp")]
38use everruns_core::resolve_runtime_capabilities;
39use everruns_core::runtime_context::AssembledTurnContext;
40use everruns_core::session::{ExecutionSession, SessionExecutionState};
41use everruns_core::session_file::{InitialFile, SessionFile};
42use everruns_core::turn::TurnStopReason;
43use everruns_core::{
44 InputMessage, MessageRetriever, ResolvedExecutionSnapshot, session_files::SessionFileSystem,
45};
46use everruns_core::{
47 connection_services::UserConnectionResolver, event_emitter::EventEmitter,
48 execution_loading::AgentStore, execution_loading::HarnessStore,
49 execution_loading::SessionStore, provider_resolution::ProviderStore,
50 session_services::SessionStorageStore,
51};
52use everruns_engine::{
53 ActOutcome, ActivityOutcome, Execution, HostFacts, TurnPlan, TurnState, reason_schedules_act,
54};
55use everruns_engine::{InputAtomInput, ReasonInput};
56use everruns_provider::driver_registry::DriverRegistry;
57use everruns_provider::error::{AgentLoopError, Result};
58use everruns_provider::typed_id::{AgentId, MessageId, OrgId, SessionId, TurnId};
59use sha2::{Digest, Sha256};
60use std::collections::VecDeque;
61use std::path::Path;
62use std::sync::{Arc, Mutex};
63
64const HASH_INPUT_CAP_BYTES: usize = 128;
70
71pub fn in_process_internal_org_id(public_org_id: &str) -> i64 {
84 if public_org_id == everruns_core::DEFAULT_ORG_PUBLIC_ID {
85 return everruns_core::DEFAULT_ORG_ID;
86 }
87
88 let Ok(parsed) = public_org_id.parse::<OrgId>() else {
89 return hash_public_org_id(public_org_id);
90 };
91 let raw: u128 = parsed.uuid().as_u128();
92 if raw == 0 {
93 return hash_public_org_id(public_org_id);
94 }
95
96 if raw <= i64::MAX as u128 {
99 return raw as i64;
100 }
101
102 hash_public_org_id(public_org_id)
103}
104
105fn hash_public_org_id(public_org_id: &str) -> i64 {
110 let bytes = public_org_id.as_bytes();
111 let bounded = &bytes[..bytes.len().min(HASH_INPUT_CAP_BYTES)];
112 let digest = Sha256::digest(bounded);
113 let mut buf = [0u8; 8];
114 buf.copy_from_slice(&digest[..8]);
115 let raw = u64::from_be_bytes(buf);
116 ((raw % ((i64::MAX - 1) as u64)) as i64) + 2
117}
118
119#[derive(Debug, Clone)]
120pub struct TurnResult {
121 pub response: String,
123 pub iterations: usize,
125 pub tool_calls_count: usize,
127 pub success: bool,
129 pub error: Option<String>,
131 pub stop_reason: TurnStopReason,
133 pub turn_id: everruns_provider::typed_id::TurnId,
135}
136
137#[derive(Clone, Debug)]
145pub struct AcceptedTurnInput {
146 message_id: MessageId,
147 input: InputMessage,
148}
149
150impl AcceptedTurnInput {
151 pub fn new(input: impl Into<InputMessage>) -> Self {
152 Self {
153 message_id: MessageId::new(),
154 input: input.into(),
155 }
156 }
157
158 pub fn message_id(&self) -> MessageId {
159 self.message_id
160 }
161
162 pub fn input(&self) -> &InputMessage {
163 &self.input
164 }
165
166 fn into_message(self) -> Message {
167 message_from_input_with_id(self.message_id, self.input)
168 }
169}
170
171#[derive(Clone, Debug)]
188pub struct TurnSteering {
189 state: Arc<Mutex<TurnSteeringState>>,
190}
191
192#[derive(Debug)]
197pub enum TurnSteeringPushError {
198 Closed(Box<AcceptedTurnInput>),
200 Full(Box<AcceptedTurnInput>),
202}
203
204const TURN_STEERING_CAPACITY: usize = 256;
207
208#[derive(Debug, Default)]
209struct TurnSteeringState {
210 open: bool,
211 inputs: VecDeque<AcceptedTurnInput>,
212}
213
214impl TurnSteering {
215 pub fn new() -> Self {
216 Self {
217 state: Arc::new(Mutex::new(TurnSteeringState {
218 open: true,
219 inputs: VecDeque::new(),
220 })),
221 }
222 }
223
224 pub fn try_push(
225 &self,
226 input: AcceptedTurnInput,
227 ) -> std::result::Result<(), TurnSteeringPushError> {
228 let mut state = self.state.lock().expect("turn steering lock poisoned");
229 if !state.open {
230 return Err(TurnSteeringPushError::Closed(Box::new(input)));
231 }
232 if state.inputs.len() >= TURN_STEERING_CAPACITY {
233 return Err(TurnSteeringPushError::Full(Box::new(input)));
234 }
235 state.inputs.push_back(input);
236 Ok(())
237 }
238
239 fn drain(&self) -> Vec<AcceptedTurnInput> {
240 let mut state = self.state.lock().expect("turn steering lock poisoned");
241 state.inputs.drain(..).collect()
242 }
243
244 fn drain_or_close(&self) -> Vec<AcceptedTurnInput> {
246 let mut state = self.state.lock().expect("turn steering lock poisoned");
247 if state.inputs.is_empty() {
248 state.open = false;
249 return vec![];
250 }
251 state.inputs.drain(..).collect()
252 }
253
254 pub fn close(&self) {
255 self.state.lock().expect("turn steering lock poisoned").open = false;
256 }
257
258 pub fn close_and_drain(&self) -> Vec<AcceptedTurnInput> {
259 let mut state = self.state.lock().expect("turn steering lock poisoned");
260 state.open = false;
261 state.inputs.drain(..).collect()
262 }
263}
264
265impl Default for TurnSteering {
266 fn default() -> Self {
267 Self::new()
268 }
269}
270
271#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct CapabilityDelta {
278 pub capability_id: String,
280 pub changed: bool,
282 pub active: bool,
284 pub surfaces_dirty: bool,
286}
287
288fn finish_turn(
296 turn_id: everruns_provider::typed_id::TurnId,
297 stop_reason: TurnStopReason,
298 error: Option<String>,
299 response: String,
300 iterations: usize,
301 tool_calls_count: usize,
302) -> TurnResult {
303 if error.is_some() {
304 return TurnResult {
305 response: String::new(),
306 iterations,
307 tool_calls_count: 0,
308 success: false,
309 error,
310 stop_reason,
311 turn_id,
312 };
313 }
314
315 TurnResult {
316 response,
317 iterations,
318 tool_calls_count,
319 success: true,
320 error: None,
321 stop_reason,
322 turn_id,
323 }
324}
325
326pub struct InProcessRuntimeBuilder {
336 host_composition: HostComposition,
337 default_provider: Option<(everruns_provider::runtime_provider::Provider, String)>,
341 replacement_providers: Vec<everruns_provider::runtime_provider::Provider>,
344 providers: Vec<everruns_provider::runtime_provider::Provider>,
345 model_spec: Option<everruns_provider::model_spec::ModelSpec>,
346 backends: Option<HostBackends>,
347 workspace_policy: Option<everruns_core::WorkspacePolicy>,
348 session_file_system_factory_context: SessionFileSystemFactoryContext,
349 harnesses: Vec<crate::builders::SeededHarness>,
350 agents: Vec<AgentDefinition>,
351 sessions: Vec<ExecutionSession>,
352 default_session_id: Option<SessionId>,
353 seeded_files: Vec<(SessionId, InitialFile)>,
354 #[cfg(feature = "mcp")]
355 mcp_auth_provider: Option<Arc<dyn everruns_mcp::McpAuthProvider>>,
356 provider_retry_config: Option<everruns_provider::llm_retry::LlmRetryConfig>,
357 provider_stall_timeout: Option<std::time::Duration>,
358 plugin_capability_configs: Vec<everruns_capability::CapabilityRef>,
364 plugin_warnings: Vec<String>,
366}
367
368impl Default for InProcessRuntimeBuilder {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373
374impl InProcessRuntimeBuilder {
375 pub fn new() -> Self {
385 Self {
386 host_composition: HostComposition::builder()
387 .capability_registry(crate::runtime_capability_registry())
388 .driver_registry(DriverRegistry::new())
389 .egress_service(crate::runtime_egress_service())
390 .session_file_system_factory(Arc::new(InMemorySessionFileSystemFactory))
391 .build(),
392 default_provider: None,
393 replacement_providers: Vec::new(),
394 providers: Vec::new(),
395 model_spec: None,
396 backends: None,
397 workspace_policy: None,
398 session_file_system_factory_context: SessionFileSystemFactoryContext::new(),
399 harnesses: Vec::new(),
400 agents: Vec::new(),
401 sessions: Vec::new(),
402 default_session_id: None,
403 seeded_files: Vec::new(),
404 #[cfg(feature = "mcp")]
405 mcp_auth_provider: None,
406 provider_retry_config: None,
407 provider_stall_timeout: None,
408 plugin_capability_configs: Vec::new(),
409 plugin_warnings: Vec::new(),
410 }
411 }
412
413 #[cfg(feature = "mcp")]
417 pub fn mcp_auth_provider(mut self, provider: Arc<dyn everruns_mcp::McpAuthProvider>) -> Self {
418 self.mcp_auth_provider = Some(provider);
419 self
420 }
421
422 pub fn host_composition(mut self, host_composition: HostComposition) -> Self {
424 self.host_composition = host_composition;
425 self
426 }
427
428 pub fn capability<C: Capability + 'static>(self, capability: C) -> Self {
430 self.host_composition
431 .register_capability_overriding(Arc::new(capability));
432 self
433 }
434
435 pub fn driver_registry(mut self, driver_registry: DriverRegistry) -> Self {
437 *self.host_composition.driver_registry_mut() = driver_registry;
438 self
439 }
440
441 pub fn provider(mut self, provider: everruns_provider::runtime_provider::Provider) -> Self {
443 self.providers.push(provider);
444 self
445 }
446
447 pub fn replace_provider(
454 mut self,
455 provider: everruns_provider::runtime_provider::Provider,
456 ) -> Self {
457 self.replacement_providers.push(provider);
458 self
459 }
460
461 pub fn provider_with_default_model(
469 mut self,
470 provider: everruns_provider::runtime_provider::Provider,
471 model_id: impl Into<String>,
472 ) -> Self {
473 self.default_provider = Some((provider, model_id.into()));
474 self
475 }
476
477 pub fn default_model(mut self, model: everruns_provider::model_spec::ModelSpec) -> Self {
479 self.model_spec = Some(model);
480 self
481 }
482
483 pub fn model_spec(mut self, model: everruns_provider::model_spec::ModelSpec) -> Self {
485 self.model_spec = Some(model);
486 self
487 }
488
489 pub fn backends(mut self, backends: HostBackends) -> Self {
491 self.backends = Some(backends);
492 self
493 }
494
495 pub fn workspace_policy(mut self, policy: everruns_core::WorkspacePolicy) -> Self {
501 self.workspace_policy = Some(policy);
502 self
503 }
504
505 pub fn with_session_task_registry(
509 mut self,
510 registry: Arc<dyn everruns_core::session_task::SessionTaskRegistry>,
511 ) -> Self {
512 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
513 self.backends = Some(backends.with_session_task_registry(registry));
514 self
515 }
516
517 pub fn with_schedule_store_factory(
519 mut self,
520 factory: crate::backends::ScheduleStoreFactory,
521 ) -> Self {
522 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
523 self.backends = Some(backends.with_schedule_store_factory(factory));
524 self
525 }
526
527 pub fn with_tool_context_extensions_factory(
529 mut self,
530 factory: crate::ToolContextExtensionsFactory,
531 ) -> Self {
532 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
533 self.backends = Some(backends.with_tool_context_extensions_factory(factory));
534 self
535 }
536
537 pub fn with_subagent_delegate_factory(
539 mut self,
540 factory: crate::SubagentDelegateFactory,
541 ) -> Self {
542 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
543 self.backends = Some(backends.with_subagent_delegate_factory(factory));
544 self
545 }
546
547 pub fn with_tool_augmentor(mut self, augmentor: Arc<dyn crate::HostToolAugmentor>) -> Self {
549 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
550 self.backends = Some(backends.with_tool_augmentor(augmentor));
551 self
552 }
553
554 pub fn session_file_system_factory_context(
556 mut self,
557 context: SessionFileSystemFactoryContext,
558 ) -> Self {
559 self.session_file_system_factory_context = context;
560 self
561 }
562
563 pub fn provider_retry_config(
565 mut self,
566 config: everruns_provider::llm_retry::LlmRetryConfig,
567 ) -> Self {
568 self.provider_retry_config = Some(config);
569 self
570 }
571
572 pub fn provider_stall_timeout(mut self, timeout: std::time::Duration) -> Self {
574 self.provider_stall_timeout = Some(timeout);
575 self
576 }
577
578 pub fn harness(mut self, harness: crate::builders::SeededHarness) -> Self {
581 self.harnesses.push(harness);
582 self
583 }
584
585 pub fn agent(mut self, agent: AgentDefinition) -> Self {
587 self.agents.push(agent);
588 self
589 }
590
591 pub fn session(mut self, session: ExecutionSession) -> Self {
593 self.sessions.push(session);
594 self
595 }
596
597 pub fn single_session<F>(mut self, configure: F) -> Self
602 where
603 F: FnOnce(SingleSessionBuilder) -> SingleSessionBuilder,
604 {
605 let (harness, agent, session, session_id) =
606 configure(SingleSessionBuilder::default()).build();
607 self.harnesses.push(harness);
608 self.agents.push(agent);
609 self.sessions.push(session);
610 self.default_session_id = Some(session_id);
611 self
612 }
613
614 pub fn seed_text_file(
618 mut self,
619 session_id: SessionId,
620 path: impl Into<String>,
621 content: impl Into<String>,
622 ) -> Self {
623 self.seeded_files.push((
624 session_id,
625 InitialFile {
626 path: path.into(),
627 content: content.into(),
628 encoding: "text".to_string(),
629 is_readonly: false,
630 },
631 ));
632 self
633 }
634
635 pub fn with_plugin_dir(mut self, path: &Path) -> Result<Self> {
656 let file_set = PluginFileSet::from_dir(path)
657 .map_err(|e| AgentLoopError::config(format!("plugin directory load failed: {e}")))?;
658 let compiled = compile_plugin(&file_set)
659 .map_err(|e| AgentLoopError::config(format!("plugin compilation failed: {e}")))?;
660
661 for warning in &compiled.warnings {
662 tracing::warn!(plugin = %compiled.definition.name, warning = %warning, "plugin compile warning");
663 }
664 self.plugin_warnings.extend(compiled.warnings);
665
666 let cap_id = plugin_capability_id(&compiled.definition.name);
667 let hydrated_config = serde_json::to_value(&compiled.definition)
668 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
669 self.plugin_capability_configs
670 .push(everruns_capability::CapabilityRef::with_config(
671 cap_id,
672 hydrated_config,
673 ));
674
675 Ok(self)
676 }
677
678 pub fn plugin_capability(&self, name: &str) -> Option<everruns_capability::CapabilityRef> {
684 let cap_id = plugin_capability_id(name);
685 self.plugin_capability_configs
686 .iter()
687 .find(|c| c.capability_id() == cap_id)
688 .cloned()
689 }
690
691 pub async fn build(mut self) -> Result<InProcessRuntime> {
696 let backends = match self.backends.take() {
697 Some(backends) => backends,
698 None => HostBackends::in_memory(),
699 };
700 let mut file_store = resolve_session_file_system(
701 &self.host_composition,
702 self.session_file_system_factory_context.clone(),
703 )
704 .await?;
705 if let Some(policy) = self.workspace_policy.take() {
706 file_store = Arc::new(crate::PolicyFileStore::new(file_store, policy));
707 }
708
709 if let Some((provider, model_id)) = self.default_provider.take() {
710 let provider_key = provider.id().clone();
711 self.host_composition
713 .driver_registry_mut()
714 .replace_provider(provider);
715
716 if self.model_spec.is_none() {
717 self.model_spec = Some(everruns_provider::model_spec::ModelSpec::on(
718 provider_key,
719 model_id,
720 ));
721 }
722 }
723
724 for provider in self.replacement_providers {
725 self.host_composition
726 .driver_registry_mut()
727 .replace_provider(provider);
728 }
729
730 for provider in self.providers {
731 self.host_composition
732 .driver_registry_mut()
733 .register_provider(provider)?;
734 }
735
736 let model_spec = self.model_spec.ok_or_else(|| {
737 AgentLoopError::config(
738 "in-process runtime requires a default model; call \
739 InProcessRuntimeBuilder::model_spec(...) or \
740 InProcessRuntimeBuilder::provider_with_default_model(...) \
741 (e.g. the everruns-llmsim `.llm_sim_as_default(...)` extension)",
742 )
743 })?;
744
745 backends
746 .provider_store
747 .set_default_model_spec(model_spec)
748 .await?;
749
750 for harness in &mut self.harnesses {
754 hydrate_plugin_refs(
755 &mut harness.definition.capabilities,
756 &self.plugin_capability_configs,
757 );
758 }
759 for agent in &mut self.agents {
760 hydrate_plugin_refs(&mut agent.capabilities, &self.plugin_capability_configs);
761 }
762 for session in &mut self.sessions {
763 hydrate_plugin_refs(&mut session.capabilities, &self.plugin_capability_configs);
764 }
765
766 for harness in &self.harnesses {
767 backends
768 .harness_store
769 .add_harness(harness.id, harness.definition.clone())
770 .await?;
771 }
772 for agent in &self.agents {
773 backends.agent_store.add_agent(agent.clone()).await?;
774 }
775 for session in &self.sessions {
776 backends.session_store.add_session(session.clone()).await?;
777 }
778
779 for session in &self.sessions {
780 seed_runtime_initial_files(
781 backends.harness_store.as_ref(),
782 backends.agent_store.as_ref(),
783 file_store.as_ref(),
784 session,
785 )
786 .await?;
787 }
788
789 for (session_id, file) in &self.seeded_files {
790 file_store.seed_initial_file(*session_id, file).await?;
791 }
792
793 let event_log = backends.event_log.clone();
794 let event_history = Arc::new(EventHistory::new(event_log.clone()));
795 let event_emitter = Arc::new(HostEventEmitter::new(
796 event_log.clone(),
797 backends.event_sink.clone(),
798 ));
799
800 let (session_task_registry, session_wake_queue) = match backends.session_task_registry {
806 Some(inner) => {
807 let wake_queue = Arc::new(everruns_core::SessionWakeQueue::new());
808 let observing = everruns_core::ObservingTaskRegistry::new(inner)
809 .with_observer(wake_queue.clone());
810 let wrapped: Arc<dyn everruns_core::session_task::SessionTaskRegistry> =
811 Arc::new(observing);
812 (Some(wrapped), Some(wake_queue))
813 }
814 None => (None, None),
815 };
816
817 let seeded_session_ids = self.sessions.iter().map(|session| session.id).collect();
818 let runtime = InProcessRuntime {
819 host_composition: Arc::new(self.host_composition),
820 harness_store: backends.harness_store,
821 agent_store: backends.agent_store,
822 session_store: backends.session_store,
823 default_session_id: self.default_session_id,
824 seeded_session_ids,
825 event_log,
826 event_history,
827 native_async_store: backends.native_async_store,
828 compaction_checkpoint_store: backends.compaction_checkpoint_store,
829 provider_store: backends.provider_store,
830 event_emitter,
831 file_store,
832 storage_store: backends.storage_store,
833 connection_resolver: backends.connection_resolver,
834 session_task_registry,
835 session_wake_queue,
836 schedule_store_factory: backends.schedule_store_factory,
837 tool_context_extensions_factory: backends.tool_context_extensions_factory,
838 subagent_delegate_factory: backends.subagent_delegate_factory,
839 tool_augmentor: backends.tool_augmentor,
840 #[cfg(feature = "mcp")]
841 mcp_auth_provider: self
842 .mcp_auth_provider
843 .unwrap_or_else(|| Arc::new(everruns_mcp::NoAuthProvider)),
844 provider_retry_config: self.provider_retry_config,
845 provider_stall_timeout: self.provider_stall_timeout,
846 #[cfg(feature = "mcp")]
847 mcp_discovery_cache: Arc::new(crate::mcp_cache::McpDiscoveryCache::new()),
848 plugin_warnings: self.plugin_warnings,
849 };
850 for session in &self.sessions {
851 runtime.ensure_session_started(session).await?;
852 }
853 Ok(runtime)
854 }
855}
856
857async fn resolve_session_file_system(
858 host_composition: &HostComposition,
859 file_system_factory_context: SessionFileSystemFactoryContext,
860) -> Result<Arc<dyn SessionFileSystem>> {
861 let file_system_factory = host_composition.session_file_system_factory();
862 if file_system_factory.is_disabled() {
863 Ok(Arc::new(InMemorySessionFileStore::new()))
864 } else {
865 Ok(file_system_factory
866 .create_session_file_system(file_system_factory_context)
867 .await?)
868 }
869}
870
871#[derive(Clone)]
872pub struct InProcessRuntime {
878 host_composition: Arc<HostComposition>,
879 harness_store: Arc<dyn RuntimeHarnessStore>,
880 agent_store: Arc<dyn RuntimeAgentStore>,
881 session_store: Arc<dyn RuntimeSessionStore>,
882 default_session_id: Option<SessionId>,
883 seeded_session_ids: Vec<SessionId>,
884 event_log: Arc<dyn EventLog>,
885 event_history: Arc<EventHistory>,
886 native_async_store: Option<Arc<dyn everruns_core::native_async_store::NativeAsyncStore>>,
887 compaction_checkpoint_store: Arc<dyn everruns_core::CompactionCheckpointStore>,
888 provider_store: Arc<dyn RuntimeProviderStore>,
889 event_emitter: Arc<HostEventEmitter>,
890 file_store: Arc<dyn SessionFileSystem>,
891 storage_store: Arc<dyn SessionStorageStore>,
892 connection_resolver: Option<Arc<dyn UserConnectionResolver>>,
893 session_task_registry: Option<Arc<dyn everruns_core::session_task::SessionTaskRegistry>>,
894 session_wake_queue: Option<Arc<everruns_core::SessionWakeQueue>>,
898 schedule_store_factory: Option<crate::backends::ScheduleStoreFactory>,
899 tool_context_extensions_factory: Option<crate::ToolContextExtensionsFactory>,
900 subagent_delegate_factory: Option<crate::SubagentDelegateFactory>,
901 tool_augmentor: Option<Arc<dyn crate::HostToolAugmentor>>,
902 #[cfg(feature = "mcp")]
903 mcp_auth_provider: Arc<dyn everruns_mcp::McpAuthProvider>,
904 provider_retry_config: Option<everruns_provider::llm_retry::LlmRetryConfig>,
905 provider_stall_timeout: Option<std::time::Duration>,
906 #[cfg(feature = "mcp")]
907 mcp_discovery_cache: Arc<crate::mcp_cache::McpDiscoveryCache>,
908 plugin_warnings: Vec<String>,
911}
912
913impl InProcessRuntime {
914 async fn ensure_session_started(&self, session: &ExecutionSession) -> Result<()> {
915 if self
916 .event_history
917 .contains_event_type(session.id, everruns_core::events::SESSION_STARTED)
918 .await
919 .map_err(|error| AgentLoopError::store(error.to_string()))?
920 {
921 return Ok(());
922 }
923 self.event_emitter
924 .emit(EventRequest::new(
925 session.id,
926 EventContext::empty(),
927 SessionStartedData {
928 harness_id: session.harness_id,
929 agent_id: session.agent_id,
930 model_id: session.model_id,
931 },
932 ))
933 .await?;
934 Ok(())
935 }
936
937 pub fn host_event_emitter(&self) -> Arc<HostEventEmitter> {
942 self.event_emitter.clone()
943 }
944
945 pub fn event_log(&self) -> Arc<dyn EventLog> {
947 self.event_log.clone()
948 }
949
950 #[cfg(feature = "mcp")]
953 fn mcp_client(&self) -> Arc<everruns_mcp::McpClient> {
954 Arc::new(everruns_mcp::McpClient::with_url_elicitation(
958 self.host_composition.egress_service(),
959 self.mcp_auth_provider.clone(),
960 Arc::new(everruns_mcp::RelayUrlElicitations),
961 ))
962 }
963
964 #[cfg(feature = "mcp")]
966 async fn session_mcp_servers(
967 &self,
968 session: &ExecutionSession,
969 agent: Option<&AgentDefinition>,
970 ) -> everruns_core::ScopedMcpServers {
971 let harness = self
972 .harness_store
973 .get_harness(session.harness_id)
974 .await
975 .ok()
976 .flatten()
977 .unwrap_or_default();
978 let resolved = resolve_runtime_capabilities(
979 &harness,
980 agent,
981 session,
982 &self.host_composition.capability_registry(),
983 );
984 let contributed = collect_capability_mcp_servers(
985 &resolved.resolved_capability_configs,
986 &self.host_composition.capability_registry(),
987 );
988 let explicit = crate::mcp::merge_session_scoped_servers(&harness, agent, session);
989 everruns_core::merge_scoped_mcp_servers(&contributed, &explicit)
990 }
991 pub fn builder() -> InProcessRuntimeBuilder {
993 InProcessRuntimeBuilder::new()
994 }
995
996 pub fn default_session_id(&self) -> Option<SessionId> {
999 self.default_session_id
1000 }
1001
1002 pub fn plugin_warnings(&self) -> &[String] {
1007 &self.plugin_warnings
1008 }
1009
1010 pub fn register_capability(&self, capability: Arc<dyn Capability>) -> Result<()> {
1027 self.host_composition
1028 .register_capability(capability)
1029 .map_err(|error| AgentLoopError::config(format!("cannot register capability: {error}")))
1030 }
1031
1032 pub fn is_capability_registered(&self, capability_id: &str) -> bool {
1034 self.host_composition
1035 .is_capability_registered(capability_id)
1036 }
1037
1038 pub async fn activate_capability(
1044 &self,
1045 session_id: SessionId,
1046 capability: impl Into<everruns_capability::CapabilityRef>,
1047 ) -> Result<CapabilityDelta> {
1048 let mut capability = capability.into();
1049 let registry = self.host_composition.capability_registry();
1050 let registered = registry.get(capability.capability_id()).ok_or_else(|| {
1051 AgentLoopError::config(format!(
1052 "unknown capability: {}",
1053 capability.capability_id()
1054 ))
1055 })?;
1056 if !registered.status().is_active() {
1062 let reason = if registered.status() == CapabilityStatus::Retired {
1063 "capability has been removed"
1064 } else {
1065 "capability is not available"
1066 };
1067 return Err(AgentLoopError::config(format!(
1068 "{reason}: {}",
1069 capability.capability_id()
1070 )));
1071 }
1072 registered
1073 .validate_config(capability.config_value())
1074 .map_err(|error| {
1075 AgentLoopError::config(format!("invalid capability config: {error}"))
1076 })?;
1077
1078 let canonical_id = registered.id().to_string();
1079 capability.set_id(canonical_id.clone());
1080 let context = self.load_context(session_id).await?;
1081 if context
1082 .resolved_capability_configs
1083 .iter()
1084 .any(|config| config.capability_id() == canonical_id)
1085 {
1086 return Ok(CapabilityDelta {
1087 capability_id: canonical_id,
1088 changed: false,
1089 active: true,
1090 surfaces_dirty: false,
1091 });
1092 }
1093
1094 let mut candidate = context.snapshot.capabilities;
1095 candidate.push(capability.clone());
1096 resolve_capability_configs(&candidate, ®istry)
1097 .map_err(|error| AgentLoopError::config(error.to_string()))?;
1098
1099 self.session_store
1100 .upsert_session_capability(session_id, capability)
1101 .await?;
1102 #[cfg(feature = "mcp")]
1103 self.mcp_discovery_cache
1104 .invalidate_session(session_id.uuid());
1105 Ok(CapabilityDelta {
1106 capability_id: canonical_id,
1107 changed: true,
1108 active: true,
1109 surfaces_dirty: true,
1110 })
1111 }
1112
1113 pub async fn deactivate_capability(
1118 &self,
1119 session_id: SessionId,
1120 capability_id: &str,
1121 ) -> Result<CapabilityDelta> {
1122 let registry = self.host_composition.capability_registry();
1123 let registered = registry.get(capability_id).ok_or_else(|| {
1124 AgentLoopError::config(format!("unknown capability: {capability_id}"))
1125 })?;
1126 let canonical_id = registered.id().to_string();
1127 let context = self.load_context(session_id).await?;
1128 if !context
1129 .resolved_capability_configs
1130 .iter()
1131 .any(|config| config.capability_id() == canonical_id)
1132 {
1133 return Ok(CapabilityDelta {
1134 capability_id: canonical_id,
1135 changed: false,
1136 active: false,
1137 surfaces_dirty: false,
1138 });
1139 }
1140
1141 let session = self
1142 .session_store
1143 .get_session(session_id)
1144 .await?
1145 .ok_or_else(|| AgentLoopError::session_not_found(session_id))?;
1146 let session_capability_id = session
1147 .capabilities
1148 .iter()
1149 .find(|config| {
1150 registry
1151 .get(config.capability_id())
1152 .is_some_and(|capability| capability.id() == canonical_id)
1153 })
1154 .map(|config| config.capability_id().to_string())
1155 .ok_or_else(|| {
1156 AgentLoopError::config(format!(
1157 "capability {canonical_id} is inherited and cannot be deactivated at the session layer"
1158 ))
1159 })?;
1160
1161 self.session_store
1162 .remove_session_capability(session_id, &session_capability_id)
1163 .await?;
1164 #[cfg(feature = "mcp")]
1165 self.mcp_discovery_cache
1166 .invalidate_session(session_id.uuid());
1167 Ok(CapabilityDelta {
1168 capability_id: canonical_id,
1169 changed: true,
1170 active: false,
1171 surfaces_dirty: true,
1172 })
1173 }
1174
1175 pub async fn run_turn(
1182 &self,
1183 session_id: SessionId,
1184 input: impl Into<InputMessage>,
1185 ) -> Result<TurnResult> {
1186 self.run_steerable_turn(
1187 session_id,
1188 AcceptedTurnInput::new(input),
1189 TurnId::new(),
1190 TurnSteering::new(),
1191 )
1192 .await
1193 }
1194
1195 pub async fn run_steerable_turn(
1200 &self,
1201 session_id: SessionId,
1202 input: AcceptedTurnInput,
1203 turn_id: TurnId,
1204 steering: TurnSteering,
1205 ) -> Result<TurnResult> {
1206 let snapshot = self.resolved_execution_snapshot(session_id).await?;
1210
1211 let input_message = input.into_message();
1214 self.event_emitter
1215 .emit(EventRequest::new(
1216 session_id,
1217 EventContext::empty(),
1218 InputMessageData::new(input_message.clone()),
1219 ))
1220 .await?;
1221
1222 let org_id = in_process_internal_org_id(&snapshot.organization_id);
1223
1224 let state = TurnState {
1230 org_id,
1231 session_id,
1232 harness_id: snapshot.harness_id,
1233 agent_id: snapshot.agent_id,
1234 input_message_id: input_message.id,
1235 turn_id: None,
1236 previous_response_id: None,
1237 iteration: 1,
1238 request_id: None,
1239 started_at: None,
1240 cumulative_usage: None,
1241 tool_call_count: 0,
1242 llm_call_count: 0,
1243 time_to_first_token_ms: None,
1244 final_message_id: None,
1245 final_answer_preview: None,
1246 };
1247
1248 let base_context = |exec: bool| {
1249 let context = ExecutionContext::new(session_id, turn_id, input_message.id)
1250 .with_workspace_id(snapshot.workspace_id);
1251 if exec { context.next_exec() } else { context }
1252 };
1253
1254 execute_input_activity(
1258 self,
1259 org_id,
1260 InputAtomInput {
1261 context: base_context(false),
1262 },
1263 )
1264 .await?;
1265 let mut execution = InProcessExecution::new(state);
1266 let transition = execution.advance(
1267 ActivityOutcome::ProcessInput {
1268 turn_id: Some(turn_id),
1269 },
1270 0,
1271 Utc::now(),
1272 HostFacts::default(),
1273 );
1274 crate::turn_strategy::perform_effects(self, org_id, session_id, transition.effects).await?;
1275 let mut plan = transition.plan;
1276
1277 let mut iterations: usize = 0;
1280 let mut tool_calls_count: usize = 0;
1281 let mut last_response = String::new();
1282 let mut pending_prompt_message_ids = Vec::new();
1283
1284 loop {
1285 match plan {
1286 TurnPlan::ScheduleReason(_) => {
1287 let state = execution.state().clone();
1288 let mut prompt_message_ids = std::mem::take(&mut pending_prompt_message_ids);
1289 prompt_message_ids.extend(
1290 self.inject_steering_inputs(session_id, steering.drain())
1291 .await?,
1292 );
1293 prompt_message_ids.extend(self.drain_and_inject_wakes(session_id).await?);
1299 if state.iteration == 1 {
1300 prompt_message_ids.insert(0, input_message.id);
1301 }
1302 let reason_result = execute_reason_activity_with_prompt_messages(
1303 self,
1304 org_id,
1305 ReasonInput {
1306 context: base_context(true),
1307 harness_id: snapshot.harness_id,
1308 agent_id: snapshot.agent_id,
1309 org_id,
1310 mcp_tool_definitions: vec![],
1311 previous_response_id: state.previous_response_id.clone(),
1312 iteration: state.iteration,
1313 },
1314 prompt_message_ids,
1315 )
1316 .await?;
1317
1318 iterations += 1;
1319 if !reason_result.text.is_empty() {
1320 last_response = reason_result.text.clone();
1321 }
1322
1323 let pending_wake_count = usize::from(
1328 self.session_wake_queue
1329 .as_ref()
1330 .is_some_and(|q| q.has_pending(session_id)),
1331 );
1332 let can_continue = reason_result.success
1333 && state.iteration < reason_result.max_iterations as u32;
1334 let pending_steering = if !reason_schedules_act(&state, &reason_result)
1335 && can_continue
1336 && pending_wake_count == 0
1337 {
1338 steering.drain_or_close()
1339 } else {
1340 vec![]
1341 };
1342 let pending_steering_count = pending_steering.len();
1343 if !pending_steering.is_empty() {
1344 pending_prompt_message_ids.extend(
1345 self.inject_steering_inputs(session_id, pending_steering)
1346 .await?,
1347 );
1348 }
1349 if !can_continue {
1350 steering.close();
1351 }
1352 let pending_user_message_count = pending_wake_count + pending_steering_count;
1353
1354 let act_scheduling =
1355 crate::turn_strategy::resolve_act_scheduling(self, &state, &reason_result)
1356 .await?;
1357 let transition = execution.advance(
1358 ActivityOutcome::Reason(Box::new(reason_result)),
1359 pending_user_message_count,
1360 Utc::now(),
1361 HostFacts {
1362 act_scheduling,
1363 ..HostFacts::default()
1364 },
1365 );
1366 crate::turn_strategy::perform_effects(
1367 self,
1368 org_id,
1369 session_id,
1370 transition.effects,
1371 )
1372 .await?;
1373 plan = transition.plan;
1374 }
1375 TurnPlan::ScheduleAct(act_plan) => {
1376 tool_calls_count += act_plan.input.tool_calls.len();
1377 let act_result = execute_act_activity(self, act_plan.input).await?;
1378 let outcome = ActOutcome {
1379 blocked: act_result.blocked,
1380 waiting_for_tool_results: act_result.waiting_for_tool_results,
1381 waiting_for_url_elicitation: act_result.waiting_for_url_elicitation,
1382 };
1383 let hints = crate::turn_strategy::resolve_pause_hints(
1384 self, org_id, session_id, outcome,
1385 )
1386 .await;
1387 let transition = execution.advance(
1388 ActivityOutcome::Act(outcome),
1389 0,
1390 Utc::now(),
1391 HostFacts {
1392 setup_connection_hint_enabled: hints.setup_connection,
1393 url_elicitation_hint_enabled: hints.url_elicitation,
1394 ..HostFacts::default()
1395 },
1396 );
1397 crate::turn_strategy::perform_effects(
1398 self,
1399 org_id,
1400 session_id,
1401 transition.effects,
1402 )
1403 .await?;
1404 plan = transition.plan;
1405 }
1406 TurnPlan::Complete { stop_reason, error } => {
1407 steering.close();
1408 return Ok(finish_turn(
1409 turn_id,
1410 stop_reason,
1411 error,
1412 last_response,
1413 iterations,
1414 tool_calls_count,
1415 ));
1416 }
1417 TurnPlan::WaitForToolResults { .. } => {
1421 steering.close();
1422 self.append_accepted_inputs(session_id, turn_id, steering.drain())
1423 .await?;
1424 return Ok(finish_turn(
1425 turn_id,
1426 TurnStopReason::EndTurn,
1427 None,
1428 last_response,
1429 iterations,
1430 tool_calls_count,
1431 ));
1432 }
1433 }
1434 }
1435 }
1436
1437 pub async fn run_text_turn(
1438 &self,
1439 session_id: SessionId,
1440 text: impl Into<String>,
1441 ) -> Result<TurnResult> {
1442 self.run_turn(session_id, InputMessage::user(text)).await
1443 }
1444
1445 async fn inject_steering_inputs(
1446 &self,
1447 session_id: SessionId,
1448 inputs: Vec<AcceptedTurnInput>,
1449 ) -> Result<Vec<MessageId>> {
1450 let mut message_ids = Vec::with_capacity(inputs.len());
1451 for input in inputs {
1452 let message = input.into_message();
1453 message_ids.push(message.id);
1454 self.event_emitter
1455 .emit(EventRequest::new(
1456 session_id,
1457 EventContext::empty(),
1458 InputMessageData::new(message),
1459 ))
1460 .await?;
1461 }
1462 Ok(message_ids)
1463 }
1464
1465 pub async fn append_accepted_inputs(
1471 &self,
1472 session_id: SessionId,
1473 turn_id: TurnId,
1474 inputs: Vec<AcceptedTurnInput>,
1475 ) -> Result<()> {
1476 if inputs.is_empty() {
1477 return Ok(());
1478 }
1479
1480 let snapshot = self.resolved_execution_snapshot(session_id).await?;
1481 let org_id = in_process_internal_org_id(&snapshot.organization_id);
1482 for input in inputs {
1483 let mut message = input.into_message();
1484 let hook_input = ReasonInput {
1485 context: ExecutionContext::new(session_id, turn_id, message.id)
1486 .with_workspace_id(snapshot.workspace_id),
1487 harness_id: snapshot.harness_id,
1488 agent_id: snapshot.agent_id,
1489 org_id,
1490 mcp_tool_definitions: vec![],
1491 previous_response_id: None,
1492 iteration: 1,
1493 };
1494 if let Some(result) = run_user_prompt_submit_for_message(
1495 self,
1496 org_id,
1497 &hook_input,
1498 message.content_to_llm_string(),
1499 )
1500 .await?
1501 {
1502 let enforced = match result.decision {
1503 UserPromptDecision::Continue { message }
1504 if message != result.original_message =>
1505 {
1506 Some((message, false))
1507 }
1508 UserPromptDecision::Block {
1509 reason,
1510 user_message,
1511 } => Some((user_message.unwrap_or(reason), true)),
1512 UserPromptDecision::Continue { .. } => None,
1513 };
1514 if let Some((safe_text, blocked)) = enforced {
1515 if blocked {
1518 message.content.clear();
1519 } else {
1520 message
1521 .content
1522 .retain(|part| !matches!(part, ContentPart::Text(_)));
1523 }
1524 message.content.insert(0, ContentPart::text(safe_text));
1525 }
1526 }
1527 self.event_emitter
1528 .emit(EventRequest::new(
1529 session_id,
1530 EventContext::empty(),
1531 InputMessageData::new(message),
1532 ))
1533 .await?;
1534 }
1535 Ok(())
1536 }
1537
1538 async fn drain_and_inject_wakes(&self, session_id: SessionId) -> Result<Vec<MessageId>> {
1550 let Some(queue) = &self.session_wake_queue else {
1551 return Ok(vec![]);
1552 };
1553 let wakes = queue.drain(session_id);
1554 if wakes.is_empty() {
1555 return Ok(vec![]);
1556 }
1557 let mut message_ids = Vec::with_capacity(wakes.len());
1558 for wake in wakes {
1559 let message = message_from_input(InputMessage::user(wake.text));
1561 message_ids.push(message.id);
1562 self.event_emitter
1563 .emit(EventRequest::new(
1564 session_id,
1565 EventContext::empty(),
1566 InputMessageData::new(message),
1567 ))
1568 .await?;
1569 }
1570 Ok(message_ids)
1571 }
1572
1573 pub async fn messages(&self, session_id: SessionId) -> Result<Vec<Message>> {
1575 self.event_history.load(session_id).await
1576 }
1577
1578 pub async fn read_file(
1580 &self,
1581 session_id: SessionId,
1582 path: &str,
1583 ) -> Result<Option<SessionFile>> {
1584 self.file_store.read_file(session_id, path).await
1585 }
1586
1587 pub async fn load_context(&self, session_id: SessionId) -> Result<AssembledTurnContext> {
1589 let session = self
1590 .session_store
1591 .get_session(session_id)
1592 .await?
1593 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1594 #[cfg(feature = "mcp")]
1595 let agent = match session.agent_id {
1596 Some(agent_id) => self.agent_store.get_agent(agent_id).await?,
1597 None => None,
1598 };
1599 #[cfg(feature = "mcp")]
1600 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1601 #[cfg(feature = "mcp")]
1602 let mcp_tool_definitions = if scoped_servers.is_empty() {
1603 vec![]
1604 } else {
1605 crate::mcp::discover_tool_definitions(
1606 &self.mcp_discovery_cache,
1607 self.mcp_client(),
1608 session_id.uuid(),
1609 &scoped_servers,
1610 )
1611 .await
1612 };
1613 #[cfg(not(feature = "mcp"))]
1614 let mcp_tool_definitions = vec![];
1615 self.inspect_context_with_ids(
1616 session_id,
1617 session.harness_id,
1618 session.agent_id,
1619 &mcp_tool_definitions,
1620 )
1621 .await
1622 }
1623
1624 pub async fn events(&self) -> Result<Vec<Event>> {
1629 let limit = EventReadLimit::default();
1630 let session_id = self
1631 .default_session_id
1632 .or_else(|| (self.seeded_session_ids.len() == 1).then(|| self.seeded_session_ids[0]))
1633 .ok_or_else(|| {
1634 AgentLoopError::config(
1635 "events() requires exactly one seeded session; use event_log() for bounded per-session replay",
1636 )
1637 })?;
1638 let mut request = EventReadRequest::new(session_id, limit);
1639 let mut events = Vec::new();
1640 loop {
1641 let page = self
1642 .event_log
1643 .read_page(request)
1644 .await
1645 .map_err(|error| AgentLoopError::store(error.to_string()))?;
1646 if events.len().saturating_add(page.events.len())
1647 > crate::events::MAX_EVENT_HISTORY_REPLAY
1648 {
1649 return Err(AgentLoopError::store("event replay bound exceeded"));
1650 }
1651 events.extend(page.events);
1652 let Some(cursor) = page.next_cursor else {
1653 return Ok(events);
1654 };
1655 request = EventReadRequest::from_cursor(cursor, limit);
1656 }
1657 }
1658
1659 pub async fn execute_command(
1669 &self,
1670 session_id: SessionId,
1671 request: everruns_core::command::ExecuteCommandRequest,
1672 ) -> Result<everruns_core::command::CommandResult> {
1673 let ctx = self.load_context(session_id).await?;
1674 let registry = self.host_composition.capability_registry();
1675 let host = crate::StoreCommandHost::new(
1679 session_id,
1680 self.harness_store.clone(),
1681 self.agent_store.clone(),
1682 self.session_store.clone(),
1683 self.event_history.clone(),
1684 self.provider_store.clone(),
1685 (*registry).clone(),
1686 self.host_composition.driver_registry().clone(),
1687 )
1688 .with_file_store(self.file_store.clone())
1689 .with_assembled_context(ctx.clone());
1690 let exec_ctx =
1691 everruns_core::command::CommandExecutionContext::new(session_id, Arc::new(host));
1692 for config in &ctx.resolved_capability_configs {
1693 let Some(capability) = registry.get(config.capability_id()) else {
1694 continue;
1695 };
1696 if capability.commands().iter().any(|c| c.name == request.name) {
1697 return capability.execute_command(&request, &exec_ctx).await;
1698 }
1699 }
1700 Err(AgentLoopError::config(format!(
1701 "no capability declares command /{}",
1702 request.name
1703 )))
1704 }
1705
1706 pub async fn list_commands(
1716 &self,
1717 session_id: SessionId,
1718 ) -> Result<Vec<everruns_core::command::CommandDescriptor>> {
1719 let ctx = self.load_context(session_id).await?;
1720 let registry = self.host_composition.capability_registry();
1721 let mut seen = std::collections::HashSet::new();
1722 let mut commands = Vec::new();
1723 for config in &ctx.resolved_capability_configs {
1724 let Some(capability) = registry.get(config.capability_id()) else {
1725 continue;
1726 };
1727 for command in capability.commands() {
1728 if seen.insert(command.name.clone()) {
1729 commands.push(command);
1730 }
1731 }
1732 }
1733 Ok(commands)
1734 }
1735
1736 async fn attached_session(&self, session_id: SessionId) -> Result<ExecutionSession> {
1740 let mut session = self
1741 .session_store
1742 .get_session(session_id)
1743 .await?
1744 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1745 everruns_core::ard_attachment::apply_session_attachments(
1746 self.storage_store.as_ref(),
1747 &mut session,
1748 )
1749 .await;
1750 Ok(session)
1751 }
1752
1753 async fn resolved_execution_snapshot(
1757 &self,
1758 session_id: SessionId,
1759 ) -> Result<ResolvedExecutionSnapshot> {
1760 let session = self.attached_session(session_id).await?;
1761 crate::load_execution_snapshot_for_session(
1762 self.harness_store.as_ref(),
1763 self.agent_store.as_ref(),
1764 &session,
1765 )
1766 .await
1767 }
1768
1769 async fn inspect_context_with_ids(
1770 &self,
1771 session_id: SessionId,
1772 harness_id: everruns_provider::typed_id::HarnessId,
1773 agent_id: Option<AgentId>,
1774 mcp_tool_definitions: &[everruns_provider::tool_types::ToolDefinition],
1775 ) -> Result<AssembledTurnContext> {
1776 crate::inspect_turn_context(
1777 self.harness_store.as_ref(),
1778 self.agent_store.as_ref(),
1779 self.session_store.as_ref(),
1780 self.event_history.as_ref(),
1781 self.provider_store.as_ref(),
1782 &self.host_composition.capability_registry(),
1783 self.host_composition.driver_registry(),
1784 session_id,
1785 harness_id,
1786 agent_id,
1787 mcp_tool_definitions,
1788 Some(self.file_store.clone()),
1789 )
1790 .await
1791 }
1792}
1793
1794#[async_trait]
1795impl RuntimeHostAdapter for InProcessRuntime {
1796 async fn set_session_status(
1797 &self,
1798 _org_id: i64,
1799 session_id: SessionId,
1800 _status: SessionExecutionState,
1801 ) -> Result<()> {
1802 self.session_store
1807 .get_session(session_id)
1808 .await?
1809 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1810 Ok(())
1811 }
1812
1813 async fn load_resolved_turn(
1814 &self,
1815 _org_id: i64,
1816 session_id: SessionId,
1817 ) -> Result<ResolvedTurnInputs> {
1818 let session = self.attached_session(session_id).await?;
1819 let agent = match session.agent_id {
1820 Some(agent_id) => self.agent_store.get_agent(agent_id).await?,
1821 None => None,
1822 };
1823 let harness = self
1824 .harness_store
1825 .get_harness(session.harness_id)
1826 .await?
1827 .ok_or_else(|| AgentLoopError::harness_not_found(session.harness_id))?;
1828 let snapshot = ResolvedExecutionSnapshot::project(&harness, agent.as_ref(), &session)?;
1831 let messages = self.event_history.load(session_id).await?;
1832
1833 #[cfg(feature = "mcp")]
1836 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1837 #[cfg(feature = "mcp")]
1838 let mcp_tool_definitions = if scoped_servers.is_empty() {
1839 vec![]
1840 } else {
1841 crate::mcp::discover_tool_definitions(
1842 &self.mcp_discovery_cache,
1843 self.mcp_client(),
1844 session_id.uuid(),
1845 &scoped_servers,
1846 )
1847 .await
1848 };
1849 #[cfg(not(feature = "mcp"))]
1850 let mcp_tool_definitions = vec![];
1851
1852 Ok(ResolvedTurnInputs {
1853 snapshot,
1854 messages,
1855 mcp_tool_definitions,
1856 })
1857 }
1858
1859 #[cfg(feature = "mcp")]
1860 async fn mcp_executor(
1861 &self,
1862 _org_id: i64,
1863 session_id: SessionId,
1864 ) -> Option<Arc<dyn everruns_core::McpToolInvoker>> {
1865 let session = self.session_store.get_session(session_id).await.ok()??;
1866 let agent = match session.agent_id {
1867 Some(agent_id) => self.agent_store.get_agent(agent_id).await.ok().flatten(),
1868 None => None,
1869 };
1870 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1871 crate::mcp::build_executor(self.mcp_client(), &scoped_servers)
1872 .map(|executor| executor as Arc<dyn everruns_core::McpToolInvoker>)
1873 }
1874
1875 fn capability_registry(&self) -> CapabilityRegistry {
1876 (*self.host_composition.capability_registry()).clone()
1879 }
1880
1881 fn driver_registry(&self) -> DriverRegistry {
1882 self.host_composition.driver_registry().clone()
1883 }
1884
1885 fn harness_store(&self, _org_id: i64) -> Arc<dyn HarnessStore> {
1886 self.harness_store.clone()
1887 }
1888
1889 fn agent_store(&self, _org_id: i64) -> Arc<dyn AgentStore> {
1890 self.agent_store.clone()
1891 }
1892
1893 fn session_store(&self, _org_id: i64) -> Arc<dyn SessionStore> {
1894 self.session_store.clone()
1895 }
1896
1897 fn session_mutator(&self, _org_id: i64) -> Arc<dyn SessionMutator> {
1898 self.session_store.clone()
1899 }
1900
1901 fn provider_store(&self, _org_id: i64) -> Arc<dyn ProviderStore> {
1902 self.provider_store.clone()
1903 }
1904
1905 fn message_store(&self) -> Arc<dyn MessageRetriever> {
1906 self.event_history.clone()
1907 }
1908
1909 fn native_async_store(
1910 &self,
1911 ) -> Option<Arc<dyn everruns_core::native_async_store::NativeAsyncStore>> {
1912 self.native_async_store.clone()
1913 }
1914
1915 fn compaction_checkpoint_store(
1916 &self,
1917 ) -> Option<Arc<dyn everruns_core::CompactionCheckpointStore>> {
1918 Some(self.compaction_checkpoint_store.clone())
1919 }
1920
1921 fn event_emitter(&self) -> Arc<dyn EventEmitter> {
1922 self.event_emitter.clone()
1923 }
1924
1925 fn file_store(&self) -> Arc<dyn SessionFileSystem> {
1926 self.file_store.clone()
1927 }
1928
1929 fn storage_store(&self) -> Option<Arc<dyn SessionStorageStore>> {
1930 Some(self.storage_store.clone())
1931 }
1932
1933 fn connection_resolver(&self) -> Option<Arc<dyn UserConnectionResolver>> {
1934 self.connection_resolver.clone()
1935 }
1936
1937 fn session_task_registry(
1938 &self,
1939 ) -> Option<Arc<dyn everruns_core::session_task::SessionTaskRegistry>> {
1940 self.session_task_registry.clone()
1941 }
1942
1943 fn schedule_store(
1944 &self,
1945 org_id: i64,
1946 ) -> Option<Arc<dyn everruns_core::session_services::SessionScheduleStore>> {
1947 self.schedule_store_factory
1948 .as_ref()
1949 .map(|factory| factory(org_id))
1950 }
1951
1952 fn tool_context_extensions(
1953 &self,
1954 org_id: i64,
1955 session_id: SessionId,
1956 ) -> everruns_core::tool_context::ToolContextExtensions {
1957 self.tool_context_extensions_factory
1958 .as_ref()
1959 .map(|factory| factory(org_id, session_id))
1960 .unwrap_or_default()
1961 }
1962
1963 fn subagent_delegate(
1964 &self,
1965 org_id: i64,
1966 session_id: SessionId,
1967 ) -> Option<Arc<dyn everruns_core::subagent_delegation::SubagentSessionDelegate>> {
1968 self.subagent_delegate_factory
1969 .as_ref()
1970 .map(|factory| factory(org_id, session_id))
1971 }
1972
1973 fn tool_augmentor(&self) -> Option<Arc<dyn crate::HostToolAugmentor>> {
1974 self.tool_augmentor.clone()
1975 }
1976
1977 fn utility_llm_service(&self) -> Option<Arc<dyn everruns_core::UtilityLlmService>> {
1978 Some(self.host_composition.utility_llm_service())
1979 }
1980
1981 fn egress_service(&self) -> Option<Arc<dyn everruns_core::EgressService>> {
1982 Some(self.host_composition.egress_service())
1983 }
1984
1985 fn provider_retry_config(&self) -> Option<everruns_provider::llm_retry::LlmRetryConfig> {
1986 self.provider_retry_config.clone()
1987 }
1988
1989 fn provider_stall_timeout(&self) -> Option<std::time::Duration> {
1990 self.provider_stall_timeout
1991 }
1992}
1993
1994fn effective_overlay(
1995 harness: &HarnessDefinition,
1996 agent: Option<&AgentDefinition>,
1997 session: &ExecutionSession,
1998) -> AgentConfigOverlay {
1999 let agent_layers = agent.into_iter().map(AgentConfigOverlay::from);
2000 AgentConfigOverlay::fold(
2001 [AgentConfigOverlay::from(harness)]
2002 .into_iter()
2003 .chain(agent_layers)
2004 .chain([AgentConfigOverlay::from(session)]),
2005 )
2006}
2007
2008fn hydrate_plugin_refs(
2015 capabilities: &mut [everruns_capability::CapabilityRef],
2016 plugin_configs: &[everruns_capability::CapabilityRef],
2017) {
2018 for cap in capabilities.iter_mut() {
2019 let cap_id = cap.capability_id();
2020 if !everruns_capability::is_plugin_capability(cap_id) {
2021 continue;
2022 }
2023 let is_bare = cap.config_value().is_null()
2025 || cap
2026 .config_value()
2027 .clone()
2028 .as_object()
2029 .map(|o| o.is_empty())
2030 .unwrap_or(false);
2031 if !is_bare {
2032 continue;
2033 }
2034 if let Some(hydrated) = plugin_configs.iter().find(|c| c.capability_id() == cap_id) {
2035 cap.set_config(hydrated.config_value().clone());
2036 }
2037 }
2038}
2039
2040async fn seed_runtime_initial_files(
2041 harness_store: &dyn RuntimeHarnessStore,
2042 agent_store: &dyn RuntimeAgentStore,
2043 file_store: &dyn SessionFileSystem,
2044 session: &ExecutionSession,
2045) -> Result<()> {
2046 let harness = harness_store
2047 .get_harness(session.harness_id)
2048 .await?
2049 .ok_or_else(|| {
2050 AgentLoopError::store(format!(
2051 "harness not found while seeding files: {}",
2052 session.harness_id
2053 ))
2054 })?;
2055 let agent = match session.agent_id {
2056 Some(agent_id) => Some(
2057 agent_store
2058 .get_agent(agent_id)
2059 .await?
2060 .ok_or_else(|| AgentLoopError::store(format!("agent not found: {agent_id}")))?,
2061 ),
2062 None => None,
2063 };
2064 let overlay = effective_overlay(&harness, agent.as_ref(), session);
2065 let seed_key = SessionId::from_uuid(session.workspace_id.uuid());
2068 for file in &overlay.initial_files {
2069 file_store.seed_initial_file(seed_key, file).await?;
2070 }
2071 Ok(())
2072}
2073
2074fn message_from_input(input: InputMessage) -> Message {
2075 message_from_input_with_id(MessageId::new(), input)
2076}
2077
2078fn message_from_input_with_id(message_id: MessageId, input: InputMessage) -> Message {
2079 Message {
2080 id: message_id,
2081 role: input.role,
2082 content: input.content,
2083 phase: None,
2084 phase_source: None,
2085 controls: input.controls,
2086 metadata: input.metadata,
2087 external_actor: None,
2088 created_at: Utc::now(),
2089 }
2090}
2091
2092#[cfg(test)]
2093mod org_id_mapping_tests {
2094 use super::*;
2095 use everruns_core::{DEFAULT_ORG_ID, DEFAULT_ORG_PUBLIC_ID, org_public_id_from_internal};
2096
2097 #[test]
2098 fn default_public_id_maps_to_default_org() {
2099 assert_eq!(
2100 in_process_internal_org_id(DEFAULT_ORG_PUBLIC_ID),
2101 DEFAULT_ORG_ID
2102 );
2103 }
2104
2105 #[test]
2106 fn invalid_public_id_does_not_fall_back_to_default() {
2107 for invalid in [
2108 "",
2109 "not-an-org",
2110 "org_short",
2111 "org_ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
2112 "ORG_00000000000000000000000000000001",
2113 ] {
2114 let mapped = in_process_internal_org_id(invalid);
2115 assert_ne!(mapped, everruns_core::DEFAULT_ORG_ID);
2116 assert!(
2117 mapped >= 2,
2118 "invalid input {invalid:?} should not map to default"
2119 );
2120 }
2121 }
2122
2123 #[test]
2124 fn zero_public_id_does_not_fall_back_to_default() {
2125 let mapped = in_process_internal_org_id("org_00000000000000000000000000000000");
2128 assert_ne!(mapped, everruns_core::DEFAULT_ORG_ID);
2129 assert!(mapped >= 2, "all-zero id should not map to default");
2130 }
2131
2132 #[test]
2133 fn synthetic_public_id_round_trips_with_internal_helper() {
2134 for internal in [1_i64, 2, 42, 1_000_000, i64::MAX - 1, i64::MAX] {
2135 let public = org_public_id_from_internal(internal);
2136 assert_eq!(
2137 in_process_internal_org_id(&public),
2138 internal,
2139 "round-trip failed for internal={internal}"
2140 );
2141 }
2142 }
2143
2144 #[test]
2145 fn distinct_synthetic_ids_map_to_distinct_internal_ids() {
2146 let a = org_public_id_from_internal(7);
2147 let b = org_public_id_from_internal(8);
2148 assert_ne!(a, b);
2149 assert_ne!(
2150 in_process_internal_org_id(&a),
2151 in_process_internal_org_id(&b)
2152 );
2153 }
2154
2155 #[test]
2156 fn high_entropy_uuid_style_id_hashes_into_reserved_range() {
2157 let high = "org_80000000000000000000000000000000";
2161 let mapped = in_process_internal_org_id(high);
2162 assert!(mapped >= 2, "mapped id {mapped} must be >= 2");
2163 assert_ne!(mapped, DEFAULT_ORG_ID);
2164
2165 assert_eq!(mapped, in_process_internal_org_id(high));
2167 }
2168
2169 #[test]
2170 fn high_entropy_ids_are_isolated_from_each_other() {
2171 let a = in_process_internal_org_id("org_80000000000000000000000000000001");
2172 let b = in_process_internal_org_id("org_80000000000000000000000000000002");
2173 assert_ne!(a, b);
2174 assert_ne!(a, DEFAULT_ORG_ID);
2175 assert_ne!(b, DEFAULT_ORG_ID);
2176 }
2177
2178 #[test]
2179 fn hash_uses_stable_sha256_truncation() {
2180 let mapped = in_process_internal_org_id("org_80000000000000000000000000000000");
2184 let expected = {
2185 let digest = sha2::Sha256::digest(b"org_80000000000000000000000000000000");
2186 let mut buf = [0u8; 8];
2187 buf.copy_from_slice(&digest[..8]);
2188 let raw = u64::from_be_bytes(buf);
2189 ((raw % ((i64::MAX - 1) as u64)) as i64) + 2
2190 };
2191 assert_eq!(mapped, expected);
2192 }
2193
2194 #[test]
2195 fn oversize_input_is_bounded_and_does_not_collide_silently() {
2196 let oversize = "x".repeat(super::HASH_INPUT_CAP_BYTES * 4);
2202 let mapped = in_process_internal_org_id(&oversize);
2203 assert!(mapped >= 2);
2204 assert_ne!(mapped, DEFAULT_ORG_ID);
2205 }
2206}