Skip to main content

Skill System

Skills extend Soul capabilities through a plugin architecture, enabling vision, voice, actuators, and custom behaviors.

Overview

The Skill System allows Souls to:
  • Load capabilities dynamically
  • Execute skills in isolated contexts
  • Share data between skills safely
  • Update skills without kernel changes

Architecture

Skill ABI (Application Binary Interface)

/// Core trait that all Skills must implement
pub trait Skill: Send + Sync {
    /// Unique name of the skill
    fn name(&self) -> &str;
    
    /// Semantic version of the skill
    fn version(&self) -> &str;
    
    /// List of capabilities this skill provides
    fn capabilities(&self) -> Vec<Capability>;
    
    /// Execute the skill with given input
    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError>;
    
    /// Initialize the skill (optional)
    fn initialize(&mut self) -> Result<(), SkillError> {
        Ok(())
    }
    
    /// Shutdown the skill (optional)
    fn shutdown(&mut self) -> Result<(), SkillError> {
        Ok(())
    }
}

Capability Types

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Capability {
    TextProcessing,
    ImageProcessing,
    AudioProcessing,
    VoiceSynthesis,
    VisionAnalysis,
    MotorControl,
    DataStorage,
    NetworkAccess,
    Custom(String),
}

Input/Output Types

/// Input types that can be passed to skills
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SkillInput {
    Text(String),
    Image(Vec<u8>),
    Audio(Vec<f32>),
    Json(serde_json::Value),
    Binary(Vec<u8>),
}

/// Output types that skills can return
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SkillOutput {
    Text(String),
    Image(Vec<u8>),
    Audio(Vec<f32>),
    Json(serde_json::Value),
    Action(ActionCommand),
    Binary(Vec<u8>),
}

/// Commands for actuator control
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionCommand {
    pub target: String,
    pub action: String,
    pub parameters: serde_json::Value,
}

Error Handling

#[derive(Debug)]
pub enum SkillError {
    InvalidInput(String),
    ExecutionError(String),
    NotImplemented(String),
    ResourceUnavailable(String),
    Custom(Box<dyn Error + Send + Sync>),
}

Creating a Skill

1. Basic Skill Structure

// hello_skill/src/lib.rs
use soul_kernel_skill_abi::{
    export_skill, Capability, Skill, SkillError, SkillInput, SkillOutput,
};

pub struct HelloSkill {
    greeting: String,
}

impl HelloSkill {
    pub fn new() -> Self {
        Self {
            greeting: "Hello from Soul Kernel!".to_string(),
        }
    }
}

impl Skill for HelloSkill {
    fn name(&self) -> &str {
        "hello"
    }

    fn version(&self) -> &str {
        env!("CARGO_PKG_VERSION")
    }

    fn capabilities(&self) -> Vec<Capability> {
        vec![Capability::TextProcessing]
    }

    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError> {
        match input {
            SkillInput::Text(name) => {
                let response = if name.is_empty() {
                    self.greeting.clone()
                } else {
                    format!("{} Nice to meet you, {}!", self.greeting, name)
                };
                Ok(SkillOutput::Text(response))
            }
            _ => Err(SkillError::InvalidInput(
                "Hello skill expects text input".to_string(),
            )),
        }
    }
}

// Export the skill for dynamic loading
export_skill!(HelloSkill);

2. Skill Cargo.toml

[package]
name = "hello-skill"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
soul-kernel-skill-abi = { path = "../skill-abi" }
serde = { version = "1.0", features = ["derive"] }

3. Building & Loading

# Build as dynamic library
cargo build --release

# The skill will be available as a .dylib/.so/.dll file
ls target/release/libhello_skill.dylib

Skill Registry

The Soul Kernel provides a thread-safe skill registry for managing loaded skills:
use soul_kernel_core::SkillRegistry;

// Create registry
let registry = SkillRegistry::new();

// Load skill from dynamic library
let skill_id = registry.load_skill_from_library("./libhello_skill.dylib")?;

// Execute skill
let input = SkillInput::Text("Alice".to_string());
let output = registry.execute_skill(&skill_id, input)?;

// List loaded skills
for (name, version) in registry.list_skills() {
    println!("Loaded: {} v{}", name, version);
}

// Unload skill when done
registry.unload_skill(&skill_id)?;

Built-in Example Skills

1. Hello Skill

Basic greeting skill demonstrating text processing.

2. Echo Skill

Multi-format skill that echoes any input type:
impl Skill for EchoSkill {
    fn capabilities(&self) -> Vec<Capability> {
        vec![
            Capability::TextProcessing,
            Capability::ImageProcessing,
            Capability::AudioProcessing,
        ]
    }

    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError> {
        // Echo skill simply returns the input unchanged
        match input {
            SkillInput::Text(text) => Ok(SkillOutput::Text(text)),
            SkillInput::Image(data) => Ok(SkillOutput::Image(data)),
            SkillInput::Audio(data) => Ok(SkillOutput::Audio(data)),
            SkillInput::Json(json) => Ok(SkillOutput::Json(json)),
            SkillInput::Binary(data) => Ok(SkillOutput::Binary(data)),
        }
    }
}

Skill Development Best Practices

  1. Thread Safety - Skills must be Send + Sync
  2. Error Handling - Never panic, return proper errors
  3. Resource Management - Use initialize() and shutdown()
  4. Clear Capabilities - Declare what your skill can do
  5. Version Carefully - Use semantic versioning

Security Considerations

  • Skills run in the same process (sandboxing planned for future)
  • Validate all inputs before processing
  • Be careful with file system and network access
  • Sign skills for authenticity (future feature)

Advanced Topics

Direct Skill Registration

For built-in skills that don’t need dynamic loading:
let skill = Box::new(MyBuiltInSkill::new());
let skill_id = registry.register_skill(skill);

Action Commands

Skills can return commands for physical actuators:
Ok(SkillOutput::Action(ActionCommand {
    target: "arm".to_string(),
    action: "wave".to_string(),
    parameters: json!({
        "duration": 2.0,
        "amplitude": 0.5
    }),
}))

Skill Composition (Future)

// Planned feature for composing multiple skills
let composite = SkillComposer::new()
    .add("vision")
    .add("language")
    .add("voice")
    .build();

Next Steps

Change Log

  • 2025-06-12: Updated to reflect actual Skill ABI V1 implementation from Story-2
    • Added complete trait definition with Send + Sync bounds
    • Updated capability types to match implementation
    • Added ActionCommand for actuator control
    • Updated code examples to use actual API
    • Added skill registry usage examples
    • Clarified FFI-safe implementation details