Skip to main content

oxios_kernel/kernel_handle/
mod.rs

1//! Kernel facade — 12 domain Facades composing the System Call API.
2
3pub mod a2a_api;
4pub mod agent_api;
5pub mod browser_api;
6pub mod exec_api;
7pub mod extension_api;
8pub mod infra_api;
9pub mod knowledge_lens;
10pub mod mcp_api;
11pub mod persona_api;
12pub mod security_api;
13pub mod space_api;
14pub mod state_api;
15
16pub use a2a_api::A2aApi;
17pub use agent_api::AgentApi;
18pub use browser_api::BrowserApi;
19pub use exec_api::ExecApi;
20pub use extension_api::ExtensionApi;
21pub use infra_api::InfraApi;
22pub use knowledge_lens::{
23    CopilotResponse, KnowledgeContext, KnowledgeLens, KnowledgeNote, MemoryNote,
24};
25pub use mcp_api::McpApi;
26pub use persona_api::PersonaApi;
27pub use security_api::SecurityApi;
28pub use space_api::SpaceApi;
29pub use state_api::StateApi;
30
31use crate::a2a::A2AProtocol;
32use crate::access_manager::AccessManager;
33use crate::audit_trail::AuditTrail;
34use crate::auth::AuthManager;
35use crate::budget::BudgetManager;
36use crate::config::OxiosConfig;
37use crate::cron::CronScheduler;
38use crate::event_bus::EventBus;
39use crate::git_layer::CommitInfo;
40use crate::git_layer::GitLayer;
41use crate::host_tools::HostToolValidator;
42use crate::mcp::McpBridge;
43use crate::memory::MemoryManager;
44use crate::persona_manager::PersonaManager;
45use crate::program::ProgramManager;
46use crate::resource_monitor::ResourceMonitor;
47use crate::scheduler::AgentScheduler;
48use crate::skill::SkillStore;
49use crate::space::SpaceManager;
50use crate::state_store::StateStore;
51use crate::supervisor::Supervisor;
52use serde::Serialize;
53use std::sync::Arc;
54use std::time::Instant;
55
56/// Oxios kernel System Call API — composed of 12 domain Facades.
57///
58/// Each Facade groups related system calls:
59/// - [`StateApi`]     — data persistence, sessions
60/// - [`AgentApi`]     — agent lifecycle, budgets, memory
61/// - [`SecurityApi`]  — auth, audit trail, RBAC, approvals
62/// - [`PersonaApi`]   — multi-persona management
63/// - [`ExtensionApi`] — programs, skills, host tools
64/// - [`McpApi`]       — MCP server bridge
65/// - [`SpaceApi`]     — Space management, knowledge flow
66/// - [`ExecApi`]      — execution config, access management
67/// - [`BrowserApi`]   — browser backend
68/// - [`A2aApi`]       — agent-to-agent communication
69/// - [`KnowledgeBase`] — markdown note management (kernel-free, via oxios-markdown)
70pub struct KernelHandle {
71    /// State management: save/load/sessions.
72    pub state: StateApi,
73    /// Agent management: lifecycle/budgets/memory.
74    pub agents: AgentApi,
75    /// Security: auth/audit/RBAC/approvals.
76    pub security: SecurityApi,
77    /// Persona management.
78    pub persona: PersonaApi,
79    /// Extensions: programs/skills/host tools.
80    pub extensions: ExtensionApi,
81    /// MCP server bridge.
82    pub mcp: McpApi,
83    /// Infrastructure: Git/scheduler/cron/resources/events/system.
84    pub infra: InfraApi,
85    /// Space management: context partitioning, knowledge flow.
86    pub spaces: SpaceApi,
87    /// Execution: config + access management.
88    pub exec: ExecApi,
89    /// Browser backend (zero-sized when `browser` feature is disabled).
90    pub browser: BrowserApi,
91    /// Agent-to-agent communication.
92    pub a2a: A2aApi,
93    /// Knowledge base: markdown notes (direct access, no kernel dependency).
94    pub knowledge: Arc<oxios_markdown::KnowledgeBase>,
95    /// Semantic knowledge overlay (HNSW index + agent recall).
96    pub knowledge_lens: Arc<KnowledgeLens>,
97}
98
99impl KernelHandle {
100    /// Create a new KernelHandle from 12 domain Facades.
101    ///
102    /// Each Facade is assembled independently in `kernel.rs` and passed here.
103    /// This enables testing individual Facades without the full kernel.
104    #[allow(clippy::too_many_arguments)]
105    pub fn new(
106        state: StateApi,
107        agents: AgentApi,
108        security: SecurityApi,
109        persona: PersonaApi,
110        extensions: ExtensionApi,
111        mcp: McpApi,
112        infra: InfraApi,
113        spaces: SpaceApi,
114        exec: ExecApi,
115        browser: BrowserApi,
116        a2a: A2aApi,
117        knowledge: Arc<oxios_markdown::KnowledgeBase>,
118        knowledge_lens: Arc<KnowledgeLens>,
119    ) -> Self {
120        Self {
121            state,
122            agents,
123            security,
124            persona,
125            extensions,
126            mcp,
127            infra,
128            spaces,
129            exec,
130            browser,
131            a2a,
132            knowledge,
133            knowledge_lens,
134        }
135    }
136
137    /// Build a KernelHandle from raw subsystem parameters.
138    ///
139    /// Prefer [`KernelHandle::new()`] which takes pre-built Facades.
140    #[deprecated(note = "Use KernelHandle::new() with pre-built Facades instead")]
141    #[allow(clippy::too_many_arguments)]
142    pub fn from_subsystems(
143        state_store: Arc<StateStore>,
144        event_bus: EventBus,
145        supervisor: Arc<dyn Supervisor>,
146        scheduler: Arc<AgentScheduler>,
147        memory_manager: Arc<MemoryManager>,
148        git_layer: Arc<GitLayer>,
149        audit_trail: Arc<AuditTrail>,
150        budget_manager: Arc<BudgetManager>,
151        resource_monitor: Arc<ResourceMonitor>,
152        cron_scheduler: Arc<CronScheduler>,
153        program_manager: Arc<ProgramManager>,
154        skill_store: Arc<SkillStore>,
155        persona_manager: Arc<PersonaManager>,
156        mcp_bridge: Arc<McpBridge>,
157        auth_manager: Arc<parking_lot::Mutex<AuthManager>>,
158        access_manager: Arc<parking_lot::Mutex<AccessManager>>,
159        host_tool_validator: Arc<HostToolValidator>,
160        config: OxiosConfig,
161        start_time: Instant,
162        space_manager: Arc<SpaceManager>,
163    ) -> Self {
164        let knowledge_dir = state_store.base_path.join("knowledge");
165        let knowledge = Arc::new(
166            oxios_markdown::KnowledgeBase::new(knowledge_dir)
167                .expect("Failed to create KnowledgeBase"),
168        );
169        let knowledge_lens = Arc::new(
170            KnowledgeLens::new(knowledge.clone(), memory_manager.clone())
171                .expect("Failed to create KnowledgeLens"),
172        );
173        Self {
174            security: SecurityApi::new(
175                auth_manager.clone(),
176                audit_trail,
177                access_manager.clone(),
178                state_store.clone(),
179            ),
180            state: StateApi::new(state_store),
181            agents: AgentApi::new(
182                supervisor,
183                budget_manager,
184                memory_manager,
185                Some(event_bus.clone()),
186            ),
187            persona: PersonaApi::new(persona_manager),
188            extensions: ExtensionApi::new(program_manager, skill_store, host_tool_validator),
189            mcp: McpApi::new(mcp_bridge),
190            infra: InfraApi::new(
191                git_layer,
192                scheduler,
193                cron_scheduler,
194                resource_monitor,
195                event_bus.clone(),
196                config.clone(),
197                start_time,
198            ),
199            spaces: SpaceApi::new(space_manager, event_bus),
200            exec: ExecApi::new(Arc::new(config.exec.clone()), access_manager),
201            #[allow(clippy::default_trait_access)]
202            browser: BrowserApi::default(),
203            a2a: A2aApi::new(Arc::new(A2AProtocol::new(crate::EventBus::new(0)))),
204            knowledge,
205            knowledge_lens,
206        }
207    }
208
209    // ═══════════════════════════════════════════════════════════════════════
210    // Convenience methods (cross-Facades orchestration)
211    // ═══════════════════════════════════════════════════════════════════════
212
213    /// Save data and commit to git (State + Infra).
214    pub async fn save_and_commit<T: Serialize>(
215        &self,
216        category: &str,
217        name: &str,
218        data: &T,
219    ) -> anyhow::Result<()> {
220        self.state.save(category, name, data).await?;
221        let git = self.infra.git();
222        if git.is_enabled() {
223            let rel_path = format!("{}/{}.json", category, name);
224            let _ = git.commit_file(&rel_path, &format!("save {}/{}", category, name));
225        }
226        Ok(())
227    }
228
229    /// Save markdown and commit to git (State + Infra).
230    pub async fn save_markdown_and_commit(
231        &self,
232        category: &str,
233        name: &str,
234        content: &str,
235    ) -> anyhow::Result<()> {
236        self.state.save_markdown(category, name, content).await?;
237        let git = self.infra.git();
238        if git.is_enabled() {
239            let rel_path = format!("{}/{}.md", category, name);
240            let _ = git.commit_file(&rel_path, &format!("save {}/{}", category, name));
241        }
242        Ok(())
243    }
244
245    /// Delete a file and commit the removal to git (State + Infra).
246    pub async fn delete_and_commit(&self, category: &str, name: &str) -> anyhow::Result<bool> {
247        let deleted = self.state.delete(category, name).await?;
248        if deleted {
249            let git = self.infra.git();
250            if git.is_enabled() {
251                let rel_path = format!("{}/{}.json", category, name);
252                let _ = git.remove_file(&rel_path, &format!("delete {}/{}", category, name));
253            }
254        }
255        Ok(deleted)
256    }
257
258    /// Commit all current changes to git.
259    pub fn commit_all(&self, message: &str) -> anyhow::Result<Option<CommitInfo>> {
260        self.state.commit_all(self.infra.git(), message)
261    }
262
263    /// Flush audit trail and commit to git (Security + Infra).
264    pub fn flush_audit(&self) -> anyhow::Result<()> {
265        self.security.flush(self.infra.git())
266    }
267
268    /// Schedule a cron job by expression (convenience wrapper).
269    pub async fn schedule(
270        &self,
271        cron_expr: &str,
272        task: &str,
273        persona: Option<&str>,
274    ) -> anyhow::Result<String> {
275        let _persona = persona.unwrap_or("default");
276        let job = crate::cron::CronJob::new(
277            format!("job_{}", uuid::Uuid::new_v4()),
278            cron_expr.to_string(),
279            task.to_string(),
280        );
281        let job_id = self.infra.add_cron(job).await?;
282        Ok(job_id.to_string())
283    }
284
285    /// Unschedule a cron job by string ID (convenience wrapper).
286    pub async fn unschedule(&self, job_id: &str) -> anyhow::Result<bool> {
287        let uuid =
288            uuid::Uuid::parse_str(job_id).map_err(|e| anyhow::anyhow!("invalid job id: {e}"))?;
289        match self.infra.remove_cron(uuid).await {
290            Ok(()) => Ok(true),
291            Err(_) => Ok(false),
292        }
293    }
294
295    /// List cron jobs (convenience wrapper).
296    pub fn list_schedules(&self) -> Vec<crate::cron::CronJob> {
297        self.infra.list_crons()
298    }
299
300    /// Load JSON from state store.
301    pub async fn load_json<T: serde::de::DeserializeOwned>(
302        &self,
303        category: &str,
304        name: &str,
305    ) -> anyhow::Result<Option<T>> {
306        self.state.load(category, name).await
307    }
308
309    /// Get kernel start time.
310    pub fn start_time(&self) -> std::time::Instant {
311        self.infra.start_time
312    }
313
314    /// Activate a Space.
315    ///
316    /// Note: Knowledge base is global (`~/.oxios/knowledge/`), not Space-scoped.
317    /// Space activation only switches the agent's execution context and memory graph.
318    pub async fn activate_space(&self, id: &str) -> anyhow::Result<()> {
319        self.spaces.activate(id).await?;
320        tracing::info!(space_id = %id, "Space activated (knowledge base is global)");
321        Ok(())
322    }
323}