Skip to main content

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:

  1. Understanding Skills - What skills are and how they work
  2. Built-in Skills - Pre-built skills available in the SDK
  3. Adding Skills - How to add skills to your agents
  4. Custom Skills - Creating your own skills
  5. 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 SkillsWith Skills
Write weather functionself.add_skill("weather")
Add API integration
Write promptsDone!
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

SkillDescription
datetimeGet current date and time
mathPerform calculations
weather_apiGet weather information
jokeTell jokes
play_background_filePlay audio files
swml_transferTransfer calls to SWML endpoints
datasphereSearch DataSphere documents

Chapter Contents

SectionDescription
Understanding SkillsHow skills work internally
Built-in SkillsReference for included skills
Adding SkillsHow to use skills in your agents
Custom SkillsCreating your own skills
Skill ConfigurationParameters and advanced options

Skills vs Functions

AspectSWAIG FunctionSkill
ScopeSingle functionMultiple functions + prompts + hints
ReusabilityPer-agentAcross all agents
Setupdefine_tool()add_skill()
CustomizationFull controlParameters only
MaintenanceYou maintainSDK 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 skill
  • register_tools() - Register SWAIG functions

Optional Methods:

  • get_hints() - Speech recognition hints
  • get_global_data() - Session data
  • get_prompt_sections() - Prompt additions
  • cleanup() - 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:

FilePurpose
skill.pySkill class implementation
__init__.pyPython package marker
requirements.txtOptional 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
StageDescription
DiscoverRegistry finds skill class in directory
LoadSkill class is imported and validated
Setupsetup() validates requirements, initializes resources
Registerregister_tools() adds functions to agent
ActiveSkill is ready, functions can be called
Cleanupcleanup() 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:

PrioritySourceExample
1Already registered skills (in memory)-
2Entry points (pip installed packages)entry_points={'signalwire_agents.skills': ['my_skill = pkg:Skill']}
3Built-in skills directorysignalwire_agents/skills/
4External pathsskill_registry.add_skill_directory('/opt/custom_skills')
5Environment variable pathsSIGNALWIRE_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/