lash_core/runtime/
environment.rs1use std::sync::Arc;
28
29use lash_trace::{TraceContext, TraceLevel, TraceSink};
30
31#[cfg(test)]
32use super::InlineEffectHost;
33use super::process::ProcessRegistry;
34use super::{EffectHost, RuntimeHostConfig, TerminationPolicy};
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
45pub enum Residency {
46 #[default]
49 KeepAll,
50 ActivePathOnly,
56}
57
58#[derive(Clone)]
66pub struct RuntimeEnvironment {
67 pub plugin_host: Option<Arc<crate::PluginHost>>,
70
71 pub residency: Residency,
77
78 pub process_registry: Option<Arc<dyn ProcessRegistry>>,
80
81 pub trigger_store: Option<Arc<dyn crate::TriggerStore>>,
83
84 pub session_store_factory: Option<Arc<dyn crate::SessionStoreFactory>>,
87
88 pub process_work_driver: Option<super::ProcessWorkDriver>,
91
92 pub queued_work_driver: Option<super::QueuedWorkDriver>,
96
97 pub core: RuntimeHostConfig,
98}
99
100impl RuntimeEnvironment {
101 pub fn builder() -> RuntimeEnvironmentBuilder {
102 RuntimeEnvironmentBuilder::default()
103 }
104}
105
106pub struct ParkedSession {
111 pub(crate) session_id: String,
112 pub(crate) store: Arc<dyn crate::store::RuntimePersistence>,
113 pub(crate) policy: crate::SessionPolicy,
114}
115
116impl ParkedSession {
117 pub fn session_id(&self) -> &str {
118 &self.session_id
119 }
120}
121
122pub struct RuntimeEnvironmentBuilder {
124 env: RuntimeEnvironment,
125}
126
127impl Default for RuntimeEnvironmentBuilder {
128 fn default() -> Self {
129 Self {
134 env: RuntimeEnvironment {
135 plugin_host: None,
136 residency: Residency::default(),
137 process_registry: None,
138 trigger_store: Some(Arc::new(crate::InMemoryTriggerStore::default())),
139 session_store_factory: None,
140 process_work_driver: None,
141 queued_work_driver: None,
142 core: RuntimeHostConfig::in_memory(),
143 },
144 }
145 }
146}
147
148impl RuntimeEnvironmentBuilder {
149 pub fn with_plugin_host(mut self, host: Arc<crate::PluginHost>) -> Self {
150 self.env.plugin_host = Some(host);
151 self
152 }
153
154 pub fn with_residency(mut self, residency: Residency) -> Self {
155 self.env.residency = residency;
156 self
157 }
158
159 pub fn with_process_registry(mut self, process_registry: Arc<dyn ProcessRegistry>) -> Self {
160 self.env.process_registry = Some(process_registry);
161 self
162 }
163
164 pub fn with_trigger_store(mut self, store: Arc<dyn crate::TriggerStore>) -> Self {
165 self.env.trigger_store = Some(store);
166 self
167 }
168
169 pub fn with_session_store_factory(
170 mut self,
171 factory: Arc<dyn crate::SessionStoreFactory>,
172 ) -> Self {
173 self.env.session_store_factory = Some(factory);
174 self
175 }
176
177 pub fn with_process_work_driver(mut self, driver: super::ProcessWorkDriver) -> Self {
180 self.env.process_work_driver = Some(driver);
181 self
182 }
183
184 pub fn with_queued_work_driver(mut self, driver: super::QueuedWorkDriver) -> Self {
185 self.env.queued_work_driver = Some(driver);
186 self
187 }
188
189 pub fn with_runtime_host_config(mut self, core: RuntimeHostConfig) -> Self {
190 self.env.core = core;
191 self
192 }
193
194 pub fn with_attachment_store(mut self, store: Arc<dyn crate::AttachmentStore>) -> Self {
195 self.env.core.durability.attachment_store = store;
196 self
197 }
198
199 pub fn with_prompt_template(mut self, template: crate::PromptTemplate) -> Self {
200 self.env.core.prompt.prompt.template = Some(template);
201 self
202 }
203
204 pub fn with_prompt_contribution(mut self, contribution: crate::PromptContribution) -> Self {
205 self.env.core.prompt.prompt.add_contribution(contribution);
206 self
207 }
208
209 pub fn with_replaced_prompt_slot(
210 mut self,
211 slot: crate::PromptSlot,
212 contributions: impl IntoIterator<Item = crate::PromptContribution>,
213 ) -> Self {
214 self.env
215 .core
216 .prompt
217 .prompt
218 .replace_slot(slot, contributions);
219 self
220 }
221
222 pub fn with_cleared_prompt_slot(mut self, slot: crate::PromptSlot) -> Self {
223 self.env.core.prompt.prompt.clear_slot(slot);
224 self
225 }
226
227 pub fn with_prompt_layer(mut self, prompt: crate::PromptLayer) -> Self {
228 self.env.core.prompt.prompt = prompt;
229 self
230 }
231
232 pub fn with_trace_sink(mut self, sink: Option<Arc<dyn TraceSink>>) -> Self {
233 self.env.core.tracing.trace_sink = sink;
234 self
235 }
236
237 pub fn with_trace_level(mut self, level: TraceLevel) -> Self {
238 self.env.core.tracing.trace_level = level;
239 self
240 }
241
242 pub fn with_trace_context(mut self, context: TraceContext) -> Self {
243 self.env.core.tracing.trace_context = context;
244 self
245 }
246
247 pub fn with_termination(mut self, termination: TerminationPolicy) -> Self {
248 self.env.core.control.termination = termination;
249 self
250 }
251
252 pub fn with_effect_host(mut self, effect_host: Arc<dyn EffectHost>) -> Self {
253 self.env.core.control.effect_host = effect_host;
254 self
255 }
256
257 pub fn with_provider_resolver(
258 mut self,
259 provider_resolver: Arc<dyn crate::RuntimeProviderResolver>,
260 ) -> Self {
261 self.env.core.providers.provider_resolver = provider_resolver;
262 self
263 }
264
265 pub fn build(self) -> RuntimeEnvironment {
266 self.env
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn builder_methods_configure_runtime_host() {
276 let attachment_store: Arc<dyn crate::AttachmentStore> =
277 Arc::new(crate::InMemoryAttachmentStore::new());
278 let effect_host: Arc<dyn EffectHost> = Arc::new(InlineEffectHost::default());
279 let trace_context = TraceContext::default().for_session("session-1");
280 let termination = TerminationPolicy {
281 treat_missing_done_as_failure: false,
282 };
283
284 let env = RuntimeEnvironment::builder()
285 .with_attachment_store(Arc::clone(&attachment_store))
286 .with_prompt_template(crate::default_prompt_template())
287 .with_trace_sink(Some(Arc::new(lash_trace::JsonlTraceSink::new(
288 std::env::temp_dir().join("lash-runtime-environment-builder-test.jsonl"),
289 ))))
290 .with_trace_level(TraceLevel::Extended)
291 .with_trace_context(trace_context.clone())
292 .with_termination(termination.clone())
293 .with_effect_host(Arc::clone(&effect_host))
294 .build();
295
296 assert!(Arc::ptr_eq(
297 &env.core.durability.attachment_store,
298 &attachment_store
299 ));
300 assert!(env.core.prompt.prompt.template.is_some());
301 assert!(env.core.tracing.trace_sink.is_some());
302 assert_eq!(env.core.tracing.trace_level, TraceLevel::Extended);
303 assert_eq!(env.core.tracing.trace_context, trace_context);
304 assert_eq!(
305 env.core.control.termination.treat_missing_done_as_failure,
306 termination.treat_missing_done_as_failure
307 );
308 assert!(Arc::ptr_eq(&env.core.control.effect_host, &effect_host));
309 }
310
311 #[test]
312 fn runtime_host_config_replaces_core_config() {
313 let mut core = RuntimeHostConfig::in_memory();
314 core.tracing.trace_level = TraceLevel::Extended;
315 core.control.termination = TerminationPolicy {
316 treat_missing_done_as_failure: false,
317 };
318
319 let env = RuntimeEnvironment::builder()
320 .with_trace_level(TraceLevel::Standard)
321 .with_runtime_host_config(core)
322 .build();
323
324 assert_eq!(env.core.tracing.trace_level, TraceLevel::Extended);
325 assert!(!env.core.control.termination.treat_missing_done_as_failure);
326 }
327
328 #[test]
329 fn runtime_environment_does_not_mirror_runtime_host_config_fields() {
330 let source = std::fs::read_to_string(
331 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/runtime/environment.rs"),
332 )
333 .expect("read environment source");
334 for field in [
335 ["pub ", "attachment_store:"].concat(),
336 ["pub ", "prompt:"].concat(),
337 ["pub ", "trace_sink:"].concat(),
338 ["pub ", "trace_level:"].concat(),
339 ["pub ", "trace_context:"].concat(),
340 ["pub ", "termination:"].concat(),
341 ["pub ", "effect_host:"].concat(),
342 ["mirror ", "`RuntimeHostConfig`"].concat(),
343 ] {
344 assert!(
345 !source.contains(&field),
346 "found mirrored field/comment: {field}"
347 );
348 }
349 }
350}