Skip to main content

Skill Development Guide

This guide walks you through creating custom Skills for Soul Kernel from concept to deployment.

Prerequisites

  • Rust development environment
  • Soul Kernel CLI installed (soul --version)
  • Basic understanding of the Skill System

Quick Start: Hello World Skill

1. Create New Skill Project

Use the Soul CLI to scaffold a new skill:
# Create a new skill project
soul new skill hello-world
cd hello-world-skill
This creates:
hello-world-skill/
├── Cargo.toml
├── src/
│   └── lib.rs
└── tests/
    └── integration_test.rs

2. Configure Cargo.toml

The CLI generates a proper Cargo.toml:
[package]
name = "hello-world-skill"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]  # Important: Build as dynamic library

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

3. Implement the Skill

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

pub struct HelloWorldSkill {
    greeting: String,
}

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

impl Skill for HelloWorldSkill {
    fn name(&self) -> &str {
        "hello-world"
    }
    
    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(
                "Expected text input".to_string()
            ))
        }
    }
}

// Required: Export the skill for dynamic loading
export_skill!(HelloWorldSkill);

4. Test Your Skill

Create tests/integration_test.rs:
use hello_world_skill::HelloWorldSkill;
use soul_kernel_skill_abi::{Skill, SkillInput, SkillOutput};

#[test]
fn test_greeting() {
    let skill = HelloWorldSkill::new();
    let input = SkillInput::Text("Alice".to_string());
    let output = skill.execute(input).unwrap();
    
    match output {
        SkillOutput::Text(text) => {
            assert!(text.contains("Alice"));
            assert!(text.contains("Hello"));
        }
        _ => panic!("Unexpected output type"),
    }
}

#[test]
fn test_empty_name() {
    let skill = HelloWorldSkill::new();
    let input = SkillInput::Text("".to_string());
    let output = skill.execute(input).unwrap();
    
    match output {
        SkillOutput::Text(text) => {
            assert_eq!(text, "Hello from Soul Kernel!");
        }
        _ => panic!("Unexpected output type"),
    }
}
Run tests:
# Run tests using Soul CLI
soul test skill

# Or use cargo directly
cargo test

5. Build the Skill

# Build using Soul CLI (recommended)
soul build skill --release

# Or use cargo directly
cargo build --release

# Your skill library will be at:
# - macOS: target/release/libhello_world_skill.dylib
# - Linux: target/release/libhello_world_skill.so
# - Windows: target/release/hello_world_skill.dll

6. Test Your Skill Locally

Use the Soul CLI to test your skill interactively:
# Test skill in REPL mode
soul repl --skill ./target/release/libhello_world_skill.dylib

# Or run automated tests
soul test skill --integration
In the REPL:
> load hello-world
Loaded skill: hello-world v0.1.0

> exec hello-world "Alice"
Hello from Soul Kernel! Nice to meet you, Alice!

> capabilities hello-world
- TextProcessing

7. Package and Install

# Package the skill for distribution
soul package skill
# Creates: hello-world-skill-0.1.0.skill

# Install locally for development
soul install skill ./hello-world-skill-0.1.0.skill --dev

# List installed skills
soul list skills

Advanced Skill Development

Using the Skill Template Generator

Soul CLI provides templates for common skill types:
# Create a perception skill (vision, audio)
soul new skill my-vision --template perception

# Create an action skill (motors, actuators)
soul new skill my-motor --template action

# Create a cognitive skill (reasoning, planning)
soul new skill my-planner --template cognitive

# List available templates
soul new skill --list-templates

Multi-Format Skills

Skills can handle multiple input/output formats:
pub struct VersatileSkill;

impl Skill for VersatileSkill {
    fn capabilities(&self) -> Vec<Capability> {
        vec![
            Capability::TextProcessing,
            Capability::ImageProcessing,
            Capability::AudioProcessing,
        ]
    }
    
    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError> {
        match input {
            SkillInput::Text(text) => {
                // Process text
                Ok(SkillOutput::Text(text.to_uppercase()))
            }
            SkillInput::Image(data) => {
                // Process image
                Ok(SkillOutput::Json(json!({
                    "size": data.len(),
                    "format": "processed"
                })))
            }
            SkillInput::Audio(samples) => {
                // Process audio
                let avg_amplitude = samples.iter().sum::<f32>() / samples.len() as f32;
                Ok(SkillOutput::Json(json!({
                    "average_amplitude": avg_amplitude
                })))
            }
            _ => Err(SkillError::NotImplemented(
                "Format not supported".to_string()
            ))
        }
    }
}

Stateful Skills

Skills can maintain state between executions:
use std::sync::Mutex;

pub struct CounterSkill {
    count: Mutex<u32>,
}

impl CounterSkill {
    pub fn new() -> Self {
        Self {
            count: Mutex::new(0),
        }
    }
}

impl Skill for CounterSkill {
    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError> {
        let mut count = self.count.lock().unwrap();
        *count += 1;
        
        Ok(SkillOutput::Json(json!({
            "count": *count,
            "input": format!("{:?}", input)
        })))
    }
}

Resource Management

Use initialize() and shutdown() for resource management:
pub struct DatabaseSkill {
    connection: Option<Connection>,
}

impl Skill for DatabaseSkill {
    fn initialize(&mut self) -> Result<(), SkillError> {
        self.connection = Some(
            Connection::open("soul.db")
                .map_err(|e| SkillError::ExecutionError(e.to_string()))?
        );
        Ok(())
    }
    
    fn shutdown(&mut self) -> Result<(), SkillError> {
        if let Some(conn) = self.connection.take() {
            conn.close()
                .map_err(|e| SkillError::ExecutionError(e.to_string()))?;
        }
        Ok(())
    }
}

Action Commands for Physical AI

Skills can control actuators through action commands:
pub struct MotorSkill;

impl Skill for MotorSkill {
    fn capabilities(&self) -> Vec<Capability> {
        vec![Capability::MotorControl]
    }
    
    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError> {
        match input {
            SkillInput::Text(command) => {
                match command.as_str() {
                    "wave" => Ok(SkillOutput::Action(ActionCommand {
                        target: "arm".to_string(),
                        action: "wave".to_string(),
                        parameters: json!({
                            "duration": 2.0,
                            "amplitude": 0.5,
                            "frequency": 2.0
                        }),
                    })),
                    "move_forward" => Ok(SkillOutput::Action(ActionCommand {
                        target: "wheels".to_string(),
                        action: "move".to_string(),
                        parameters: json!({
                            "direction": "forward",
                            "speed": 0.5,
                            "distance": 1.0
                        }),
                    })),
                    _ => Err(SkillError::InvalidInput(
                        format!("Unknown command: {}", command)
                    ))
                }
            }
            _ => Err(SkillError::InvalidInput("Expected text command".to_string()))
        }
    }
}

Error Handling Best Practices

Create custom error types for better error handling:
use thiserror::Error;

#[derive(Debug, Error)]
pub enum VisionError {
    #[error("Camera not available")]
    CameraUnavailable,
    
    #[error("Image processing failed: {0}")]
    ProcessingError(String),
    
    #[error("Invalid image format")]
    InvalidFormat,
}

impl From<VisionError> for SkillError {
    fn from(err: VisionError) -> Self {
        SkillError::ExecutionError(err.to_string())
    }
}

pub struct VisionSkill;

impl Skill for VisionSkill {
    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError> {
        match input {
            SkillInput::Image(data) => {
                if data.is_empty() {
                    return Err(VisionError::InvalidFormat.into());
                }
                // Process image...
                Ok(SkillOutput::Text("Image processed".to_string()))
            }
            _ => Err(SkillError::InvalidInput("Expected image input".to_string()))
        }
    }
}

Testing Strategies

Unit Tests

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_skill_metadata() {
        let skill = MySkill::new();
        assert_eq!(skill.name(), "my-skill");
        assert!(!skill.version().is_empty());
        assert!(!skill.capabilities().is_empty());
    }
    
    #[test]
    fn test_invalid_input() {
        let skill = MySkill::new();
        let result = skill.execute(SkillInput::Binary(vec![]));
        assert!(result.is_err());
    }
}

Integration Tests with Soul CLI

# Run integration tests
soul test skill --integration

# Test with specific input files
soul test skill --input test_data/

# Benchmark performance
soul bench skill

Testing with Mock Soul

#[test]
fn test_skill_with_mock_soul() {
    // Soul CLI provides test utilities
    use soul_kernel_test::MockSoul;
    
    let mut soul = MockSoul::new();
    soul.load_skill("./target/release/libmy_skill.dylib").unwrap();
    
    let response = soul.ask("test question").unwrap();
    assert!(!response.is_empty());
}

Performance Optimization

Profiling with Soul CLI

# Profile skill performance
soul profile skill ./my-skill.skill

# Generate flamegraph
soul profile skill --flamegraph

# Memory usage analysis
soul analyze skill --memory

Lazy Initialization

use once_cell::sync::Lazy;

static MODEL: Lazy<Model> = Lazy::new(|| {
    Model::load("model.onnx").expect("Failed to load model")
});

pub struct MLSkill;

impl Skill for MLSkill {
    fn execute(&self, input: SkillInput) -> Result<SkillOutput, SkillError> {
        // MODEL is initialized only on first use
        let result = MODEL.predict(&input)?;
        Ok(SkillOutput::Json(result))
    }
}

Caching Results

use lru::LruCache;
use std::sync::Mutex;

pub struct CachedSkill {
    cache: Mutex<LruCache<String, String>>,
}

impl CachedSkill {
    pub fn new() -> Self {
        Self {
            cache: Mutex::new(LruCache::new(100)),
        }
    }
}

Deployment

Publishing to Soul Registry

# Login to Soul Registry
soul login

# Publish your skill
soul publish skill
# Validates, packages, and uploads to registry

# Publish with specific visibility
soul publish skill --visibility public
soul publish skill --visibility private
soul publish skill --visibility unlisted

Installing from Registry

# Install a skill from registry
soul install skill weather-skill

# Install specific version
soul install skill [email protected]

# Install with dependencies
soul install skill vision-skill --with-deps

Building for Different Platforms

# Build for multiple targets
soul build skill --target all

# Build for specific platforms
soul build skill --target macos,linux,windows

# Cross-compile for edge devices
soul build skill --target aarch64-unknown-linux-gnu

Size Optimization

# In Cargo.toml
[profile.release]
opt-level = "z"     # Optimize for size
lto = true          # Link-time optimization
codegen-units = 1   # Single codegen unit
strip = true        # Strip symbols
Or use Soul CLI:
# Build with size optimization
soul build skill --optimize size

Soul CLI Commands Reference

Development Commands

# Create new skill
soul new skill <name> [--template <template>]

# Build skill
soul build skill [--release] [--target <target>]

# Test skill
soul test skill [--integration] [--bench]

# Run skill in REPL
soul repl --skill <path>

# Package skill
soul package skill [--sign]

# Profile skill
soul profile skill <path> [--flamegraph]

Management Commands

# List installed skills
soul list skills [--verbose]

# Install skill
soul install skill <name|path> [--dev]

# Uninstall skill
soul uninstall skill <name>

# Update skill
soul update skill <name>

# Show skill info
soul info skill <name>

Registry Commands

# Login to registry
soul login [--token <token>]

# Publish skill
soul publish skill [--visibility <level>]

# Search registry
soul search skill <query>

# Download skill
soul download skill <name> [--version <version>]

Best Practices Checklist

  • Use soul new skill to start projects
  • Test with soul test skill before publishing
  • Profile performance with soul profile skill
  • Document skill with soul doc skill
  • Sign releases with soul package skill --sign
  • Use semantic versioning
  • Include comprehensive tests
  • Handle errors gracefully
  • Validate all inputs
  • Clean up resources properly

Example Skills

Check out the example skills in the Soul Kernel repository:

Next Steps

Change Log

  • 2025-06-12: Complete rewrite based on actual Skill ABI V1 implementation
    • Updated all code examples to match real API
    • Added export_skill! macro usage
    • Included ActionCommand examples for Physical AI
    • Added proper error handling patterns
    • Updated build instructions for dynamic libraries
    • Added registry integration examples
  • 2025-06-12: Added comprehensive Soul CLI integration
    • Added CLI commands throughout the guide
    • Included REPL testing examples
    • Added registry publishing workflow
    • Included profiling and optimization commands