Semantic gates
A semantic gate is a yes/no question about the call, such as “Has the caller explicitly asked to cancel their ride?”, that a decision model must answer yes before your AI agent can run a SWAIG function. When the answer isn’t yes, the function doesn’t run, and the agent gets a response you wrote instead.
Bayview Taxi’s dispatcher agent, Ada, shows why that matters. She can cancel a booked ride, but a
caller who asks “What happens if I cancel?” hasn’t asked her to. A language model can read it that
way and call cancel_ride anyway. A line in the prompt makes that less likely, while a gate on
cancel_ride holds the function until the decision model is confident the caller asked to cancel.
The same goes for any action you don’t want taken on a guess: refunding, sharing account details,
moving money, or anything that needs the caller’s explicit consent.
This guide assumes you already have an agent with SWAIG functions. If you don’t, start with tool calling.
How a gate works
A gate sits between the agent’s decision to call a function and the function itself. When the agent calls a gated function, the platform holds the call and sends the decision model what it needs to answer each of the function’s questions. The model returns, for each question, the probability that the answer is yes, and each gate compares that to the threshold you set.
- Every gate passes: the function runs as usual. Your webhook is called, or the
data_mapruns. - Any gate fails: the function doesn’t run. The agent gets the failing gate’s
on_failresponse as the function’s result, and answers the caller from it.
All of a function’s gates are checked at once, and a function can have up to eight. When more than one fails, the first failing gate in your list decides what the agent gets. A check that can’t be completed, because it ran out of time or the agent’s arguments were missing or not valid JSON, blocks the function too.
Gate a function
Ada’s cancel_ride function gets one gate. It asks whether the caller explicitly asked to cancel,
and spells out the near miss in criteria: asking about cancelling isn’t asking to cancel. The
threshold is high because a cancelled ride is hard to undo.
The Server SDKs check your gates by the platform’s rules when you define the tool, and raise an error rather than send a function the platform would refuse.
The gates reference lists every gate property and its limits.
Write questions the decision model can answer
The decision model reads the question literally and answers only it, so the question does most of the work. It reads these inputs, under these names, and nothing else:
Name the input in backticks
End or anchor each question with the name of the input it’s about, in backticks: “Has the caller
explicitly asked to cancel their ride in conversation?” The backticks tell the decision model to
look in the input it was handed under that name, rather than read “conversation” as an ordinary
English word.
The names aren’t variables. Nothing replaces `conversation` with the transcript, and the
platform passes the backticks through as written; they’re a convention for the decision model. The
only text substituted in a question is a ${...} variable from global_data, such as
${global_data.pickup_address}, expanded when the check runs. @{...} functions aren’t run.
Keep each question to one thing
- Ask one thing per question. Split “Did the caller confirm the pickup address and the time?” into two gates, so each answer is a clean yes or no.
- Use
criteriafor the near misses. Say what counts as yes and what counts as no, especially where the two sound alike: “asked about cancelling” isn’t “asked to cancel”. - Don’t ask the decision model to calculate or compare dates. Work the answer out in your
application, store it in
semantic_state, and ask about that. - Keep it short. A question can be up to 8 KB, but that’s a ceiling, not a target. Most good questions are one sentence.
Thresholds run from just above 0 to 1, and higher is stricter. Start at 0.9 or above for actions
that are hard to undo, then test against real conversations, including the near misses you wrote
into criteria, before you settle on a value.
Rely on what your application recorded
A caller can say “I already confirmed all that”. Whether they did is something your application
knows, not something the conversation proves. Gates read global_data.semantic_state as
semantic_state, and that’s where your application records the facts a gate should rely on: a
ride it looked up, an identity it verified, a refund it worked out the caller is eligible for.
When Ada looks up a trip, the handler stores what the booking system says about it. The
refund_fare function then carries two gates: one asks about the recorded fact, the other about
the caller’s agreement to the amount the agent is about to refund.
Each write of semantic_state replaces the whole object, so to change one field, send the whole
state with that field changed. Send {} to clear it. The state lasts for the call, and carries over
to later ai methods in the same call while persist_global_data is on, which it is by default.
Nothing else in global_data reaches the decision model. If you set the swaig_set_global_data
param to false, functions can’t write semantic_state either.
Keep semantic_state to what your application established, never what the caller claimed. For how
global_data works more broadly, see state management.
Write the response a blocked call returns
on_fail is what the agent gets instead of the function’s result. It’s shaped like any SWAIG
function result: a response, plus optional action and post_process. The response is required,
because a blocked function must never read as a success to the agent.
The object form splits the result in two. tool_result is what the agent sees as the function’s
result (“cancel_ride was not run.”), and tool_prompt is an instruction placed right after it (“Ask
the caller whether they want to cancel the ride.”). Realtime (speech-to-speech) agents don’t use
tool_prompt, so for those, put everything the agent needs into tool_result or a plain string.
Actions in on_fail run as you wrote them, such as a set_global_data that records the attempt.
${...} variables in them aren’t expanded, though a SWML action resolves the variables in its own
document when it runs. action must be an array, even for a single action.
Choose what the caller hears during the check
The check takes a moment, and what fills it depends on the function’s settings. A gated function’s
wait_file plays only once its gates pass, so a blocked call never plays it.
With the default, the caller hears the function’s fillers even when a gate then blocks it, and on
standard agents those fillers become part of the conversation the gates read. So if a function’s
fillers announce the action, such as “OK, cancelling your ride now”, give it neutral gate_fillers
like “Let me check that”, as Ada’s cancel_ride does, or set fillers_at_gates to false.
Set it for every function at once in SWAIG.defaults; a function’s own setting wins.
On standard agents, a function with wait_for_fillers: true still waits for its fillers. With the
default, they play first and the gates are checked after them, which adds to the wait; gate_fillers
are never waited for before the check. With semantic_gates_enabled: false, fillers and the wait
file behave as they do for any function.
The Server SDKs take gate_fillers as a tool option. They have no dedicated option for
fillers_at_gates, so pass it through as an extra function field:
Look at the caller’s video
On a video call, a gate can look at the caller’s camera. Set visual_input on the gate to the
number of newest frames it should see, up to 5, and name `visual_input` in the question:
Frames are taken when the check starts, at most one per second and none older than 8 seconds. If
the call has no video frame to show, the gate fails without being asked, so a gate with
visual_input always fails when the post-prompt agent calls the function after the call. A
function’s own visual_input sends frames to your webhook too, taken at the moment its gates were
checked and never later.
On a video call without avatar clips, capturing frames shows the caller a still image, black by
default. Set the
video_blank_image param to a PNG
URL to show your own image instead.
The Python Server SDK accepts visual_input on a gate from version 3.7.1. The TypeScript Server SDK
doesn’t accept it yet, so define video gates in SWML.
Tune or turn off gates for a call
Three ai.params control checking for the whole call:
Check that your gates work
Call your agent and ask about cancelling without asking to cancel (“What happens if I cancel my ride?”). Ada should answer the question and leave the booking alone. Then ask her to cancel it: the function should run.
Every checked call records what happened. Its entry in the swaig_log, which arrives in the
post-prompt callback and the calling.ai.swaig event, carries a semantic_gates
object: the outcome, whether the function ran (dispatched), and each gate’s confidence
against its threshold.
If a function runs when it shouldn’t, compare its gate’s confidence to the threshold, and tighten
the question or its criteria before raising the threshold. If a function never runs, look at the
outcome:
failmeans a gate answered under its threshold. Check that the question names the right input, and that the conversation orsemantic_stateactually holds what the gate needs.timeoutmeans the check ran out of time. Raisesemantic_gate_timeout_ms.invalid_argumentsmeans the agent’s arguments were missing or weren’t a valid JSON object.- The function is missing from the agent altogether: a gate is invalid, so the platform didn’t add
the function. A
calling.errorevent witherror_reasoninvalid_semantic_gatenames the function and the broken rule.
Any outcome other than a pass, a fail, or gates turned off also fires a calling.error event. The
post-prompt callback adds semantic_gates_blocked, the functions the post-prompt agent was blocked
from running, and a semantic_gates tally for the whole call.