Skip to main content

everruns_core/
platform_definition.rs

1//! Platform definition for embeddable Everruns deployments.
2//!
3//! `PlatformDefinition` is the shared composition root for server and worker
4//! runtime surface. Embedders can add or remove capabilities, LLM drivers,
5//! connection providers, and built-in harness templates without patching
6//! internal startup code.
7//!
8//! Server-only concerns such as route wiring, auth backends, and background
9//! task scheduling stay outside this module so the type can be reused from any
10//! binary crate. Platform-wide host services belong here as factories, not as
11//! server-owned concrete dependencies.
12
13use crate::{
14    Capability, CapabilityRegistry, Connector, ConnectorRegistry, DriverRegistry, EgressService,
15    EmailSender, UtilityLlmService,
16    traits::{DisabledSessionFileSystemFactory, SessionFileSystemFactory},
17    vector_store::{InMemoryVectorStore, VectorStore},
18};
19use serde_json::Value;
20use std::sync::Arc;
21
22/// Stable role assigned to a built-in harness template.
23///
24/// Roles let the server resolve special harness behavior without assuming a
25/// specific harness name. For example, a platform can provide a base harness
26/// named "Minimal" and still mark it as the `Base` harness.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum BuiltInHarnessRole {
29    /// Harness used when session creation omits `harness_id` and the org has no
30    /// explicit `base_harness_id` configured yet.
31    Base,
32    /// Harness selected as the default in organization settings when the org is
33    /// first initialized.
34    Default,
35    /// Harness used by the global chat endpoint.
36    Chat,
37}
38
39/// Capability entry for a built-in harness template.
40#[derive(Debug, Clone)]
41pub struct BuiltInCapabilityDefinition {
42    /// Capability identifier.
43    pub id: String,
44    /// Per-harness capability config passed to capability resolution.
45    pub config: Value,
46}
47
48impl BuiltInCapabilityDefinition {
49    /// Create a capability entry with an empty config object.
50    pub fn new(id: impl Into<String>) -> Self {
51        Self {
52            id: id.into(),
53            config: serde_json::json!({}),
54        }
55    }
56
57    /// Create a capability entry with explicit config.
58    pub fn with_config(id: impl Into<String>, config: Value) -> Self {
59        Self {
60            id: id.into(),
61            config,
62        }
63    }
64}
65
66/// Built-in harness template provisioned by a platform definition.
67///
68/// Built-in harnesses are identified by `name` (unique per org). IDs are
69/// assigned by the seeder at provisioning time — never hardcoded.
70#[derive(Debug, Clone)]
71pub struct BuiltInHarnessDefinition {
72    /// Name, unique per org (e.g. "generic").
73    pub name: String,
74    /// Human-readable display name shown in UI.
75    pub display_name: String,
76    /// Human-readable description.
77    pub description: String,
78    /// Base system prompt for the harness.
79    pub system_prompt: String,
80    /// Optional parent harness name to inherit from during provisioning.
81    pub parent_name: Option<String>,
82    /// Tags applied to the harness.
83    pub tags: Vec<String>,
84    /// Capabilities enabled by default for the harness.
85    pub capabilities: Vec<BuiltInCapabilityDefinition>,
86    /// Special roles for platform behavior.
87    pub roles: Vec<BuiltInHarnessRole>,
88}
89
90impl BuiltInHarnessDefinition {
91    /// Create a built-in harness template.
92    pub fn new(
93        name: impl Into<String>,
94        display_name: impl Into<String>,
95        description: impl Into<String>,
96        system_prompt: impl Into<String>,
97    ) -> Self {
98        Self {
99            name: name.into(),
100            display_name: display_name.into(),
101            description: description.into(),
102            system_prompt: system_prompt.into(),
103            parent_name: None,
104            tags: Vec::new(),
105            capabilities: Vec::new(),
106            roles: Vec::new(),
107        }
108    }
109
110    /// Replace the harness tags.
111    pub fn with_tags<I, S>(mut self, tags: I) -> Self
112    where
113        I: IntoIterator<Item = S>,
114        S: Into<String>,
115    {
116        self.tags = tags.into_iter().map(Into::into).collect();
117        self
118    }
119
120    /// Set the parent harness name used for inheritance during provisioning.
121    pub fn with_parent_name(mut self, parent_name: impl Into<String>) -> Self {
122        self.parent_name = Some(parent_name.into());
123        self
124    }
125
126    /// Replace the harness capabilities.
127    pub fn with_capabilities<I>(mut self, capabilities: I) -> Self
128    where
129        I: IntoIterator<Item = BuiltInCapabilityDefinition>,
130    {
131        self.capabilities = capabilities.into_iter().collect();
132        self
133    }
134
135    /// Replace the harness roles.
136    pub fn with_roles<I>(mut self, roles: I) -> Self
137    where
138        I: IntoIterator<Item = BuiltInHarnessRole>,
139    {
140        self.roles = roles.into_iter().collect();
141        self
142    }
143
144    /// Check whether this harness has a specific role.
145    pub fn has_role(&self, role: BuiltInHarnessRole) -> bool {
146        self.roles.contains(&role)
147    }
148}
149
150/// Shared definition of the Everruns platform surface.
151///
152/// `PlatformDefinition` lets an embedder decide which capabilities, LLM
153/// drivers, connection providers, built-in harness templates, and platform
154/// service factories exist at runtime. Server and worker code should consume
155/// the same definition so the control plane and execution plane stay aligned.
156///
157/// # Example
158///
159/// ```rust,ignore
160/// use everruns_core::{
161///     BuiltInCapabilityDefinition, BuiltInHarnessDefinition, BuiltInHarnessRole,
162///     DriverRegistry, PlatformDefinition,
163/// };
164///
165/// let mut drivers = DriverRegistry::new();
166/// everruns_openai::register_driver(&mut drivers);
167///
168/// let platform = PlatformDefinition::builder()
169///     .driver_registry(drivers)
170///     .capability(everruns_core::CurrentTimeCapability)
171///     .add_built_in_harness(
172///         BuiltInHarnessDefinition::new(
173///             "minimal",
174///             "Minimal",
175///             "Small default harness for an embedded deployment.",
176///             "You are a helpful assistant.",
177///         )
178///         .with_roles([BuiltInHarnessRole::Base, BuiltInHarnessRole::Default])
179///         .with_capabilities([BuiltInCapabilityDefinition::new("current_time")]),
180///     )
181///     .build();
182/// ```
183#[derive(Clone)]
184pub struct PlatformDefinition {
185    capability_registry: CapabilityRegistry,
186    driver_registry: DriverRegistry,
187    connectors: ConnectorRegistry,
188    built_in_harnesses: Vec<BuiltInHarnessDefinition>,
189    egress_service: Arc<dyn EgressService>,
190    email_sender: Arc<dyn EmailSender>,
191    utility_llm_service: Arc<dyn UtilityLlmService>,
192    session_file_system_factory: Arc<dyn SessionFileSystemFactory>,
193    vector_store: Arc<dyn VectorStore>,
194}
195
196impl PlatformDefinition {
197    /// Create a platform definition from explicit registries.
198    pub fn new(capability_registry: CapabilityRegistry, driver_registry: DriverRegistry) -> Self {
199        Self {
200            capability_registry,
201            driver_registry,
202            connectors: ConnectorRegistry::new(),
203            built_in_harnesses: Vec::new(),
204            egress_service: Arc::new(crate::DirectEgressService::default()),
205            email_sender: Arc::new(crate::DisabledEmailSender),
206            utility_llm_service: Arc::new(crate::DisabledUtilityLlmService),
207            session_file_system_factory: Arc::new(DisabledSessionFileSystemFactory),
208            vector_store: Arc::new(InMemoryVectorStore::new()),
209        }
210    }
211
212    /// Create a builder for fluent platform composition.
213    pub fn builder() -> PlatformDefinitionBuilder {
214        PlatformDefinitionBuilder::new()
215    }
216
217    /// Immutable access to the capability registry.
218    pub fn capability_registry(&self) -> &CapabilityRegistry {
219        &self.capability_registry
220    }
221
222    /// Mutable access to the capability registry.
223    pub fn capability_registry_mut(&mut self) -> &mut CapabilityRegistry {
224        &mut self.capability_registry
225    }
226
227    /// Immutable access to the driver registry.
228    pub fn driver_registry(&self) -> &DriverRegistry {
229        &self.driver_registry
230    }
231
232    /// Mutable access to the driver registry.
233    pub fn driver_registry_mut(&mut self) -> &mut DriverRegistry {
234        &mut self.driver_registry
235    }
236
237    /// Immutable access to the connector registry.
238    pub fn connectors(&self) -> &ConnectorRegistry {
239        &self.connectors
240    }
241
242    /// Mutable access to the connector registry.
243    pub fn connectors_mut(&mut self) -> &mut ConnectorRegistry {
244        &mut self.connectors
245    }
246
247    /// Built-in harness templates provisioned by this platform.
248    pub fn built_in_harnesses(&self) -> &[BuiltInHarnessDefinition] {
249        &self.built_in_harnesses
250    }
251
252    /// Mutable access to the built-in harness templates.
253    pub fn built_in_harnesses_mut(&mut self) -> &mut Vec<BuiltInHarnessDefinition> {
254        &mut self.built_in_harnesses
255    }
256
257    /// System-wide outbound network boundary.
258    pub fn egress_service(&self) -> Arc<dyn EgressService> {
259        self.egress_service.clone()
260    }
261
262    /// System-wide email sender for product and operational flows.
263    pub fn email_sender(&self) -> Arc<dyn EmailSender> {
264        self.email_sender.clone()
265    }
266
267    /// System-wide utility LLM service for capability internals.
268    pub fn utility_llm_service(&self) -> Arc<dyn UtilityLlmService> {
269        self.utility_llm_service.clone()
270    }
271
272    /// Factory for the platform-selected session filesystem implementation.
273    pub fn session_file_system_factory(&self) -> Arc<dyn SessionFileSystemFactory> {
274        self.session_file_system_factory.clone()
275    }
276
277    /// Platform-selected vector store backing Knowledge Index embeddings.
278    /// Defaults to the in-memory backend; production deployments override it.
279    pub fn vector_store(&self) -> Arc<dyn VectorStore> {
280        self.vector_store.clone()
281    }
282
283    /// Append a built-in harness template.
284    pub fn add_built_in_harness(&mut self, harness: BuiltInHarnessDefinition) {
285        self.built_in_harnesses.push(harness);
286    }
287
288    /// Find the first built-in harness with the requested role.
289    pub fn harness_for_role(&self, role: BuiltInHarnessRole) -> Option<&BuiltInHarnessDefinition> {
290        self.built_in_harnesses.iter().find(|h| h.has_role(role))
291    }
292}
293
294impl Default for PlatformDefinition {
295    fn default() -> Self {
296        Self::new(CapabilityRegistry::new(), DriverRegistry::new())
297    }
298}
299
300impl std::fmt::Debug for PlatformDefinition {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        let harness_keys: Vec<_> = self.built_in_harnesses.iter().map(|h| &h.name).collect();
303        f.debug_struct("PlatformDefinition")
304            .field("capabilities", &self.capability_registry)
305            .field("drivers", &self.driver_registry.registered_providers())
306            .field("connectors", &self.connectors)
307            .field("built_in_harnesses", &harness_keys)
308            .field("egress_service", &self.egress_service.name())
309            .field("email_sender", &self.email_sender.name())
310            .field("utility_llm_service", &self.utility_llm_service.name())
311            .field(
312                "session_file_system_factory",
313                &self.session_file_system_factory.name(),
314            )
315            .field("vector_store", &"<dyn VectorStore>")
316            .finish()
317    }
318}
319
320/// Builder for `PlatformDefinition`.
321pub struct PlatformDefinitionBuilder {
322    platform: PlatformDefinition,
323}
324
325impl PlatformDefinitionBuilder {
326    /// Create a new empty builder.
327    pub fn new() -> Self {
328        Self {
329            platform: PlatformDefinition::default(),
330        }
331    }
332
333    /// Replace the capability registry.
334    pub fn capability_registry(mut self, registry: CapabilityRegistry) -> Self {
335        self.platform.capability_registry = registry;
336        self
337    }
338
339    /// Register a capability on the platform.
340    pub fn capability(mut self, capability: impl Capability + 'static) -> Self {
341        self.platform.capability_registry.register(capability);
342        self
343    }
344
345    /// Replace the driver registry.
346    pub fn driver_registry(mut self, registry: DriverRegistry) -> Self {
347        self.platform.driver_registry = registry;
348        self
349    }
350
351    /// Replace the connection-provider registry.
352    pub fn connectors(mut self, registry: ConnectorRegistry) -> Self {
353        self.platform.connectors = registry;
354        self
355    }
356
357    /// Register a connector on the platform.
358    pub fn connector(mut self, provider: impl Connector + 'static) -> Self {
359        self.platform.connectors.register(provider);
360        self
361    }
362
363    /// Replace the built-in harness templates.
364    pub fn built_in_harnesses<I>(mut self, harnesses: I) -> Self
365    where
366        I: IntoIterator<Item = BuiltInHarnessDefinition>,
367    {
368        self.platform.built_in_harnesses = harnesses.into_iter().collect();
369        self
370    }
371
372    /// Append a built-in harness template.
373    pub fn add_built_in_harness(mut self, harness: BuiltInHarnessDefinition) -> Self {
374        self.platform.built_in_harnesses.push(harness);
375        self
376    }
377
378    /// Set the system-wide outbound egress service.
379    pub fn egress_service(mut self, service: Arc<dyn EgressService>) -> Self {
380        self.platform.egress_service = service;
381        self
382    }
383
384    /// Set the system-wide email sender.
385    pub fn email_sender(mut self, sender: Arc<dyn EmailSender>) -> Self {
386        self.platform.email_sender = sender;
387        self
388    }
389
390    /// Set the system-wide utility LLM service.
391    pub fn utility_llm_service(mut self, service: Arc<dyn UtilityLlmService>) -> Self {
392        self.platform.utility_llm_service = service;
393        self
394    }
395
396    /// Set the platform-wide session filesystem factory.
397    pub fn session_file_system_factory(
398        mut self,
399        factory: Arc<dyn SessionFileSystemFactory>,
400    ) -> Self {
401        self.platform.session_file_system_factory = factory;
402        self
403    }
404
405    /// Set the platform-wide vector store for Knowledge Index embeddings.
406    pub fn vector_store(mut self, vector_store: Arc<dyn VectorStore>) -> Self {
407        self.platform.vector_store = vector_store;
408        self
409    }
410
411    /// Build the platform definition.
412    pub fn build(self) -> PlatformDefinition {
413        self.platform
414    }
415}
416
417impl Default for PlatformDefinitionBuilder {
418    fn default() -> Self {
419        Self::new()
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426    use crate::connector::{ConnectorFormSchema, ConnectorType, ConnectorValidation, FormField};
427    use crate::{CapabilityStatus, CurrentTimeCapability};
428    use async_trait::async_trait;
429
430    struct TestProvider;
431
432    #[async_trait]
433    impl Connector for TestProvider {
434        fn provider_id(&self) -> &str {
435            "test_provider"
436        }
437
438        fn display_name(&self) -> &str {
439            "Test Provider"
440        }
441
442        fn description(&self) -> &str {
443            "Test connection provider"
444        }
445
446        fn icon(&self) -> &str {
447            "plug"
448        }
449
450        fn connection_type(&self) -> ConnectorType {
451            ConnectorType::ApiKey
452        }
453
454        fn form_schema(&self) -> Option<ConnectorFormSchema> {
455            Some(ConnectorFormSchema {
456                fields: vec![FormField::password("api_key", "API Key").required()],
457                instructions_markdown: "Enter the API key.".to_string(),
458            })
459        }
460
461        async fn validate(&self, _credential: &str) -> Result<ConnectorValidation, String> {
462            Ok(ConnectorValidation {
463                provider_username: Some("test-user".to_string()),
464                provider_metadata: None,
465            })
466        }
467    }
468
469    // Registers the llmsim driver, so it follows the `llmsim` feature gate.
470    #[cfg(feature = "llmsim")]
471    #[test]
472    fn test_platform_definition_builder() {
473        let mut drivers = DriverRegistry::new();
474        crate::llmsim_driver::register_driver(&mut drivers);
475
476        let platform = PlatformDefinition::builder()
477            .driver_registry(drivers.clone())
478            .capability(CurrentTimeCapability)
479            .connector(TestProvider)
480            .add_built_in_harness(
481                BuiltInHarnessDefinition::new(
482                    "minimal",
483                    "Minimal",
484                    "Minimal harness",
485                    "You are helpful.",
486                )
487                .with_roles([BuiltInHarnessRole::Base, BuiltInHarnessRole::Default]),
488            )
489            .build();
490
491        assert!(platform.capability_registry().has("current_time"));
492        assert!(platform.connectors().has("test_provider"));
493        assert_eq!(
494            platform
495                .harness_for_role(BuiltInHarnessRole::Base)
496                .unwrap()
497                .name,
498            "minimal"
499        );
500        assert!(
501            platform
502                .driver_registry()
503                .has_driver(&crate::DriverId::LlmSim)
504        );
505    }
506
507    #[test]
508    fn test_platform_definition_mutation() {
509        let mut platform = PlatformDefinition::default();
510        platform
511            .capability_registry_mut()
512            .register(CurrentTimeCapability);
513        platform.connectors_mut().register(TestProvider);
514        platform.add_built_in_harness(
515            BuiltInHarnessDefinition::new("chat", "Chat", "Chat harness", "You are helpful.")
516                .with_roles([BuiltInHarnessRole::Chat]),
517        );
518
519        let info = crate::CapabilityInfo::from_core(
520            platform
521                .capability_registry()
522                .get("current_time")
523                .expect("current_time registered")
524                .as_ref(),
525        );
526        assert_eq!(info.status, CapabilityStatus::Available);
527        assert!(platform.connectors().has("test_provider"));
528        assert_eq!(
529            platform
530                .harness_for_role(BuiltInHarnessRole::Chat)
531                .unwrap()
532                .name,
533            "chat"
534        );
535    }
536}