Learn

Resources

Use Cases

AI Agents

— AI Agents

AI Voice agents you can actually put in production

Define the agent in Python. Scope its tools and data per step. Deploy on real telephony, with your code governing every turn.

The hard part

Voice AI demos pass. Production is where they break.

Every voice AI failure traces to one problem: the call is stateful and real-time, but traditional platforms treat it as stateless transactions. So your app becomes the state machine, and everything hard about voice AI breaks from there.

The model acts on stale context and drifts out of bounds. Every decision crosses a network hop, so the conversation lags. And the record is scattered across services, so you're debugging blind. When the platform owns the call's state, these problems go with it: your code governs the agent against the call's real-time state, the AI runs in the media path for low latency, and every step lands on one record you can inspect.

A program that uses a model, not a model that uses tools.

The model makes it natural. The software makes it correct.

Try it now

Talk it through with Sigmond

Sigmond is a voice AI agent running on the SignalWire infrastructure this page is about. Ask him about anything you just read.

Sigmond

The event stream

Every step, tool call, and decision, on one record

A live call, mid-conversation: speech detected with confidence scores, the response timed at every stage, a tool result from your code, and the step change it triggered. This is what debugging voice AI looks like when the platform owns the record.

A live call's event stream with latencies

How it works

A look inside an AI agent, mid-turn

Foundation models are brilliant at conversation and unreliable at everything else. Mid-turn, five things work at once: the model brings the language, your code brings the process.

01

AI runs in the media engine

The AI kernel runs inside the same media engine as the call, not as a separate service across the network. Speech, model, and voice are orchestrated from inside the audio path, which is what keeps latency low.

AI kernel → in the media path, not over a hop

02

Your code governs the model

You define the agent as steps, and at each step the model sees only the tools and data you scope to it, so it can't act outside what you allowed.

steps → a defined state machine, not one big prompt

03

The model calls your tools

The model reads the conversation and decides which of your tools to call. Your function runs, works with data the model never sees, and returns a prompt carrying the answer or the instructions the model needs.

SWAIG function → your handler returns the result

04

Real telephony under the agent

The agent can transfer to a human, pull someone into a conference, and start a recording, all as native call control on a real telephony platform.

05

Every step is observable

Every step, tool call, and decision lands on one structured event stream, so you can see exactly what the agent did and why.

01

AI runs in the media engine

The AI kernel runs inside the same media engine as the call, not as a separate service across the network. Speech, model, and voice are orchestrated from inside the audio path, which is what keeps latency low.

AI kernel → in the media path, not over a hop

02

Your code governs the model

You define the agent as steps, and at each step the model sees only the tools and data you scope to it, so it can't act outside what you allowed.

steps → a defined state machine, not one big prompt

03

The model calls your tools

The model reads the conversation and decides which of your tools to call. Your function runs, works with data the model never sees, and returns a prompt carrying the answer or the instructions the model needs.

SWAIG function → your handler returns the result

04

Real telephony under the agent

The agent can transfer to a human, pull someone into a conference, and start a recording, all as native call control on a real telephony platform.

05

Every step is observable

Every step, tool call, and decision lands on one structured event stream, so you can see exactly what the agent did and why.

01

AI runs in the media engine

The AI kernel runs inside the same media engine as the call, not as a separate service across the network. Speech, model, and voice are orchestrated from inside the audio path, which is what keeps latency low.

AI kernel → in the media path, not over a hop

02

Your code governs the model

You define the agent as steps, and at each step the model sees only the tools and data you scope to it, so it can't act outside what you allowed.

steps → a defined state machine, not one big prompt

03

The model calls your tools

The model reads the conversation and decides which of your tools to call. Your function runs, works with data the model never sees, and returns a prompt carrying the answer or the instructions the model needs.

SWAIG function → your handler returns the result

04

Real telephony under the agent

The agent can transfer to a human, pull someone into a conference, and start a recording, all as native call control on a real telephony platform.

05

Every step is observable

Every step, tool call, and decision lands on one structured event stream, so you can see exactly what the agent did and why.

Build it

Build a voice agent in one Python file

From idea to phone number in an afternoon: define the agent, scope each step, and deploy with telephony built in. Your code stays in control the whole way.

AI.PY

copy

01

02

03

04

05

06

07

08

09

10

11

12

from signalwire import AgentBase
from signalwire.core.function_result import FunctionResult

def lookup_order(order_id):  # swap for your real backend
    return {"id": order_id, "status": "shipped, arriving Tuesday"}

class SupportAgent(AgentBase):
    def __init__(self):
        super().__init__(name="support-agent")
        self.prompt_add_section("Role", body="You help callers with order issues.")

        # A context is a state machine. Each step whitelists the tools that
        # exist while it's active — the agent can't escalate before it looks up.
        steps = self.define_contexts().add_context("default")
        steps.add_step("triage") \
            .set_text("Greet the caller and ask for their 8-digit order number.") \
            .set_functions(["check_order"])
        steps.add_step("resolve") \
            .set_text("Their order is ${global_data.order_status}. Share it, "
                      "and offer a specialist if they're unhappy.") \
            .set_functions(["escalate_to_human"])

    # Type hints + docstring generate the schema — no JSON to hand-write.
    # The model decides WHEN to call it; your code decides WHAT it does.
    @AgentBase.tool(name="check_order")
    def check_order(self, order_id: str):
        """Look up an order by ID. Call before quoting any status."""
        order = lookup_order(order_id)
        # One result that SPEAKS, saves state for the next step, and advances
        # the state machine from your code — not the model's discretion.
        return FunctionResult(f"Order {order['id']} is {order['status']}.") \
            .update_global_data({"order_status": order["status"]}) \
            .swml_change_step("resolve")

    @AgentBase.tool(name="escalate_to_human")
    def escalate_to_human(self):
        """Hand the call to a live specialist."""
        # connect() is native call control: a real transfer, no extra infra.
        return FunctionResult("Connecting you now.", post_process=True) \
            .connect("+15551234567", final=True)

AI.PY

copy

01

02

03

04

05

06

07

08

09

10

11

12

from signalwire import AgentBase
from signalwire.core.function_result import FunctionResult

def lookup_order(order_id):  # swap for your real backend
    return {"id": order_id, "status": "shipped, arriving Tuesday"}

class SupportAgent(AgentBase):
    def __init__(self):
        super().__init__(name="support-agent")
        self.prompt_add_section("Role", body="You help callers with order issues.")

        # A context is a state machine. Each step whitelists the tools that
        # exist while it's active — the agent can't escalate before it looks up.
        steps = self.define_contexts().add_context("default")
        steps.add_step("triage") \
            .set_text("Greet the caller and ask for their 8-digit order number.") \
            .set_functions(["check_order"])
        steps.add_step("resolve") \
            .set_text("Their order is ${global_data.order_status}. Share it, "
                      "and offer a specialist if they're unhappy.") \
            .set_functions(["escalate_to_human"])

    # Type hints + docstring generate the schema — no JSON to hand-write.
    # The model decides WHEN to call it; your code decides WHAT it does.
    @AgentBase.tool(name="check_order")
    def check_order(self, order_id: str):
        """Look up an order by ID. Call before quoting any status."""
        order = lookup_order(order_id)
        # One result that SPEAKS, saves state for the next step, and advances
        # the state machine from your code — not the model's discretion.
        return FunctionResult(f"Order {order['id']} is {order['status']}.") \
            .update_global_data({"order_status": order["status"]}) \
            .swml_change_step("resolve")

    @AgentBase.tool(name="escalate_to_human")
    def escalate_to_human(self):
        """Hand the call to a live specialist."""
        # connect() is native call control: a real transfer, no extra infra.
        return FunctionResult("Connecting you now.", post_process=True) \
            .connect("+15551234567", final=True)

AI.PY

copy

01

02

03

04

05

06

07

08

09

10

11

12

from signalwire import AgentBase
from signalwire.core.function_result import FunctionResult

def lookup_order(order_id):  # swap for your real backend
    return {"id": order_id, "status": "shipped, arriving Tuesday"}

class SupportAgent(AgentBase):
    def __init__(self):
        super().__init__(name="support-agent")
        self.prompt_add_section("Role", body="You help callers with order issues.")

        # A context is a state machine. Each step whitelists the tools that
        # exist while it's active — the agent can't escalate before it looks up.
        steps = self.define_contexts().add_context("default")
        steps.add_step("triage") \
            .set_text("Greet the caller and ask for their 8-digit order number.") \
            .set_functions(["check_order"])
        steps.add_step("resolve") \
            .set_text("Their order is ${global_data.order_status}. Share it, "
                      "and offer a specialist if they're unhappy.") \
            .set_functions(["escalate_to_human"])

    # Type hints + docstring generate the schema — no JSON to hand-write.
    # The model decides WHEN to call it; your code decides WHAT it does.
    @AgentBase.tool(name="check_order")
    def check_order(self, order_id: str):
        """Look up an order by ID. Call before quoting any status."""
        order = lookup_order(order_id)
        # One result that SPEAKS, saves state for the next step, and advances
        # the state machine from your code — not the model's discretion.
        return FunctionResult(f"Order {order['id']} is {order['status']}.") \
            .update_global_data({"order_status": order["status"]}) \
            .swml_change_step("resolve")

    @AgentBase.tool(name="escalate_to_human")
    def escalate_to_human(self):
        """Hand the call to a live specialist."""
        # connect() is native call control: a real transfer, no extra infra.
        return FunctionResult("Connecting you now.", post_process=True) \
            .connect("+15551234567", final=True)

AI.PY

copy

01

02

03

04

05

06

07

08

09

10

11

12

from signalwire import AgentBase
from signalwire.core.function_result import FunctionResult

def lookup_order(order_id):  # swap for your real backend
    return {"id": order_id, "status": "shipped, arriving Tuesday"}

class SupportAgent(AgentBase):
    def __init__(self):
        super().__init__(name="support-agent")
        self.prompt_add_section("Role", body="You help callers with order issues.")

        # A context is a state machine. Each step whitelists the tools that
        # exist while it's active — the agent can't escalate before it looks up.
        steps = self.define_contexts().add_context("default")
        steps.add_step("triage") \
            .set_text("Greet the caller and ask for their 8-digit order number.") \
            .set_functions(["check_order"])
        steps.add_step("resolve") \
            .set_text("Their order is ${global_data.order_status}. Share it, "
                      "and offer a specialist if they're unhappy.") \
            .set_functions(["escalate_to_human"])

    # Type hints + docstring generate the schema — no JSON to hand-write.
    # The model decides WHEN to call it; your code decides WHAT it does.
    @AgentBase.tool(name="check_order")
    def check_order(self, order_id: str):
        """Look up an order by ID. Call before quoting any status."""
        order = lookup_order(order_id)
        # One result that SPEAKS, saves state for the next step, and advances
        # the state machine from your code — not the model's discretion.
        return FunctionResult(f"Order {order['id']} is {order['status']}.") \
            .update_global_data({"order_status": order["status"]}) \
            .swml_change_step("resolve")

    @AgentBase.tool(name="escalate_to_human")
    def escalate_to_human(self):
        """Hand the call to a live specialist."""
        # connect() is native call control: a real transfer, no extra infra.
        return FunctionResult("Connecting you now.", post_process=True) \
            .connect("+15551234567", final=True)

What you’ll get from the code example

An agent can only do what your code allows, at every step

Native telephony: transfer, conference, and record as built-in call control

One event stream showing what the agent did and why

Real-time translation, with STT, TTS, and voices coordinated inside the platform

What you’ll get from the code example

An agent can only do what your code allows, at every step

Native telephony: transfer, conference, and record as built-in call control

One event stream showing what the agent did and why

Real-time translation, with STT, TTS, and voices coordinated inside the platform

What you’ll get from the code example

An agent can only do what your code allows, at every step

Native telephony: transfer, conference, and record as built-in call control

One event stream showing what the agent did and why

Real-time translation, with STT, TTS, and voices coordinated inside the platform

Ten languages, one mental model

The same concepts and names across Python, TypeScript, Go, Java, Ruby, and more.

Test before you dial

Run every tool and inspect the exact document a call would get, locally, from the CLI.

Your AI, your webhooks

Tools call your systems with per-call security tokens minted and validated for you.

Composed in code

The developer authors the model’s entire world

What it sees, what exists, where it can go: each step has its own instructions, its own tools, its own exits.

greet

tools: none · next: collect

Instructions only.

collect

tools: check_calendar · next: confirm

Finds real openings.

confirm

tools: book_appointment · next: wrap_up

The software acts; the model only requests.

wrap_up

tools: none · next: none

Structurally complete.

When the model requests a tool, your code reads data the model never sees, acts, and returns a new prompt into its context. One returned result atomically updates the data, moves the step, and controls the call.

The platform behind it

One call, every capability

Build the agent in code, give it tools, and steer the live call as it happens. The control plane owns the call's state so your code decides the outcome, not the model.


Server SDKs

Build AI voice agents in ten languages, same concepts and names: prompts, tools, and multi-step flows, all in code.

Tool Calling

Write SWAIG functions your agent can call. The model invokes them, your code runs and decides the outcome.

SWML

Declarative markup that unifies the model, speech, and telephony in one call flow the platform runs. Write it directly, or generate it from code.

RELAY

A persistent WebSocket for real-time call control: transfer, hold, record, or bridge a live call as events happen. Or, connect your own AI to our voice and telephony.

REST APIs

Provision numbers, manage resources, and control calls, messaging, and AI over HTTP.

Browser SDK

Add voice, video, and chat to your web, mobile, or desktop app as a connected client.

Server SDKs

Build AI voice agents in ten languages, same concepts and names: prompts, tools, and multi-step flows, all in code.

Tool Calling

Write SWAIG functions your agent can call. The model invokes them, your code runs and decides the outcome.

SWML

Declarative markup that unifies the model, speech, and telephony in one call flow the platform runs. Write it directly, or generate it from code.

RELAY

A persistent WebSocket for real-time call control: transfer, hold, record, or bridge a live call as events happen. Or, connect your own AI to our voice and telephony.

REST APIs

Provision numbers, manage resources, and control calls, messaging, and AI over HTTP.

Browser SDK

Add voice, video, and chat to your web, mobile, or desktop app as a connected client.

Server SDKs

Build AI voice agents in ten languages, same concepts and names: prompts, tools, and multi-step flows, all in code.

Tool Calling

Write SWAIG functions your agent can call. The model invokes them, your code runs and decides the outcome.

SWML

Declarative markup that unifies the model, speech, and telephony in one call flow the platform runs. Write it directly, or generate it from code.

RELAY

A persistent WebSocket for real-time call control: transfer, hold, record, or bridge a live call as events happen. Or, connect your own AI to our voice and telephony.

REST APIs

Provision numbers, manage resources, and control calls, messaging, and AI over HTTP.

Browser SDK

Add voice, video, and chat to your web, mobile, or desktop app as a connected client.

The hard parts

The hard parts and how SignalWire handles them

Dimension

Traditional CPaaS

SignalWire

Call state

Lives in your application

Owned by the platform

Transfers

Context is dropped

Context travels with the call

AI governance

Prompt and hope

Deterministic, inside the call

Vendor stack

Five vendors stitched together

One platform

Voice AI latency

2–4s, compounded

~1200ms typical conversational turn

Dimension

Traditional CPaaS

Call state

Lives in your application

Transfers

Context is dropped

AI governance

Prompt and hope

Vendor stack

Five vendors stitched together

Voice AI latency

2–4s, compounded

Dimension

SignalWire

Call state

Owned by the platform

Transfers

Context travels with the call

AI governance

Deterministic, inside the call

Vendor stack

One platform

Voice AI latency

~1200ms typical conversational turn

In production

Built by teams that ship production voice AI at scale

Trusted by forward-thinking companies

Angular Gradient Image

"I don't have any desire to deal with the underlying telephony if it's been solved in a good way. Where the calls are low-latency, I'm willing to pay a price for that. It saves me time and allows me to ship faster."

Avatar

Justin Massey

Founder and CEO at RelayHawk

Angular Gradient Image

"I don't have any desire to deal with the underlying telephony if it's been solved in a good way. Where the calls are low-latency, I'm willing to pay a price for that. It saves me time and allows me to ship faster."

Avatar

Justin Massey

Founder and CEO at RelayHawk

20 yrs

carrier-grade engineering, from the creators of FreeSWITCH

2,700+

companies run on the platform today

2.7B

minutes and messages a year on the same infrastructure

SOC 2 · HIPAA · PCI · GDPR · ISO 27001. Regulated workloads welcome: healthcare, finance, and payments run here because the controls are structural, not promised.

Your calls inherit twenty years of hardening.

FAQ

Frequently asked questions

The questions we hear most, from how agents hold context to how they route calls.

How does SignalWire's AI Voice Agent API work?

 It integrates speech recognition, LLM orchestration, and text-to-speech natively into the media and signaling layers. Use the server SDKs in 10 different languages, including  Python, to define agent logic, connect to APIs mid-call, and route to humans when needed.

How do SignalWire AI agents handle multi-step workflows without losing context?

Agents progress through explicitly defined Contexts and Steps with completion criteria. At each transition, an updated state is passed into the next step's prompt, so the AI gets a fresh, accurate context window instead of carrying (and drifting on) full conversation history.

What is the SignalWire AI kernel?

The orchestration layer sitting between telecom infrastructure and the language model. Call state, routing logic, and telephony actions stay in the infrastructure layer, with AI used as a tool inside that controlled environment so the LLM can't lose context or corrupt the session.

How does SignalWire handle state management in AI voice agents?

Your agent server can be stateless because the platform carries the call state and presents it to your code at each step. The AI kernel holds all call state (caller data, step progress, variables) and injects it into the prompt dynamically at each step, so the AI always sees exactly what it needs.

Do SignalWire AI agents work with PSTN, SIP, and WebRTC?

Yes. The same logic and workflows work across PSTN phone numbers, SIP endpoints, and WebRTC browser or app sessions.

How do SignalWire AI voice agents handle complex workflows like booking appointments or triggering SMS mid-call?

Agents use SWAIG (SignalWire AI Gateway) to trigger programmable functions mid-conversation, sending an SMS confirmation, querying a CRM, or inserting a calendar appointment, without human intervention or ending the call.

Angular Gradient Image

Ready to ship your first AI agent?

Build a working voice agent in one Python file. Every SignalWire Space includes free credit, and voice AI runs $0.16 per minute: transparent usage pricing.

Angular Gradient Image

Ready to ship your first AI agent?

Build a working voice agent in one Python file. Every SignalWire Space includes free credit, and voice AI runs $0.16 per minute: transparent usage pricing.

Angular Gradient Image

Ready to ship your first AI agent?

Build a working voice agent in one Python file. Every SignalWire Space includes free credit, and voice AI runs $0.16 per minute: transparent usage pricing.