1use crate::backends::{
6 HostBackends, RuntimeAgentStore, RuntimeHarnessStore, RuntimeProviderStore, RuntimeSessionStore,
7};
8use crate::builders::SingleSessionBuilder;
9use crate::events::{EventHistory, EventLog, EventReadLimit, EventReadRequest, HostEventEmitter};
10use crate::host::{
11 RuntimeHostAdapter, RuntimeHostTurnContext, execute_act_activity, execute_input_activity,
12 execute_reason_activity_with_prompt_messages,
13};
14use crate::in_memory::{InMemorySessionFileStore, InMemorySessionFileSystemFactory};
15use crate::turn_strategy::{RuntimeTurnPlan, RuntimeTurnState};
16use async_trait::async_trait;
17use chrono::Utc;
18use everruns_core::agent::Agent;
19use everruns_core::atoms::{AtomContext, InputAtomInput, ReasonInput};
20use everruns_core::capabilities::{
21 Capability, CapabilityRegistry, CapabilityStatus, collect_capability_mcp_servers,
22 resolve_capability_configs,
23};
24use everruns_core::config_layer::AgentConfigOverlay;
25use everruns_core::driver_registry::{DriverId, DriverRegistry};
26use everruns_core::error::{AgentLoopError, Result};
27use everruns_core::events::{
28 Event, EventContext, EventRequest, InputMessageData, SessionStartedData,
29};
30use everruns_core::harness::Harness;
31use everruns_core::llmsim_driver::{LlmSimConfig, LlmSimDriver};
32use everruns_core::message::Message;
33use everruns_core::platform_definition::PlatformDefinition;
34use everruns_core::plugins::{PluginFileSet, compile_plugin};
35use everruns_core::runtime_context::{AssembledTurnContext, inspect_turn_context};
36use everruns_core::session::{Session, SessionStatus};
37use everruns_core::session_file::{InitialFile, SessionFile};
38use everruns_core::traits::{
39 AgentStore, EventEmitter, HarnessStore, ProviderStore, ResolvedModel, SessionMutator,
40 SessionStorageStore, SessionStore, UserConnectionResolver,
41};
42use everruns_core::turn::TurnStopReason;
43use everruns_core::typed_id::{AgentId, HarnessId, MessageId, OrgId, SessionId, TurnId};
44use everruns_core::{
45 AgentCapabilityConfig, CapabilityId, InputMessage, MessageRetriever, SessionFileSystem,
46 SessionFileSystemFactoryContext, plugin_capability_id, resolve_runtime_capabilities,
47};
48use everruns_engine::{
49 ActOutcome, plan_after_act, plan_after_process_input, plan_after_reason, reason_schedules_act,
50};
51use sha2::{Digest, Sha256};
52use std::collections::VecDeque;
53use std::path::Path;
54use std::sync::{Arc, Mutex};
55
56const HASH_INPUT_CAP_BYTES: usize = 128;
62
63pub fn in_process_internal_org_id(public_org_id: &str) -> i64 {
76 if public_org_id == everruns_core::DEFAULT_ORG_PUBLIC_ID {
77 return everruns_core::DEFAULT_ORG_ID;
78 }
79
80 let Ok(parsed) = public_org_id.parse::<OrgId>() else {
81 return hash_public_org_id(public_org_id);
82 };
83 let raw: u128 = parsed.uuid().as_u128();
84 if raw == 0 {
85 return hash_public_org_id(public_org_id);
86 }
87
88 if raw <= i64::MAX as u128 {
91 return raw as i64;
92 }
93
94 hash_public_org_id(public_org_id)
95}
96
97fn hash_public_org_id(public_org_id: &str) -> i64 {
102 let bytes = public_org_id.as_bytes();
103 let bounded = &bytes[..bytes.len().min(HASH_INPUT_CAP_BYTES)];
104 let digest = Sha256::digest(bounded);
105 let mut buf = [0u8; 8];
106 buf.copy_from_slice(&digest[..8]);
107 let raw = u64::from_be_bytes(buf);
108 ((raw % ((i64::MAX - 1) as u64)) as i64) + 2
109}
110
111#[derive(Debug, Clone)]
112pub struct TurnResult {
113 pub response: String,
115 pub iterations: usize,
117 pub tool_calls_count: usize,
119 pub success: bool,
121 pub error: Option<String>,
123 pub stop_reason: TurnStopReason,
125 pub turn_id: everruns_core::typed_id::TurnId,
127}
128
129#[doc(hidden)]
134#[derive(Clone, Debug)]
135pub struct AcceptedTurnInput {
136 message_id: MessageId,
137 input: InputMessage,
138}
139
140impl AcceptedTurnInput {
141 pub fn new(input: impl Into<InputMessage>) -> Self {
142 Self {
143 message_id: MessageId::new(),
144 input: input.into(),
145 }
146 }
147
148 pub fn message_id(&self) -> MessageId {
149 self.message_id
150 }
151
152 pub fn input(&self) -> &InputMessage {
153 &self.input
154 }
155
156 fn into_message(self) -> Message {
157 message_from_input_with_id(self.message_id, self.input)
158 }
159}
160
161#[doc(hidden)]
167#[derive(Clone, Debug)]
168pub struct TurnSteering {
169 state: Arc<Mutex<TurnSteeringState>>,
170}
171
172#[doc(hidden)]
173#[derive(Debug)]
174pub enum TurnSteeringPushError {
175 Closed(Box<AcceptedTurnInput>),
176 Full(Box<AcceptedTurnInput>),
177}
178
179const TURN_STEERING_CAPACITY: usize = 256;
182
183#[derive(Debug, Default)]
184struct TurnSteeringState {
185 open: bool,
186 inputs: VecDeque<AcceptedTurnInput>,
187}
188
189impl TurnSteering {
190 pub fn new() -> Self {
191 Self {
192 state: Arc::new(Mutex::new(TurnSteeringState {
193 open: true,
194 inputs: VecDeque::new(),
195 })),
196 }
197 }
198
199 pub fn try_push(
200 &self,
201 input: AcceptedTurnInput,
202 ) -> std::result::Result<(), TurnSteeringPushError> {
203 let mut state = self.state.lock().expect("turn steering lock poisoned");
204 if !state.open {
205 return Err(TurnSteeringPushError::Closed(Box::new(input)));
206 }
207 if state.inputs.len() >= TURN_STEERING_CAPACITY {
208 return Err(TurnSteeringPushError::Full(Box::new(input)));
209 }
210 state.inputs.push_back(input);
211 Ok(())
212 }
213
214 fn drain(&self) -> Vec<AcceptedTurnInput> {
215 let mut state = self.state.lock().expect("turn steering lock poisoned");
216 state.inputs.drain(..).collect()
217 }
218
219 fn drain_or_close(&self) -> Vec<AcceptedTurnInput> {
221 let mut state = self.state.lock().expect("turn steering lock poisoned");
222 if state.inputs.is_empty() {
223 state.open = false;
224 return vec![];
225 }
226 state.inputs.drain(..).collect()
227 }
228
229 pub fn close(&self) {
230 self.state.lock().expect("turn steering lock poisoned").open = false;
231 }
232
233 pub fn close_and_drain(&self) -> Vec<AcceptedTurnInput> {
234 let mut state = self.state.lock().expect("turn steering lock poisoned");
235 state.open = false;
236 state.inputs.drain(..).collect()
237 }
238}
239
240impl Default for TurnSteering {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct CapabilityDelta {
253 pub capability_id: String,
255 pub changed: bool,
257 pub active: bool,
259 pub surfaces_dirty: bool,
261}
262
263fn finish_turn(
271 turn_id: everruns_core::typed_id::TurnId,
272 stop_reason: TurnStopReason,
273 error: Option<String>,
274 response: String,
275 iterations: usize,
276 tool_calls_count: usize,
277) -> TurnResult {
278 if error.is_some() {
279 return TurnResult {
280 response: String::new(),
281 iterations,
282 tool_calls_count: 0,
283 success: false,
284 error,
285 stop_reason,
286 turn_id,
287 };
288 }
289
290 TurnResult {
291 response,
292 iterations,
293 tool_calls_count,
294 success: true,
295 error: None,
296 stop_reason,
297 turn_id,
298 }
299}
300
301pub struct InProcessRuntimeBuilder {
311 platform_definition: PlatformDefinition,
312 llm_sim_config: Option<LlmSimConfig>,
313 providers: Vec<everruns_core::Provider>,
314 model_spec: Option<everruns_core::ModelSpec>,
315 legacy_provider_config: Option<everruns_core::ProviderConfig>,
316 backends: Option<HostBackends>,
317 workspace_policy: Option<everruns_core::WorkspacePolicy>,
318 session_file_system_factory_context: SessionFileSystemFactoryContext,
319 harnesses: Vec<Harness>,
320 agents: Vec<Agent>,
321 sessions: Vec<Session>,
322 default_session_id: Option<SessionId>,
323 seeded_files: Vec<(SessionId, InitialFile)>,
324 mcp_auth_provider: Option<Arc<dyn everruns_mcp::McpAuthProvider>>,
325 provider_retry_config: Option<everruns_core::llm_retry::LlmRetryConfig>,
326 provider_stall_timeout: Option<std::time::Duration>,
327 plugin_capability_configs: Vec<AgentCapabilityConfig>,
333 plugin_warnings: Vec<String>,
335}
336
337impl Default for InProcessRuntimeBuilder {
338 fn default() -> Self {
339 Self::new()
340 }
341}
342
343impl InProcessRuntimeBuilder {
344 pub fn new() -> Self {
352 Self {
353 platform_definition: PlatformDefinition::builder()
354 .capability_registry(CapabilityRegistry::runtime_builtins())
355 .driver_registry(DriverRegistry::new())
356 .session_file_system_factory(Arc::new(InMemorySessionFileSystemFactory))
357 .build(),
358 llm_sim_config: None,
359 providers: Vec::new(),
360 model_spec: None,
361 legacy_provider_config: None,
362 backends: None,
363 workspace_policy: None,
364 session_file_system_factory_context: SessionFileSystemFactoryContext::new(),
365 harnesses: Vec::new(),
366 agents: Vec::new(),
367 sessions: Vec::new(),
368 default_session_id: None,
369 seeded_files: Vec::new(),
370 mcp_auth_provider: None,
371 provider_retry_config: None,
372 provider_stall_timeout: None,
373 plugin_capability_configs: Vec::new(),
374 plugin_warnings: Vec::new(),
375 }
376 }
377
378 pub fn mcp_auth_provider(mut self, provider: Arc<dyn everruns_mcp::McpAuthProvider>) -> Self {
382 self.mcp_auth_provider = Some(provider);
383 self
384 }
385
386 pub fn platform_definition(mut self, platform_definition: PlatformDefinition) -> Self {
388 self.platform_definition = platform_definition;
389 self
390 }
391
392 pub fn capability<C: Capability + 'static>(mut self, capability: C) -> Self {
394 self.platform_definition
395 .capability_registry_mut()
396 .register(capability);
397 self
398 }
399
400 pub fn driver_registry(mut self, driver_registry: DriverRegistry) -> Self {
402 *self.platform_definition.driver_registry_mut() = driver_registry;
403 self
404 }
405
406 pub fn provider(mut self, provider: everruns_core::Provider) -> Self {
408 self.providers.push(provider);
409 self
410 }
411
412 pub fn llm_sim(mut self, config: LlmSimConfig) -> Self {
414 self.llm_sim_config = Some(config);
415 self
416 }
417
418 pub fn default_model(mut self, model: ResolvedModel) -> Self {
420 let (spec, provider_config) = model.canonical_parts();
421 self.model_spec = Some(spec);
422 self.legacy_provider_config = Some(provider_config);
423 self
424 }
425
426 pub fn model_spec(mut self, model: everruns_core::ModelSpec) -> Self {
428 self.model_spec = Some(model);
429 self.legacy_provider_config = None;
430 self
431 }
432
433 pub fn backends(mut self, backends: HostBackends) -> Self {
435 self.backends = Some(backends);
436 self
437 }
438
439 pub fn workspace_policy(mut self, policy: everruns_core::WorkspacePolicy) -> Self {
445 self.workspace_policy = Some(policy);
446 self
447 }
448
449 pub fn with_session_task_registry(
453 mut self,
454 registry: Arc<dyn everruns_core::session_task::SessionTaskRegistry>,
455 ) -> Self {
456 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
457 self.backends = Some(backends.with_session_task_registry(registry));
458 self
459 }
460
461 pub fn with_schedule_store_factory(
463 mut self,
464 factory: crate::backends::ScheduleStoreFactory,
465 ) -> Self {
466 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
467 self.backends = Some(backends.with_schedule_store_factory(factory));
468 self
469 }
470
471 pub fn with_platform_store_factory(
473 mut self,
474 factory: crate::backends::PlatformStoreFactory,
475 ) -> Self {
476 let backends = self.backends.take().unwrap_or_else(HostBackends::in_memory);
477 self.backends = Some(backends.with_platform_store_factory(factory));
478 self
479 }
480
481 pub fn session_file_system_factory_context(
483 mut self,
484 context: SessionFileSystemFactoryContext,
485 ) -> Self {
486 self.session_file_system_factory_context = context;
487 self
488 }
489
490 pub fn provider_retry_config(
492 mut self,
493 config: everruns_core::llm_retry::LlmRetryConfig,
494 ) -> Self {
495 self.provider_retry_config = Some(config);
496 self
497 }
498
499 pub fn provider_stall_timeout(mut self, timeout: std::time::Duration) -> Self {
501 self.provider_stall_timeout = Some(timeout);
502 self
503 }
504
505 pub fn harness(mut self, harness: Harness) -> Self {
507 self.harnesses.push(harness);
508 self
509 }
510
511 pub fn agent(mut self, agent: Agent) -> Self {
513 self.agents.push(agent);
514 self
515 }
516
517 pub fn session(mut self, session: Session) -> Self {
519 self.sessions.push(session);
520 self
521 }
522
523 pub fn single_session<F>(mut self, configure: F) -> Self
528 where
529 F: FnOnce(SingleSessionBuilder) -> SingleSessionBuilder,
530 {
531 let (harness, agent, session, session_id) =
532 configure(SingleSessionBuilder::default()).build();
533 self.harnesses.push(harness);
534 self.agents.push(agent);
535 self.sessions.push(session);
536 self.default_session_id = Some(session_id);
537 self
538 }
539
540 pub fn seed_text_file(
544 mut self,
545 session_id: SessionId,
546 path: impl Into<String>,
547 content: impl Into<String>,
548 ) -> Self {
549 self.seeded_files.push((
550 session_id,
551 InitialFile {
552 path: path.into(),
553 content: content.into(),
554 encoding: "text".to_string(),
555 is_readonly: false,
556 },
557 ));
558 self
559 }
560
561 pub fn with_plugin_dir(mut self, path: &Path) -> Result<Self> {
582 let file_set = PluginFileSet::from_dir(path)
583 .map_err(|e| AgentLoopError::config(format!("plugin directory load failed: {e}")))?;
584 let compiled = compile_plugin(&file_set)
585 .map_err(|e| AgentLoopError::config(format!("plugin compilation failed: {e}")))?;
586
587 for warning in &compiled.warnings {
588 tracing::warn!(plugin = %compiled.definition.name, warning = %warning, "plugin compile warning");
589 }
590 self.plugin_warnings.extend(compiled.warnings);
591
592 let cap_id = plugin_capability_id(&compiled.definition.name);
593 let hydrated_config = serde_json::to_value(&compiled.definition)
594 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
595 self.plugin_capability_configs
596 .push(AgentCapabilityConfig::with_config(cap_id, hydrated_config));
597
598 Ok(self)
599 }
600
601 pub fn plugin_capability(&self, name: &str) -> Option<AgentCapabilityConfig> {
607 let cap_id = plugin_capability_id(name);
608 self.plugin_capability_configs
609 .iter()
610 .find(|c| c.capability_id() == cap_id)
611 .cloned()
612 }
613
614 pub async fn build(mut self) -> Result<InProcessRuntime> {
619 let backends = match self.backends.take() {
620 Some(backends) => backends,
621 None => HostBackends::in_memory(),
622 };
623 let mut file_store = resolve_session_file_system(
624 &self.platform_definition,
625 self.session_file_system_factory_context.clone(),
626 )
627 .await?;
628 if let Some(policy) = self.workspace_policy.take() {
629 file_store = Arc::new(crate::PolicyFileStore::new(file_store, policy));
630 }
631
632 if let Some(config) = self.llm_sim_config.take() {
633 let driver = LlmSimDriver::new(config);
634 self.platform_definition
636 .driver_registry_mut()
637 .replace_provider(everruns_core::Provider::new("llmsim", driver));
638
639 if self.model_spec.is_none() {
640 self.model_spec = Some(everruns_core::ModelSpec::on("llmsim", "llmsim-model"));
641 }
642 }
643
644 for provider in self.providers {
645 self.platform_definition
646 .driver_registry_mut()
647 .register_provider(provider)?;
648 }
649
650 let model_spec = self.model_spec.ok_or_else(|| {
651 AgentLoopError::config(
652 "in-process runtime requires a default model; call \
653 InProcessRuntimeBuilder::default_model(...) or \
654 InProcessRuntimeBuilder::llm_sim(...)",
655 )
656 })?;
657
658 let legacy_config = self.legacy_provider_config.take();
662 let provider_type = legacy_config
663 .as_ref()
664 .map(|config| config.provider_type.clone())
665 .unwrap_or_else(|| DriverId::external(model_spec.provider.as_str()));
666 if let Some(config) = legacy_config
667 && self
668 .platform_definition
669 .driver_registry()
670 .provider(&model_spec.provider)
671 .is_none()
672 {
673 let driver = self
674 .platform_definition
675 .driver_registry()
676 .create_chat_driver(&config)?;
677 self.platform_definition
678 .driver_registry_mut()
679 .register_provider(everruns_core::Provider::new(
680 model_spec.provider.clone(),
681 driver,
682 ))?;
683 }
684
685 let provider_metadata = if provider_type.as_str() == model_spec.provider.as_str() {
686 None
687 } else {
688 Some(everruns_core::ProviderMetadata {
689 extra: Some(serde_json::json!({
690 "provider_id": model_spec.provider.as_str(),
691 })),
692 ..Default::default()
693 })
694 };
695 let default_model = ResolvedModel {
696 model: model_spec.model,
697 provider_type,
698 api_key: None,
699 base_url: None,
700 provider_metadata,
701 };
702
703 backends
704 .provider_store
705 .set_default_model(default_model)
706 .await?;
707
708 for harness in &mut self.harnesses {
712 hydrate_plugin_refs(&mut harness.capabilities, &self.plugin_capability_configs);
713 }
714 for agent in &mut self.agents {
715 hydrate_plugin_refs(&mut agent.capabilities, &self.plugin_capability_configs);
716 }
717 for session in &mut self.sessions {
718 hydrate_plugin_refs(&mut session.capabilities, &self.plugin_capability_configs);
719 }
720
721 for harness in &self.harnesses {
722 backends.harness_store.add_harness(harness.clone()).await?;
723 }
724 for agent in &self.agents {
725 backends.agent_store.add_agent(agent.clone()).await?;
726 }
727 for session in &self.sessions {
728 backends.session_store.add_session(session.clone()).await?;
729 }
730
731 for session in &self.sessions {
732 seed_runtime_initial_files(
733 backends.harness_store.as_ref(),
734 backends.agent_store.as_ref(),
735 file_store.as_ref(),
736 session,
737 )
738 .await?;
739 }
740
741 for (session_id, file) in &self.seeded_files {
742 file_store.seed_initial_file(*session_id, file).await?;
743 }
744
745 let event_log = backends.event_log.clone();
746 let event_history = Arc::new(EventHistory::new(event_log.clone()));
747 let event_emitter = Arc::new(HostEventEmitter::new(
748 event_log.clone(),
749 backends.event_sink.clone(),
750 ));
751
752 let (session_task_registry, session_wake_queue) = match backends.session_task_registry {
758 Some(inner) => {
759 let wake_queue = Arc::new(everruns_core::SessionWakeQueue::new());
760 let observing = everruns_core::ObservingTaskRegistry::new(inner)
761 .with_observer(wake_queue.clone());
762 let wrapped: Arc<dyn everruns_core::session_task::SessionTaskRegistry> =
763 Arc::new(observing);
764 (Some(wrapped), Some(wake_queue))
765 }
766 None => (None, None),
767 };
768
769 let seeded_session_ids = self.sessions.iter().map(|session| session.id).collect();
770 let runtime = InProcessRuntime {
771 platform_definition: Arc::new(self.platform_definition),
772 harness_store: backends.harness_store,
773 agent_store: backends.agent_store,
774 session_store: backends.session_store,
775 default_session_id: self.default_session_id,
776 seeded_session_ids,
777 event_log,
778 event_history,
779 compaction_checkpoint_store: backends.compaction_checkpoint_store,
780 provider_store: backends.provider_store,
781 event_emitter,
782 file_store,
783 storage_store: backends.storage_store,
784 connection_resolver: backends.connection_resolver,
785 session_task_registry,
786 session_wake_queue,
787 schedule_store_factory: backends.schedule_store_factory,
788 platform_store_factory: backends.platform_store_factory,
789 mcp_auth_provider: self
790 .mcp_auth_provider
791 .unwrap_or_else(|| Arc::new(everruns_mcp::NoAuthProvider)),
792 provider_retry_config: self.provider_retry_config,
793 provider_stall_timeout: self.provider_stall_timeout,
794 mcp_discovery_cache: Arc::new(crate::mcp_cache::McpDiscoveryCache::new()),
795 plugin_warnings: self.plugin_warnings,
796 };
797 for session in &self.sessions {
798 runtime.ensure_session_started(session).await?;
799 }
800 Ok(runtime)
801 }
802}
803
804async fn resolve_session_file_system(
805 platform_definition: &PlatformDefinition,
806 file_system_factory_context: SessionFileSystemFactoryContext,
807) -> Result<Arc<dyn SessionFileSystem>> {
808 let file_system_factory = platform_definition.session_file_system_factory();
809 if file_system_factory.is_disabled() {
810 Ok(Arc::new(InMemorySessionFileStore::new()))
811 } else {
812 Ok(file_system_factory
813 .create_session_file_system(file_system_factory_context)
814 .await?)
815 }
816}
817
818#[derive(Clone)]
819pub struct InProcessRuntime {
825 platform_definition: Arc<PlatformDefinition>,
826 harness_store: Arc<dyn RuntimeHarnessStore>,
827 agent_store: Arc<dyn RuntimeAgentStore>,
828 session_store: Arc<dyn RuntimeSessionStore>,
829 default_session_id: Option<SessionId>,
830 seeded_session_ids: Vec<SessionId>,
831 event_log: Arc<dyn EventLog>,
832 event_history: Arc<EventHistory>,
833 compaction_checkpoint_store: Arc<dyn everruns_core::CompactionCheckpointStore>,
834 provider_store: Arc<dyn RuntimeProviderStore>,
835 event_emitter: Arc<HostEventEmitter>,
836 file_store: Arc<dyn SessionFileSystem>,
837 storage_store: Arc<dyn SessionStorageStore>,
838 connection_resolver: Option<Arc<dyn UserConnectionResolver>>,
839 session_task_registry: Option<Arc<dyn everruns_core::session_task::SessionTaskRegistry>>,
840 session_wake_queue: Option<Arc<everruns_core::SessionWakeQueue>>,
844 schedule_store_factory: Option<crate::backends::ScheduleStoreFactory>,
845 platform_store_factory: Option<crate::backends::PlatformStoreFactory>,
846 mcp_auth_provider: Arc<dyn everruns_mcp::McpAuthProvider>,
847 provider_retry_config: Option<everruns_core::llm_retry::LlmRetryConfig>,
848 provider_stall_timeout: Option<std::time::Duration>,
849 mcp_discovery_cache: Arc<crate::mcp_cache::McpDiscoveryCache>,
850 plugin_warnings: Vec<String>,
853}
854
855impl InProcessRuntime {
856 async fn ensure_session_started(&self, session: &Session) -> Result<()> {
857 if self
858 .event_history
859 .contains_event_type(session.id, everruns_core::events::SESSION_STARTED)
860 .await
861 .map_err(|error| AgentLoopError::store(error.to_string()))?
862 {
863 return Ok(());
864 }
865 self.event_emitter
866 .emit(EventRequest::new(
867 session.id,
868 EventContext::empty(),
869 SessionStartedData {
870 harness_id: session.harness_id,
871 agent_id: session.agent_id,
872 model_id: session.model_id,
873 },
874 ))
875 .await?;
876 Ok(())
877 }
878
879 pub fn host_event_emitter(&self) -> Arc<HostEventEmitter> {
884 self.event_emitter.clone()
885 }
886
887 pub fn event_log(&self) -> Arc<dyn EventLog> {
889 self.event_log.clone()
890 }
891
892 fn mcp_client(&self) -> Arc<everruns_mcp::McpClient> {
895 Arc::new(everruns_mcp::McpClient::new(
896 self.platform_definition.egress_service(),
897 self.mcp_auth_provider.clone(),
898 ))
899 }
900
901 async fn session_mcp_servers(
903 &self,
904 session: &Session,
905 agent: Option<&Agent>,
906 ) -> everruns_core::ScopedMcpServers {
907 let harness_chain = self
908 .harness_store
909 .get_harness_chain(session.harness_id)
910 .await
911 .unwrap_or_default();
912 let resolved = resolve_runtime_capabilities(
913 &harness_chain,
914 agent,
915 session,
916 self.platform_definition.capability_registry(),
917 );
918 let contributed = collect_capability_mcp_servers(
919 &resolved.resolved_capability_configs,
920 self.platform_definition.capability_registry(),
921 );
922 let explicit = crate::mcp::merge_session_scoped_servers(&harness_chain, agent, session);
923 everruns_core::merge_scoped_mcp_servers(&contributed, &explicit)
924 }
925 pub fn builder() -> InProcessRuntimeBuilder {
927 InProcessRuntimeBuilder::new()
928 }
929
930 pub fn default_session_id(&self) -> Option<SessionId> {
933 self.default_session_id
934 }
935
936 pub fn plugin_warnings(&self) -> &[String] {
941 &self.plugin_warnings
942 }
943
944 pub async fn activate_capability(
950 &self,
951 session_id: SessionId,
952 capability: impl Into<AgentCapabilityConfig>,
953 ) -> Result<CapabilityDelta> {
954 let mut capability = capability.into();
955 let registry = self.platform_definition.capability_registry();
956 let registered = registry.get(capability.capability_id()).ok_or_else(|| {
957 AgentLoopError::config(format!(
958 "unknown capability: {}",
959 capability.capability_id()
960 ))
961 })?;
962 if registered.status() != CapabilityStatus::Available {
963 return Err(AgentLoopError::config(format!(
964 "capability is not available: {}",
965 capability.capability_id()
966 )));
967 }
968 registered
969 .validate_config(&capability.config)
970 .map_err(|error| {
971 AgentLoopError::config(format!("invalid capability config: {error}"))
972 })?;
973
974 let canonical_id = registered.id().to_string();
975 capability.capability_ref = CapabilityId::new(canonical_id.clone());
976 let context = self.load_context(session_id).await?;
977 if context
978 .resolved_capability_configs
979 .iter()
980 .any(|config| config.capability_id() == canonical_id)
981 {
982 return Ok(CapabilityDelta {
983 capability_id: canonical_id,
984 changed: false,
985 active: true,
986 surfaces_dirty: false,
987 });
988 }
989
990 let mut candidate = context.effective_overlay.capabilities;
991 candidate.push(capability.clone());
992 resolve_capability_configs(&candidate, registry)
993 .map_err(|error| AgentLoopError::config(error.to_string()))?;
994
995 self.session_store
996 .upsert_session_capability(session_id, capability)
997 .await?;
998 self.mcp_discovery_cache
999 .invalidate_session(session_id.uuid());
1000 Ok(CapabilityDelta {
1001 capability_id: canonical_id,
1002 changed: true,
1003 active: true,
1004 surfaces_dirty: true,
1005 })
1006 }
1007
1008 pub async fn deactivate_capability(
1013 &self,
1014 session_id: SessionId,
1015 capability_id: &str,
1016 ) -> Result<CapabilityDelta> {
1017 let registry = self.platform_definition.capability_registry();
1018 let registered = registry.get(capability_id).ok_or_else(|| {
1019 AgentLoopError::config(format!("unknown capability: {capability_id}"))
1020 })?;
1021 let canonical_id = registered.id().to_string();
1022 let context = self.load_context(session_id).await?;
1023 if !context
1024 .resolved_capability_configs
1025 .iter()
1026 .any(|config| config.capability_id() == canonical_id)
1027 {
1028 return Ok(CapabilityDelta {
1029 capability_id: canonical_id,
1030 changed: false,
1031 active: false,
1032 surfaces_dirty: false,
1033 });
1034 }
1035
1036 let session_capability_id = context
1037 .session
1038 .capabilities
1039 .iter()
1040 .find(|config| {
1041 registry
1042 .get(config.capability_id())
1043 .is_some_and(|capability| capability.id() == canonical_id)
1044 })
1045 .map(|config| config.capability_id().to_string())
1046 .ok_or_else(|| {
1047 AgentLoopError::config(format!(
1048 "capability {canonical_id} is inherited and cannot be deactivated at the session layer"
1049 ))
1050 })?;
1051
1052 self.session_store
1053 .remove_session_capability(session_id, &session_capability_id)
1054 .await?;
1055 self.mcp_discovery_cache
1056 .invalidate_session(session_id.uuid());
1057 Ok(CapabilityDelta {
1058 capability_id: canonical_id,
1059 changed: true,
1060 active: false,
1061 surfaces_dirty: true,
1062 })
1063 }
1064
1065 pub async fn run_turn(
1072 &self,
1073 session_id: SessionId,
1074 input: impl Into<InputMessage>,
1075 ) -> Result<TurnResult> {
1076 self.run_steerable_turn(
1077 session_id,
1078 AcceptedTurnInput::new(input),
1079 TurnId::new(),
1080 TurnSteering::new(),
1081 )
1082 .await
1083 }
1084
1085 #[doc(hidden)]
1088 pub async fn run_steerable_turn(
1089 &self,
1090 session_id: SessionId,
1091 input: AcceptedTurnInput,
1092 turn_id: TurnId,
1093 steering: TurnSteering,
1094 ) -> Result<TurnResult> {
1095 let session = self
1096 .session_store
1097 .get_session(session_id)
1098 .await?
1099 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1100
1101 let input_message = input.into_message();
1104 self.event_emitter
1105 .emit(EventRequest::new(
1106 session_id,
1107 EventContext::empty(),
1108 InputMessageData::new(input_message.clone()),
1109 ))
1110 .await?;
1111
1112 let org_id = in_process_internal_org_id(&session.organization_id);
1113
1114 let mut state = RuntimeTurnState {
1120 org_id,
1121 session_id,
1122 harness_id: session.harness_id,
1123 agent_id: session.agent_id,
1124 input_message_id: input_message.id,
1125 turn_id: None,
1126 previous_response_id: None,
1127 iteration: 1,
1128 request_id: None,
1129 started_at: None,
1130 cumulative_usage: None,
1131 tool_call_count: 0,
1132 llm_call_count: 0,
1133 time_to_first_token_ms: None,
1134 final_message_id: None,
1135 final_answer_preview: None,
1136 };
1137
1138 let base_context = |exec: bool| {
1139 let context = AtomContext::new(session_id, turn_id, input_message.id)
1140 .with_workspace_id(session.workspace_id);
1141 if exec { context.next_exec() } else { context }
1142 };
1143
1144 execute_input_activity(
1148 self,
1149 org_id,
1150 InputAtomInput {
1151 context: base_context(false),
1152 },
1153 )
1154 .await?;
1155 let mut plan = plan_after_process_input(&state, Some(turn_id), Utc::now());
1156
1157 let mut iterations: usize = 0;
1160 let mut tool_calls_count: usize = 0;
1161 let mut last_response = String::new();
1162 let mut pending_prompt_message_ids = Vec::new();
1163
1164 loop {
1165 match plan {
1166 RuntimeTurnPlan::ScheduleReason(next_state) => {
1167 state = next_state;
1168 let mut prompt_message_ids = std::mem::take(&mut pending_prompt_message_ids);
1169 prompt_message_ids.extend(
1170 self.inject_steering_inputs(session_id, steering.drain())
1171 .await?,
1172 );
1173 prompt_message_ids.extend(self.drain_and_inject_wakes(session_id).await?);
1179 if state.iteration == 1 {
1180 prompt_message_ids.insert(0, input_message.id);
1181 }
1182 let reason_result = execute_reason_activity_with_prompt_messages(
1183 self,
1184 org_id,
1185 ReasonInput {
1186 context: base_context(true),
1187 harness_id: session.harness_id,
1188 agent_id: session.agent_id,
1189 org_id,
1190 mcp_tool_definitions: vec![],
1191 previous_response_id: state.previous_response_id.clone(),
1192 iteration: state.iteration,
1193 },
1194 prompt_message_ids,
1195 )
1196 .await?;
1197
1198 iterations += 1;
1199 if !reason_result.text.is_empty() {
1200 last_response = reason_result.text.clone();
1201 }
1202
1203 let pending_wake_count = usize::from(
1208 self.session_wake_queue
1209 .as_ref()
1210 .is_some_and(|q| q.has_pending(session_id)),
1211 );
1212 let can_continue = reason_result.success
1213 && state.iteration < reason_result.max_iterations as u32;
1214 let pending_steering = if !reason_schedules_act(&state, &reason_result)
1215 && can_continue
1216 && pending_wake_count == 0
1217 {
1218 steering.drain_or_close()
1219 } else {
1220 vec![]
1221 };
1222 let pending_steering_count = pending_steering.len();
1223 if !pending_steering.is_empty() {
1224 pending_prompt_message_ids.extend(
1225 self.inject_steering_inputs(session_id, pending_steering)
1226 .await?,
1227 );
1228 }
1229 if !can_continue {
1230 steering.close();
1231 }
1232 let pending_user_message_count = pending_wake_count + pending_steering_count;
1233
1234 let act_scheduling =
1235 crate::turn_strategy::resolve_act_scheduling(self, &state, &reason_result)
1236 .await?;
1237 let (next, effects) = plan_after_reason(
1238 &state,
1239 reason_result,
1240 pending_user_message_count,
1241 Utc::now(),
1242 act_scheduling,
1243 );
1244 crate::turn_strategy::perform_effects(self, org_id, session_id, effects).await;
1245 plan = next;
1246 }
1247 RuntimeTurnPlan::ScheduleAct(act_plan) => {
1248 tool_calls_count += act_plan.input.tool_calls.len();
1249 state = *act_plan.resume_state;
1253 state.previous_response_id = act_plan.previous_response_id;
1254 state.iteration = act_plan.iteration;
1255 state.request_id = act_plan.request_id;
1256
1257 let act_result = execute_act_activity(self, act_plan.input).await?;
1258 let outcome = ActOutcome {
1259 blocked: act_result.blocked,
1260 waiting_for_tool_results: act_result.waiting_for_tool_results,
1261 };
1262 let setup_connection_hint_enabled =
1263 crate::turn_strategy::resolve_setup_connection_hint(
1264 self, org_id, session_id, outcome,
1265 )
1266 .await;
1267 let (next, effects) =
1268 plan_after_act(&state, outcome, setup_connection_hint_enabled);
1269 crate::turn_strategy::perform_effects(self, org_id, session_id, effects).await;
1270 plan = next;
1271 }
1272 RuntimeTurnPlan::Complete { stop_reason, error } => {
1273 steering.close();
1274 return Ok(finish_turn(
1275 turn_id,
1276 stop_reason,
1277 error,
1278 last_response,
1279 iterations,
1280 tool_calls_count,
1281 ));
1282 }
1283 RuntimeTurnPlan::WaitForToolResults { .. } => {
1287 steering.close();
1288 self.inject_steering_inputs(session_id, steering.drain())
1289 .await?;
1290 return Ok(finish_turn(
1291 turn_id,
1292 TurnStopReason::EndTurn,
1293 None,
1294 last_response,
1295 iterations,
1296 tool_calls_count,
1297 ));
1298 }
1299 }
1300 }
1301 }
1302
1303 pub async fn run_text_turn(
1304 &self,
1305 session_id: SessionId,
1306 text: impl Into<String>,
1307 ) -> Result<TurnResult> {
1308 self.run_turn(session_id, InputMessage::user(text)).await
1309 }
1310
1311 async fn inject_steering_inputs(
1312 &self,
1313 session_id: SessionId,
1314 inputs: Vec<AcceptedTurnInput>,
1315 ) -> Result<Vec<MessageId>> {
1316 let mut message_ids = Vec::with_capacity(inputs.len());
1317 for input in inputs {
1318 let message = input.into_message();
1319 message_ids.push(message.id);
1320 self.event_emitter
1321 .emit(EventRequest::new(
1322 session_id,
1323 EventContext::empty(),
1324 InputMessageData::new(message),
1325 ))
1326 .await?;
1327 }
1328 Ok(message_ids)
1329 }
1330
1331 #[doc(hidden)]
1333 pub async fn append_accepted_inputs(
1334 &self,
1335 session_id: SessionId,
1336 inputs: Vec<AcceptedTurnInput>,
1337 ) -> Result<()> {
1338 self.inject_steering_inputs(session_id, inputs)
1339 .await
1340 .map(|_| ())
1341 }
1342
1343 async fn drain_and_inject_wakes(&self, session_id: SessionId) -> Result<Vec<MessageId>> {
1355 let Some(queue) = &self.session_wake_queue else {
1356 return Ok(vec![]);
1357 };
1358 let wakes = queue.drain(session_id);
1359 if wakes.is_empty() {
1360 return Ok(vec![]);
1361 }
1362 let mut message_ids = Vec::with_capacity(wakes.len());
1363 for wake in wakes {
1364 let message = message_from_input(InputMessage::user(wake.text));
1366 message_ids.push(message.id);
1367 self.event_emitter
1368 .emit(EventRequest::new(
1369 session_id,
1370 EventContext::empty(),
1371 InputMessageData::new(message),
1372 ))
1373 .await?;
1374 }
1375 Ok(message_ids)
1376 }
1377
1378 pub async fn messages(&self, session_id: SessionId) -> Result<Vec<Message>> {
1380 self.event_history.load(session_id).await
1381 }
1382
1383 pub async fn read_file(
1385 &self,
1386 session_id: SessionId,
1387 path: &str,
1388 ) -> Result<Option<SessionFile>> {
1389 self.file_store.read_file(session_id, path).await
1390 }
1391
1392 pub async fn load_context(&self, session_id: SessionId) -> Result<AssembledTurnContext> {
1394 let session = self
1395 .session_store
1396 .get_session(session_id)
1397 .await?
1398 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1399 let agent = match session.agent_id {
1400 Some(agent_id) => self.agent_store.get_agent(agent_id).await?,
1401 None => None,
1402 };
1403 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1404 let mcp_tool_definitions = if scoped_servers.is_empty() {
1405 vec![]
1406 } else {
1407 crate::mcp::discover_tool_definitions(
1408 &self.mcp_discovery_cache,
1409 self.mcp_client(),
1410 session_id.uuid(),
1411 &scoped_servers,
1412 )
1413 .await
1414 };
1415 self.inspect_context_with_ids(
1416 session_id,
1417 session.harness_id,
1418 session.agent_id,
1419 &mcp_tool_definitions,
1420 )
1421 .await
1422 }
1423
1424 pub async fn events(&self) -> Result<Vec<Event>> {
1429 let limit = EventReadLimit::default();
1430 let session_id = self
1431 .default_session_id
1432 .or_else(|| (self.seeded_session_ids.len() == 1).then(|| self.seeded_session_ids[0]))
1433 .ok_or_else(|| {
1434 AgentLoopError::config(
1435 "events() requires exactly one seeded session; use event_log() for bounded per-session replay",
1436 )
1437 })?;
1438 let mut request = EventReadRequest::new(session_id, limit);
1439 let mut events = Vec::new();
1440 loop {
1441 let page = self
1442 .event_log
1443 .read_page(request)
1444 .await
1445 .map_err(|error| AgentLoopError::store(error.to_string()))?;
1446 if events.len().saturating_add(page.events.len())
1447 > crate::events::MAX_EVENT_HISTORY_REPLAY
1448 {
1449 return Err(AgentLoopError::store("event replay bound exceeded"));
1450 }
1451 events.extend(page.events);
1452 let Some(cursor) = page.next_cursor else {
1453 return Ok(events);
1454 };
1455 request = EventReadRequest::from_cursor(cursor, limit);
1456 }
1457 }
1458
1459 pub async fn execute_command(
1469 &self,
1470 session_id: SessionId,
1471 request: everruns_core::command::ExecuteCommandRequest,
1472 ) -> Result<everruns_core::command::CommandResult> {
1473 let ctx = self.load_context(session_id).await?;
1474 let registry = self.platform_definition.capability_registry();
1475 let host = everruns_core::command_host::StoreCommandHost::new(
1479 session_id,
1480 self.harness_store.clone(),
1481 self.agent_store.clone(),
1482 self.session_store.clone(),
1483 self.event_history.clone(),
1484 self.provider_store.clone(),
1485 registry.clone(),
1486 self.platform_definition.driver_registry().clone(),
1487 )
1488 .with_file_store(self.file_store.clone())
1489 .with_assembled_context(ctx.clone());
1490 let exec_ctx =
1491 everruns_core::command::CommandExecutionContext::new(session_id, Arc::new(host));
1492 for config in &ctx.resolved_capability_configs {
1493 let Some(capability) = registry.get(config.capability_id()) else {
1494 continue;
1495 };
1496 if capability.commands().iter().any(|c| c.name == request.name) {
1497 return capability.execute_command(&request, &exec_ctx).await;
1498 }
1499 }
1500 Err(AgentLoopError::config(format!(
1501 "no capability declares command /{}",
1502 request.name
1503 )))
1504 }
1505
1506 pub async fn list_commands(
1516 &self,
1517 session_id: SessionId,
1518 ) -> Result<Vec<everruns_core::command::CommandDescriptor>> {
1519 let ctx = self.load_context(session_id).await?;
1520 let registry = self.platform_definition.capability_registry();
1521 let mut seen = std::collections::HashSet::new();
1522 let mut commands = Vec::new();
1523 for config in &ctx.resolved_capability_configs {
1524 let Some(capability) = registry.get(config.capability_id()) else {
1525 continue;
1526 };
1527 for command in capability.commands() {
1528 if seen.insert(command.name.clone()) {
1529 commands.push(command);
1530 }
1531 }
1532 }
1533 Ok(commands)
1534 }
1535
1536 async fn inspect_context_with_ids(
1537 &self,
1538 session_id: SessionId,
1539 harness_id: everruns_core::HarnessId,
1540 agent_id: Option<AgentId>,
1541 mcp_tool_definitions: &[everruns_core::ToolDefinition],
1542 ) -> Result<AssembledTurnContext> {
1543 inspect_turn_context(
1544 self.harness_store.as_ref(),
1545 self.agent_store.as_ref(),
1546 self.session_store.as_ref(),
1547 self.event_history.as_ref(),
1548 self.provider_store.as_ref(),
1549 self.platform_definition.capability_registry(),
1550 session_id,
1551 harness_id,
1552 agent_id,
1553 mcp_tool_definitions,
1554 Some(self.file_store.clone()),
1555 )
1556 .await
1557 }
1558}
1559
1560#[async_trait]
1561impl RuntimeHostAdapter for InProcessRuntime {
1562 async fn get_agent(&self, _org_id: i64, agent_id: AgentId) -> Result<Option<Agent>> {
1563 self.agent_store.get_agent(agent_id).await
1564 }
1565
1566 async fn get_harness(&self, _org_id: i64, harness_id: HarnessId) -> Result<Option<Harness>> {
1567 let chain = self.harness_store.get_harness_chain(harness_id).await?;
1568 Ok(chain.into_iter().last())
1569 }
1570
1571 async fn set_session_status(
1572 &self,
1573 _org_id: i64,
1574 session_id: SessionId,
1575 _status: SessionStatus,
1576 ) -> Result<Session> {
1577 self.session_store
1581 .get_session(session_id)
1582 .await?
1583 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))
1584 }
1585
1586 async fn load_turn_context(
1587 &self,
1588 _org_id: i64,
1589 session_id: SessionId,
1590 ) -> Result<RuntimeHostTurnContext> {
1591 let mut session = self
1592 .session_store
1593 .get_session(session_id)
1594 .await?
1595 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1596 everruns_core::ard_attachment::apply_session_attachments(
1599 self.storage_store.as_ref(),
1600 &mut session,
1601 )
1602 .await;
1603 let agent = match session.agent_id {
1604 Some(agent_id) => self.agent_store.get_agent(agent_id).await?,
1605 None => None,
1606 };
1607 let messages = self.event_history.load(session_id).await?;
1608 let model = self.provider_store.get_default_model().await?;
1609
1610 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1613 let mcp_tool_definitions = if scoped_servers.is_empty() {
1614 vec![]
1615 } else {
1616 crate::mcp::discover_tool_definitions(
1617 &self.mcp_discovery_cache,
1618 self.mcp_client(),
1619 session_id.uuid(),
1620 &scoped_servers,
1621 )
1622 .await
1623 };
1624
1625 Ok(RuntimeHostTurnContext {
1626 agent,
1627 session,
1628 messages,
1629 model,
1630 mcp_tool_definitions,
1631 })
1632 }
1633
1634 async fn mcp_executor(
1635 &self,
1636 _org_id: i64,
1637 session_id: SessionId,
1638 ) -> Option<Arc<everruns_mcp::McpExecutor>> {
1639 let session = self.session_store.get_session(session_id).await.ok()??;
1640 let agent = match session.agent_id {
1641 Some(agent_id) => self.agent_store.get_agent(agent_id).await.ok().flatten(),
1642 None => None,
1643 };
1644 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1645 crate::mcp::build_executor(self.mcp_client(), &scoped_servers)
1646 }
1647
1648 fn capability_registry(&self) -> CapabilityRegistry {
1649 self.platform_definition.capability_registry().clone()
1650 }
1651
1652 fn driver_registry(&self) -> DriverRegistry {
1653 self.platform_definition.driver_registry().clone()
1654 }
1655
1656 fn harness_store(&self, _org_id: i64) -> Arc<dyn HarnessStore> {
1657 self.harness_store.clone()
1658 }
1659
1660 fn agent_store(&self, _org_id: i64) -> Arc<dyn AgentStore> {
1661 self.agent_store.clone()
1662 }
1663
1664 fn session_store(&self, _org_id: i64) -> Arc<dyn SessionStore> {
1665 self.session_store.clone()
1666 }
1667
1668 fn session_mutator(&self, _org_id: i64) -> Arc<dyn SessionMutator> {
1669 self.session_store.clone()
1670 }
1671
1672 fn provider_store(&self, _org_id: i64) -> Arc<dyn ProviderStore> {
1673 self.provider_store.clone()
1674 }
1675
1676 fn message_store(&self) -> Arc<dyn MessageRetriever> {
1677 self.event_history.clone()
1678 }
1679
1680 fn compaction_checkpoint_store(
1681 &self,
1682 ) -> Option<Arc<dyn everruns_core::CompactionCheckpointStore>> {
1683 Some(self.compaction_checkpoint_store.clone())
1684 }
1685
1686 fn event_emitter(&self) -> Arc<dyn EventEmitter> {
1687 self.event_emitter.clone()
1688 }
1689
1690 fn file_store(&self) -> Arc<dyn SessionFileSystem> {
1691 self.file_store.clone()
1692 }
1693
1694 fn storage_store(&self) -> Option<Arc<dyn SessionStorageStore>> {
1695 Some(self.storage_store.clone())
1696 }
1697
1698 fn connection_resolver(&self) -> Option<Arc<dyn UserConnectionResolver>> {
1699 self.connection_resolver.clone()
1700 }
1701
1702 fn session_task_registry(
1703 &self,
1704 ) -> Option<Arc<dyn everruns_core::session_task::SessionTaskRegistry>> {
1705 self.session_task_registry.clone()
1706 }
1707
1708 fn schedule_store(
1709 &self,
1710 org_id: i64,
1711 ) -> Option<Arc<dyn everruns_core::traits::SessionScheduleStore>> {
1712 self.schedule_store_factory
1713 .as_ref()
1714 .map(|factory| factory(org_id))
1715 }
1716
1717 fn platform_store(
1718 &self,
1719 org_id: i64,
1720 session_id: SessionId,
1721 ) -> Option<Arc<dyn everruns_platform::PlatformStore>> {
1722 self.platform_store_factory
1723 .as_ref()
1724 .map(|factory| factory(org_id, session_id))
1725 }
1726
1727 fn utility_llm_service(&self) -> Option<Arc<dyn everruns_core::UtilityLlmService>> {
1728 Some(self.platform_definition.utility_llm_service())
1729 }
1730
1731 fn egress_service(&self) -> Option<Arc<dyn everruns_core::EgressService>> {
1732 Some(self.platform_definition.egress_service())
1733 }
1734
1735 fn provider_retry_config(&self) -> Option<everruns_core::llm_retry::LlmRetryConfig> {
1736 self.provider_retry_config.clone()
1737 }
1738
1739 fn provider_stall_timeout(&self) -> Option<std::time::Duration> {
1740 self.provider_stall_timeout
1741 }
1742}
1743
1744fn effective_overlay(
1745 harness_chain: &[Harness],
1746 agent: Option<&Agent>,
1747 session: &Session,
1748) -> AgentConfigOverlay {
1749 let harness_layers = harness_chain.iter().map(AgentConfigOverlay::from);
1750 let agent_layers = agent.into_iter().map(AgentConfigOverlay::from);
1751 AgentConfigOverlay::fold(
1752 harness_layers
1753 .chain(agent_layers)
1754 .chain([AgentConfigOverlay::from(session)]),
1755 )
1756}
1757
1758fn hydrate_plugin_refs(
1765 capabilities: &mut [AgentCapabilityConfig],
1766 plugin_configs: &[AgentCapabilityConfig],
1767) {
1768 for cap in capabilities.iter_mut() {
1769 let cap_id = cap.capability_id();
1770 if !everruns_core::is_plugin_capability(cap_id) {
1771 continue;
1772 }
1773 let is_bare = cap.config.is_null()
1775 || cap
1776 .config
1777 .as_object()
1778 .map(|o| o.is_empty())
1779 .unwrap_or(false);
1780 if !is_bare {
1781 continue;
1782 }
1783 if let Some(hydrated) = plugin_configs.iter().find(|c| c.capability_id() == cap_id) {
1784 cap.config = hydrated.config.clone();
1785 }
1786 }
1787}
1788
1789async fn seed_runtime_initial_files(
1790 harness_store: &dyn RuntimeHarnessStore,
1791 agent_store: &dyn RuntimeAgentStore,
1792 file_store: &dyn SessionFileSystem,
1793 session: &Session,
1794) -> Result<()> {
1795 let harness_chain = harness_store.get_harness_chain(session.harness_id).await?;
1796 if harness_chain.is_empty() {
1797 return Err(AgentLoopError::store(format!(
1798 "harness not found while seeding files: {}",
1799 session.harness_id
1800 )));
1801 }
1802 let agent = match session.agent_id {
1803 Some(agent_id) => Some(
1804 agent_store
1805 .get_agent(agent_id)
1806 .await?
1807 .ok_or_else(|| AgentLoopError::store(format!("agent not found: {agent_id}")))?,
1808 ),
1809 None => None,
1810 };
1811 let overlay = effective_overlay(&harness_chain, agent.as_ref(), session);
1812 let seed_key = SessionId::from_uuid(session.workspace_id.uuid());
1815 for file in &overlay.initial_files {
1816 file_store.seed_initial_file(seed_key, file).await?;
1817 }
1818 Ok(())
1819}
1820
1821fn message_from_input(input: InputMessage) -> Message {
1822 message_from_input_with_id(MessageId::new(), input)
1823}
1824
1825fn message_from_input_with_id(message_id: MessageId, input: InputMessage) -> Message {
1826 Message {
1827 id: message_id,
1828 role: input.role,
1829 content: input.content,
1830 phase: None,
1831 thinking: None,
1832 thinking_signature: None,
1833 controls: input.controls,
1834 metadata: input.metadata,
1835 external_actor: None,
1836 created_at: Utc::now(),
1837 }
1838}
1839
1840#[cfg(test)]
1841mod org_id_mapping_tests {
1842 use super::*;
1843 use everruns_core::{DEFAULT_ORG_ID, DEFAULT_ORG_PUBLIC_ID, org_public_id_from_internal};
1844
1845 #[test]
1846 fn default_public_id_maps_to_default_org() {
1847 assert_eq!(
1848 in_process_internal_org_id(DEFAULT_ORG_PUBLIC_ID),
1849 DEFAULT_ORG_ID
1850 );
1851 }
1852
1853 #[test]
1854 fn invalid_public_id_does_not_fall_back_to_default() {
1855 for invalid in [
1856 "",
1857 "not-an-org",
1858 "org_short",
1859 "org_ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
1860 "ORG_00000000000000000000000000000001",
1861 ] {
1862 let mapped = in_process_internal_org_id(invalid);
1863 assert_ne!(mapped, everruns_core::DEFAULT_ORG_ID);
1864 assert!(
1865 mapped >= 2,
1866 "invalid input {invalid:?} should not map to default"
1867 );
1868 }
1869 }
1870
1871 #[test]
1872 fn zero_public_id_does_not_fall_back_to_default() {
1873 let mapped = in_process_internal_org_id("org_00000000000000000000000000000000");
1876 assert_ne!(mapped, everruns_core::DEFAULT_ORG_ID);
1877 assert!(mapped >= 2, "all-zero id should not map to default");
1878 }
1879
1880 #[test]
1881 fn synthetic_public_id_round_trips_with_internal_helper() {
1882 for internal in [1_i64, 2, 42, 1_000_000, i64::MAX - 1, i64::MAX] {
1883 let public = org_public_id_from_internal(internal);
1884 assert_eq!(
1885 in_process_internal_org_id(&public),
1886 internal,
1887 "round-trip failed for internal={internal}"
1888 );
1889 }
1890 }
1891
1892 #[test]
1893 fn distinct_synthetic_ids_map_to_distinct_internal_ids() {
1894 let a = org_public_id_from_internal(7);
1895 let b = org_public_id_from_internal(8);
1896 assert_ne!(a, b);
1897 assert_ne!(
1898 in_process_internal_org_id(&a),
1899 in_process_internal_org_id(&b)
1900 );
1901 }
1902
1903 #[test]
1904 fn high_entropy_uuid_style_id_hashes_into_reserved_range() {
1905 let high = "org_80000000000000000000000000000000";
1909 let mapped = in_process_internal_org_id(high);
1910 assert!(mapped >= 2, "mapped id {mapped} must be >= 2");
1911 assert_ne!(mapped, DEFAULT_ORG_ID);
1912
1913 assert_eq!(mapped, in_process_internal_org_id(high));
1915 }
1916
1917 #[test]
1918 fn high_entropy_ids_are_isolated_from_each_other() {
1919 let a = in_process_internal_org_id("org_80000000000000000000000000000001");
1920 let b = in_process_internal_org_id("org_80000000000000000000000000000002");
1921 assert_ne!(a, b);
1922 assert_ne!(a, DEFAULT_ORG_ID);
1923 assert_ne!(b, DEFAULT_ORG_ID);
1924 }
1925
1926 #[test]
1927 fn hash_uses_stable_sha256_truncation() {
1928 let mapped = in_process_internal_org_id("org_80000000000000000000000000000000");
1932 let expected = {
1933 let digest = sha2::Sha256::digest(b"org_80000000000000000000000000000000");
1934 let mut buf = [0u8; 8];
1935 buf.copy_from_slice(&digest[..8]);
1936 let raw = u64::from_be_bytes(buf);
1937 ((raw % ((i64::MAX - 1) as u64)) as i64) + 2
1938 };
1939 assert_eq!(mapped, expected);
1940 }
1941
1942 #[test]
1943 fn oversize_input_is_bounded_and_does_not_collide_silently() {
1944 let oversize = "x".repeat(super::HASH_INPUT_CAP_BYTES * 4);
1950 let mapped = in_process_internal_org_id(&oversize);
1951 assert!(mapped >= 2);
1952 assert_ne!(mapped, DEFAULT_ORG_ID);
1953 }
1954}