Documentation Index
Fetch the complete documentation index at: https://doc.ambientsoul.ai/llms.txt
Use this file to discover all available pages before exploring further.
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
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
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());
}
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 weather-skill@1.2.0
# Install with dependencies
soul install skill vision-skill --with-deps
# 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
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