Skip to main content

bamboo_server/app_state/
mod.rs

1//! Unified application state management for the Bamboo server
2//!
3//! This module provides the central AppState struct that consolidates all
4//! server state including sessions, storage, LLM providers, tools, and metrics.
5//!
6//! # Architecture
7//!
8//! The AppState uses a unified design that eliminates the proxy pattern where
9//! web_service created an AgentAppState that called back via HTTP. Instead, it
10//! provides direct access to all components.
11//!
12//! ```text
13//! ┌────────────────────────────────────────────────────┐
14//! │              AppState (Unified)                    │
15//! │                                                    │
16//! │  ┌──────────────┐      ┌──────────────┐          │
17//! │  │   Config     │      │   Provider   │          │
18//! │  │  (Hot-reload)│◄────►│   (LLM)      │          │
19//! │  └──────────────┘      └──────────────┘          │
20//! │                                                    │
21//! │  ┌──────────────┐      ┌──────────────┐          │
22//! │  │   Sessions   │      │   Storage    │          │
23//! │  │  (In-memory) │      │  (Persistent)│          │
24//! │  └──────────────┘      └──────────────┘          │
25//! │                                                    │
26//! │  ┌──────────────┐      ┌──────────────┐          │
27//! │  │    Tools     │      │    Skills    │          │
28//! │  │ (Builtin+MCP)│      │   Manager    │          │
29//! │  └──────────────┘      └──────────────┘          │
30//! │                                                    │
31//! │  ┌──────────────┐      ┌──────────────┐          │
32//! │  │     MCP      │      │   Metrics    │          │
33//! │  │   Manager    │      │   Service    │          │
34//! │  └──────────────┘      └──────────────┘          │
35//! └────────────────────────────────────────────────────┘
36//! ```
37//!
38//! # Key Features
39//!
40//! - **Hot-reloadable configuration**: Config and provider can be reloaded at runtime
41//! - **Direct provider access**: No HTTP proxy overhead
42//! - **Session management**: In-memory session cache with persistent storage
43//! - **Tool composition**: Combines built-in and MCP tools
44//! - **Metrics collection**: Integrated metrics and event tracking
45//!
46//! # Usage Example
47//!
48//! ```rust,no_run
49//! use bamboo_server::app_state::AppState;
50//! use std::path::PathBuf;
51//!
52//! #[tokio::main]
53//! async fn main() {
54//!     // Initialize app state
55//!     let app_data_dir = PathBuf::from("/path/to/.bamboo");
56//!     let state = AppState::new(app_data_dir)
57//!         .await
58//!         .expect("failed to initialize app state");
59//!
60//!     // Access components
61//!     let provider = state.get_provider().await;
62//!     let schemas = state.get_all_tool_schemas();
63//!
64//!     // Hot reload configuration
65//!     state.reload_config().await;
66//!     state.reload_provider().await.ok();
67//! }
68//! ```
69
70use std::collections::HashMap;
71use std::path::PathBuf;
72use std::sync::Arc;
73
74use async_trait::async_trait;
75use tokio::sync::{broadcast, RwLock};
76use tokio_util::sync::CancellationToken;
77
78use crate::error::AppError;
79use crate::schedule_app::{ScheduleManager, ScheduleStore};
80use bamboo_agent_core::storage::Storage;
81use bamboo_agent_core::AgentEvent;
82use bamboo_agent_core::{tools::ToolSchema, Message};
83use bamboo_engine::execution::spawn::SpawnScheduler;
84use bamboo_infrastructure::process::registry::ProcessRegistry;
85use bamboo_llm::Config;
86use bamboo_llm::{LLMError, LLMProvider, LLMStream};
87use bamboo_mcp::manager::McpServerManager;
88use bamboo_metrics::metrics_service::MetricsService;
89use bamboo_skills::SkillManager;
90use bamboo_storage::LockedSessionStore;
91use bamboo_storage::SessionStoreV2;
92
93// Context functions moved to bamboo-agent-runtime::context
94pub use bamboo_engine::context::{
95    build_env_prompt_context, build_workspace_prompt_context, workspace_prompt_guidance,
96    DEFAULT_BASE_PROMPT, ENV_CONTEXT_END_MARKER, ENV_CONTEXT_START_MARKER,
97    WORKSPACE_CONTEXT_END_MARKER, WORKSPACE_CONTEXT_PREFIX, WORKSPACE_CONTEXT_START_MARKER,
98};
99
100/// Placeholder provider used when the configured provider cannot be initialized.
101///
102/// This keeps the server usable for configuration/UX flows while ensuring we fail fast
103/// (instead of silently switching to a different provider or model).
104struct UnconfiguredProvider {
105    message: String,
106}
107
108#[async_trait]
109impl LLMProvider for UnconfiguredProvider {
110    async fn chat_stream(
111        &self,
112        _messages: &[Message],
113        _tools: &[ToolSchema],
114        _max_output_tokens: Option<u32>,
115        _model: &str,
116    ) -> bamboo_llm::provider::Result<LLMStream> {
117        Err(LLMError::Auth(format!(
118            "LLM provider is not configured: {}",
119            self.message
120        )))
121    }
122
123    async fn list_models(&self) -> bamboo_llm::provider::Result<Vec<String>> {
124        Err(LLMError::Auth(format!(
125            "LLM provider is not configured: {}",
126            self.message
127        )))
128    }
129}
130
131// Re-export execution types from the runtime crate.
132pub use bamboo_engine::execution::runner_state::{AgentRunner, AgentStatus};
133
134/// Unified application state consolidating web_service and agent/server state
135///
136/// This struct holds all the state needed to run the Bamboo server, including
137/// configuration, LLM providers, sessions, storage, tools, skills, and metrics.
138///
139/// # Design Goals
140///
141/// - **Direct access**: Components are directly accessible without HTTP proxies
142/// - **Hot reload**: Configuration and providers can be reloaded at runtime
143/// - **Thread safety**: Uses Arc<RwLock> for concurrent access
144/// - **Persistence**: Integrates with JsonlStorage for session persistence
145///
146/// # Component Overview
147///
148/// | Component | Purpose | Thread-Safe |
149/// |-----------|---------|--------------|
150/// | `config` | Application configuration | Yes (RwLock) |
151/// | `provider` | Hot-reloadable LLM provider | Yes (RwLock) |
152/// | `sessions` | Active conversation sessions | Yes (RwLock) |
153/// | `storage` | Persistent session storage | Yes (Arc) |
154/// | `tools` | Tool execution (builtin + MCP) | Yes (Arc) |
155/// | `skill_manager` | Skill registry and execution | Yes (Arc) |
156/// | `mcp_manager` | MCP server lifecycle | Yes (Arc) |
157/// | `metrics_service` | Usage metrics collection | Yes (Arc) |
158/// | `agent_runners` | Active agent executions | Yes (RwLock) |
159pub struct AppState {
160    /// Application data directory (configured via `BAMBOO_DATA_DIR`; default `${HOME}/.bamboo`)
161    pub app_data_dir: PathBuf,
162
163    /// Hot-reloadable application configuration
164    ///
165    /// Can be reloaded from disk at runtime using `reload_config()`.
166    pub config: Arc<RwLock<Config>>,
167
168    /// Hot-reloadable LLM provider with direct access
169    ///
170    /// This eliminates the proxy pattern where we created an AgentAppState
171    /// that called back to web_service via HTTP. Now we have direct provider access.
172    pub provider: Arc<RwLock<Arc<dyn LLMProvider>>>,
173
174    /// Stable handle that always delegates to the latest provider in `provider`.
175    ///
176    /// This avoids stale provider snapshots after runtime config updates.
177    provider_handle: Arc<dyn LLMProvider>,
178
179    /// Active conversation sessions (in-memory cache)
180    ///
181    /// Maps session IDs to Session objects. Persisted to storage
182    /// via the `storage` field.
183    pub sessions: bamboo_engine::SessionCache,
184
185    /// Persistent storage backend for sessions (V2).
186    ///
187    /// Implemented as folder-per-session with a global `sessions.json` index.
188    pub storage: Arc<dyn Storage>,
189
190    /// Concrete session store implementation (for index/list/cleanup APIs).
191    pub session_store: Arc<SessionStoreV2>,
192
193    /// Per-session write serialisation + metadata-merge persistence layer.
194    ///
195    /// Wraps the same [`Storage`] as `self.storage`, adding per-session
196    /// `Mutex` guards and authoritative-metadata-group merge semantics.
197    /// Use `self.persistence.merge_save_runtime(...)` for any write that
198    /// may race with a UI metadata update.
199    pub persistence: Arc<LockedSessionStore>,
200
201    /// Framework-owned session coordinator (cache + storage + persistence).
202    /// The canonical load/save coordination lives here in `bamboo-engine`, not
203    /// on `AppState`; the inherent `AppState::load_session`/`save_and_cache_session`
204    /// methods now delegate to it. Holds clones of the same `Arc`s as the
205    /// `sessions`/`storage`/`persistence` fields above.
206    pub session_repo: bamboo_engine::SessionRepository,
207
208    /// Background scheduler for async sub-session spawning.
209    pub spawn_scheduler: Arc<SpawnScheduler>,
210
211    /// Coordinates child completion notifications into parent resume.
212    pub child_completion_coordinator: Arc<bamboo_engine::ChildCompletionCoordinator>,
213
214    /// Schedule store (timed tasks).
215    pub schedule_store: Arc<ScheduleStore>,
216
217    /// Background schedule manager that triggers scheduled runs.
218    pub schedule_manager: Arc<ScheduleManager>,
219
220    /// Tool surface factory providing pre-built tool executors for each session type.
221    ///
222    /// Use `state.tools_for(ToolSurface::Root)` for root sessions,
223    /// `state.tools_for(ToolSurface::Child)` for child sessions, etc.
224    pub tool_factory: crate::tools::ToolSurfaceFactory,
225
226    /// Subagent profile registry (role definitions for child sessions).
227    ///
228    /// Composed from built-in profiles + user/project/env overrides at
229    /// startup. Currently provided as ambient state for future PRs that
230    /// will plumb it into child-session creation and tool-surface filtering;
231    /// the runtime does not read from it yet, so adding this field is a
232    /// no-op for existing behaviour.
233    pub subagent_profiles: Arc<bamboo_domain::subagent::SubagentProfileRegistry>,
234
235    /// Cancellation tokens for in-flight requests
236    ///
237    /// Maps request/session IDs to their cancellation tokens,
238    /// allowing graceful shutdown of long-running operations.
239    pub cancel_tokens: Arc<RwLock<HashMap<String, CancellationToken>>>,
240
241    /// Skill manager for prompt-based skill execution
242    ///
243    /// Manages the skill registry and handles skill lookup,
244    /// validation, and execution.
245    pub skill_manager: Arc<SkillManager>,
246
247    /// MCP server manager for external tool servers
248    ///
249    /// Handles lifecycle of Model Context Protocol servers,
250    /// including initialization, tool discovery, and shutdown.
251    pub mcp_manager: Arc<McpServerManager>,
252
253    /// Metrics collection and persistence service
254    ///
255    /// Tracks token usage, costs, and performance metrics
256    /// across all sessions.
257    pub metrics_service: Arc<MetricsService>,
258
259    /// Active agent runners indexed by session ID
260    ///
261    /// Each runner manages event broadcasting and cancellation
262    /// for an active agent execution.
263    pub agent_runners: Arc<RwLock<HashMap<String, AgentRunner>>>,
264
265    /// Session-scoped event streams (long-lived).
266    ///
267    /// Unlike `agent_runners`, these senders exist even when no agent execution is running.
268    /// They are used for:
269    /// - UI subscriptions to `/api/v1/events/{session_id}` (background tasks, etc.)
270    /// - sub-session forwarding (child -> parent)
271    pub session_event_senders: Arc<RwLock<HashMap<String, broadcast::Sender<AgentEvent>>>>,
272
273    /// Account-scoped durable change feed (powers `GET /api/v1/stream`).
274    ///
275    /// Unlike `session_event_senders`, this is a single account-wide sink: all
276    /// durable change events (message appended, session metadata, task updates,
277    /// terminal status) across every session are sequenced, journaled to disk,
278    /// and broadcast here for resumable multi-client sync.
279    pub account_sink: Arc<bamboo_engine::events::AccountEventSink>,
280
281    /// Registry for tracking external processes.
282    pub process_registry: Arc<ProcessRegistry>,
283
284    /// Optional metrics bus for event streaming
285    ///
286    /// When enabled, allows subscribing to metrics events
287    /// in real-time.
288    pub metrics_bus: Option<bamboo_metrics::bus::MetricsBus>,
289
290    /// Unified agent execution runtime holding shared resources.
291    pub agent: Arc<bamboo_engine::Agent>,
292
293    /// Multi-provider registry (used when features.provider_model_ref is enabled).
294    pub provider_registry: Arc<bamboo_llm::ProviderRegistry>,
295
296    /// Provider/model router (used when features.provider_model_ref is enabled).
297    pub provider_router: Arc<bamboo_llm::ProviderModelRouter>,
298
299    /// Unified model catalog service (used when features.provider_model_ref is enabled).
300    pub model_catalog: Arc<bamboo_llm::ModelCatalogService>,
301
302    /// Tracks session ids whose auto-title generation is currently in flight.
303    ///
304    /// Used by [`crate::title_gen`] to dedupe concurrent invocations
305    /// (e.g. execute handler firing while a regenerate-title request is running).
306    pub title_gen_in_flight: Arc<dashmap::DashSet<String>>,
307}
308
309impl AppState {
310    /// Try to claim the title-generation slot for `session_id`.
311    /// Returns `true` on success, `false` if generation is already in flight.
312    pub fn title_gen_acquire(&self, session_id: &str) -> bool {
313        self.title_gen_in_flight.insert(session_id.to_string())
314    }
315
316    /// Release the title-generation slot for `session_id`. Idempotent.
317    pub fn title_gen_release(&self, session_id: &str) {
318        self.title_gen_in_flight.remove(session_id);
319    }
320}
321
322mod agent_session_context;
323mod builder;
324mod config_runtime;
325pub mod init;
326mod persistence;
327mod provider_api;
328pub mod resume_adapter;
329pub mod runner_lifecycle;
330pub(crate) mod session_events;
331mod session_loader;
332mod tools;
333
334#[cfg(test)]
335mod tests;
336
337#[derive(Debug, Clone, Copy, Default)]
338pub struct ConfigUpdateEffects {
339    pub reload_provider: bool,
340    pub reconcile_mcp: bool,
341}