clawdesk-skills
Skill system for dynamically extending agent capabilities. Skills are modular, hot-reloadable units of functionality that can be activated/deactivated at runtime. The skill selector matches incoming messages to relevant skills.
Dependencies
Internal: clawdesk-types, clawdesk-domain
External: tokio, async-trait, serde, tracing, thiserror
Modules
| Module | Description |
|---|---|
definition | Skill struct and metadata schema |
registry | SkillRegistry — tracks available skills and their state |
resolver | Resolves skill locations from filesystem |
loader | Loads skill definitions from TOML/YAML files |
selector | Matches messages to relevant skills based on triggers |
Key Types
/// Skill definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Skill {
pub id: String,
pub name: String,
pub version: String,
pub description: String,
pub triggers: Vec<SkillTrigger>,
pub tools: Vec<ToolDefinition>,
pub system_prompt_addition: Option<String>,
pub active: bool,
}
/// How a skill is triggered
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SkillTrigger {
Keyword(Vec<String>),
Regex(String),
Intent(String),
Always,
}
/// Skill registry managing available skills
pub struct SkillRegistry {
skills: Arc<RwLock<HashMap<String, Skill>>>,
}
impl SkillRegistry {
pub async fn register(&self, skill: Skill) -> Result<(), SkillError> { /* ... */ }
pub async fn activate(&self, id: &str) -> Result<(), SkillError> { /* ... */ }
pub async fn deactivate(&self, id: &str) -> Result<(), SkillError> { /* ... */ }
pub async fn reload_all(&self) -> Result<usize, SkillError> { /* ... */ }
pub async fn list(&self) -> Vec<Skill> { /* ... */ }
}
/// Matches messages to relevant skills
pub struct SkillSelector {
registry: Arc<SkillRegistry>,
}
impl SkillSelector {
pub async fn select(&self, message: &Message) -> Vec<Skill> {
// Returns all skills whose triggers match the message
}
}
Skill Definition File
# skills/web-search.toml
id = "web-search"
name = "Web Search"
version = "1.0.0"
description = "Search the web using multiple providers"
active = true
[[triggers]]
type = "keyword"
values = ["search", "find", "look up", "google"]
[[triggers]]
type = "intent"
value = "information_retrieval"
[[tools]]
name = "web_search"
description = "Search the web for information"
[tools.parameters]
type = "object"
properties = { query = { type = "string" } }
required = ["query"]
Example Usage
use clawdesk_skills::{SkillRegistry, SkillSelector, Skill};
let registry = Arc::new(SkillRegistry::new());
// Load skills from directory
let loader = SkillLoader::new("./skills/");
let skills = loader.load_all()?;
for skill in skills {
registry.register(skill).await?;
}
// Select skills for a message
let selector = SkillSelector::new(registry.clone());
let matched = selector.select(&message).await;
// Activate/deactivate at runtime
registry.activate("web-search").await?;
registry.deactivate("code-execution").await?;
// Hot-reload all skills
let count = registry.reload_all().await?;
info
Skills inject tools and system prompt additions into the agent pipeline dynamically. Only active skills participate in message processing.