1use crate::backends::{
6 EventBus, RuntimeAgentStore, RuntimeBackends, RuntimeHarnessStore, RuntimeMessageStore,
7 RuntimeProviderStore, RuntimeSessionStore,
8};
9use crate::builders::SingleSessionBuilder;
10use crate::host::{
11 RuntimeHostAdapter, RuntimeHostTurnContext, RuntimeSessionLifecycle, execute_act_activity,
12 execute_input_activity, execute_reason_activity,
13};
14use crate::in_memory::{InMemorySessionFileStore, InMemorySessionFileSystemFactory};
15use async_trait::async_trait;
16use everruns_core::agent::Agent;
17use everruns_core::atoms::{ActInput, AtomContext, InputAtomInput, ReasonInput};
18use everruns_core::capabilities::{
19 Capability, CapabilityRegistry, CapabilityStatus, collect_capability_mcp_servers,
20 resolve_capability_configs,
21};
22use everruns_core::config_layer::AgentConfigOverlay;
23use everruns_core::driver_registry::{DriverId, DriverRegistry};
24use everruns_core::error::{AgentLoopError, Result};
25use everruns_core::events::{
26 Event, EventContext, EventData, EventRequest, InputMessageData, OutputMessageCompletedData,
27 ToolCompletedData,
28};
29use everruns_core::harness::Harness;
30use everruns_core::llmsim_driver::{LlmSimConfig, LlmSimDriver};
31use everruns_core::message::{ContentPart, Message};
32use everruns_core::platform_definition::PlatformDefinition;
33use everruns_core::plugins::{PluginFileSet, compile_plugin};
34use everruns_core::runtime_context::{AssembledTurnContext, inspect_turn_context};
35use everruns_core::session::{Session, SessionStatus};
36use everruns_core::session_file::{InitialFile, SessionFile};
37use everruns_core::tools::ToolResultImage;
38use everruns_core::traits::{
39 AgentStore, EventEmitter, HarnessStore, ProviderStore, ResolvedModel, SessionMutator,
40 SessionStorageStore, SessionStore, UserConnectionResolver,
41};
42use everruns_core::turn::{TurnAction, TurnContext, TurnOutcome, TurnStateMachine};
43use everruns_core::typed_id::{AgentId, HarnessId, OrgId, SessionId};
44use everruns_core::{
45 AgentCapabilityConfig, CapabilityId, InputMessage, MessageRetriever, SessionFileSystem,
46 SessionFileSystemFactoryContext, plugin_capability_id, resolve_runtime_capabilities,
47};
48use sha2::{Digest, Sha256};
49use std::path::Path;
50use std::sync::Arc;
51
52const HASH_INPUT_CAP_BYTES: usize = 128;
58
59pub fn in_process_internal_org_id(public_org_id: &str) -> i64 {
72 if public_org_id == everruns_core::DEFAULT_ORG_PUBLIC_ID {
73 return everruns_core::DEFAULT_ORG_ID;
74 }
75
76 let Ok(parsed) = public_org_id.parse::<OrgId>() else {
77 return hash_public_org_id(public_org_id);
78 };
79 let raw: u128 = parsed.uuid().as_u128();
80 if raw == 0 {
81 return hash_public_org_id(public_org_id);
82 }
83
84 if raw <= i64::MAX as u128 {
87 return raw as i64;
88 }
89
90 hash_public_org_id(public_org_id)
91}
92
93fn hash_public_org_id(public_org_id: &str) -> i64 {
98 let bytes = public_org_id.as_bytes();
99 let bounded = &bytes[..bytes.len().min(HASH_INPUT_CAP_BYTES)];
100 let digest = Sha256::digest(bounded);
101 let mut buf = [0u8; 8];
102 buf.copy_from_slice(&digest[..8]);
103 let raw = u64::from_be_bytes(buf);
104 ((raw % ((i64::MAX - 1) as u64)) as i64) + 2
105}
106
107#[derive(Debug, Clone)]
108pub struct TurnResult {
109 pub response: String,
111 pub iterations: usize,
113 pub tool_calls_count: usize,
115 pub success: bool,
117 pub error: Option<String>,
119 pub turn_id: everruns_core::typed_id::TurnId,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct CapabilityDelta {
130 pub capability_id: String,
132 pub changed: bool,
134 pub active: bool,
136 pub surfaces_dirty: bool,
138}
139
140impl TurnResult {
141 fn from_outcome(outcome: TurnOutcome, turn_id: everruns_core::typed_id::TurnId) -> Self {
142 match outcome {
143 TurnOutcome::Success {
144 response,
145 iterations,
146 tool_calls_count,
147 } => Self {
148 response,
149 iterations,
150 tool_calls_count,
151 success: true,
152 error: None,
153 turn_id,
154 },
155 TurnOutcome::Failed { error, iterations } => Self {
156 response: String::new(),
157 iterations,
158 tool_calls_count: 0,
159 success: false,
160 error: Some(error),
161 turn_id,
162 },
163 TurnOutcome::MaxIterationsReached {
164 response,
165 iterations,
166 tool_calls_count,
167 } => Self {
168 response,
169 iterations,
170 tool_calls_count,
171 success: true,
172 error: None,
173 turn_id,
174 },
175 TurnOutcome::Sealed {
178 reason,
179 response,
180 iterations,
181 tool_calls_count,
182 } => Self {
183 response,
184 iterations,
185 tool_calls_count,
186 success: false,
187 error: Some(format!("turn sealed: {reason}")),
188 turn_id,
189 },
190 }
191 }
192}
193
194pub struct InProcessRuntimeBuilder {
204 platform_definition: PlatformDefinition,
205 llm_sim_config: Option<LlmSimConfig>,
206 default_model: Option<ResolvedModel>,
207 backends: Option<RuntimeBackends>,
208 session_file_system_factory_context: SessionFileSystemFactoryContext,
209 harnesses: Vec<Harness>,
210 agents: Vec<Agent>,
211 sessions: Vec<Session>,
212 default_session_id: Option<SessionId>,
213 seeded_files: Vec<(SessionId, InitialFile)>,
214 mcp_auth_provider: Option<Arc<dyn everruns_mcp::McpAuthProvider>>,
215 plugin_capability_configs: Vec<AgentCapabilityConfig>,
221 plugin_warnings: Vec<String>,
223}
224
225impl Default for InProcessRuntimeBuilder {
226 fn default() -> Self {
227 Self::new()
228 }
229}
230
231impl InProcessRuntimeBuilder {
232 pub fn new() -> Self {
240 Self {
241 platform_definition: PlatformDefinition::builder()
242 .capability_registry(CapabilityRegistry::runtime_builtins())
243 .driver_registry(DriverRegistry::new())
244 .session_file_system_factory(Arc::new(InMemorySessionFileSystemFactory))
245 .build(),
246 llm_sim_config: None,
247 default_model: None,
248 backends: None,
249 session_file_system_factory_context: SessionFileSystemFactoryContext::new(),
250 harnesses: Vec::new(),
251 agents: Vec::new(),
252 sessions: Vec::new(),
253 default_session_id: None,
254 seeded_files: Vec::new(),
255 mcp_auth_provider: None,
256 plugin_capability_configs: Vec::new(),
257 plugin_warnings: Vec::new(),
258 }
259 }
260
261 pub fn mcp_auth_provider(mut self, provider: Arc<dyn everruns_mcp::McpAuthProvider>) -> Self {
265 self.mcp_auth_provider = Some(provider);
266 self
267 }
268
269 pub fn platform_definition(mut self, platform_definition: PlatformDefinition) -> Self {
271 self.platform_definition = platform_definition;
272 self
273 }
274
275 pub fn capability<C: Capability + 'static>(mut self, capability: C) -> Self {
277 self.platform_definition
278 .capability_registry_mut()
279 .register(capability);
280 self
281 }
282
283 pub fn driver_registry(mut self, driver_registry: DriverRegistry) -> Self {
285 *self.platform_definition.driver_registry_mut() = driver_registry;
286 self
287 }
288
289 pub fn llm_sim(mut self, config: LlmSimConfig) -> Self {
291 self.llm_sim_config = Some(config);
292 self
293 }
294
295 pub fn default_model(mut self, model: ResolvedModel) -> Self {
297 self.default_model = Some(model);
298 self
299 }
300
301 pub fn backends(mut self, backends: RuntimeBackends) -> Self {
303 self.backends = Some(backends);
304 self
305 }
306
307 pub fn with_session_task_registry(
311 mut self,
312 registry: Arc<dyn everruns_core::session_task::SessionTaskRegistry>,
313 ) -> Self {
314 let backends = self
315 .backends
316 .take()
317 .unwrap_or_else(RuntimeBackends::in_memory);
318 self.backends = Some(backends.with_session_task_registry(registry));
319 self
320 }
321
322 pub fn with_schedule_store_factory(
324 mut self,
325 factory: crate::backends::ScheduleStoreFactory,
326 ) -> Self {
327 let backends = self
328 .backends
329 .take()
330 .unwrap_or_else(RuntimeBackends::in_memory);
331 self.backends = Some(backends.with_schedule_store_factory(factory));
332 self
333 }
334
335 pub fn with_platform_store_factory(
337 mut self,
338 factory: crate::backends::PlatformStoreFactory,
339 ) -> Self {
340 let backends = self
341 .backends
342 .take()
343 .unwrap_or_else(RuntimeBackends::in_memory);
344 self.backends = Some(backends.with_platform_store_factory(factory));
345 self
346 }
347
348 pub fn session_file_system_factory_context(
350 mut self,
351 context: SessionFileSystemFactoryContext,
352 ) -> Self {
353 self.session_file_system_factory_context = context;
354 self
355 }
356
357 pub fn harness(mut self, harness: Harness) -> Self {
359 self.harnesses.push(harness);
360 self
361 }
362
363 pub fn agent(mut self, agent: Agent) -> Self {
365 self.agents.push(agent);
366 self
367 }
368
369 pub fn session(mut self, session: Session) -> Self {
371 self.sessions.push(session);
372 self
373 }
374
375 pub fn single_session<F>(mut self, configure: F) -> Self
380 where
381 F: FnOnce(SingleSessionBuilder) -> SingleSessionBuilder,
382 {
383 let (harness, agent, session, session_id) =
384 configure(SingleSessionBuilder::default()).build();
385 self.harnesses.push(harness);
386 self.agents.push(agent);
387 self.sessions.push(session);
388 self.default_session_id = Some(session_id);
389 self
390 }
391
392 pub fn seed_text_file(
396 mut self,
397 session_id: SessionId,
398 path: impl Into<String>,
399 content: impl Into<String>,
400 ) -> Self {
401 self.seeded_files.push((
402 session_id,
403 InitialFile {
404 path: path.into(),
405 content: content.into(),
406 encoding: "text".to_string(),
407 is_readonly: false,
408 },
409 ));
410 self
411 }
412
413 pub fn with_plugin_dir(mut self, path: &Path) -> Result<Self> {
434 let file_set = PluginFileSet::from_dir(path)
435 .map_err(|e| AgentLoopError::config(format!("plugin directory load failed: {e}")))?;
436 let compiled = compile_plugin(&file_set)
437 .map_err(|e| AgentLoopError::config(format!("plugin compilation failed: {e}")))?;
438
439 for warning in &compiled.warnings {
440 tracing::warn!(plugin = %compiled.definition.name, warning = %warning, "plugin compile warning");
441 }
442 self.plugin_warnings.extend(compiled.warnings);
443
444 let cap_id = plugin_capability_id(&compiled.definition.name);
445 let hydrated_config = serde_json::to_value(&compiled.definition)
446 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
447 self.plugin_capability_configs
448 .push(AgentCapabilityConfig::with_config(cap_id, hydrated_config));
449
450 Ok(self)
451 }
452
453 pub fn plugin_capability(&self, name: &str) -> Option<AgentCapabilityConfig> {
459 let cap_id = plugin_capability_id(name);
460 self.plugin_capability_configs
461 .iter()
462 .find(|c| c.capability_id() == cap_id)
463 .cloned()
464 }
465
466 pub async fn build(mut self) -> Result<InProcessRuntime> {
471 let backends = match self.backends.take() {
472 Some(backends) => backends,
473 None => RuntimeBackends::in_memory(),
474 };
475 let file_store = resolve_session_file_system(
476 &self.platform_definition,
477 self.session_file_system_factory_context.clone(),
478 )
479 .await?;
480
481 if let Some(config) = self.llm_sim_config.take() {
482 let driver = LlmSimDriver::new(config);
483 self.platform_definition
486 .driver_registry_mut()
487 .register_or_replace(DriverId::LlmSim, move |_config| Box::new(driver.clone()));
488
489 if self.default_model.is_none() {
490 self.default_model = Some(ResolvedModel {
491 model: "llmsim-model".to_string(),
492 provider_type: DriverId::LlmSim,
493 api_key: Some("fake-key".to_string()),
494 base_url: None,
495 provider_metadata: None,
496 });
497 }
498 }
499
500 let default_model = self.default_model.ok_or_else(|| {
501 AgentLoopError::config(
502 "in-process runtime requires a default model; call \
503 InProcessRuntimeBuilder::default_model(...) or \
504 InProcessRuntimeBuilder::llm_sim(...)",
505 )
506 })?;
507
508 backends
509 .provider_store
510 .set_default_model(default_model)
511 .await?;
512
513 for harness in &mut self.harnesses {
517 hydrate_plugin_refs(&mut harness.capabilities, &self.plugin_capability_configs);
518 }
519 for agent in &mut self.agents {
520 hydrate_plugin_refs(&mut agent.capabilities, &self.plugin_capability_configs);
521 }
522 for session in &mut self.sessions {
523 hydrate_plugin_refs(&mut session.capabilities, &self.plugin_capability_configs);
524 }
525
526 for harness in &self.harnesses {
527 backends.harness_store.add_harness(harness.clone()).await?;
528 }
529 for agent in &self.agents {
530 backends.agent_store.add_agent(agent.clone()).await?;
531 }
532 for session in &self.sessions {
533 backends.session_store.add_session(session.clone()).await?;
534 }
535
536 for session in &self.sessions {
537 seed_runtime_initial_files(
538 backends.harness_store.as_ref(),
539 backends.agent_store.as_ref(),
540 file_store.as_ref(),
541 session,
542 )
543 .await?;
544 }
545
546 for (session_id, file) in &self.seeded_files {
547 file_store.seed_initial_file(*session_id, file).await?;
548 }
549
550 let persisting_emitter =
551 PersistingEventEmitter::new(backends.event_bus.clone(), backends.message_store.clone());
552
553 let (session_task_registry, session_wake_queue) = match backends.session_task_registry {
559 Some(inner) => {
560 let wake_queue = Arc::new(everruns_core::SessionWakeQueue::new());
561 let observing = everruns_core::ObservingTaskRegistry::new(inner)
562 .with_observer(wake_queue.clone());
563 let wrapped: Arc<dyn everruns_core::session_task::SessionTaskRegistry> =
564 Arc::new(observing);
565 (Some(wrapped), Some(wake_queue))
566 }
567 None => (None, None),
568 };
569
570 Ok(InProcessRuntime {
571 platform_definition: Arc::new(self.platform_definition),
572 harness_store: backends.harness_store,
573 agent_store: backends.agent_store,
574 session_store: backends.session_store,
575 default_session_id: self.default_session_id,
576 message_store: backends.message_store,
577 provider_store: backends.provider_store,
578 event_bus: backends.event_bus,
579 persisting_emitter,
580 file_store,
581 storage_store: backends.storage_store,
582 connection_resolver: backends.connection_resolver,
583 session_task_registry,
584 session_wake_queue,
585 schedule_store_factory: backends.schedule_store_factory,
586 platform_store_factory: backends.platform_store_factory,
587 mcp_auth_provider: self
588 .mcp_auth_provider
589 .unwrap_or_else(|| Arc::new(everruns_mcp::NoAuthProvider)),
590 mcp_discovery_cache: Arc::new(crate::mcp_cache::McpDiscoveryCache::new()),
591 plugin_warnings: self.plugin_warnings,
592 })
593 }
594}
595
596async fn resolve_session_file_system(
597 platform_definition: &PlatformDefinition,
598 file_system_factory_context: SessionFileSystemFactoryContext,
599) -> Result<Arc<dyn SessionFileSystem>> {
600 let file_system_factory = platform_definition.session_file_system_factory();
601 if file_system_factory.is_disabled() {
602 Ok(Arc::new(InMemorySessionFileStore::new()))
603 } else {
604 Ok(file_system_factory
605 .create_session_file_system(file_system_factory_context)
606 .await?)
607 }
608}
609
610#[derive(Clone)]
611pub struct InProcessRuntime {
617 platform_definition: Arc<PlatformDefinition>,
618 harness_store: Arc<dyn RuntimeHarnessStore>,
619 agent_store: Arc<dyn RuntimeAgentStore>,
620 session_store: Arc<dyn RuntimeSessionStore>,
621 default_session_id: Option<SessionId>,
622 message_store: Arc<dyn RuntimeMessageStore>,
623 provider_store: Arc<dyn RuntimeProviderStore>,
624 event_bus: Arc<dyn EventBus>,
625 persisting_emitter: PersistingEventEmitter,
626 file_store: Arc<dyn SessionFileSystem>,
627 storage_store: Arc<dyn SessionStorageStore>,
628 connection_resolver: Option<Arc<dyn UserConnectionResolver>>,
629 session_task_registry: Option<Arc<dyn everruns_core::session_task::SessionTaskRegistry>>,
630 session_wake_queue: Option<Arc<everruns_core::SessionWakeQueue>>,
634 schedule_store_factory: Option<crate::backends::ScheduleStoreFactory>,
635 platform_store_factory: Option<crate::backends::PlatformStoreFactory>,
636 mcp_auth_provider: Arc<dyn everruns_mcp::McpAuthProvider>,
637 mcp_discovery_cache: Arc<crate::mcp_cache::McpDiscoveryCache>,
638 plugin_warnings: Vec<String>,
641}
642
643impl InProcessRuntime {
644 fn mcp_client(&self) -> Arc<everruns_mcp::McpClient> {
647 Arc::new(everruns_mcp::McpClient::new(
648 self.platform_definition.egress_service(),
649 self.mcp_auth_provider.clone(),
650 ))
651 }
652
653 async fn session_mcp_servers(
655 &self,
656 session: &Session,
657 agent: Option<&Agent>,
658 ) -> everruns_core::ScopedMcpServers {
659 let harness_chain = self
660 .harness_store
661 .get_harness_chain(session.harness_id)
662 .await
663 .unwrap_or_default();
664 let resolved = resolve_runtime_capabilities(
665 &harness_chain,
666 agent,
667 session,
668 self.platform_definition.capability_registry(),
669 );
670 let contributed = collect_capability_mcp_servers(
671 &resolved.resolved_capability_configs,
672 self.platform_definition.capability_registry(),
673 );
674 let explicit = crate::mcp::merge_session_scoped_servers(&harness_chain, agent, session);
675 everruns_core::merge_scoped_mcp_servers(&contributed, &explicit)
676 }
677 pub fn builder() -> InProcessRuntimeBuilder {
679 InProcessRuntimeBuilder::new()
680 }
681
682 pub fn default_session_id(&self) -> Option<SessionId> {
685 self.default_session_id
686 }
687
688 pub fn plugin_warnings(&self) -> &[String] {
693 &self.plugin_warnings
694 }
695
696 pub async fn activate_capability(
702 &self,
703 session_id: SessionId,
704 capability: impl Into<AgentCapabilityConfig>,
705 ) -> Result<CapabilityDelta> {
706 let mut capability = capability.into();
707 let registry = self.platform_definition.capability_registry();
708 let registered = registry.get(capability.capability_id()).ok_or_else(|| {
709 AgentLoopError::config(format!(
710 "unknown capability: {}",
711 capability.capability_id()
712 ))
713 })?;
714 if registered.status() != CapabilityStatus::Available {
715 return Err(AgentLoopError::config(format!(
716 "capability is not available: {}",
717 capability.capability_id()
718 )));
719 }
720 registered
721 .validate_config(&capability.config)
722 .map_err(|error| {
723 AgentLoopError::config(format!("invalid capability config: {error}"))
724 })?;
725
726 let canonical_id = registered.id().to_string();
727 capability.capability_ref = CapabilityId::new(canonical_id.clone());
728 let context = self.load_context(session_id).await?;
729 if context
730 .resolved_capability_configs
731 .iter()
732 .any(|config| config.capability_id() == canonical_id)
733 {
734 return Ok(CapabilityDelta {
735 capability_id: canonical_id,
736 changed: false,
737 active: true,
738 surfaces_dirty: false,
739 });
740 }
741
742 let mut candidate = context.effective_overlay.capabilities;
743 candidate.push(capability.clone());
744 resolve_capability_configs(&candidate, registry)
745 .map_err(|error| AgentLoopError::config(error.to_string()))?;
746
747 self.session_store
748 .upsert_session_capability(session_id, capability)
749 .await?;
750 self.mcp_discovery_cache
751 .invalidate_session(session_id.uuid());
752 Ok(CapabilityDelta {
753 capability_id: canonical_id,
754 changed: true,
755 active: true,
756 surfaces_dirty: true,
757 })
758 }
759
760 pub async fn deactivate_capability(
765 &self,
766 session_id: SessionId,
767 capability_id: &str,
768 ) -> Result<CapabilityDelta> {
769 let registry = self.platform_definition.capability_registry();
770 let registered = registry.get(capability_id).ok_or_else(|| {
771 AgentLoopError::config(format!("unknown capability: {capability_id}"))
772 })?;
773 let canonical_id = registered.id().to_string();
774 let context = self.load_context(session_id).await?;
775 if !context
776 .resolved_capability_configs
777 .iter()
778 .any(|config| config.capability_id() == canonical_id)
779 {
780 return Ok(CapabilityDelta {
781 capability_id: canonical_id,
782 changed: false,
783 active: false,
784 surfaces_dirty: false,
785 });
786 }
787
788 let session_capability_id = context
789 .session
790 .capabilities
791 .iter()
792 .find(|config| {
793 registry
794 .get(config.capability_id())
795 .is_some_and(|capability| capability.id() == canonical_id)
796 })
797 .map(|config| config.capability_id().to_string())
798 .ok_or_else(|| {
799 AgentLoopError::config(format!(
800 "capability {canonical_id} is inherited and cannot be deactivated at the session layer"
801 ))
802 })?;
803
804 self.session_store
805 .remove_session_capability(session_id, &session_capability_id)
806 .await?;
807 self.mcp_discovery_cache
808 .invalidate_session(session_id.uuid());
809 Ok(CapabilityDelta {
810 capability_id: canonical_id,
811 changed: true,
812 active: false,
813 surfaces_dirty: true,
814 })
815 }
816
817 pub async fn run_turn(
823 &self,
824 session_id: SessionId,
825 input: impl Into<InputMessage>,
826 ) -> Result<TurnResult> {
827 let session = self
828 .session_store
829 .get_session(session_id)
830 .await?
831 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
832
833 let input_message = self
838 .message_store
839 .add_input_message(session_id, input.into())
840 .await?;
841 self.event_bus
842 .emit(EventRequest::new(
843 session_id,
844 EventContext::empty(),
845 InputMessageData::new(input_message.clone()),
846 ))
847 .await?;
848
849 let assembled = self
850 .inspect_context_with_ids(session_id, session.harness_id, session.agent_id)
851 .await?;
852 let synthetic_agent_id = session
853 .agent_id
854 .unwrap_or_else(|| AgentId::from_uuid(session.id.uuid()));
855 let org_id = in_process_internal_org_id(&session.organization_id);
856 let mut state_machine = TurnStateMachine::new(
857 TurnContext::new(session_id, input_message.id, synthetic_agent_id, org_id),
858 assembled.runtime_agent.max_iterations,
859 );
860
861 let mut previous_response_id: Option<String> = None;
862 let mut last_reason_result: Option<everruns_core::ReasonResult> = None;
863
864 loop {
865 match state_machine.next_action() {
866 TurnAction::ExecuteInput => {
867 let ctx = state_machine.context();
868 let base_context =
869 AtomContext::new(ctx.session_id, ctx.turn_id, ctx.input_message_id)
870 .with_workspace_id(session.workspace_id);
871 execute_input_activity(
872 self,
873 org_id,
874 InputAtomInput {
875 context: base_context,
876 },
877 )
878 .await?;
879 state_machine.on_input_completed();
880 }
881 TurnAction::ExecuteReason => {
882 let ctx = state_machine.context();
883 let session_id = ctx.session_id;
884 self.drain_and_inject_wakes(session_id).await?;
890 let base_context =
891 AtomContext::new(ctx.session_id, ctx.turn_id, ctx.input_message_id)
892 .with_workspace_id(session.workspace_id);
893 let reason_result = execute_reason_activity(
894 self,
895 org_id,
896 ReasonInput {
897 context: base_context.next_exec(),
898 harness_id: session.harness_id,
899 agent_id: session.agent_id,
900 org_id,
901 mcp_tool_definitions: vec![],
902 previous_response_id: previous_response_id.take(),
903 iteration: state_machine.current_iteration() as u32 + 1,
904 },
905 )
906 .await?;
907 previous_response_id = reason_result.response_id.clone();
908 let has_pending_wakes = self
913 .session_wake_queue
914 .as_ref()
915 .is_some_and(|q| q.has_pending(session_id));
916 state_machine.on_reason_completed(
917 reason_result.text.clone(),
918 reason_result.has_tool_calls,
919 reason_result.tool_calls.len(),
920 reason_result.success,
921 reason_result.error.clone(),
922 has_pending_wakes,
923 );
924 if reason_result.has_tool_calls {
925 last_reason_result = Some(reason_result);
926 }
927 }
928 TurnAction::ExecuteAct => {
929 let reason_result = last_reason_result
930 .take()
931 .expect("ExecuteAct requires a prior ReasonResult");
932 let ctx = state_machine.context();
933 let base_context =
934 AtomContext::new(ctx.session_id, ctx.turn_id, ctx.input_message_id)
935 .with_workspace_id(session.workspace_id);
936 execute_act_activity(
937 self,
938 ActInput {
939 org_id: Some(org_id),
940 context: base_context.next_exec(),
941 harness_id: session.harness_id,
942 agent_id: session.agent_id,
943 tool_calls: reason_result.tool_calls,
944 tool_definitions: reason_result.tool_definitions,
945 locale: reason_result.locale,
946 blueprint_id: None,
947 network_access: reason_result.network_access,
948 parallel_tool_calls: reason_result.parallel_tool_calls,
951 },
952 )
953 .await?;
954 state_machine.on_act_completed();
955 }
956 TurnAction::Complete(outcome) => {
957 let ctx = state_machine.context();
958 let lifecycle =
959 RuntimeSessionLifecycle::new(self.clone(), org_id, ctx.session_id);
960 let turn_succeeded = matches!(
961 &outcome,
962 TurnOutcome::Success { .. } | TurnOutcome::MaxIterationsReached { .. }
963 );
964 match &outcome {
965 TurnOutcome::Success { iterations, .. }
966 | TurnOutcome::MaxIterationsReached { iterations, .. } => {
967 lifecycle
968 .turn_completed(
969 ctx.turn_id,
970 ctx.input_message_id,
971 *iterations as u32,
972 None,
973 None,
974 )
975 .await;
976 }
977 TurnOutcome::Failed { error, .. } => {
978 lifecycle
979 .turn_failed(ctx.turn_id, ctx.input_message_id, error, None)
980 .await;
981 }
982 TurnOutcome::Sealed {
983 reason, iterations, ..
984 } => {
985 lifecycle
986 .turn_sealed(
987 ctx.turn_id,
988 ctx.input_message_id,
989 reason.as_str(),
990 *iterations as u32,
991 None,
992 )
993 .await;
994 }
995 }
996 lifecycle
999 .fire_turn_end_hooks(
1000 session.harness_id,
1001 session.agent_id,
1002 ctx.turn_id,
1003 turn_succeeded,
1004 )
1005 .await;
1006 return Ok(TurnResult::from_outcome(outcome, ctx.turn_id));
1007 }
1008 }
1009 }
1010 }
1011
1012 pub async fn run_text_turn(
1013 &self,
1014 session_id: SessionId,
1015 text: impl Into<String>,
1016 ) -> Result<TurnResult> {
1017 self.run_turn(session_id, InputMessage::user(text)).await
1018 }
1019
1020 async fn drain_and_inject_wakes(&self, session_id: SessionId) -> Result<usize> {
1031 let Some(queue) = &self.session_wake_queue else {
1032 return Ok(0);
1033 };
1034 let wakes = queue.drain(session_id);
1035 if wakes.is_empty() {
1036 return Ok(0);
1037 }
1038 let count = wakes.len();
1039 for wake in wakes {
1040 let message = self
1045 .message_store
1046 .add_input_message(session_id, InputMessage::user(wake.text))
1047 .await?;
1048 self.event_bus
1049 .emit(EventRequest::new(
1050 session_id,
1051 EventContext::empty(),
1052 InputMessageData::new(message),
1053 ))
1054 .await?;
1055 }
1056 Ok(count)
1057 }
1058
1059 pub async fn messages(&self, session_id: SessionId) -> Result<Vec<Message>> {
1061 self.message_store.load(session_id).await
1062 }
1063
1064 pub async fn read_file(
1066 &self,
1067 session_id: SessionId,
1068 path: &str,
1069 ) -> Result<Option<SessionFile>> {
1070 self.file_store.read_file(session_id, path).await
1071 }
1072
1073 pub async fn load_context(&self, session_id: SessionId) -> Result<AssembledTurnContext> {
1075 let session = self
1076 .session_store
1077 .get_session(session_id)
1078 .await?
1079 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1080 self.inspect_context_with_ids(session_id, session.harness_id, session.agent_id)
1081 .await
1082 }
1083
1084 pub async fn events(&self) -> Result<Vec<Event>> {
1089 Ok(self.event_bus.collected_events().await)
1090 }
1091
1092 pub async fn execute_command(
1102 &self,
1103 session_id: SessionId,
1104 request: everruns_core::command::ExecuteCommandRequest,
1105 ) -> Result<everruns_core::command::CommandResult> {
1106 let ctx = self.load_context(session_id).await?;
1107 let registry = self.platform_definition.capability_registry();
1108 let host = everruns_core::command_host::StoreCommandHost::new(
1112 session_id,
1113 self.harness_store.clone(),
1114 self.agent_store.clone(),
1115 self.session_store.clone(),
1116 self.message_store.clone(),
1117 self.provider_store.clone(),
1118 registry.clone(),
1119 self.platform_definition.driver_registry().clone(),
1120 )
1121 .with_file_store(self.file_store.clone())
1122 .with_assembled_context(ctx.clone());
1123 let exec_ctx =
1124 everruns_core::command::CommandExecutionContext::new(session_id, Arc::new(host));
1125 for config in &ctx.resolved_capability_configs {
1126 let Some(capability) = registry.get(config.capability_id()) else {
1127 continue;
1128 };
1129 if capability.commands().iter().any(|c| c.name == request.name) {
1130 return capability.execute_command(&request, &exec_ctx).await;
1131 }
1132 }
1133 Err(AgentLoopError::config(format!(
1134 "no capability declares command /{}",
1135 request.name
1136 )))
1137 }
1138
1139 pub async fn list_commands(
1149 &self,
1150 session_id: SessionId,
1151 ) -> Result<Vec<everruns_core::command::CommandDescriptor>> {
1152 let ctx = self.load_context(session_id).await?;
1153 let registry = self.platform_definition.capability_registry();
1154 let mut seen = std::collections::HashSet::new();
1155 let mut commands = Vec::new();
1156 for config in &ctx.resolved_capability_configs {
1157 let Some(capability) = registry.get(config.capability_id()) else {
1158 continue;
1159 };
1160 for command in capability.commands() {
1161 if seen.insert(command.name.clone()) {
1162 commands.push(command);
1163 }
1164 }
1165 }
1166 Ok(commands)
1167 }
1168
1169 async fn inspect_context_with_ids(
1170 &self,
1171 session_id: SessionId,
1172 harness_id: everruns_core::HarnessId,
1173 agent_id: Option<AgentId>,
1174 ) -> Result<AssembledTurnContext> {
1175 inspect_turn_context(
1176 self.harness_store.as_ref(),
1177 self.agent_store.as_ref(),
1178 self.session_store.as_ref(),
1179 self.message_store.as_ref(),
1180 self.provider_store.as_ref(),
1181 self.platform_definition.capability_registry(),
1182 session_id,
1183 harness_id,
1184 agent_id,
1185 &[],
1186 Some(self.file_store.clone()),
1187 )
1188 .await
1189 }
1190}
1191
1192#[async_trait]
1193impl RuntimeHostAdapter for InProcessRuntime {
1194 async fn get_agent(&self, _org_id: i64, agent_id: AgentId) -> Result<Option<Agent>> {
1195 self.agent_store.get_agent(agent_id).await
1196 }
1197
1198 async fn get_harness(&self, _org_id: i64, harness_id: HarnessId) -> Result<Option<Harness>> {
1199 let chain = self.harness_store.get_harness_chain(harness_id).await?;
1200 Ok(chain.into_iter().last())
1201 }
1202
1203 async fn set_session_status(
1204 &self,
1205 _org_id: i64,
1206 session_id: SessionId,
1207 _status: SessionStatus,
1208 ) -> Result<Session> {
1209 self.session_store
1213 .get_session(session_id)
1214 .await?
1215 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))
1216 }
1217
1218 async fn load_turn_context(
1219 &self,
1220 _org_id: i64,
1221 session_id: SessionId,
1222 ) -> Result<RuntimeHostTurnContext> {
1223 let mut session = self
1224 .session_store
1225 .get_session(session_id)
1226 .await?
1227 .ok_or_else(|| AgentLoopError::store(format!("session not found: {session_id}")))?;
1228 everruns_core::ard_attachment::apply_session_attachments(
1231 self.storage_store.as_ref(),
1232 &mut session,
1233 )
1234 .await;
1235 let agent = match session.agent_id {
1236 Some(agent_id) => self.agent_store.get_agent(agent_id).await?,
1237 None => None,
1238 };
1239 let messages = self.message_store.load(session_id).await?;
1240 let model = self.provider_store.get_default_model().await?;
1241
1242 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1245 let mcp_tool_definitions = if scoped_servers.is_empty() {
1246 vec![]
1247 } else {
1248 crate::mcp::discover_tool_definitions(
1249 &self.mcp_discovery_cache,
1250 self.mcp_client(),
1251 session_id.uuid(),
1252 &scoped_servers,
1253 )
1254 .await
1255 };
1256
1257 Ok(RuntimeHostTurnContext {
1258 agent,
1259 session,
1260 messages,
1261 model,
1262 mcp_tool_definitions,
1263 })
1264 }
1265
1266 async fn mcp_executor(
1267 &self,
1268 _org_id: i64,
1269 session_id: SessionId,
1270 ) -> Option<Arc<everruns_mcp::McpExecutor>> {
1271 let session = self.session_store.get_session(session_id).await.ok()??;
1272 let agent = match session.agent_id {
1273 Some(agent_id) => self.agent_store.get_agent(agent_id).await.ok().flatten(),
1274 None => None,
1275 };
1276 let scoped_servers = self.session_mcp_servers(&session, agent.as_ref()).await;
1277 crate::mcp::build_executor(self.mcp_client(), &scoped_servers)
1278 }
1279
1280 fn capability_registry(&self) -> CapabilityRegistry {
1281 self.platform_definition.capability_registry().clone()
1282 }
1283
1284 fn driver_registry(&self) -> DriverRegistry {
1285 self.platform_definition.driver_registry().clone()
1286 }
1287
1288 fn harness_store(&self, _org_id: i64) -> Arc<dyn HarnessStore> {
1289 self.harness_store.clone()
1290 }
1291
1292 fn agent_store(&self, _org_id: i64) -> Arc<dyn AgentStore> {
1293 self.agent_store.clone()
1294 }
1295
1296 fn session_store(&self, _org_id: i64) -> Arc<dyn SessionStore> {
1297 self.session_store.clone()
1298 }
1299
1300 fn session_mutator(&self, _org_id: i64) -> Arc<dyn SessionMutator> {
1301 self.session_store.clone()
1302 }
1303
1304 fn provider_store(&self, _org_id: i64) -> Arc<dyn ProviderStore> {
1305 self.provider_store.clone()
1306 }
1307
1308 fn message_store(&self) -> Arc<dyn MessageRetriever> {
1309 self.message_store.clone()
1310 }
1311
1312 fn event_emitter(&self) -> Arc<dyn EventEmitter> {
1313 Arc::new(self.persisting_emitter.clone())
1314 }
1315
1316 fn file_store(&self) -> Arc<dyn SessionFileSystem> {
1317 self.file_store.clone()
1318 }
1319
1320 fn storage_store(&self) -> Option<Arc<dyn SessionStorageStore>> {
1321 Some(self.storage_store.clone())
1322 }
1323
1324 fn connection_resolver(&self) -> Option<Arc<dyn UserConnectionResolver>> {
1325 self.connection_resolver.clone()
1326 }
1327
1328 fn session_task_registry(
1329 &self,
1330 ) -> Option<Arc<dyn everruns_core::session_task::SessionTaskRegistry>> {
1331 self.session_task_registry.clone()
1332 }
1333
1334 fn schedule_store(
1335 &self,
1336 org_id: i64,
1337 ) -> Option<Arc<dyn everruns_core::traits::SessionScheduleStore>> {
1338 self.schedule_store_factory
1339 .as_ref()
1340 .map(|factory| factory(org_id))
1341 }
1342
1343 fn platform_store(
1344 &self,
1345 org_id: i64,
1346 session_id: SessionId,
1347 ) -> Option<Arc<dyn everruns_core::platform_store::PlatformStore>> {
1348 self.platform_store_factory
1349 .as_ref()
1350 .map(|factory| factory(org_id, session_id))
1351 }
1352
1353 fn utility_llm_service(&self) -> Option<Arc<dyn everruns_core::UtilityLlmService>> {
1354 Some(self.platform_definition.utility_llm_service())
1355 }
1356
1357 fn egress_service(&self) -> Option<Arc<dyn everruns_core::EgressService>> {
1358 Some(self.platform_definition.egress_service())
1359 }
1360}
1361
1362#[derive(Clone)]
1363struct PersistingEventEmitter {
1364 inner: Arc<dyn EventBus>,
1365 message_store: Arc<dyn RuntimeMessageStore>,
1366}
1367
1368impl PersistingEventEmitter {
1369 fn new(inner: Arc<dyn EventBus>, message_store: Arc<dyn RuntimeMessageStore>) -> Self {
1370 Self {
1371 inner,
1372 message_store,
1373 }
1374 }
1375}
1376
1377#[async_trait]
1378impl EventEmitter for PersistingEventEmitter {
1379 async fn emit(&self, request: EventRequest) -> Result<Event> {
1380 let event = self.inner.emit(request.clone()).await?;
1381 if let Some(message) = message_from_event(&event.data) {
1382 self.message_store
1383 .store_message(request.session_id, message)
1384 .await?;
1385 }
1386 Ok(event)
1387 }
1388}
1389
1390fn effective_overlay(
1391 harness_chain: &[Harness],
1392 agent: Option<&Agent>,
1393 session: &Session,
1394) -> AgentConfigOverlay {
1395 let harness_layers = harness_chain.iter().map(AgentConfigOverlay::from);
1396 let agent_layers = agent.into_iter().map(AgentConfigOverlay::from);
1397 AgentConfigOverlay::fold(
1398 harness_layers
1399 .chain(agent_layers)
1400 .chain([AgentConfigOverlay::from(session)]),
1401 )
1402}
1403
1404fn hydrate_plugin_refs(
1411 capabilities: &mut [AgentCapabilityConfig],
1412 plugin_configs: &[AgentCapabilityConfig],
1413) {
1414 for cap in capabilities.iter_mut() {
1415 let cap_id = cap.capability_id();
1416 if !everruns_core::is_plugin_capability(cap_id) {
1417 continue;
1418 }
1419 let is_bare = cap.config.is_null()
1421 || cap
1422 .config
1423 .as_object()
1424 .map(|o| o.is_empty())
1425 .unwrap_or(false);
1426 if !is_bare {
1427 continue;
1428 }
1429 if let Some(hydrated) = plugin_configs.iter().find(|c| c.capability_id() == cap_id) {
1430 cap.config = hydrated.config.clone();
1431 }
1432 }
1433}
1434
1435async fn seed_runtime_initial_files(
1436 harness_store: &dyn RuntimeHarnessStore,
1437 agent_store: &dyn RuntimeAgentStore,
1438 file_store: &dyn SessionFileSystem,
1439 session: &Session,
1440) -> Result<()> {
1441 let harness_chain = harness_store.get_harness_chain(session.harness_id).await?;
1442 if harness_chain.is_empty() {
1443 return Err(AgentLoopError::store(format!(
1444 "harness not found while seeding files: {}",
1445 session.harness_id
1446 )));
1447 }
1448 let agent = match session.agent_id {
1449 Some(agent_id) => Some(
1450 agent_store
1451 .get_agent(agent_id)
1452 .await?
1453 .ok_or_else(|| AgentLoopError::store(format!("agent not found: {agent_id}")))?,
1454 ),
1455 None => None,
1456 };
1457 let overlay = effective_overlay(&harness_chain, agent.as_ref(), session);
1458 let seed_key = SessionId::from_uuid(session.workspace_id.uuid());
1461 for file in &overlay.initial_files {
1462 file_store.seed_initial_file(seed_key, file).await?;
1463 }
1464 Ok(())
1465}
1466
1467fn message_from_event(data: &EventData) -> Option<Message> {
1468 match data {
1469 EventData::InputMessage(data) => Some(data.message.clone()),
1470 EventData::OutputMessageCompleted(OutputMessageCompletedData { message, .. }) => {
1471 Some(message.clone())
1472 }
1473 EventData::ToolCompleted(data) => Some(tool_completed_to_message(data.clone())),
1474 _ => None,
1475 }
1476}
1477
1478fn tool_completed_to_message(data: ToolCompletedData) -> Message {
1479 let mut images: Vec<ToolResultImage> = Vec::new();
1480 let metadata = tool_result_metadata(&data);
1481 let result = data.result.map(|parts| {
1482 for part in &parts {
1483 if let ContentPart::Image(img) = part
1484 && let (Some(base64), Some(media_type)) = (&img.base64, &img.media_type)
1485 {
1486 images.push(ToolResultImage {
1487 base64: base64.clone(),
1488 media_type: media_type.clone(),
1489 });
1490 }
1491 }
1492
1493 let text_parts: Vec<&ContentPart> = parts
1494 .iter()
1495 .filter(|part| matches!(part, ContentPart::Text(_)))
1496 .collect();
1497 if text_parts.len() == 1
1498 && let ContentPart::Text(text) = text_parts[0]
1499 {
1500 return parse_structured_tool_result_text(&text.text);
1501 }
1502 if !text_parts.is_empty() {
1503 serde_json::to_value(&text_parts).unwrap_or_default()
1504 } else {
1505 serde_json::Value::Null
1506 }
1507 });
1508
1509 let mut message = if images.is_empty() {
1510 Message::tool_result(&data.tool_call_id, result, data.error)
1511 } else {
1512 Message::tool_result_with_images(&data.tool_call_id, result, images)
1513 };
1514 message.metadata = metadata;
1515 message
1516}
1517
1518fn tool_result_metadata(
1519 data: &ToolCompletedData,
1520) -> Option<std::collections::HashMap<String, serde_json::Value>> {
1521 let mut metadata = std::collections::HashMap::new();
1522 metadata.insert("tool_name".to_string(), serde_json::json!(data.tool_name));
1523 if let Some(fingerprint) = &data.tool_call_fingerprint {
1524 metadata.insert(
1525 "tool_call_fingerprint".to_string(),
1526 serde_json::json!(fingerprint),
1527 );
1528 }
1529 if let Some(fingerprint) = &data.tool_result_fingerprint {
1530 metadata.insert(
1531 "tool_result_fingerprint".to_string(),
1532 serde_json::json!(fingerprint),
1533 );
1534 }
1535 (!metadata.is_empty()).then_some(metadata)
1536}
1537
1538fn parse_structured_tool_result_text(text: &str) -> serde_json::Value {
1539 let trimmed = text.trim_start();
1540 if !trimmed.starts_with('{') && !trimmed.starts_with('[') {
1541 return serde_json::Value::String(text.to_string());
1542 }
1543
1544 match serde_json::from_str(text) {
1545 Ok(value @ (serde_json::Value::Object(_) | serde_json::Value::Array(_))) => value,
1546 _ => serde_json::Value::String(text.to_string()),
1547 }
1548}
1549
1550#[cfg(test)]
1551mod tool_completed_replay_tests {
1552 use super::*;
1553
1554 #[test]
1555 fn tool_completed_replay_preserves_json_object_shape() {
1556 let data = ToolCompletedData::success(
1557 "call_read".to_string(),
1558 "read_file".to_string(),
1559 vec![ContentPart::text(
1560 serde_json::json!({
1561 "path": "/workspace/src/lib.rs",
1562 "content": "1|fn main() {}"
1563 })
1564 .to_string(),
1565 )],
1566 Some(1),
1567 );
1568
1569 let message = tool_completed_to_message(data);
1570 let result = message
1571 .tool_result_content()
1572 .and_then(|content| content.result.as_ref())
1573 .expect("tool result should be present");
1574
1575 assert_eq!(result["path"], "/workspace/src/lib.rs");
1576 assert_eq!(result["content"], "1|fn main() {}");
1577 }
1578
1579 #[test]
1580 fn tool_completed_replay_keeps_scalar_json_as_text() {
1581 let data = ToolCompletedData::success(
1582 "call_scalar".to_string(),
1583 "custom_tool".to_string(),
1584 vec![ContentPart::text("123")],
1585 Some(1),
1586 );
1587
1588 let message = tool_completed_to_message(data);
1589 let result = message
1590 .tool_result_content()
1591 .and_then(|content| content.result.as_ref())
1592 .expect("tool result should be present");
1593
1594 assert_eq!(result, &serde_json::Value::String("123".to_string()));
1595 }
1596
1597 #[test]
1598 fn tool_completed_replay_preserves_fingerprints_as_metadata() {
1599 let data = ToolCompletedData::success(
1600 "call_read".to_string(),
1601 "read_file".to_string(),
1602 vec![ContentPart::text("{}")],
1603 Some(1),
1604 )
1605 .with_fingerprints("sha256:call".to_string(), "sha256:result".to_string());
1606
1607 let message = tool_completed_to_message(data);
1608 let metadata = message.metadata.expect("metadata should be present");
1609
1610 assert_eq!(metadata["tool_name"], "read_file");
1611 assert_eq!(metadata["tool_call_fingerprint"], "sha256:call");
1612 assert_eq!(metadata["tool_result_fingerprint"], "sha256:result");
1613 }
1614}
1615
1616#[cfg(test)]
1617mod org_id_mapping_tests {
1618 use super::*;
1619 use everruns_core::{DEFAULT_ORG_ID, DEFAULT_ORG_PUBLIC_ID, org_public_id_from_internal};
1620
1621 #[test]
1622 fn default_public_id_maps_to_default_org() {
1623 assert_eq!(
1624 in_process_internal_org_id(DEFAULT_ORG_PUBLIC_ID),
1625 DEFAULT_ORG_ID
1626 );
1627 }
1628
1629 #[test]
1630 fn invalid_public_id_does_not_fall_back_to_default() {
1631 for invalid in [
1632 "",
1633 "not-an-org",
1634 "org_short",
1635 "org_ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
1636 "ORG_00000000000000000000000000000001",
1637 ] {
1638 let mapped = in_process_internal_org_id(invalid);
1639 assert_ne!(mapped, everruns_core::DEFAULT_ORG_ID);
1640 assert!(
1641 mapped >= 2,
1642 "invalid input {invalid:?} should not map to default"
1643 );
1644 }
1645 }
1646
1647 #[test]
1648 fn zero_public_id_does_not_fall_back_to_default() {
1649 let mapped = in_process_internal_org_id("org_00000000000000000000000000000000");
1652 assert_ne!(mapped, everruns_core::DEFAULT_ORG_ID);
1653 assert!(mapped >= 2, "all-zero id should not map to default");
1654 }
1655
1656 #[test]
1657 fn synthetic_public_id_round_trips_with_internal_helper() {
1658 for internal in [1_i64, 2, 42, 1_000_000, i64::MAX - 1, i64::MAX] {
1659 let public = org_public_id_from_internal(internal);
1660 assert_eq!(
1661 in_process_internal_org_id(&public),
1662 internal,
1663 "round-trip failed for internal={internal}"
1664 );
1665 }
1666 }
1667
1668 #[test]
1669 fn distinct_synthetic_ids_map_to_distinct_internal_ids() {
1670 let a = org_public_id_from_internal(7);
1671 let b = org_public_id_from_internal(8);
1672 assert_ne!(a, b);
1673 assert_ne!(
1674 in_process_internal_org_id(&a),
1675 in_process_internal_org_id(&b)
1676 );
1677 }
1678
1679 #[test]
1680 fn high_entropy_uuid_style_id_hashes_into_reserved_range() {
1681 let high = "org_80000000000000000000000000000000";
1685 let mapped = in_process_internal_org_id(high);
1686 assert!(mapped >= 2, "mapped id {mapped} must be >= 2");
1687 assert_ne!(mapped, DEFAULT_ORG_ID);
1688
1689 assert_eq!(mapped, in_process_internal_org_id(high));
1691 }
1692
1693 #[test]
1694 fn high_entropy_ids_are_isolated_from_each_other() {
1695 let a = in_process_internal_org_id("org_80000000000000000000000000000001");
1696 let b = in_process_internal_org_id("org_80000000000000000000000000000002");
1697 assert_ne!(a, b);
1698 assert_ne!(a, DEFAULT_ORG_ID);
1699 assert_ne!(b, DEFAULT_ORG_ID);
1700 }
1701
1702 #[test]
1703 fn hash_uses_stable_sha256_truncation() {
1704 let mapped = in_process_internal_org_id("org_80000000000000000000000000000000");
1708 let expected = {
1709 let digest = sha2::Sha256::digest(b"org_80000000000000000000000000000000");
1710 let mut buf = [0u8; 8];
1711 buf.copy_from_slice(&digest[..8]);
1712 let raw = u64::from_be_bytes(buf);
1713 ((raw % ((i64::MAX - 1) as u64)) as i64) + 2
1714 };
1715 assert_eq!(mapped, expected);
1716 }
1717
1718 #[test]
1719 fn oversize_input_is_bounded_and_does_not_collide_silently() {
1720 let oversize = "x".repeat(super::HASH_INPUT_CAP_BYTES * 4);
1726 let mapped = in_process_internal_org_id(&oversize);
1727 assert!(mapped >= 2);
1728 assert_ne!(mapped, DEFAULT_ORG_ID);
1729 }
1730}