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;
21use systemprompt_extension::ExtensionRegistry;
22use systemprompt_files::FileRepository;
23use systemprompt_marketplace::inventory::PublishGuard;
24use systemprompt_marketplace::managed::ManagedRepository;
25use systemprompt_marketplace::{MarketplaceCache, MarketplaceFilter};
26use systemprompt_mcp::repository::McpSessionRepository;
27use systemprompt_mcp::services::registry::RegistryService;
28use systemprompt_models::services::SystemAdmin;
29use systemprompt_models::{Config, ContentConfigRaw, ContentRouting, RouteClassifier};
30use systemprompt_oauth::repository::OAuthRepositories;
31use systemprompt_security::authz::SharedAuthzHook;
32use systemprompt_security::policy::GovernanceEngine;
33use systemprompt_traits::FileStorage;
34use systemprompt_users::{UserRepository, UserService};
35
36mod context_loaders;
37mod debug_impls;
38mod repositories;
39mod services;
40mod shutdown;
41
42pub use shutdown::ShutdownRequest;
43
44use crate::builder::AppContextBuilder;
45use crate::error::RuntimeResult;
46use crate::registry::ModuleApiRegistry;
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 feedback_snapshots_repository:
66        Arc<systemprompt_analytics::snapshots::FeedbackSnapshotsRepository>,
67    pub feedback_facts_repository: Arc<systemprompt_analytics::feedback::FeedbackFactsRepository>,
68    pub file_repository: Arc<FileRepository>,
69    pub mcp_session_repository: Arc<McpSessionRepository>,
70    pub managed_repository: Arc<ManagedRepository>,
71}
72
73#[derive(Clone)]
74pub struct ConfigPlane {
75    pub config: Arc<Config>,
76    pub app_paths: Arc<AppPaths>,
77    pub content_config: Option<Arc<ContentConfigRaw>>,
78    pub route_classifier: Arc<RouteClassifier>,
79}
80
81#[derive(Clone)]
82pub struct Plugins {
83    pub extension_registry: Arc<ExtensionRegistry>,
84    pub api_registry: Arc<ModuleApiRegistry>,
85    pub mcp_registry: RegistryService,
86    pub marketplace_filter: Arc<dyn MarketplaceFilter>,
87    pub marketplace_cache: Arc<MarketplaceCache>,
88}
89
90#[derive(Clone)]
91pub struct Subsystems {
92    pub system_admin: Arc<SystemAdmin>,
93    pub authz_hook: SharedAuthzHook,
94    pub governance: Arc<GovernanceEngine>,
95    pub ai_service: Option<Arc<systemprompt_ai::AiService>>,
96    pub artifact_ingest: Arc<systemprompt_mcp::ArtifactIngest>,
97    pub schema_install: Arc<SchemaInstallReport>,
98    pub event_bridge: Arc<OnceLock<EventBridgeHandle>>,
99    pub geoip_reader: Option<GeoIpReader>,
100    pub file_storage: Arc<dyn FileStorage>,
101    pub shutdown: ShutdownRequest,
102    pub publish_guard: Arc<tokio::sync::Mutex<PublishGuard>>,
103    pub snapshot_wakeup: Arc<crate::reporting::SnapshotWakeup>,
104}
105
106/// Application-wide runtime container shared across the HTTP server, the
107/// scheduler, and CLI commands.
108///
109/// Handles are grouped into four cohesive planes ([`DataPlane`],
110/// [`ConfigPlane`], [`Plugins`], [`Subsystems`]); each field is an [`Arc`] (or
111/// an `Arc`-internal handle such as [`DbPool`]), so `clone` is a
112/// reference-count bump rather than a deep copy. Construct it via
113/// [`AppContext::builder`] (or [`AppContext::new`] for the default build);
114/// [`AppContext::from_parts`] bypasses the bootstrap and is intended for tests
115/// and embedders that assemble the planes themselves. Read individual handles
116/// through the accessor methods.
117#[derive(Clone)]
118pub struct AppContext {
119    pub(crate) data: DataPlane,
120    pub(crate) cfg: ConfigPlane,
121    pub(crate) plugins: Plugins,
122    pub(crate) subsystems: Subsystems,
123}
124
125impl AppContext {
126    pub async fn new() -> RuntimeResult<Self> {
127        Self::builder().build().await
128    }
129
130    #[must_use]
131    pub fn builder() -> AppContextBuilder {
132        AppContextBuilder::new()
133    }
134
135    #[must_use]
136    pub const fn from_parts(
137        data: DataPlane,
138        cfg: ConfigPlane,
139        plugins: Plugins,
140        subsystems: Subsystems,
141    ) -> Self {
142        Self {
143            data,
144            cfg,
145            plugins,
146            subsystems,
147        }
148    }
149
150    pub fn load_geoip_database(
151        config: &Config,
152        show_warnings: bool,
153    ) -> Result<Option<GeoIpReader>, crate::error::RuntimeError> {
154        context_loaders::load_geoip_database(config, show_warnings)
155    }
156
157    pub fn load_content_config(
158        config: &Config,
159        app_paths: &AppPaths,
160    ) -> Option<Arc<ContentConfigRaw>> {
161        context_loaders::load_content_config(config, app_paths)
162    }
163
164    pub fn config(&self) -> &Config {
165        &self.cfg.config
166    }
167
168    pub fn content_config(&self) -> Option<&ContentConfigRaw> {
169        self.cfg.content_config.as_ref().map(AsRef::as_ref)
170    }
171
172    pub fn content_routing(&self) -> Option<Arc<dyn ContentRouting>> {
173        let concrete = Arc::clone(self.cfg.content_config.as_ref()?);
174        let routing: Arc<dyn ContentRouting> = concrete;
175        Some(routing)
176    }
177
178    pub const fn db_pool(&self) -> &DbPool {
179        &self.data.database
180    }
181
182    pub fn api_registry(&self) -> &ModuleApiRegistry {
183        &self.plugins.api_registry
184    }
185
186    pub fn extension_registry(&self) -> &ExtensionRegistry {
187        &self.plugins.extension_registry
188    }
189
190    pub fn server_address(&self) -> String {
191        format!("{}:{}", self.cfg.config.host, self.cfg.config.port)
192    }
193
194    pub const fn geoip_reader(&self) -> Option<&GeoIpReader> {
195        self.subsystems.geoip_reader.as_ref()
196    }
197
198    pub const fn file_storage(&self) -> &Arc<dyn FileStorage> {
199        &self.subsystems.file_storage
200    }
201
202    pub const fn analytics_service(&self) -> &Arc<AnalyticsService> {
203        &self.data.analytics_service
204    }
205
206    #[must_use]
207    pub fn session_usage(&self) -> systemprompt_traits::DynSessionUsageCounters {
208        self.data.analytics_repositories.sessions.owner()
209    }
210
211    pub fn context_materializer(&self) -> systemprompt_traits::DynContextMaterializer {
212        Arc::new(systemprompt_agent::services::ContextProviderService::new(
213            self.data.a2a_repositories.contexts.clone(),
214        ))
215    }
216
217    pub const fn route_classifier(&self) -> &Arc<RouteClassifier> {
218        &self.cfg.route_classifier
219    }
220
221    pub fn app_paths(&self) -> &AppPaths {
222        &self.cfg.app_paths
223    }
224
225    pub const fn app_paths_arc(&self) -> &Arc<AppPaths> {
226        &self.cfg.app_paths
227    }
228
229    pub fn marketplace_filter(&self) -> &Arc<dyn MarketplaceFilter> {
230        &self.plugins.marketplace_filter
231    }
232
233    pub const fn marketplace_cache(&self) -> &Arc<MarketplaceCache> {
234        &self.plugins.marketplace_cache
235    }
236
237    pub const fn event_bridge(&self) -> &Arc<OnceLock<EventBridgeHandle>> {
238        &self.subsystems.event_bridge
239    }
240
241    // Why: the guard memoises per-entry tree digests across passes; the
242    // scheduled job and the manual route share it so neither re-captures a
243    // tree the other already published.
244    pub const fn publish_guard(&self) -> &Arc<tokio::sync::Mutex<PublishGuard>> {
245        &self.subsystems.publish_guard
246    }
247
248    pub const fn snapshot_wakeup(&self) -> &Arc<crate::reporting::SnapshotWakeup> {
249        &self.subsystems.snapshot_wakeup
250    }
251
252    pub fn system_admin(&self) -> &SystemAdmin {
253        &self.subsystems.system_admin
254    }
255
256    pub const fn mcp_registry(&self) -> &RegistryService {
257        &self.plugins.mcp_registry
258    }
259
260    pub const fn authz_hook(&self) -> &SharedAuthzHook {
261        &self.subsystems.authz_hook
262    }
263
264    #[must_use]
265    pub fn schema_install(&self) -> &SchemaInstallReport {
266        &self.subsystems.schema_install
267    }
268
269    pub const fn shutdown_request(&self) -> &ShutdownRequest {
270        &self.subsystems.shutdown
271    }
272
273    pub fn request_restart(&self, reason: &str) {
274        self.subsystems.shutdown.request(reason);
275    }
276}