//! Kernel - central coordinator mod adapters; mod agents; mod messaging; mod skills; mod hands; mod triggers; mod approvals; mod orchestration; #[cfg(feature = "multi-agent")] mod a2a; use std::sync::Arc; use tokio::sync::{broadcast, Mutex}; use zclaw_types::{Event, Result, AgentState}; #[cfg(feature = "multi-agent")] use zclaw_types::AgentId; #[cfg(feature = "multi-agent")] use zclaw_protocols::A2aRouter; use crate::registry::AgentRegistry; use crate::capabilities::CapabilityManager; use crate::events::EventBus; use crate::config::KernelConfig; use zclaw_memory::MemoryStore; use zclaw_runtime::{LlmDriver, ToolRegistry, tool::SkillExecutor}; use zclaw_skills::SkillRegistry; use zclaw_hands::{HandRegistry, hands::{BrowserHand, SlideshowHand, SpeechHand, QuizHand, WhiteboardHand, ResearcherHand, CollectorHand, ClipHand, TwitterHand, quiz::LlmQuizGenerator}}; pub use adapters::KernelSkillExecutor; pub use messaging::ChatModeConfig; /// The ZCLAW Kernel pub struct Kernel { config: KernelConfig, registry: AgentRegistry, capabilities: CapabilityManager, events: EventBus, memory: Arc, driver: Arc, llm_completer: Arc, skills: Arc, skill_executor: Arc, hands: Arc, trigger_manager: crate::trigger_manager::TriggerManager, pending_approvals: Arc>>, /// Running hand runs that can be cancelled (run_id -> cancelled flag) running_hand_runs: Arc>>, /// Shared memory storage backend for Growth system viking: Arc, /// Optional LLM driver for memory extraction (set by Tauri desktop layer) extraction_driver: Option>, /// MCP tool adapters — shared with Tauri MCP manager, updated dynamically mcp_adapters: Arc>>, /// A2A router for inter-agent messaging (gated by multi-agent feature) #[cfg(feature = "multi-agent")] a2a_router: Arc, /// Per-agent A2A inbox receivers (supports re-queuing non-matching messages) #[cfg(feature = "multi-agent")] a2a_inboxes: Arc>>>, } impl Kernel { /// Boot the kernel with the given configuration pub async fn boot(config: KernelConfig) -> Result { // Initialize memory store let memory = Arc::new(MemoryStore::new(&config.database_url).await?); // Initialize driver based on config let driver = config.create_driver()?; // Initialize subsystems let registry = AgentRegistry::new(); let capabilities = CapabilityManager::new(); let events = EventBus::new(); // Initialize skill registry let skills = Arc::new(SkillRegistry::new()); // Scan skills directory if configured if let Some(ref skills_dir) = config.skills_dir { if skills_dir.exists() { skills.add_skill_dir(skills_dir.clone()).await?; } } // Initialize hand registry with built-in hands let hands = Arc::new(HandRegistry::new()); let quiz_model = config.model().to_string(); let quiz_generator = Arc::new(LlmQuizGenerator::new(driver.clone(), quiz_model)); hands.register(Arc::new(BrowserHand::new())).await; hands.register(Arc::new(SlideshowHand::new())).await; hands.register(Arc::new(SpeechHand::new())).await; hands.register(Arc::new(QuizHand::with_generator(quiz_generator))).await; hands.register(Arc::new(WhiteboardHand::new())).await; hands.register(Arc::new(ResearcherHand::new())).await; hands.register(Arc::new(CollectorHand::new())).await; hands.register(Arc::new(ClipHand::new())).await; hands.register(Arc::new(TwitterHand::new())).await; // Create skill executor let skill_executor = Arc::new(KernelSkillExecutor::new(skills.clone(), driver.clone())); // Create LLM completer for skill system (shared with skill_executor) let llm_completer: Arc = Arc::new(adapters::LlmDriverAdapter { driver: driver.clone(), max_tokens: config.max_tokens(), temperature: config.temperature(), }); // Initialize trigger manager let trigger_manager = crate::trigger_manager::TriggerManager::new(hands.clone()); // Initialize Growth system — shared VikingAdapter for memory storage let viking = Arc::new(zclaw_runtime::VikingAdapter::in_memory()); // Restore persisted agents with their runtime state let persisted = memory.list_agents_with_runtime().await?; for (agent, state_str, msg_count) in persisted { let state = match state_str.as_str() { "running" => AgentState::Running, "suspended" => AgentState::Suspended, _ => AgentState::Terminated, }; // Only auto-resume agents that were running; suspended/terminated stay as-is let restored_state = if state == AgentState::Running { AgentState::Running } else { state }; registry.register_with_runtime(agent, restored_state, msg_count); } // Initialize A2A router for multi-agent support #[cfg(feature = "multi-agent")] let a2a_router = { let kernel_agent_id = AgentId::new(); Arc::new(A2aRouter::new(kernel_agent_id)) }; Ok(Self { config, registry, capabilities, events, memory, driver, llm_completer, skills, skill_executor, hands, trigger_manager, pending_approvals: Arc::new(Mutex::new(Vec::new())), running_hand_runs: Arc::new(dashmap::DashMap::new()), viking, extraction_driver: None, mcp_adapters: Arc::new(std::sync::RwLock::new(Vec::new())), #[cfg(feature = "multi-agent")] a2a_router, #[cfg(feature = "multi-agent")] a2a_inboxes: Arc::new(dashmap::DashMap::new()), }) } /// Create a tool registry with built-in tools + MCP tools. /// When `subagent_enabled` is false, TaskTool is excluded to prevent /// the LLM from attempting sub-agent delegation in non-Ultra modes. pub(crate) fn create_tool_registry(&self, subagent_enabled: bool) -> ToolRegistry { let mut tools = ToolRegistry::new(); zclaw_runtime::tool::builtin::register_builtin_tools(&mut tools); // Register TaskTool only when sub-agent mode is enabled (Ultra mode) if subagent_enabled { let task_tool = zclaw_runtime::tool::builtin::TaskTool::new( self.driver.clone(), self.memory.clone(), self.config.model(), ); tools.register(Box::new(task_tool)); } // Register MCP tools (dynamically updated by Tauri MCP manager) if let Ok(adapters) = self.mcp_adapters.read() { for adapter in adapters.iter() { let wrapper = zclaw_runtime::tool::builtin::McpToolWrapper::new( std::sync::Arc::new(adapter.clone()) ); tools.register(Box::new(wrapper)); } } tools } /// Create the middleware chain for the agent loop. /// /// When middleware is configured, cross-cutting concerns (compaction, loop guard, /// token calibration, etc.) are delegated to the chain. When no middleware is /// registered, the legacy inline path in `AgentLoop` is used instead. pub(crate) fn create_middleware_chain(&self) -> Option { let mut chain = zclaw_runtime::middleware::MiddlewareChain::new(); // Butler router — semantic skill routing context injection { use std::sync::Arc; use zclaw_runtime::middleware::butler_router::{ButlerRouterBackend, RoutingHint}; use async_trait::async_trait; use zclaw_skills::semantic_router::SemanticSkillRouter; /// Adapter bridging `SemanticSkillRouter` (zclaw-skills) to `ButlerRouterBackend`. /// Lives here in kernel because kernel depends on both zclaw-runtime and zclaw-skills. struct SemanticRouterAdapter { router: Arc, } impl SemanticRouterAdapter { fn new(router: Arc) -> Self { Self { router } } } #[async_trait] impl ButlerRouterBackend for SemanticRouterAdapter { async fn classify(&self, query: &str) -> Option { let result: Option<_> = self.router.route(query).await; result.map(|r| RoutingHint { category: "semantic_skill".to_string(), confidence: r.confidence, skill_id: Some(r.skill_id), domain_prompt: None, }) } } // Build semantic router from the skill registry (75 SKILL.md loaded at boot) let semantic_router = SemanticSkillRouter::new_tf_idf_only(self.skills.clone()); let adapter = SemanticRouterAdapter::new(Arc::new(semantic_router)); let mw = zclaw_runtime::middleware::butler_router::ButlerRouterMiddleware::with_router( Box::new(adapter) ); chain.register(Arc::new(mw)); } // Data masking middleware — mask sensitive entities before any other processing { use std::sync::Arc; let masker = Arc::new(zclaw_runtime::middleware::data_masking::DataMasker::new()); let mw = zclaw_runtime::middleware::data_masking::DataMaskingMiddleware::new(masker); chain.register(Arc::new(mw)); } // Growth integration — shared VikingAdapter for memory middleware & compaction let mut growth = zclaw_runtime::GrowthIntegration::new(self.viking.clone()); if let Some(ref driver) = self.extraction_driver { growth = growth.with_llm_driver(driver.clone()); } // Compaction middleware — only register when threshold > 0 let threshold = self.config.compaction_threshold(); if threshold > 0 { use std::sync::Arc; let mut growth_for_compaction = zclaw_runtime::GrowthIntegration::new(self.viking.clone()); if let Some(ref driver) = self.extraction_driver { growth_for_compaction = growth_for_compaction.with_llm_driver(driver.clone()); } let mw = zclaw_runtime::middleware::compaction::CompactionMiddleware::new( threshold, zclaw_runtime::CompactionConfig::default(), Some(self.driver.clone()), Some(growth_for_compaction), ); chain.register(Arc::new(mw)); } // Memory middleware — auto-extract memories after conversations { use std::sync::Arc; let mw = zclaw_runtime::middleware::memory::MemoryMiddleware::new(growth); chain.register(Arc::new(mw)); } // Loop guard middleware { use std::sync::Arc; let mw = zclaw_runtime::middleware::loop_guard::LoopGuardMiddleware::with_defaults(); chain.register(Arc::new(mw)); } // Token calibration middleware { use std::sync::Arc; let mw = zclaw_runtime::middleware::token_calibration::TokenCalibrationMiddleware::new(); chain.register(Arc::new(mw)); } // Skill index middleware — inject lightweight index instead of full descriptions { use std::sync::Arc; let entries = self.skill_executor.list_skill_index(); if !entries.is_empty() { let mw = zclaw_runtime::middleware::skill_index::SkillIndexMiddleware::new(entries); chain.register(Arc::new(mw)); } } // Title middleware — auto-generate conversation titles after first exchange { use std::sync::Arc; let mw = zclaw_runtime::middleware::title::TitleMiddleware::new(); chain.register(Arc::new(mw)); } // Dangling tool repair — patch missing tool results before LLM calls { use std::sync::Arc; let mw = zclaw_runtime::middleware::dangling_tool::DanglingToolMiddleware::new(); chain.register(Arc::new(mw)); } // Tool error middleware — format tool errors for LLM recovery { use std::sync::Arc; let mw = zclaw_runtime::middleware::tool_error::ToolErrorMiddleware::new(); chain.register(Arc::new(mw)); } // Tool output guard — post-execution output sanitization checks { use std::sync::Arc; let mw = zclaw_runtime::middleware::tool_output_guard::ToolOutputGuardMiddleware::new(); chain.register(Arc::new(mw)); } // Guardrail middleware — safety rules for tool calls { use std::sync::Arc; let mw = zclaw_runtime::middleware::guardrail::GuardrailMiddleware::new(true) .with_builtin_rules(); chain.register(Arc::new(mw)); } // Sub-agent limit — cap concurrent sub-agent spawning { use std::sync::Arc; let mw = zclaw_runtime::middleware::subagent_limit::SubagentLimitMiddleware::new(); chain.register(Arc::new(mw)); } // Only return Some if we actually registered middleware if chain.is_empty() { None } else { tracing::info!("[Kernel] Middleware chain created with {} middlewares", chain.len()); Some(chain) } } /// Subscribe to events pub fn subscribe(&self) -> broadcast::Receiver { self.events.subscribe() } /// Shutdown the kernel pub async fn shutdown(&self) -> Result<()> { // Persist all agent runtime states before shutdown let agents = self.registry.list(); for info in &agents { let state_str = match info.state { AgentState::Running => "running", AgentState::Suspended => "suspended", AgentState::Terminated => "terminated", }; if let Err(e) = self.memory .update_agent_runtime(&info.id, state_str, info.message_count as u64) .await { tracing::warn!("[Kernel] Failed to persist agent {} state: {}", info.id, e); } } tracing::info!("[Kernel] Persisted runtime state for {} agents", agents.len()); self.events.publish(Event::KernelShutdown); Ok(()) } /// Get the kernel configuration pub fn config(&self) -> &KernelConfig { &self.config } /// Get the LLM driver pub fn driver(&self) -> Arc { self.driver.clone() } /// Replace the default in-memory VikingAdapter with a persistent one. /// /// Called by the Tauri desktop layer after `Kernel::boot()` to bridge /// the kernel's Growth system to the same SqliteStorage used by /// viking_commands and intelligence_hooks. pub fn set_viking(&mut self, viking: Arc) { tracing::info!("[Kernel] Replacing in-memory VikingAdapter with persistent storage"); self.viking = viking; } /// Get a reference to the shared VikingAdapter pub fn viking(&self) -> Arc { self.viking.clone() } /// Set the LLM extraction driver for the Growth system. /// /// Required for `MemoryMiddleware` to extract memories from conversations /// via LLM analysis. If not set, memory extraction is silently skipped. pub fn set_extraction_driver(&mut self, driver: Arc) { tracing::info!("[Kernel] Extraction driver configured for Growth system"); self.extraction_driver = Some(driver); } /// Get a reference to the shared MCP adapters list. /// /// The Tauri MCP manager updates this list when services start/stop. /// The kernel reads it during `create_tool_registry()` to inject MCP tools /// into the LLM's available tools. pub fn mcp_adapters(&self) -> Arc>> { self.mcp_adapters.clone() } /// Replace the MCP adapters with a shared Arc (from Tauri MCP manager). /// /// Call this after boot to connect the kernel to the Tauri MCP manager's /// adapter list. After this, MCP service start/stop will automatically /// be reflected in the LLM's available tools. pub fn set_mcp_adapters(&mut self, adapters: Arc>>) { tracing::info!("[Kernel] MCP adapters bridge connected"); self.mcp_adapters = adapters; } } #[derive(Debug, Clone)] pub struct ApprovalEntry { pub id: String, pub hand_id: String, pub status: String, pub created_at: chrono::DateTime, pub input: serde_json::Value, pub reject_reason: Option, } /// Response from sending a message #[derive(Debug, Clone)] pub struct MessageResponse { pub content: String, pub input_tokens: u32, pub output_tokens: u32, }