Skip to main content

lash_core/runtime/
host.rs

1use lash_trace::{TraceContext, TraceLevel, TraceSink};
2use std::sync::Arc;
3
4use super::process::{
5    InMemoryProcessExecutionEnvStore, ProcessEngineRegistry, ProcessExecutionEnvStore,
6    ProcessRegistry,
7};
8use super::{
9    EffectHost, InlineEffectHost, ProcessWorkDriver, QueuedWorkDriver, SessionStoreFactory,
10    TerminationPolicy,
11};
12
13/// Required host configuration for all runtimes.
14#[derive(Clone)]
15pub struct RuntimeHostConfig {
16    pub durability: RuntimeDurabilityConfig,
17    pub process_engines: ProcessEngineRegistry,
18    pub providers: RuntimeProviderConfig,
19    pub prompt: RuntimePromptConfig,
20    pub control: RuntimeControlConfig,
21    pub tracing: RuntimeTracingConfig,
22    /// Injected time source. Durable timestamps and timeout/backoff logic read
23    /// this rather than the OS clock directly, so replay is reproducible and
24    /// tests can drive time. Defaults to [`SystemClock`](super::SystemClock).
25    pub clock: Arc<dyn super::Clock>,
26}
27
28#[derive(Clone)]
29pub struct RuntimeDurabilityConfig {
30    pub attachment_store: Arc<dyn crate::AttachmentStore>,
31    pub process_env_store: Arc<dyn ProcessExecutionEnvStore>,
32}
33
34#[derive(Clone)]
35pub struct RuntimeProviderConfig {
36    pub provider_resolver: Arc<dyn crate::RuntimeProviderResolver>,
37}
38
39#[derive(Clone)]
40pub struct RuntimePromptConfig {
41    pub prompt: crate::PromptLayer,
42}
43
44#[derive(Clone)]
45pub struct RuntimeControlConfig {
46    pub effect_host: Arc<dyn EffectHost>,
47    pub process_cancel_ability: Arc<dyn crate::ProcessCancelAbility>,
48    pub termination: TerminationPolicy,
49    /// Lease/claim timing capability for every durable single-writer lane this
50    /// runtime claims: session execution leases, turn-input and queued-work
51    /// claims, and process leases. Effect-replay backends accept the same type
52    /// at construction. Defaults to [`LeaseTimings::default`] (30s TTL / 10s
53    /// renew).
54    pub lease_timings: crate::LeaseTimings,
55}
56
57#[derive(Clone)]
58pub struct RuntimeTracingConfig {
59    pub trace_sink: Option<Arc<dyn TraceSink>>,
60    pub trace_level: TraceLevel,
61    pub trace_context: TraceContext,
62}
63
64impl RuntimeHostConfig {
65    /// Construct a config with the three host-owned dependencies named
66    /// explicitly.
67    ///
68    /// There is intentionally no `Default`. The effect host and stores decide
69    /// a runtime's durability, so hosts must choose them rather than silently
70    /// inheriting in-memory implementations. Use [`RuntimeHostConfig::in_memory`]
71    /// to opt into the in-process / in-memory versions by name.
72    pub fn new(
73        effect_host: Arc<dyn EffectHost>,
74        attachment_store: Arc<dyn crate::AttachmentStore>,
75        process_env_store: Arc<dyn ProcessExecutionEnvStore>,
76    ) -> Self {
77        Self {
78            durability: RuntimeDurabilityConfig {
79                attachment_store,
80                process_env_store,
81            },
82            process_engines: ProcessEngineRegistry::new(),
83            providers: RuntimeProviderConfig {
84                provider_resolver: Arc::new(crate::EmptyProviderResolver),
85            },
86            prompt: RuntimePromptConfig {
87                prompt: crate::PromptLayer::new(),
88            },
89            control: RuntimeControlConfig {
90                termination: TerminationPolicy::default(),
91                effect_host,
92                process_cancel_ability: Arc::new(crate::DefaultProcessCancelAbility),
93                lease_timings: crate::LeaseTimings::default(),
94            },
95            tracing: RuntimeTracingConfig {
96                trace_sink: None,
97                trace_level: TraceLevel::Standard,
98                trace_context: TraceContext::default(),
99            },
100            clock: Arc::new(super::SystemClock),
101        }
102    }
103
104    /// Replace the runtime time source. Hosts that need deterministic replay or
105    /// test-driven time inject their own [`Clock`](super::Clock); the default is
106    /// [`SystemClock`](super::SystemClock).
107    pub fn with_clock(mut self, clock: Arc<dyn super::Clock>) -> Self {
108        self.clock = clock;
109        self
110    }
111
112    /// Explicit in-process / in-memory configuration: an
113    /// [`InlineEffectHost`] and in-memory stores.
114    ///
115    /// Convenient for tests and local experiments; not durable. Named so the
116    /// choice is never silent.
117    pub fn in_memory() -> Self {
118        Self::new(
119            Arc::new(InlineEffectHost::default()),
120            Arc::new(crate::InMemoryAttachmentStore::new()),
121            Arc::new(InMemoryProcessExecutionEnvStore::new()),
122        )
123    }
124
125    pub fn with_process_env_store(
126        mut self,
127        process_env_store: Arc<dyn ProcessExecutionEnvStore>,
128    ) -> Self {
129        self.durability.process_env_store = process_env_store;
130        self
131    }
132
133    pub fn with_process_cancel_ability(
134        mut self,
135        process_cancel_ability: Arc<dyn crate::ProcessCancelAbility>,
136    ) -> Self {
137        self.control.process_cancel_ability = process_cancel_ability;
138        self
139    }
140
141    pub fn with_process_engine(mut self, engine: Arc<dyn crate::ProcessEngine>) -> Self {
142        self.process_engines = self.process_engines.with_engine(engine);
143        self
144    }
145
146    /// Replace the lease timing capability governing every durable lease and
147    /// claim this runtime takes.
148    pub fn with_lease_timings(mut self, lease_timings: crate::LeaseTimings) -> Self {
149        self.control.lease_timings = lease_timings;
150        self
151    }
152}
153
154/// Base host shape for embedded runtimes.
155#[derive(Clone)]
156pub struct EmbeddedRuntimeHost {
157    pub core: RuntimeHostConfig,
158    pub session_store_factory: Option<Arc<dyn SessionStoreFactory>>,
159    pub trigger_store: Option<Arc<dyn crate::TriggerStore>>,
160}
161
162impl EmbeddedRuntimeHost {
163    pub fn new(core: RuntimeHostConfig) -> Self {
164        let clock = Arc::clone(&core.clock);
165        Self {
166            core,
167            session_store_factory: None,
168            trigger_store: Some(Arc::new(crate::InMemoryTriggerStore::with_clock(clock))),
169        }
170    }
171
172    pub fn with_session_store_factory(
173        mut self,
174        session_store_factory: Arc<dyn SessionStoreFactory>,
175    ) -> Self {
176        self.session_store_factory = Some(session_store_factory);
177        self
178    }
179
180    pub fn with_trigger_store(mut self, store: Arc<dyn crate::TriggerStore>) -> Self {
181        self.trigger_store = Some(store);
182        self
183    }
184}
185
186/// Host shape for runtimes that support background plugin work.
187#[derive(Clone)]
188pub struct ProcessRuntimeHost {
189    pub embedded: EmbeddedRuntimeHost,
190    pub process_registry: Arc<dyn ProcessRegistry>,
191    pub process_work_driver: Option<ProcessWorkDriver>,
192    pub queued_work_driver: Option<QueuedWorkDriver>,
193}
194
195impl ProcessRuntimeHost {
196    pub fn new(embedded: EmbeddedRuntimeHost, process_registry: Arc<dyn ProcessRegistry>) -> Self {
197        Self {
198            embedded,
199            process_registry,
200            process_work_driver: None,
201            queued_work_driver: None,
202        }
203    }
204
205    pub fn with_process_work_driver(mut self, driver: ProcessWorkDriver) -> Self {
206        self.process_work_driver = Some(driver);
207        self
208    }
209
210    pub fn with_queued_work_driver(mut self, driver: QueuedWorkDriver) -> Self {
211        self.queued_work_driver = Some(driver);
212        self
213    }
214}
215
216#[derive(Clone)]
217pub(crate) struct RuntimeHost {
218    pub core: RuntimeHostConfig,
219    pub session_store_factory: Option<Arc<dyn SessionStoreFactory>>,
220    pub trigger_store: Option<Arc<dyn crate::TriggerStore>>,
221    pub process_registry: Option<Arc<dyn ProcessRegistry>>,
222    /// Host-owned process work driver. Absent when no process registry is wired.
223    pub process_work_driver: Option<ProcessWorkDriver>,
224    /// Host-owned queued work driver. Absent when queued work is delegated to an
225    /// external host or no session store exists.
226    pub queued_work_driver: Option<QueuedWorkDriver>,
227}
228
229impl RuntimeHost {
230    pub(crate) fn resolve_session_policy(
231        &self,
232        session_id: &str,
233        policy: crate::SessionPolicy,
234    ) -> Result<crate::RuntimeSessionPolicy, crate::SessionError> {
235        let provider_id = policy.recorded_provider_id();
236        let mut binding = self
237            .core
238            .providers
239            .provider_resolver
240            .resolve_provider_binding(provider_id)
241            .map_err(|err| match err {
242                crate::ProviderResolutionError::MissingProviderId => {
243                    crate::SessionError::ProviderUnconfigured {
244                        session_id: session_id.to_string(),
245                    }
246                }
247                crate::ProviderResolutionError::UnknownProvider { provider_id } => {
248                    crate::SessionError::ProviderUnavailable {
249                        provider_id,
250                        session_id: session_id.to_string(),
251                    }
252                }
253                crate::ProviderResolutionError::ProviderIdMismatch { expected, actual } => {
254                    crate::SessionError::ProviderMismatch {
255                        expected,
256                        actual,
257                        session_id: session_id.to_string(),
258                    }
259                }
260            })?;
261        binding.provider = binding.provider.with_clock(Arc::clone(&self.core.clock));
262        Ok(crate::RuntimeSessionPolicy::new(policy, binding))
263    }
264}
265
266impl From<EmbeddedRuntimeHost> for RuntimeHost {
267    fn from(value: EmbeddedRuntimeHost) -> Self {
268        Self {
269            core: value.core,
270            session_store_factory: value.session_store_factory,
271            trigger_store: value.trigger_store,
272            process_registry: None,
273            process_work_driver: None,
274            queued_work_driver: None,
275        }
276    }
277}
278
279impl From<ProcessRuntimeHost> for RuntimeHost {
280    fn from(value: ProcessRuntimeHost) -> Self {
281        Self {
282            core: value.embedded.core,
283            session_store_factory: value.embedded.session_store_factory,
284            trigger_store: value.embedded.trigger_store,
285            process_registry: Some(value.process_registry),
286            process_work_driver: value.process_work_driver,
287            queued_work_driver: value.queued_work_driver,
288        }
289    }
290}