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