T
Telephony SOPKnowledge Base
Search
← All topics

Agents Skills System — Prefabs, SkillBase, Multi-Instance, and Discovery

runnable

Pre-built agent prefabs (ConciergeAgent, FAQBotAgent, InfoGathererAgent, ReceptionistAgent, SurveyAgent) and the custom SkillBase pattern. Covers `add_skill`, multi-instance keying, parameter schemas, REQUIRED_PACKAGES/ENV_VARS validation, and skill discovery directories.

signalwireagents-sdkskillsprefabsskillbase
Agent trigger phrases: SignalWire skills system · ConciergeAgent FAQBotAgent · custom SignalWire skill · SkillBase REQUIRED_ENV_VARS · add_skill multi instance · register_skill add_skill_directory

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:

  1. Looks up the skill class.
  2. Runs setup() — validates env vars and packages, opens API clients.
  3. Calls register_tools() — adds SWAIG functions to the agent.
  4. Injects prompt sections from _get_prompt_sections() unless skip_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_VARS declaration — skill loads with missing config, fails at runtime.
  • Forgetting to return True from setup() — skill silently fails to load.
  • Mixing add_skill calls with manual @agent.tool decorators on the same domain — fights for tool name space.
  • Registering skills inside an FAQBotAgent or other prefab — prefabs lock down their tool space.

See also