feat: add internal ZCLAW kernel crates to git tracking
This commit is contained in:
34
crates/zclaw-kernel/Cargo.toml
Normal file
34
crates/zclaw-kernel/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "zclaw-kernel"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "ZCLAW kernel - central coordinator for all subsystems"
|
||||
|
||||
[dependencies]
|
||||
zclaw-types = { workspace = true }
|
||||
zclaw-memory = { workspace = true }
|
||||
zclaw-runtime = { workspace = true }
|
||||
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
|
||||
# Concurrency
|
||||
dashmap = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
|
||||
# Secrets
|
||||
secrecy = { workspace = true }
|
||||
|
||||
# Home directory
|
||||
dirs = { workspace = true }
|
||||
71
crates/zclaw-kernel/src/capabilities.rs
Normal file
71
crates/zclaw-kernel/src/capabilities.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Capability manager
|
||||
|
||||
use dashmap::DashMap;
|
||||
use zclaw_types::{AgentId, Capability, CapabilitySet, Result, ZclawError};
|
||||
|
||||
/// Manages capabilities for all agents
|
||||
pub struct CapabilityManager {
|
||||
capabilities: DashMap<AgentId, CapabilitySet>,
|
||||
}
|
||||
|
||||
impl CapabilityManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
capabilities: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Grant capabilities to an agent
|
||||
pub fn grant(&self, agent_id: AgentId, capabilities: Vec<Capability>) {
|
||||
let set = CapabilitySet {
|
||||
capabilities,
|
||||
};
|
||||
self.capabilities.insert(agent_id, set);
|
||||
}
|
||||
|
||||
/// Revoke all capabilities from an agent
|
||||
pub fn revoke(&self, agent_id: &AgentId) {
|
||||
self.capabilities.remove(agent_id);
|
||||
}
|
||||
|
||||
/// Check if an agent can invoke a tool
|
||||
pub fn can_invoke_tool(&self, agent_id: &AgentId, tool_name: &str) -> bool {
|
||||
self.capabilities
|
||||
.get(agent_id)
|
||||
.map(|set| set.can_invoke_tool(tool_name))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check if an agent can read memory
|
||||
pub fn can_read_memory(&self, agent_id: &AgentId, scope: &str) -> bool {
|
||||
self.capabilities
|
||||
.get(agent_id)
|
||||
.map(|set| set.can_read_memory(scope))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check if an agent can write memory
|
||||
pub fn can_write_memory(&self, agent_id: &AgentId, scope: &str) -> bool {
|
||||
self.capabilities
|
||||
.get(agent_id)
|
||||
.map(|set| set.can_write_memory(scope))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Validate capabilities don't exceed parent's
|
||||
pub fn validate(&self, capabilities: &[Capability]) -> Result<()> {
|
||||
// TODO: Implement capability validation
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get capabilities for an agent
|
||||
pub fn get(&self, agent_id: &AgentId) -> Option<CapabilitySet> {
|
||||
self.capabilities.get(agent_id).map(|c| c.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CapabilityManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
121
crates/zclaw-kernel/src/config.rs
Normal file
121
crates/zclaw-kernel/src/config.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
//! Kernel configuration
|
||||
|
||||
use std::sync::Arc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use secrecy::SecretString;
|
||||
use zclaw_types::{Result, ZclawError};
|
||||
use zclaw_runtime::{LlmDriver, AnthropicDriver, OpenAiDriver, GeminiDriver, LocalDriver};
|
||||
|
||||
/// Kernel configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct KernelConfig {
|
||||
/// Database URL (SQLite)
|
||||
#[serde(default = "default_database_url")]
|
||||
pub database_url: String,
|
||||
|
||||
/// Default LLM provider
|
||||
#[serde(default = "default_provider")]
|
||||
pub default_provider: String,
|
||||
|
||||
/// Default model
|
||||
#[serde(default = "default_model")]
|
||||
pub default_model: String,
|
||||
|
||||
/// API keys (loaded from environment)
|
||||
#[serde(skip)]
|
||||
pub anthropic_api_key: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub openai_api_key: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub gemini_api_key: Option<String>,
|
||||
|
||||
/// Local LLM base URL
|
||||
#[serde(default)]
|
||||
pub local_base_url: Option<String>,
|
||||
|
||||
/// Maximum tokens per response
|
||||
#[serde(default = "default_max_tokens")]
|
||||
pub max_tokens: u32,
|
||||
|
||||
/// Default temperature
|
||||
#[serde(default = "default_temperature")]
|
||||
pub temperature: f32,
|
||||
}
|
||||
|
||||
fn default_database_url() -> String {
|
||||
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
|
||||
let dir = home.join(".zclaw");
|
||||
format!("sqlite:{}/data.db?mode=rwc", dir.display())
|
||||
}
|
||||
|
||||
fn default_provider() -> String {
|
||||
"anthropic".to_string()
|
||||
}
|
||||
|
||||
fn default_model() -> String {
|
||||
"claude-sonnet-4-20250514".to_string()
|
||||
}
|
||||
|
||||
fn default_max_tokens() -> u32 {
|
||||
4096
|
||||
}
|
||||
|
||||
fn default_temperature() -> f32 {
|
||||
0.7
|
||||
}
|
||||
|
||||
impl Default for KernelConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
database_url: default_database_url(),
|
||||
default_provider: default_provider(),
|
||||
default_model: default_model(),
|
||||
anthropic_api_key: std::env::var("ANTHROPIC_API_KEY").ok(),
|
||||
openai_api_key: std::env::var("OPENAI_API_KEY").ok(),
|
||||
gemini_api_key: std::env::var("GEMINI_API_KEY").ok(),
|
||||
local_base_url: None,
|
||||
max_tokens: default_max_tokens(),
|
||||
temperature: default_temperature(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KernelConfig {
|
||||
/// Load configuration from file
|
||||
pub async fn load() -> Result<Self> {
|
||||
// TODO: Load from ~/.zclaw/config.toml
|
||||
Ok(Self::default())
|
||||
}
|
||||
|
||||
/// Create the default LLM driver
|
||||
pub fn create_driver(&self) -> Result<Arc<dyn LlmDriver>> {
|
||||
let driver: Arc<dyn LlmDriver> = match self.default_provider.as_str() {
|
||||
"anthropic" => {
|
||||
let key = self.anthropic_api_key.clone()
|
||||
.ok_or_else(|| ZclawError::ConfigError("ANTHROPIC_API_KEY not set".into()))?;
|
||||
Arc::new(AnthropicDriver::new(SecretString::new(key)))
|
||||
}
|
||||
"openai" => {
|
||||
let key = self.openai_api_key.clone()
|
||||
.ok_or_else(|| ZclawError::ConfigError("OPENAI_API_KEY not set".into()))?;
|
||||
Arc::new(OpenAiDriver::new(SecretString::new(key)))
|
||||
}
|
||||
"gemini" => {
|
||||
let key = self.gemini_api_key.clone()
|
||||
.ok_or_else(|| ZclawError::ConfigError("GEMINI_API_KEY not set".into()))?;
|
||||
Arc::new(GeminiDriver::new(SecretString::new(key)))
|
||||
}
|
||||
"local" | "ollama" => {
|
||||
let base_url = self.local_base_url.clone()
|
||||
.unwrap_or_else(|| "http://localhost:11434/v1".to_string());
|
||||
Arc::new(LocalDriver::new(base_url))
|
||||
}
|
||||
_ => {
|
||||
return Err(ZclawError::ConfigError(
|
||||
format!("Unknown provider: {}", self.default_provider)
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(driver)
|
||||
}
|
||||
}
|
||||
34
crates/zclaw-kernel/src/events.rs
Normal file
34
crates/zclaw-kernel/src/events.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Event bus for kernel events
|
||||
|
||||
use tokio::sync::broadcast;
|
||||
use zclaw_types::Event;
|
||||
|
||||
/// Event bus for publishing and subscribing to events
|
||||
pub struct EventBus {
|
||||
sender: broadcast::Sender<Event>,
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
/// Create a new event bus
|
||||
pub fn new() -> Self {
|
||||
let (sender, _) = broadcast::channel(1000);
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
/// Publish an event
|
||||
pub fn publish(&self, event: Event) {
|
||||
// Ignore send errors (no subscribers)
|
||||
let _ = self.sender.send(event);
|
||||
}
|
||||
|
||||
/// Subscribe to events
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
|
||||
self.sender.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventBus {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
180
crates/zclaw-kernel/src/kernel.rs
Normal file
180
crates/zclaw-kernel/src/kernel.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
//! Kernel - central coordinator
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, Event, Result};
|
||||
|
||||
use crate::registry::AgentRegistry;
|
||||
use crate::capabilities::CapabilityManager;
|
||||
use crate::events::EventBus;
|
||||
use crate::config::KernelConfig;
|
||||
use zclaw_memory::MemoryStore;
|
||||
use zclaw_runtime::{AgentLoop, LlmDriver, ToolRegistry};
|
||||
|
||||
/// The ZCLAW Kernel
|
||||
pub struct Kernel {
|
||||
config: KernelConfig,
|
||||
registry: AgentRegistry,
|
||||
capabilities: CapabilityManager,
|
||||
events: EventBus,
|
||||
memory: Arc<MemoryStore>,
|
||||
driver: Arc<dyn LlmDriver>,
|
||||
}
|
||||
|
||||
impl Kernel {
|
||||
/// Boot the kernel with the given configuration
|
||||
pub async fn boot(config: KernelConfig) -> Result<Self> {
|
||||
// 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();
|
||||
|
||||
// Restore persisted agents
|
||||
let persisted = memory.list_agents().await?;
|
||||
for agent in persisted {
|
||||
registry.register(agent);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
registry,
|
||||
capabilities,
|
||||
events,
|
||||
memory,
|
||||
driver,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a tool registry with built-in tools
|
||||
fn create_tool_registry(&self) -> ToolRegistry {
|
||||
let mut tools = ToolRegistry::new();
|
||||
zclaw_runtime::tool::builtin::register_builtin_tools(&mut tools);
|
||||
tools
|
||||
}
|
||||
|
||||
/// Spawn a new agent
|
||||
pub async fn spawn_agent(&self, config: AgentConfig) -> Result<AgentId> {
|
||||
let id = config.id;
|
||||
|
||||
// Validate capabilities
|
||||
self.capabilities.validate(&config.capabilities)?;
|
||||
|
||||
// Register in memory
|
||||
self.memory.save_agent(&config).await?;
|
||||
|
||||
// Register in registry
|
||||
self.registry.register(config);
|
||||
|
||||
// Emit event
|
||||
self.events.publish(Event::AgentSpawned {
|
||||
agent_id: id,
|
||||
name: self.registry.get(&id).map(|a| a.name.clone()).unwrap_or_default(),
|
||||
});
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Kill an agent
|
||||
pub async fn kill_agent(&self, id: &AgentId) -> Result<()> {
|
||||
// Remove from registry
|
||||
self.registry.unregister(id);
|
||||
|
||||
// Remove from memory
|
||||
self.memory.delete_agent(id).await?;
|
||||
|
||||
// Emit event
|
||||
self.events.publish(Event::AgentTerminated {
|
||||
agent_id: *id,
|
||||
reason: "killed".to_string(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List all agents
|
||||
pub fn list_agents(&self) -> Vec<AgentInfo> {
|
||||
self.registry.list()
|
||||
}
|
||||
|
||||
/// Get agent info
|
||||
pub fn get_agent(&self, id: &AgentId) -> Option<AgentInfo> {
|
||||
self.registry.get_info(id)
|
||||
}
|
||||
|
||||
/// Send a message to an agent
|
||||
pub async fn send_message(&self, agent_id: &AgentId, message: String) -> Result<MessageResponse> {
|
||||
let _agent = self.registry.get(agent_id)
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Agent not found: {}", agent_id)))?;
|
||||
|
||||
// Create or get session
|
||||
let session_id = self.memory.create_session(agent_id).await?;
|
||||
|
||||
// Create agent loop
|
||||
let tools = self.create_tool_registry();
|
||||
let loop_runner = AgentLoop::new(
|
||||
*agent_id,
|
||||
self.driver.clone(),
|
||||
tools,
|
||||
self.memory.clone(),
|
||||
);
|
||||
|
||||
// Run the loop
|
||||
let result = loop_runner.run(session_id, message).await?;
|
||||
|
||||
Ok(MessageResponse {
|
||||
content: result.response,
|
||||
input_tokens: result.input_tokens,
|
||||
output_tokens: result.output_tokens,
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a message with streaming
|
||||
pub async fn send_message_stream(
|
||||
&self,
|
||||
agent_id: &AgentId,
|
||||
message: String,
|
||||
) -> Result<mpsc::Receiver<zclaw_runtime::LoopEvent>> {
|
||||
let _agent = self.registry.get(agent_id)
|
||||
.ok_or_else(|| zclaw_types::ZclawError::NotFound(format!("Agent not found: {}", agent_id)))?;
|
||||
|
||||
// Create session
|
||||
let session_id = self.memory.create_session(agent_id).await?;
|
||||
|
||||
// Create agent loop
|
||||
let tools = self.create_tool_registry();
|
||||
let loop_runner = AgentLoop::new(
|
||||
*agent_id,
|
||||
self.driver.clone(),
|
||||
tools,
|
||||
self.memory.clone(),
|
||||
);
|
||||
|
||||
// Run with streaming
|
||||
loop_runner.run_streaming(session_id, message).await
|
||||
}
|
||||
|
||||
/// Subscribe to events
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
|
||||
self.events.subscribe()
|
||||
}
|
||||
|
||||
/// Shutdown the kernel
|
||||
pub async fn shutdown(&self) -> Result<()> {
|
||||
self.events.publish(Event::KernelShutdown);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Response from sending a message
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageResponse {
|
||||
pub content: String,
|
||||
pub input_tokens: u32,
|
||||
pub output_tokens: u32,
|
||||
}
|
||||
Reference in New Issue
Block a user