Agents Skills System
A "skill" in the SignalWire Agents SDK is a reusable bundle of: prompt sections, SWAIG functions, parameter schemas, env-var/package validation, and lifecycle hooks. Skills come in two forms — prefabs that ship with the SDK, and custom skills you write by subclassing SkillBase.
Prefabs — ready-made agents
from signalwire.prefabs import (
ConciergeAgent, FAQBotAgent, InfoGathererAgent,
ReceptionistAgent, SurveyAgent,
)
ConciergeAgent — venue/hospitality concierge
agent = ConciergeAgent(
venue_name="The Riverside Resort",
services=["room service", "spa treatments", "restaurant reservations"],
amenities={
"pool": {"hours": "6 AM - 10 PM", "location": "Ground Floor, East Wing"},
"spa": {"hours": "9 AM - 9 PM", "location": "Level 3, East Wing"},
},
hours_of_operation={"front desk": "24 hours", "concierge": "7 AM - 11 PM"},
special_instructions=["Mention the daily happy hour at the pool bar (4-6 PM)."],
welcome_message="Welcome to The Riverside Resort! How may I assist you?",
)
Built-in tools: check_availability(service, date, time), get_directions(location).
FAQBotAgent — knowledge-base FAQ bot
agent = FAQBotAgent(
faqs=[
{"question": "What is the warranty period?",
"answer": "All products come with a 2-year warranty.",
"categories": ["warranty"]},
{"question": "How do I return a product?",
"answer": "Start a return within 30 days at returns.example.com.",
"categories": ["returns"]},
],
suggest_related=True,
persona="You are a helpful product specialist for TechGadgets Inc.",
)
Best for up to 50 FAQs. For larger knowledge bases, use the native_vector_search skill instead.
InfoGathererAgent — sequential question collection
agent = InfoGathererAgent(
questions=[
{"key_name": "name", "question_text": "What is your name?"},
{"key_name": "email", "question_text": "What is your email?", "confirm": True},
{"key_name": "issue", "question_text": "Describe your issue."},
]
)
confirm: True makes the agent read the answer back for critical fields. Answers land in global_data and are accessible from SWAIG handlers.
Dynamic mode — questions decided at request time:
def get_questions(query_params, body_params, headers):
if query_params.get("type") == "support":
return [
{"key_name": "name", "question_text": "What is your name?"},
{"key_name": "issue", "question_text": "Describe your issue."},
]
return [
{"key_name": "name", "question_text": "What is your name?"},
{"key_name": "message", "question_text": "How can I help?"},
]
agent = InfoGathererAgent()
agent.set_question_callback(get_questions)
ReceptionistAgent and SurveyAgent
ReceptionistAgent — basic phone receptionist with routing. SurveyAgent — outbound or inbound surveys with structured response capture. Same constructor pattern as the others.
Adding skills to a custom agent — add_skill
from signalwire import AgentBase
agent = AgentBase(name="demo")
agent.add_skill("notify")
agent.add_skill("native_vector_search", {"index_path": "docs.index"})
agent.add_skill("datetime")
Each add_skill call:
- Looks up the skill class.
- Runs
setup()— validates env vars and packages, opens API clients. - Calls
register_tools()— adds SWAIG functions to the agent. - Injects prompt sections from
_get_prompt_sections()unlessskip_prompt: True.
Multi-instance skills — tool_name
Some skills can be added more than once (different config per instance). The tool_name parameter creates unique instance keys.
agent.add_skill("notify") # instance key: "notify"
agent.add_skill("notify", {"tool_name": "email"}) # instance key: "notify_email"
agent.add_skill("notify", {"tool_name": "sms"}) # instance key: "notify_sms"
Each instance gets its own SWAIG function namespace.
Writing a custom skill — SkillBase
import os
from signalwire.core.skill_base import SkillBase
class WeatherSkill(SkillBase):
SKILL_NAME = "weather"
SKILL_DESCRIPTION = "Provides weather information"
REQUIRED_PACKAGES = ["requests"]
REQUIRED_ENV_VARS = ["WEATHER_API_KEY"]
@classmethod
def get_parameter_schema(cls):
schema = super().get_parameter_schema()
schema.update({
"units": {
"type": "string",
"description": "Temperature units",
"default": "fahrenheit",
"enum": ["fahrenheit", "celsius"],
},
"api_key": {
"type": "string",
"description": "Weather API key",
"required": True,
"hidden": True,
"env_var": "WEATHER_API_KEY",
},
})
return schema
def setup(self) -> bool:
if not self.validate_packages():
return False
if not self.validate_env_vars():
return False
self.api_key = os.getenv("WEATHER_API_KEY")
return True
def _get_prompt_sections(self):
return [
{"title": "Weather", "body": "You can check weather using the get_weather tool."},
{"title": "Weather Guidelines", "bullets": [
"Always confirm the location before checking.",
"Report in the user's preferred units.",
]},
]
def register_tools(self):
@self.agent.tool(description="Get current weather for a city")
def get_weather(args, raw_data=None):
from signalwire import FunctionResult
import requests
r = requests.get("https://api.weatherapi.com/v1/current.json",
params={"key": self.api_key, "q": args.get("city")})
data = r.json()
return FunctionResult(
f"Weather in {args.get('city')}: {data['current']['condition']['text']}, "
f"{data['current']['temp_f']}F"
)
Required SkillBase methods
| Method | Required | Purpose |
|---|---|---|
| setup() | Yes | Validate env, packages; init clients. Return True on success. |
| register_tools() | Yes | Register SWAIG functions on self.agent. |
| _get_prompt_sections() | No | Return list of POM sections. Respects skip_prompt. |
| get_parameter_schema() (class method) | No | Document parameters for UIs / discovery. |
Class attributes
| Attribute | Notes |
|---|---|
| SKILL_NAME | String key used in add_skill("name"). |
| SKILL_DESCRIPTION | Human-readable summary. |
| REQUIRED_PACKAGES | List of pip packages required at setup time. |
| REQUIRED_ENV_VARS | List of env var names required. |
Validators on SkillBase
self.validate_packages() # True/False; logs missing packages
self.validate_env_vars() # True/False; logs missing env vars
Built-in parameter fields
Every skill accepts these:
| Parameter | Notes |
|---|---|
| swaig_fields | Extra SWAIG metadata merged into every tool definition this skill registers. |
| skip_prompt | If True, suppress prompt-section injection. |
| tool_name | Custom name for multi-instance skills. |
Skill discovery
Point an agent at a directory of skill modules to auto-register them:
from signalwire import register_skill, add_skill_directory
# Register a single skill class
register_skill(WeatherSkill)
# Auto-discover everything in a directory (each .py file = one skill class)
add_skill_directory("/path/to/my-skills")
Useful for packaging organisational tooling (CRM lookup, scheduling, knowledge bases) as reusable skill libraries.
Anti-patterns
- Putting SWAIG handler imports at module top instead of inside
register_tools()— slow startup, import errors leak before validation. - Skipping
REQUIRED_ENV_VARSdeclaration — skill loads with missing config, fails at runtime. - Forgetting to return
Truefromsetup()— skill silently fails to load. - Mixing
add_skillcalls with manual@agent.tooldecorators on the same domain — fights for tool name space. - Registering skills inside an FAQBotAgent or other prefab — prefabs lock down their tool space.