everruns_local/runtime_builder.rs
1// LocalRuntimeBuilder — optional convenience sugar over InProcessRuntimeBuilder.
2//
3// This wires a LocalProfile + LocalBackends (task registry + schedule store)
4// and a real-disk workspace file store into an `InProcessRuntimeBuilder`. It is
5// strictly optional: everything it does can be done by composing the pieces in
6// `LocalBackends` directly. The platform store is intentionally NOT wired here
7// because the local platform store needs a `LocalSessionRunner` that usually
8// wraps the built runtime (a chicken/egg). Attach it after build via
9// `LocalBackends::with_platform_runner` and rebuild, or use the standalone
10// `LocalPlatformStore`.
11
12use std::sync::Arc;
13
14use everruns_core::error::Result;
15use everruns_core::traits::{SessionFileSystemFactory, SessionFileSystemFactoryContext};
16use everruns_core::{PlatformDefinition, ResolvedModel};
17use everruns_runtime::{
18 InProcessRuntime, InProcessRuntimeBuilder, RealDiskSessionFileSystemFactory,
19};
20
21use crate::backends::LocalBackends;
22use crate::profile::LocalProfile;
23
24/// Convenience wrapper around [`InProcessRuntimeBuilder`] that wires a local
25/// profile + SQLite-backed task/schedule stores and a workspace file store.
26pub struct LocalRuntimeBuilder {
27 profile: LocalProfile,
28 inner: InProcessRuntimeBuilder,
29 file_system_factory: Option<Arc<dyn SessionFileSystemFactory>>,
30 /// Caller-supplied platform definition override. When `Some`, `build()`
31 /// installs it as-is instead of the default local one; the caller owns the
32 /// session filesystem factory in that case.
33 platform_definition: Option<PlatformDefinition>,
34}
35
36impl LocalRuntimeBuilder {
37 /// Start from a profile. Defaults the platform definition to built-ins via
38 /// `InProcessRuntimeBuilder::new()`.
39 pub fn new(profile: LocalProfile) -> Self {
40 Self {
41 profile,
42 inner: InProcessRuntimeBuilder::new(),
43 file_system_factory: None,
44 platform_definition: None,
45 }
46 }
47
48 /// Replace the platform definition (capabilities, drivers, ...). When set,
49 /// `build()` respects this definition instead of constructing the default
50 /// local one, so capability/driver overrides take effect. The caller is
51 /// then responsible for the session filesystem factory on their definition.
52 pub fn platform_definition(mut self, platform_definition: PlatformDefinition) -> Self {
53 self.platform_definition = Some(platform_definition);
54 self
55 }
56
57 /// Register the built-in `llmsim` driver for deterministic local execution.
58 pub fn llm_sim(mut self, config: everruns_core::llmsim_driver::LlmSimConfig) -> Self {
59 self.inner = self.inner.llm_sim(config);
60 self
61 }
62
63 /// Set the runtime default model.
64 pub fn default_model(mut self, model: ResolvedModel) -> Self {
65 self.inner = self.inner.default_model(model);
66 self
67 }
68
69 /// Seed a harness.
70 pub fn harness(mut self, harness: everruns_core::Harness) -> Self {
71 self.inner = self.inner.harness(harness);
72 self
73 }
74
75 /// Seed an agent.
76 pub fn agent(mut self, agent: everruns_core::Agent) -> Self {
77 self.inner = self.inner.agent(agent);
78 self
79 }
80
81 /// Seed a session.
82 pub fn session(mut self, session: everruns_core::Session) -> Self {
83 self.inner = self.inner.session(session);
84 self
85 }
86
87 /// Override the session filesystem factory. By default a real-disk factory
88 /// rooted at `profile.workspace_root` is used.
89 pub fn session_file_system_factory(
90 mut self,
91 factory: Arc<dyn SessionFileSystemFactory>,
92 ) -> Self {
93 self.file_system_factory = Some(factory);
94 self
95 }
96
97 /// Access the underlying `InProcessRuntimeBuilder` for advanced wiring.
98 pub fn inner_mut(&mut self) -> &mut InProcessRuntimeBuilder {
99 &mut self.inner
100 }
101
102 /// Build the runtime and return it along with the constructed
103 /// [`LocalBackends`] so the embedder can attach a platform runner and reuse
104 /// the local stores. The task registry and schedule store factory are
105 /// installed on the runtime; the platform store is left to the embedder.
106 pub async fn build(self) -> Result<(InProcessRuntime, LocalBackends)> {
107 self.profile
108 .ensure_dirs()
109 .map_err(|e| everruns_core::AgentLoopError::config(e.to_string()))?;
110
111 let local = LocalBackends::new(
112 self.profile.clone(),
113 everruns_runtime::RuntimeBackends::in_memory(),
114 )?;
115
116 // Respect a caller-supplied platform definition; otherwise build the
117 // default local one rooted at the profile workspace. Only install the
118 // default when the caller has not overridden it, so capability/driver
119 // overrides via `platform_definition(...)` are not silently discarded.
120 let platform_definition = match self.platform_definition {
121 Some(pd) => pd,
122 None => {
123 let factory = self.file_system_factory.unwrap_or_else(|| {
124 Arc::new(RealDiskSessionFileSystemFactory::new(
125 self.profile.workspace_root.clone(),
126 ))
127 });
128 PlatformDefinition::builder()
129 .capability_registry(everruns_core::CapabilityRegistry::with_builtins())
130 .driver_registry(everruns_core::DriverRegistry::new())
131 .session_file_system_factory(factory)
132 .build()
133 }
134 };
135
136 let runtime = self
137 .inner
138 .platform_definition(platform_definition)
139 .session_file_system_factory_context(SessionFileSystemFactoryContext::new())
140 .backends(local.runtime_backends.clone())
141 .build()
142 .await?;
143
144 Ok((runtime, local))
145 }
146}