Best practices for creating a SignalWire AI agent
Building an agent that holds up in production takes more than a good prompt. This guide collects the practices that matter across the whole design: how work divides between the prompt and your code, how to write for a real-time voice medium, how to help speech recognition, and how to test, observe, and stay compliant once callers arrive.
The examples build one agent, the dispatcher for a taxi company called Bayview Taxi, a practice at a time. The last section assembles it into a script you can run.
Split the work between the prompt and your code
The design decision that matters most is what you don’t put in the prompt.
A language model is good at conversation. It reads tone, keeps up with a caller who changes direction halfway through a sentence, and pulls a pickup address out of “yeah, I’m at Gough and Fell, the blue building on the corner.”
It is unreliable at anything with one correct answer. Arithmetic drifts, a fact written into the prompt goes stale the moment your data changes, and a rule stated in the prompt is a rule the model may or may not honor on turn nine of a difficult call.
So give each side the work it’s suited to. The prompt covers who the agent is, how it speaks, what it’s there to accomplish, and when to reach for its functions. Your code, reached through SWAIG functions (SignalWire AI Gateway, the platform’s tool calling), covers prices, inventory, calculations, policy, and anything else with a right answer.
Keep business data out of the prompt
It’s tempting to paste the fare table, the service-area map, or the policy manual into the prompt and hope the model honors all of it: prompt and pray. Resist it. The prompt is fixed when the call starts, but your data keeps changing, and a function call reads the current value at the moment the caller asks.
Staleness isn’t the only problem. Data in the prompt is recalled by the model, approximately, while data from a function call is returned by your code, exactly. The model also has no way to tell your pasted policy apart from anything else it has read. Your backend is the system of record.
Move that data behind SWAIG functions: a lookup backed by your webhook or the Server SDK, or a serverless DataMap for straightforward API calls and pattern-matched responses.
Write the prompt for conversation, not logic
With business logic out of the way, the prompt’s job is focused: define the agent’s identity, its conversational duties, and where it defers to your code. Outline those necessities clearly, then stop. Overloading the AI with instructions muddles its behavior rather than tightening it, and since the prompt is part of every conversational turn, a brief prompt is also cheaper to run. See avoiding overprompting for techniques.
Structure the prompt with Markdown: headings and lists keep it organized and legible, narrowing how the AI interprets each section.
Notice what this prompt doesn’t contain: no fare table, no service-area boundary, no driver roster. That information lives in the dispatch system behind the agent’s functions, so the prompt stays short and the answers stay accurate.
The function it calls for a fare is a handler of your own. In outline:
Putting it together, at the end of this page, has that handler as running code.
Some context does belong in the prompt. Put in it what no system of record can answer: who the agent is, the scope of what it handles, the tone it takes, how it should treat a caller who is upset, and the point at which it hands off to a person. Leaving them out is what produces a bland, directionless agent.
Business hours look like a stable fact and aren’t: holidays move them, a weather closure moves them, and a short-staffed Saturday moves them. A prompt written last quarter will state the old hours with complete confidence. If a caller could be told the wrong thing because the world changed, it belongs behind a function.
Per-call context, such as the caller’s name or account tier, can be interpolated into the prompt at request time; place it at the bottom so the stable part stays identical from turn to turn and from call to call. Interpolation is for context the conversation starts from. A live answer, like a price or an order’s status, still comes from a function at the moment the caller asks.
For the full craft of prompt writing, including structure, examples, and iterative refinement, see the prompt engineering guide.
Adjust the model parameters when the defaults aren’t working
The platform’s defaults are set to work across most agents, so building one rarely means touching
the parameters on the prompt object.
Reach for them when something specific is wrong with the way your agent talks: it wanders off
topic, it repeats a line, or it leaves a long pause after the caller stops speaking.
The prompt reference lists the default each one starts at.
Change one at a time and listen to a real call between changes.
Design for real-time voice
Voice adds a dimension that text chat doesn’t have: the caller hears every pause.
Two controls shape how those pauses feel.
Filler phrases give the AI something to say while a function call is running;
configure them per-function and
agent-wide.
Background audio, such as typing or office ambience, gives the caller familiar feedback
while processing happens; set it with
params.background_file.
Underneath those sit two moments you can also configure.
End-pointing (end-of-utterance detection) is how the AI decides the caller has finished
speaking, adjustable through confidence in the prompt object and
params.end_of_speech_timeout.
Turnaround is the interval between that decision and the start of the reply.
Judge both on live calls with your own configuration, measuring the interval the caller experiences: from the moment they stop talking to the moment they hear the reply begin. Consistency matters as much as the average. Your function handlers are part of that interval, so move slow work out of band and respond promptly.
Help speech recognition with hints
Hints boost recognition accuracy for the words that matter in your domain, so “Gough Street” doesn’t arrive as “Goff Street.”
Street names, neighborhoods, and local landmarks are exactly the vocabulary a general speech model handles worst and a dispatcher hears most, which makes them the first thing to introduce:
See hints in the SWML reference, or speech recognition hints in the Server SDK.
Test, monitor, and iterate
Test with real users before deploying. Scripted test calls follow the script; customers won’t, and beta callers surface the gaps.
Once callers arrive, review real calls. Post-call reports capture each conversation and everything the agent did during it; setting them up and reading them is covered in the tool calling guide. Read the transcripts where the agent hesitated or chose the wrong function. That’s where the next fix comes from.
Usage metrics fill in the aggregate picture: token consumption, interaction times, and call outcomes reveal both cost and quality trends. Your business changes, so revisit the prompt and functions periodically, and check the documentation and release notes for new parameters, voices, and features your agent can use as SignalWire’s AI capabilities evolve.
Stay compliant with regulations
In February 2024 the FCC ruled that an AI-generated voice counts as an artificial voice under the Telephone Consumer Protection Act (TCPA). Your agent is subject to the same rules as any other automated call.
The person on the other end has to have consented to it, the call has to identify itself as AI, and a request to stop has to be honored. Which of those rules apply, and how strictly, depends on why you’re calling and where the called party lives.
Each rule has a yes-or-no answer, which makes it work for your code rather than the prompt.
Put the AI disclosure in a static_greeting
with static_greeting_no_barge, so it plays in full before the agent’s first turn.
Verify consent, check your do-not-call list, and confirm the local hour before you ask SignalWire to dial, because once the call is placed it has already happened. Then let the agent catch an opt-out mid-conversation with a SWAIG function whose handler writes to your do-not-call list before it answers the caller.
Both halves of that, together:
The prompt asks the agent to call opt_out, and the handler is what makes the opt-out real.
The number lands on your do-not-call list before the agent says a word about it, so the record
exists even if the caller hangs up on the confirmation.
Not legal advice
This is technical guidance, not legal advice. Requirements vary by jurisdiction and by call type, and they change often. Consult qualified counsel before launching an outbound program.
Both compliance guides walk through these controls in detail:
Build TCPA-compliant outbound voice AI with consent gating, AI disclosure, calling-hour rules, and opt-out handling.
Build HIPAA-compliant voice AI that protects Protected Health Information (PHI) across the call lifecycle.
Putting it together
Here is Ada as a complete script, with each practice in place: a static greeting that identifies the call as automated, a lean prompt, fares served by the dispatch system instead of pasted text, a filler phrase and background audio to cover the lookup, hints for local street names, and a post-call report for review.
Ada answers inbound calls, so the consent check and the do-not-call lookup from
Stay compliant with regulations aren’t here: those run in your
own code before an outbound call is placed, and the opt_out function belongs with them.
When a caller asks what it costs to get to the airport, the platform sends an HTTP POST to your
function’s endpoint (the web_hook_url in SWML, or the endpoint the Server SDK serves for you):
Your server prices the trip against the same rate table the meters use and replies:
Ada relays the answer in its own voice and offers to dispatch a car. Every number in that sentence came out of the dispatch system during the call. When the rate changes tomorrow, the agent is already right, and the post-call report shows you every lookup it made.