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