Skip to main content

systemprompt_runtime/context/
mod.rs

1//! [`AppContext`] — the application-wide runtime container.
2//!
3//! Holds shared handles (config, database pool, extension registry,
4//! analytics, route classifier, etc.) cloned cheaply via [`Arc`].
5//! Constructed via [`crate::AppContextBuilder`] or [`AppContext::new`].
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10use std::sync::{Arc, OnceLock};
11
12
13use systemprompt_agent::repository::A2ARepositories;
14use systemprompt_ai::repository::AiRepositories;
15use systemprompt_analytics::repository::AnalyticsRepositories;
16use systemprompt_analytics::{AnalyticsService, FingerprintRepository, GeoIpReader};
17use systemprompt_config::paths::AppPaths;
18use systemprompt_content::repository::ContentRepositories;
19use systemprompt_database::{DbPool, SchemaInstallReport, ServiceRepository};
20use systemprompt_events::{EventBridgeHandle, EventRouter};
21use systemprompt_extension::ExtensionRegistry;
22use systemprompt_files::FileRepository;
23use systemprompt_manifest::Config;
24use systemprompt_manifest::services::SystemAdmin;
25use systemprompt_marketplace::inventory::PublishGuard;
26use systemprompt_marketplace::managed::ManagedRepository;
27use systemprompt_marketplace::{MarketplaceCache, MarketplaceFilter};
28use systemprompt_mcp::repository::McpSessionRepository;
29use systemprompt_mcp::services::registry::RegistryService;
30use systemprompt_models::{ContentConfigRaw, ContentRouting, RouteClassifier};
31use systemprompt_oauth::repository::OAuthRepositories;
32use systemprompt_security::authz::SharedAuthzHook;
33use systemprompt_security::policy::GovernanceEngine;
34use systemprompt_traits::{BackgroundTasks, CacheEpochs, FileStorage};
35use systemprompt_users::{UserRepository, UserService};
36
37mod context_loaders;
38mod debug_impls;
39mod repositories;
40mod services;
41mod shutdown;
42
43pub use shutdown::ShutdownRequest;
44
45use crate::builder::AppContextBuilder;
46use crate::error::RuntimeResult;
47
48/// Database pool and the data-access services layered on it.
49///
50/// `fingerprint_repo` and `user_service` are `None` when the corresponding
51/// resource failed to initialise; callers must degrade gracefully.
52#[derive(Clone)]
53pub struct DataPlane {
54    pub database: DbPool,
55    pub analytics_service: Arc<AnalyticsService>,
56    pub fingerprint_repo: Option<Arc<FingerprintRepository>>,
57    pub user_service: Option<Arc<UserService>>,
58    pub a2a_repositories: Arc<A2ARepositories>,
59    pub content_repositories: Arc<ContentRepositories>,
60    pub oauth_repositories: Arc<OAuthRepositories>,
61    pub user_repository: Arc<UserRepository>,
62    pub service_repository: Arc<ServiceRepository>,
63    pub ai_repositories: Arc<AiRepositories>,
64    pub analytics_repositories: Arc<AnalyticsRepositories>,
65    pub file_repository: Arc<FileRepository>,
66    pub mcp_session_repository: Arc<McpSessionRepository>,
67    pub managed_repository: Arc<ManagedRepository>,
68}
69
70#[derive(Clone)]
71pub struct ConfigPlane {
72    pub config: Arc<Config>,
73    pub app_paths: Arc<AppPaths>,
74    pub content_config: Option<Arc<ContentConfigRaw>>,
75    pub route_classifier: Arc<RouteClassifier>,
76}
77
78#[derive(Clone)]
79pub struct Plugins {
80    pub extension_registry: Arc<ExtensionRegistry>,
81    pub mcp_registry: RegistryService,
82    pub marketplace_filter: Arc<dyn MarketplaceFilter>,
83    pub marketplace_cache: Arc<MarketplaceCache>,
84}
85
86#[derive(Clone)]
87pub struct Subsystems {
88    pub system_admin: Arc<SystemAdmin>,
89    pub authz_hook: SharedAuthzHook,
90    pub governance: Arc<GovernanceEngine>,
91    pub cache_epochs: CacheEpochs,
92    pub ai_service: Option<Arc<systemprompt_ai::AiService>>,
93    pub artifact_ingest: Arc<systemprompt_mcp::ArtifactIngest>,
94    pub schema_install: Arc<crate::SchemaState>,
95    pub event_bridge: Arc<OnceLock<EventBridgeHandle>>,
96    pub event_router: EventRouter,
97    pub geoip_reader: Option<GeoIpReader>,
98    pub file_storage: Arc<dyn FileStorage>,
99    pub shutdown: ShutdownRequest,
100    pub background_tasks: BackgroundTasks,
101    pub publish_guard: Arc<tokio::sync::Mutex<PublishGuard>>,
102}
103
104/// Application-wide runtime container shared across the HTTP server, the
105/// scheduler, and CLI commands.
106///
107/// Handles are grouped into four cohesive planes ([`DataPlane`],
108/// [`ConfigPlane`], [`Plugins`], [`Subsystems`]); each field is an [`Arc`] (or
109/// an `Arc`-internal handle such as [`DbPool`]), so `clone` is a
110/// reference-count bump rather than a deep copy. Construct it via
111/// [`AppContext::builder`] (or [`AppContext::new`] for the default build);
112/// [`AppContext::from_parts`] bypasses the bootstrap and is intended for tests
113/// and embedders that assemble the planes themselves. Read individual handles
114/// through the accessor methods.
115#[derive(Clone)]
116pub struct AppContext {
117    pub(crate) data: DataPlane,
118    pub(crate) cfg: ConfigPlane,
119    pub(crate) plugins: Plugins,
120    pub(crate) subsystems: Subsystems,
121}
122
123impl AppContext {
124    pub async fn new() -> RuntimeResult<Self> {
125        Self::builder().build().await
126    }
127
128    #[must_use]
129    pub fn builder() -> AppContextBuilder {
130        AppContextBuilder::new()
131    }
132
133    #[must_use]
134    pub const fn from_parts(
135        data: DataPlane,
136        cfg: ConfigPlane,
137        plugins: Plugins,
138        subsystems: Subsystems,
139    ) -> Self {
140        Self {
141            data,
142            cfg,
143            plugins,
144            subsystems,
145        }
146    }
147
148    pub fn load_geoip_database(
149        config: &Config,
150        show_warnings: bool,
151    ) -> Result<Option<GeoIpReader>, crate::error::RuntimeError> {
152        context_loaders::load_geoip_database(config, show_warnings)
153    }
154
155    pub fn load_content_config(
156        config: &Config,
157        app_paths: &AppPaths,
158    ) -> Option<Arc<ContentConfigRaw>> {
159        context_loaders::load_content_config(config, app_paths)
160    }
161
162    pub fn config(&self) -> &Config {
163        &self.cfg.config
164    }
165
166    pub fn content_config(&self) -> Option<&ContentConfigRaw> {
167        self.cfg.content_config.as_ref().map(AsRef::as_ref)
168    }
169
170    pub fn content_routing(&self) -> Option<Arc<dyn ContentRouting>> {
171        let concrete = Arc::clone(self.cfg.content_config.as_ref()?);
172        let routing: Arc<dyn ContentRouting> = concrete;
173        Some(routing)
174    }
175
176    pub const fn db_pool(&self) -> &DbPool {
177        &self.data.database
178    }
179
180    pub fn extension_registry(&self) -> &ExtensionRegistry {
181        &self.plugins.extension_registry
182    }
183
184    pub fn server_address(&self) -> String {
185        format!("{}:{}", self.cfg.config.host, self.cfg.config.port)
186    }
187
188    pub const fn geoip_reader(&self) -> Option<&GeoIpReader> {
189        self.subsystems.geoip_reader.as_ref()
190    }
191
192    pub const fn file_storage(&self) -> &Arc<dyn FileStorage> {
193        &self.subsystems.file_storage
194    }
195
196    pub const fn analytics_service(&self) -> &Arc<AnalyticsService> {
197        &self.data.analytics_service
198    }
199
200    #[must_use]
201    pub fn session_usage(&self) -> systemprompt_traits::DynSessionUsageCounters {
202        self.session_store()
203    }
204
205    #[must_use]
206    pub fn session_store(&self) -> systemprompt_traits::DynSessionStore {
207        Arc::clone(&self.data.analytics_repositories.session_store)
208    }
209
210    pub fn context_materializer(&self) -> systemprompt_traits::DynContextMaterializer {
211        Arc::new(systemprompt_agent::services::ContextProviderService::new(
212            self.data.a2a_repositories.contexts.clone(),
213        ))
214    }
215
216    pub const fn route_classifier(&self) -> &Arc<RouteClassifier> {
217        &self.cfg.route_classifier
218    }
219
220    pub fn app_paths(&self) -> &AppPaths {
221        &self.cfg.app_paths
222    }
223
224    pub const fn app_paths_arc(&self) -> &Arc<AppPaths> {
225        &self.cfg.app_paths
226    }
227
228    pub fn marketplace_filter(&self) -> &Arc<dyn MarketplaceFilter> {
229        &self.plugins.marketplace_filter
230    }
231
232    pub const fn marketplace_cache(&self) -> &Arc<MarketplaceCache> {
233        &self.plugins.marketplace_cache
234    }
235
236    pub const fn event_bridge(&self) -> &Arc<OnceLock<EventBridgeHandle>> {
237        &self.subsystems.event_bridge
238    }
239
240    pub const fn event_router(&self) -> &EventRouter {
241        &self.subsystems.event_router
242    }
243
244    // Why: the guard memoises per-entry tree digests across passes; the
245    // scheduled job and the manual route share it so neither re-captures a
246    // tree the other already published.
247    pub const fn publish_guard(&self) -> &Arc<tokio::sync::Mutex<PublishGuard>> {
248        &self.subsystems.publish_guard
249    }
250
251    pub fn system_admin(&self) -> &SystemAdmin {
252        &self.subsystems.system_admin
253    }
254
255    pub const fn mcp_registry(&self) -> &RegistryService {
256        &self.plugins.mcp_registry
257    }
258
259    pub const fn authz_hook(&self) -> &SharedAuthzHook {
260        &self.subsystems.authz_hook
261    }
262
263    #[must_use]
264    pub fn schema_install(&self) -> &SchemaInstallReport {
265        self.subsystems.schema_install.boot()
266    }
267
268    #[must_use]
269    pub fn schema_state(&self) -> &crate::SchemaState {
270        &self.subsystems.schema_install
271    }
272
273    pub const fn shutdown_request(&self) -> &ShutdownRequest {
274        &self.subsystems.shutdown
275    }
276
277    pub const fn background_tasks(&self) -> &BackgroundTasks {
278        &self.subsystems.background_tasks
279    }
280
281    pub fn request_restart(&self, reason: &str) {
282        self.subsystems.shutdown.request(reason);
283    }
284}