Skills

View as MarkdownOpen in Claude

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 functionagent.add_skill("weather")
Add API integration
Write promptsDone!
Add speech hints
Handle errors

The add_skill() method is available across all SDK languages:

LanguageSyntax
Pythonagent.add_skill("weather")
TypeScriptagent.addSkill('weather')

Quick Start

Add a skill in one line:

1from signalwire import AgentBase
2
3class MyAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="my-agent")
6 self.add_language("English", "en-US", "rime.spore")
7
8 # Add datetime capability
9 self.add_skill("datetime")
10
11 # Add math capability
12 self.add_skill("math")
13
14 self.prompt_add_section(
15 "Role",
16 "You are a helpful assistant that can tell time and do math."
17 )

Available Built-in Skills

SkillDescription
datetimeGet current date and time
mathPerform calculations
web_searchSearch the web (requires API key)
wikipedia_searchSearch Wikipedia
weather_apiGet weather information
jokeTell jokes
play_background_filePlay audio files
swml_transferTransfer calls to SWML endpoints
datasphereSearch DataSphere documents
native_vector_searchLocal vector search

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

The add_skill() vs define_tool() distinction applies across all languages:

Languageadd_skilldefine_tool
Pythonagent.add_skill("name")agent.define_tool(...)
TypeScriptagent.addSkill('name')agent.defineTool(...)

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

1#!/usr/bin/env python3
2# assistant_agent.py - Agent with multiple skills
3from signalwire import AgentBase
4
5class AssistantAgent(AgentBase):
6 def __init__(self):
7 super().__init__(name="assistant")
8 self.add_language("English", "en-US", "rime.spore")
9
10 # Add multiple skills
11 self.add_skill("datetime")
12 self.add_skill("math")
13
14 self.prompt_add_section(
15 "Role",
16 "You are a helpful assistant named Alex."
17 )
18
19 self.prompt_add_section(
20 "Capabilities",
21 body="You can help with:",
22 bullets=[
23 "Telling the current date and time",
24 "Performing math calculations"
25 ]
26 )
27
28if __name__ == "__main__":
29 agent = AssistantAgent()
30 agent.run()

Let’s start by understanding how skills work internally.

Skill Architecture

Skill loading process diagram.
Skill Loading Process

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.

Skill Directory Structure

Built-in skills live in the SDK:

signalwire/
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. The pattern is similar across all SDK languages:

LanguagePattern
Pythonclass MySkill(SkillBase): def setup(self, agent): ...
TypeScriptclass MySkill extends SkillBase { setup(agent) { ... } }
1from signalwire.skills import SkillBase
2from signalwire.core.function_result import FunctionResult
3
4class MySkill(SkillBase):
5 # Required class attributes
6 SKILL_NAME = "my_skill"
7 SKILL_DESCRIPTION = "Does something useful"
8 SKILL_VERSION = "1.0.0"
9
10 # Optional requirements
11 REQUIRED_PACKAGES = [] # Python packages needed
12 REQUIRED_ENV_VARS = [] # Environment variables needed
13
14 # Multi-instance support
15 SUPPORTS_MULTIPLE_INSTANCES = False
16
17 def setup(self) -> bool:
18 """Initialize the skill. Return True if successful."""
19 return True
20
21 def register_tools(self) -> None:
22 """Register SWAIG tools with the agent."""
23 self.define_tool(
24 name="my_function",
25 description="Does something",
26 parameters={},
27 handler=self.my_handler
28 )
29
30 def my_handler(self, args, raw_data):
31 """Handle function calls."""
32 return FunctionResult("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)

1def register_tools(self) -> None:
2 self.define_tool(
3 name="get_time",
4 description="Get current time",
5 parameters={
6 "timezone": {
7 "type": "string",
8 "description": "Timezone name"
9 }
10 },
11 handler=self.get_time_handler
12 )

2. Prompt Sections

1def get_prompt_sections(self):
2 return [
3 {
4 "title": "Time Information",
5 "body": "You can tell users the current time.",
6 "bullets": [
7 "Use get_time for time queries",
8 "Support multiple timezones"
9 ]
10 }
11 ]

3. Speech Hints

1def get_hints(self):
2 return ["time", "date", "clock", "timezone"]

4. Global Data

1def get_global_data(self):
2 return {
3 "datetime_enabled": True,
4 "default_timezone": "UTC"
5 }

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.skills': ['my_skill = pkg:Skill']}
3Built-in skills directorysignalwire/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:

1# Skill NOT loaded yet
2agent = MyAgent()
3
4# Skill loaded when first referenced
5agent.add_skill("datetime") # datetime skill loaded here
6
7# Already loaded, reused
8agent.add_skill("datetime") # Uses cached class

Multi-Instance Skills

Some skills support multiple instances with different configurations:

1class MySkill(SkillBase):
2 SUPPORTS_MULTIPLE_INSTANCES = True
3
4 def get_instance_key(self) -> str:
5 # Unique key for this instance
6 tool_name = self.params.get('tool_name', self.SKILL_NAME)
7 return f"{self.SKILL_NAME}_{tool_name}"

Usage across languages:

LanguageAdding multiple instances
Pythonagent.add_skill("web_search", {"tool_name": "search_news", "api_key": "KEY"})
TypeScriptagent.addSkill('web_search', { toolName: 'search_news', apiKey: 'KEY' })
1# Add two instances with different configs
2agent.add_skill("web_search", {
3 "tool_name": "search_news",
4 "search_engine_id": "NEWS_ENGINE_ID",
5 "api_key": "KEY"
6})
7
8agent.add_skill("web_search", {
9 "tool_name": "search_docs",
10 "search_engine_id": "DOCS_ENGINE_ID",
11 "api_key": "KEY"
12})

Parameter Passing

Parameters flow through skills in a structured way:

At add_skill() time:

1self.add_skill("web_search", {
2 "api_key": "your-key",
3 "tool_name": "custom_search",
4 "max_results": 5
5})

The skill receives these in self.params during setup:

1def setup(self) -> bool:
2 self.api_key = self.params.get("api_key")
3 self.max_results = self.params.get("max_results", 3)
4 return True

At function call time: The AI calls the function with arguments:

1def search_handler(self, args, raw_data):
2 query = args.get("query") # From AI's function call
3 max_results = self.max_results # From skill config
4 # ...

Result Handling

Skill handlers return FunctionResult just like regular SWAIG functions:

1def my_handler(self, args, raw_data):
2 # Success case
3 return FunctionResult("The result is 42")
4
5 # With actions
6 return (
7 FunctionResult("Updated your preferences")
8 .update_global_data({"preference": "value"})
9 )
10
11 # Error case - still return a result for the AI
12 return FunctionResult("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:

1def api_handler(self, args, raw_data):
2 try:
3 result = self.call_external_api(args)
4 return FunctionResult(f"Result: {result}")
5 except requests.Timeout:
6 return FunctionResult(
7 "The service is taking too long to respond. Please try again."
8 )
9 except requests.RequestException as e:
10 self.agent.log.error(f"API error: {e}")
11 return FunctionResult(
12 "I'm having trouble connecting to the service right now."
13 )
14 except Exception as e:
15 self.agent.log.error(f"Unexpected error: {e}")
16 return FunctionResult(
17 "Something went wrong. Please try again."
18 )

Error handling principles:

  • Always return a FunctionResult, 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:

1# In your agent
2print(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 --exec skill_function_name --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
$# Find location, then look in signalwire/skills/