Skills
Summary: Skills are modular, reusable capabilities that add functions, prompts, and integrations to your agents without custom code.
What You'll Learn
This chapter covers the skills system:
- Understanding Skills - What skills are and how they work
- Built-in Skills - Pre-built skills available in the SDK
- Adding Skills - How to add skills to your agents
- Custom Skills - Creating your own skills
- Skill Configuration - Parameters and advanced options
What Are Skills?
Skills are pre-packaged capabilities that add:
- Functions - SWAIG tools the AI can call
- Prompts - Instructions for how to use the skill
- Hints - Speech recognition keywords
- Global Data - Variables available throughout the call
| Without Skills | With Skills |
|---|---|
| Write weather function | self.add_skill("weather") |
| Add API integration | |
| Write prompts | Done! |
| Add speech hints | |
| Handle errors |
Quick Start
Add a skill in one line:
from signalwire_agents import AgentBase
class MyAgent(AgentBase):
def __init__(self):
super().__init__(name="my-agent")
self.add_language("English", "en-US", "rime.spore")
# Add datetime capability
self.add_skill("datetime")
# Add math capability
self.add_skill("math")
self.prompt_add_section(
"Role",
"You are a helpful assistant that can tell time and do math."
)
Available Built-in Skills
| Skill | Description |
|---|---|
datetime | Get current date and time |
math | Perform calculations |
web_search | Search the web (requires API key) |
wikipedia_search | Search Wikipedia |
weather_api | Get weather information |
joke | Tell jokes |
play_background_file | Play audio files |
swml_transfer | Transfer calls to SWML endpoints |
datasphere | Search DataSphere documents |
native_vector_search | Local vector search |
Chapter Contents
| Section | Description |
|---|---|
| Understanding Skills | How skills work internally |
| Built-in Skills | Reference for included skills |
| Adding Skills | How to use skills in your agents |
| Custom Skills | Creating your own skills |
| Skill Configuration | Parameters and advanced options |
Skills vs Functions
| Aspect | SWAIG Function | Skill |
|---|---|---|
| Scope | Single function | Multiple functions + prompts + hints |
| Reusability | Per-agent | Across all agents |
| Setup | define_tool() | add_skill() |
| Customization | Full control | Parameters only |
| Maintenance | You maintain | SDK maintains |
When to Use Skills
Use Built-in Skills When:
- Standard capability needed (datetime, search, etc.)
- Want quick setup without custom code
- Need tested, maintained functionality
Create Custom Skills When:
- Reusing capability across multiple agents
- Want to share functionality with team/community
- Packaging complex integrations
Use SWAIG Functions When:
- One-off custom logic
- Agent-specific business rules
- Need full control over implementation
Complete Example
#!/usr/bin/env python3
# assistant_agent.py - Agent with multiple skills
from signalwire_agents import AgentBase
class AssistantAgent(AgentBase):
def __init__(self):
super().__init__(name="assistant")
self.add_language("English", "en-US", "rime.spore")
# Add multiple skills
self.add_skill("datetime")
self.add_skill("math")
self.prompt_add_section(
"Role",
"You are a helpful assistant named Alex."
)
self.prompt_add_section(
"Capabilities",
body="You can help with:",
bullets=[
"Telling the current date and time",
"Performing math calculations"
]
)
if __name__ == "__main__":
agent = AssistantAgent()
agent.run()
Let's start by understanding how skills work internally.
Skill Architecture
SkillBase (Abstract Base Class)
Required Methods:
setup()- Initialize the skillregister_tools()- Register SWAIG functions
Optional Methods:
get_hints()- Speech recognition hintsget_global_data()- Session dataget_prompt_sections()- Prompt additionscleanup()- Resource cleanup
SkillRegistry (Discovery & Loading)
- Discovers skills from directories
- Loads skills on-demand (lazy loading)
- Validates requirements (packages, env vars)
- Supports external skill paths
How Skills Work
Skills are a convenience layer built on top of SWAIG functions. When you add a skill, it registers one or more SWAIG functions with the agent, adds relevant prompts, and configures hints—all from a single add_skill() call.
Understanding this helps when debugging: a skill's function behaves exactly like a SWAIG function you'd define yourself. The only difference is that the skill packages everything together.
When you call add_skill():
┌─────────────────────────────────────────────────────────────────────────────┐
│ Skill Loading Process │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Agent calls add_skill("datetime") │
│ │ │
│ ▼ │
│ 2. SkillRegistry looks up skill class │
│ • Checks already loaded skills │
│ • Searches built-in skills directory │
│ • Searches external paths │
│ │ │
│ ▼ │
│ 3. SkillManager instantiates skill │
│ • Creates skill instance with agent reference │
│ • Passes configuration parameters │
│ │ │
│ ▼ │
│ 4. Skill setup() is called │
│ • Validates required packages │
│ • Validates environment variables │
│ • Initializes APIs/connections │
│ │ │
│ ▼ │
│ 5. Skill register_tools() is called │
│ • Registers SWAIG functions with agent │
│ │ │
│ ▼ │
│ 6. Skill contributions applied │
│ • Prompts added to agent │
│ • Hints added for speech recognition │
│ • Global data merged │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Skill Directory Structure
Built-in skills live in the SDK:
signalwire_agents/
└── skills/
├── datetime/
│ ├── __init__.py
│ └── skill.py
├── math/
│ ├── __init__.py
│ └── skill.py
├── web_search/
│ ├── __init__.py
│ ├── skill.py
│ └── requirements.txt
└── ...
Each skill directory contains:
| File | Purpose |
|---|---|
skill.py | Skill class implementation |
__init__.py | Python package marker |
requirements.txt | Optional extra dependencies |
SkillBase Class
All skills inherit from SkillBase:
from signalwire_agents.core.skill_base import SkillBase
from signalwire_agents.core.function_result import SwaigFunctionResult
class MySkill(SkillBase):
# Required class attributes
SKILL_NAME = "my_skill"
SKILL_DESCRIPTION = "Does something useful"
SKILL_VERSION = "1.0.0"
# Optional requirements
REQUIRED_PACKAGES = [] # Python packages needed
REQUIRED_ENV_VARS = [] # Environment variables needed
# Multi-instance support
SUPPORTS_MULTIPLE_INSTANCES = False
def setup(self) -> bool:
"""Initialize the skill. Return True if successful."""
return True
def register_tools(self) -> None:
"""Register SWAIG tools with the agent."""
self.define_tool(
name="my_function",
description="Does something",
parameters={},
handler=self.my_handler
)
def my_handler(self, args, raw_data):
"""Handle function calls."""
return SwaigFunctionResult("Result")
Skill Lifecycle
Discover → Load → Setup → Register → Active → Cleanup
| Stage | Description |
|---|---|
| Discover | Registry finds skill class in directory |
| Load | Skill class is imported and validated |
| Setup | setup() validates requirements, initializes resources |
| Register | register_tools() adds functions to agent |
| Active | Skill is ready, functions can be called |
| Cleanup | cleanup() releases resources on shutdown |
Skill Contributions
Skills can contribute to the agent in multiple ways:
1. Tools (Functions)
def register_tools(self) -> None:
self.define_tool(
name="get_time",
description="Get current time",
parameters={
"timezone": {
"type": "string",
"description": "Timezone name"
}
},
handler=self.get_time_handler
)
2. Prompt Sections
def get_prompt_sections(self):
return [
{
"title": "Time Information",
"body": "You can tell users the current time.",
"bullets": [
"Use get_time for time queries",
"Support multiple timezones"
]
}
]
3. Speech Hints
def get_hints(self):
return ["time", "date", "clock", "timezone"]
4. Global Data
def get_global_data(self):
return {
"datetime_enabled": True,
"default_timezone": "UTC"
}
Skill Discovery Paths
Skills are discovered from multiple locations in priority order:
| Priority | Source | Example |
|---|---|---|
| 1 | Already registered skills (in memory) | - |
| 2 | Entry points (pip installed packages) | entry_points={'signalwire_agents.skills': ['my_skill = pkg:Skill']} |
| 3 | Built-in skills directory | signalwire_agents/skills/ |
| 4 | External paths | skill_registry.add_skill_directory('/opt/custom_skills') |
| 5 | Environment variable paths | SIGNALWIRE_SKILL_PATHS=/path1:/path2 |
Lazy Loading
Skills are loaded on-demand to minimize startup time:
# Skill NOT loaded yet
agent = MyAgent()
# Skill loaded when first referenced
agent.add_skill("datetime") # datetime skill loaded here
# Already loaded, reused
agent.add_skill("datetime") # Uses cached class
Multi-Instance Skills
Some skills support multiple instances with different configurations:
class MySkill(SkillBase):
SUPPORTS_MULTIPLE_INSTANCES = True
def get_instance_key(self) -> str:
# Unique key for this instance
tool_name = self.params.get('tool_name', self.SKILL_NAME)
return f"{self.SKILL_NAME}_{tool_name}"
Usage:
# Add two instances with different configs
agent.add_skill("web_search", {
"tool_name": "search_news",
"search_engine_id": "NEWS_ENGINE_ID",
"api_key": "KEY"
})
agent.add_skill("web_search", {
"tool_name": "search_docs",
"search_engine_id": "DOCS_ENGINE_ID",
"api_key": "KEY"
})
Parameter Passing
Parameters flow through skills in a structured way:
At add_skill() time:
self.add_skill("web_search", {
"api_key": "your-key",
"tool_name": "custom_search",
"max_results": 5
})
The skill receives these in self.params during setup:
def setup(self) -> bool:
self.api_key = self.params.get("api_key")
self.max_results = self.params.get("max_results", 3)
return True
At function call time: The AI calls the function with arguments:
def search_handler(self, args, raw_data):
query = args.get("query") # From AI's function call
max_results = self.max_results # From skill config
# ...
Result Handling
Skill handlers return SwaigFunctionResult just like regular SWAIG functions:
def my_handler(self, args, raw_data):
# Success case
return SwaigFunctionResult("The result is 42")
# With actions
return (
SwaigFunctionResult("Updated your preferences")
.update_global_data({"preference": "value"})
)
# Error case - still return a result for the AI
return SwaigFunctionResult("I couldn't complete that request. Please try again.")
The result goes back to the AI, which uses it to formulate a response to the user.
Error Handling and Propagation
Skills should handle errors gracefully and return meaningful messages:
def api_handler(self, args, raw_data):
try:
result = self.call_external_api(args)
return SwaigFunctionResult(f"Result: {result}")
except requests.Timeout:
return SwaigFunctionResult(
"The service is taking too long to respond. Please try again."
)
except requests.RequestException as e:
self.agent.log.error(f"API error: {e}")
return SwaigFunctionResult(
"I'm having trouble connecting to the service right now."
)
except Exception as e:
self.agent.log.error(f"Unexpected error: {e}")
return SwaigFunctionResult(
"Something went wrong. Please try again."
)
Error handling principles:
- Always return a
SwaigFunctionResult, even on errors - Make error messages user-friendly (the AI will relay them)
- Log technical details for debugging
- Don't expose internal errors to users
Debugging Skills
When skills don't work as expected:
1. Check if the skill loaded:
# In your agent
print(f"Skills loaded: {list(self._skill_manager._skills.keys())}")
2. Verify functions are registered:
swaig-test your_agent.py --dump-swml | grep -A 5 "functions"
3. Test the function directly:
swaig-test your_agent.py --function skill_function_name --args '{"param": "value"}'
4. Check for missing requirements: Skills log warnings if required packages or environment variables are missing. Check your logs during agent startup.
5. Look at skill source: Built-in skills are in the SDK source. Examine them to understand how they work:
pip show signalwire-agents
# Find location, then look in signalwire_agents/skills/