Skip to main content

oxios_kernel/kernel_handle/
mod.rs

1//! Kernel facade — 13 domain Facades composing the System Call API.
2
3pub mod a2a_api;
4pub mod agent_api;
5pub mod calendar_api;
6pub mod email_api;
7pub mod engine_api;
8pub mod exec_api;
9pub mod extension_api;
10pub mod infra_api;
11pub mod knowledge_lens;
12pub mod marketplace_api;
13pub mod mcp_api;
14pub mod memory_api;
15pub mod persona_api;
16pub mod project_api;
17pub mod security_api;
18pub mod state_api;
19
20pub use a2a_api::A2aApi;
21pub use agent_api::AgentApi;
22pub use calendar_api::CalendarApi;
23pub use email_api::EmailApi;
24pub use engine_api::{
25    EngineApi, EngineConfigResponse, FallbackEvent, InputModality, ModelInfo, ProviderCategory,
26    ProviderInfo, RoutingConfigSnapshot, RoutingStats, RoutingStatsSnapshot, RoutingUpdate,
27    ValidateKeyResult,
28};
29pub use exec_api::ExecApi;
30pub use exec_api::SharedExecConfig;
31pub use extension_api::ExtensionApi;
32pub use infra_api::InfraApi;
33pub use knowledge_lens::{
34    CopilotResponse, KnowledgeContext, KnowledgeLens, KnowledgeNote, MemoryNote,
35};
36pub use marketplace_api::MarketplaceApi;
37pub use mcp_api::McpApi;
38pub use memory_api::MemoryApi;
39pub use persona_api::PersonaApi;
40pub use project_api::{ProjectApi, ProjectInfo};
41pub use security_api::SecurityApi;
42pub use state_api::StateApi;
43
44use crate::a2a::A2AProtocol;
45use crate::access_manager::AccessManager;
46use crate::auth::AuthManager;
47use crate::budget::BudgetManager;
48use crate::config::OxiosConfig;
49use crate::cron::CronScheduler;
50use crate::event_bus::EventBus;
51use crate::git_layer::CommitInfo;
52use crate::git_layer::GitLayer;
53use crate::mcp::McpBridge;
54use crate::memory::MemoryManager;
55use crate::persona::PersonaManager;
56use crate::resource_monitor::ResourceMonitor;
57use crate::scheduler::AgentScheduler;
58use crate::skill::clawhub::{ClawHubClient, ClawHubInstaller};
59use crate::skill::skills_sh::{SkillsShClient, SkillsShInstaller};
60use crate::skill::SkillManager;
61use crate::state_store::StateStore;
62use crate::supervisor::Supervisor;
63use oxi_sdk::observability::AuditTrail;
64use serde::Serialize;
65use std::sync::Arc;
66use std::time::Instant;
67
68/// Oxios kernel System Call API — composed of 13 domain Facades.
69///
70/// Each Facade groups related system calls:
71/// - [`StateApi`]     — data persistence, sessions
72/// - [`AgentApi`]     — agent lifecycle, budgets, memory
73/// - [`SecurityApi`]  — auth, audit trail, RBAC, approvals
74/// - [`PersonaApi`]   — multi-persona management
75/// - [`ExtensionApi`] — programs, skills, host tools
76/// - [`McpApi`]       — MCP server bridge
77/// - [`ProjectApi`]    — Project management, memory linking
78/// - [`ExecApi`]      — execution config, access management
79/// - [`A2aApi`]       — agent-to-agent communication
80/// - [`EngineApi`]    — LLM engine providers, models, config
81/// - [`KnowledgeBase`] — markdown note management (kernel-free, via oxios-markdown)
82pub struct KernelHandle {
83    /// State management: save/load/sessions.
84    pub state: StateApi,
85    /// Agent management: lifecycle/budgets/memory.
86    pub agents: AgentApi,
87    /// Security: auth/audit/RBAC/approvals.
88    pub security: SecurityApi,
89    /// Persona management.
90    pub persona: PersonaApi,
91    /// Extensions: programs/skills/host tools.
92    pub extensions: ExtensionApi,
93    /// MCP server bridge.
94    pub mcp: McpApi,
95    /// Infrastructure: Git/scheduler/cron/resources/events/system.
96    pub infra: InfraApi,
97    /// Project management: work context (RFC-011).
98    pub projects: Option<ProjectApi>,
99    /// Execution: config + access management.
100    pub exec: ExecApi,
101    /// Agent-to-agent communication.
102    pub a2a: A2aApi,
103    /// Engine: LLM providers, models, config.
104    pub engine: EngineApi,
105    /// Knowledge base: markdown notes (direct access, no kernel dependency).
106    pub knowledge: Arc<oxios_markdown::KnowledgeBase>,
107    /// Semantic knowledge overlay (HNSW index + agent recall).
108    pub knowledge_lens: Arc<KnowledgeLens>,
109    /// Marketplace API — ClawHub search, install, update.
110    pub marketplace_api: MarketplaceApi,
111    /// Calendar events — create, update, delete, list, search, freebusy.
112    pub calendar: Option<CalendarApi>,
113    /// Email — send HTML emails via SMTP, template management.
114    pub email: Option<EmailApi>,
115}
116
117impl KernelHandle {
118    /// Create a new KernelHandle from 13 domain Facades.
119    ///
120    /// Each Facade is assembled independently in `kernel.rs` and passed here.
121    /// This enables testing individual Facades without the full kernel.
122    #[allow(clippy::too_many_arguments)]
123    pub fn new(
124        state: StateApi,
125        agents: AgentApi,
126        security: SecurityApi,
127        persona: PersonaApi,
128        extensions: ExtensionApi,
129        mcp: McpApi,
130        infra: InfraApi,
131        projects: Option<ProjectApi>,
132        exec: ExecApi,
133        a2a: A2aApi,
134        engine: EngineApi,
135        knowledge: Arc<oxios_markdown::KnowledgeBase>,
136        knowledge_lens: Arc<KnowledgeLens>,
137        marketplace_api: MarketplaceApi,
138        calendar: Option<CalendarApi>,
139        email: Option<EmailApi>,
140    ) -> Self {
141        Self {
142            state,
143            agents,
144            security,
145            persona,
146            extensions,
147            mcp,
148            infra,
149            projects,
150            exec,
151            a2a,
152            engine,
153            knowledge,
154            knowledge_lens,
155            marketplace_api,
156            calendar,
157            email,
158        }
159    }
160
161    /// Build a KernelHandle from raw subsystem parameters.
162    ///
163    /// Prefer [`KernelHandle::new()`] which takes pre-built Facades.
164    #[deprecated(note = "Use KernelHandle::new() with pre-built Facades instead")]
165    #[allow(clippy::too_many_arguments)]
166    pub fn from_subsystems(
167        state_store: Arc<StateStore>,
168        event_bus: EventBus,
169        supervisor: Arc<dyn Supervisor>,
170        scheduler: Arc<AgentScheduler>,
171        memory_manager: Arc<MemoryManager>,
172        git_layer: Arc<GitLayer>,
173        audit_trail: Arc<AuditTrail>,
174        budget_manager: Arc<BudgetManager>,
175        resource_monitor: Arc<ResourceMonitor>,
176        cron_scheduler: Arc<CronScheduler>,
177        skill_manager: Arc<SkillManager>,
178        persona_manager: Arc<PersonaManager>,
179        mcp_bridge: Arc<McpBridge>,
180        auth_manager: Arc<parking_lot::Mutex<AuthManager>>,
181        access_manager: Arc<parking_lot::Mutex<AccessManager>>,
182        config: OxiosConfig,
183        start_time: Instant,
184    ) -> Self {
185        let knowledge_dir = state_store.base_path.join("knowledge");
186        let knowledge = Arc::new(
187            oxios_markdown::KnowledgeBase::new(knowledge_dir)
188                .expect("Failed to create KnowledgeBase"),
189        );
190        let knowledge_lens = Arc::new(
191            KnowledgeLens::new(knowledge.clone(), memory_manager.clone())
192                .expect("Failed to create KnowledgeLens"),
193        );
194        Self {
195            security: SecurityApi::new(
196                auth_manager.clone(),
197                audit_trail,
198                access_manager.clone(),
199                state_store.clone(),
200            ),
201            state: StateApi::new(state_store.clone()),
202            agents: AgentApi::new(
203                supervisor,
204                budget_manager,
205                memory_manager,
206                Some(event_bus.clone()),
207            ),
208            persona: PersonaApi::new(persona_manager),
209            extensions: ExtensionApi::new(skill_manager),
210            mcp: McpApi::new(mcp_bridge),
211            infra: InfraApi::new(
212                git_layer,
213                scheduler,
214                cron_scheduler,
215                resource_monitor,
216                event_bus.clone(),
217                config.clone(),
218                start_time,
219            ),
220            projects: None,
221            exec: ExecApi::new(
222                Arc::new(parking_lot::RwLock::new(config.exec.clone())),
223                access_manager,
224            ),
225            #[allow(clippy::default_trait_access)]
226            a2a: A2aApi::new(Arc::new(A2AProtocol::new(crate::EventBus::new(0)))),
227            engine: EngineApi::new(
228                Arc::new(parking_lot::RwLock::new(config.clone())),
229                std::path::PathBuf::from("~/.oxios/config.toml"),
230                Arc::new(RoutingStats::new()),
231                Arc::new(crate::engine::EngineHandle::new(Arc::new(
232                    crate::engine::OxiosEngine::new("anthropic/claude-sonnet-4-20250514"),
233                ))),
234            ),
235            knowledge,
236            knowledge_lens,
237            marketplace_api: MarketplaceApi::new(
238                Arc::new(ClawHubInstaller::new(
239                    state_store.base_path.join("skills"),
240                    state_store.base_path.clone(),
241                    config.marketplace.base_url.clone(),
242                )),
243                Arc::new(
244                    ClawHubClient::new(config.marketplace.base_url.clone())
245                        .expect("valid ClawHub client"),
246                ),
247                Arc::new(SkillsShInstaller::new(
248                    state_store.base_path.join("skills"),
249                    None,
250                    None,
251                )),
252                Arc::new(SkillsShClient::new(None, None).expect("valid Skills.sh client")),
253            ),
254            calendar: None, // from_subsystems is deprecated; calendar initialized separately
255            email: None,    // from_subsystems is deprecated; email initialized separately
256        }
257    }
258
259    // ═══════════════════════════════════════════════════════════════════════
260    // Convenience methods (cross-Facades orchestration)
261    // ═══════════════════════════════════════════════════════════════════════
262
263    /// Save data and commit to git (State + Infra).
264    pub async fn save_and_commit<T: Serialize>(
265        &self,
266        category: &str,
267        name: &str,
268        data: &T,
269    ) -> anyhow::Result<()> {
270        self.state.save(category, name, data).await?;
271        let git = self.infra.git();
272        if git.is_enabled() {
273            let rel_path = format!("{category}/{name}.json");
274            let _ = git.commit_file(&rel_path, &format!("save {category}/{name}"));
275        }
276        Ok(())
277    }
278
279    /// Save markdown and commit to git (State + Infra).
280    pub async fn save_markdown_and_commit(
281        &self,
282        category: &str,
283        name: &str,
284        content: &str,
285    ) -> anyhow::Result<()> {
286        self.state.save_markdown(category, name, content).await?;
287        let git = self.infra.git();
288        if git.is_enabled() {
289            let rel_path = format!("{category}/{name}.md");
290            let _ = git.commit_file(&rel_path, &format!("save {category}/{name}"));
291        }
292        Ok(())
293    }
294
295    /// Delete a file and commit the removal to git (State + Infra).
296    pub async fn delete_and_commit(&self, category: &str, name: &str) -> anyhow::Result<bool> {
297        let deleted = self.state.delete(category, name).await?;
298        if deleted {
299            let git = self.infra.git();
300            if git.is_enabled() {
301                let rel_path = format!("{category}/{name}.json");
302                let _ = git.remove_file(&rel_path, &format!("delete {category}/{name}"));
303            }
304        }
305        Ok(deleted)
306    }
307
308    /// Commit all current changes to git.
309    pub fn commit_all(&self, message: &str) -> anyhow::Result<Option<CommitInfo>> {
310        self.state.commit_all(self.infra.git(), message)
311    }
312
313    /// Flush audit trail and commit to git (Security + Infra).
314    pub fn flush_audit(&self) -> anyhow::Result<()> {
315        self.security.flush(self.infra.git())
316    }
317
318    /// Schedule a cron job by expression (convenience wrapper).
319    pub async fn schedule(
320        &self,
321        cron_expr: &str,
322        task: &str,
323        persona: Option<&str>,
324    ) -> anyhow::Result<String> {
325        let _persona = persona.unwrap_or("default");
326        let job = crate::cron::CronJob::new(
327            format!("job_{}", uuid::Uuid::new_v4()),
328            cron_expr.to_string(),
329            task.to_string(),
330        );
331        let job_id = self.infra.add_cron(job).await?;
332        Ok(job_id.to_string())
333    }
334
335    /// Unschedule a cron job by string ID (convenience wrapper).
336    pub async fn unschedule(&self, job_id: &str) -> anyhow::Result<bool> {
337        let uuid =
338            uuid::Uuid::parse_str(job_id).map_err(|e| anyhow::anyhow!("invalid job id: {e}"))?;
339        match self.infra.remove_cron(uuid).await {
340            Ok(()) => Ok(true),
341            Err(_) => Ok(false),
342        }
343    }
344
345    /// List cron jobs (convenience wrapper).
346    pub fn list_schedules(&self) -> Vec<crate::cron::CronJob> {
347        self.infra.list_crons()
348    }
349
350    /// Load JSON from state store.
351    pub async fn load_json<T: serde::de::DeserializeOwned>(
352        &self,
353        category: &str,
354        name: &str,
355    ) -> anyhow::Result<Option<T>> {
356        self.state.load(category, name).await
357    }
358
359    /// Get kernel start time.
360    pub fn start_time(&self) -> std::time::Instant {
361        self.infra.start_time
362    }
363
364    /// Marketplace API — ClawHub search, install, update.
365    pub fn marketplace_api(&self) -> &MarketplaceApi {
366        &self.marketplace_api
367    }
368
369    /// Get a [`MemoryApi`] facade for memory operations.
370    ///
371    /// Constructs (lazily, on first call) a `MemoryApi` that wraps the
372    /// `MemoryManager` from `AgentApi`. This is the 14th typed API in
373    /// `KernelHandle` (alongside `A2aApi`, `AgentApi`, etc.).
374    ///
375    /// The MemoryApi is cached — multiple calls return the same instance.
376    pub fn memory(&self) -> MemoryApi {
377        // We construct from AgentApi's memory_manager via the
378        // public accessor (returns &Arc<MemoryManager>).
379        // Clone the Arc to get an owned reference.
380        let mm = self.agents.memory_manager().clone();
381        MemoryApi::new(mm)
382    }
383}