ai_sidecar
ai_sidecar
Attaches a real-time AI observer to a live call. The sidecar listens as a third party and never speaks on the call — after each customer turn, it sends agent-facing advice to your application as webhook callbacks that an agent’s UI (or any consumer) can render. Think of it as a coach watching over the agent’s shoulder: it runs alongside the call rather than driving it.
Use it for live sales coaching, real-time compliance flagging, intent-based UI navigation, voice-of-customer signal
extraction, and supervisor-on-shoulder workflows. A common pattern is to attach ai_sidecar to coach a human agent on a
connect-bridged call: the customer talks to the human, and the sidecar coaches
the human’s screen.
How it works
The sidecar listens to the call but never speaks on it. It runs its own transcription, separate from
live_transcribe, so the two can run on the same call. A call can have
only one sidecar at a time.
Each time the customer finishes speaking, the sidecar evaluates the conversation. This evaluation is called a tick, and
every callback the sidecar produces carries a tick_id. On each tick:
- The sidecar detects the end of the customer’s turn — a final transcription result followed by a brief silence
(
idle_timeout_ms), or the agent starting to speak. - It sends the running transcript to the model, along with your operator prompt and your SWAIG tools.
- The model returns a single line of agent-facing advice (an
insight), or calls the built-insidecar_skiptool to stay silent when no advice is needed. - Any tools the model calls (lookups, alerts, intent triggers) run through your SWAIG functions and MCP servers.
- Every step is reported as a structured callback your application can consume. See Webhook callbacks below for the full catalog.
Properties
ai_sidecar
An object that attaches a real-time AI observer to the call. Accepts the following properties to configure the sidecar’s prompt, language, model, tools, permissions, and other settings.
ai_sidecar.prompt
The operator prompt that instructs the sidecar how to coach the agent. May be a plain string or a Prompt Object Model (POM). SignalWire automatically adds built-in instructions for the sidecar’s role, so your prompt only needs to describe the coaching behavior.
Optional — when omitted, the sidecar falls back to a minimal default prompt, so setting one is strongly recommended. See prompt for the supported forms.
ai_sidecar.lang
The conversation language as a single BCP-47 tag (e.g. en-US).
Sets the speech-recognition language, and is shared with the model as a hint.
Required to start a sidecar; a summarize request (action.summarize) can omit it.
ai_sidecar.model
The model used for the sidecar’s advice and its end-of-call summaries.
Suggested values: gpt-4o-mini, gpt-4.1-mini, gpt-4.1-nano.
ai_sidecar.direction
The call legs to observe. Possible values: remote-caller, local-caller.
Both legs are required; defaults to both legs.
ai_sidecar.customer_role
Which leg is the customer, used as the turn-end trigger source.
Possible values: remote-caller, local-caller.
ai_sidecar.url
The webhook URL the sidecar POSTs its callbacks to — both transcription events and sidecar callbacks.
When unset, callbacks are published only on the relay topic (calling.ai.sidecar) and no webhook POST is made — the relay event always fires, the webhook is opt-in.
Basic auth can be embedded in the URL in the format username:password@url.
See Webhook callbacks below for the callback payload shape.
ai_sidecar.SWAIG
SWAIG functions and MCP servers available to the sidecar.
See SWAIG for more details.
The function name sidecar_skip is reserved and auto-registered as a built-in — do not declare it.
ai_sidecar.permissions
SWAIG permission overrides. Defaults to all permissions enabled.
Setting act_on_channel to false
overrides all of these — actions are reported as callbacks but never applied to the call.
permissions.swaig_allow_swml
Whether SWAIG tools may run SWML on the call.
permissions.swaig_allow_settings
Whether the sidecar accepts a settings action returned by a SWAIG tool. Either way, a settings action does not
change the sidecar’s evaluations, which keep using the sidecar’s model and default model settings.
permissions.swaig_set_global_data
Has no effect: SWAIG tools can set and unset the sidecar’s global data whatever this is set to. To keep tool actions,
including global data changes, from taking effect, set act_on_channel to false.
ai_sidecar.global_data
A key-value object for the initial global_data. You can reference it in the prompt with variable expansion
(e.g. ${global_data.property_name}), and it is included in the requests sent to your tools.
It also persists across sessions on the same call leg through the ai_agents_global_data SWML variable, so later verbs
on the same leg can read it as ${ai_agents_global_data.property_name}.
ai_sidecar.hints
Speech-recognition hints passed to ASR to bias recognition toward specific terms — product names, competitor names,
jargon, customer names, or anything in your SWAIG enum lists. Strongly recommended.
Example: ["ACME", "Globex", "FedRAMP", "SOC 2"].
ai_sidecar.params
An object containing tuning options for the sidecar.
See params for more details.
ai_sidecar.action
Summarize the conversation instead of starting a sidecar. When you include action.summarize, the request asks the
sidecar already attached to the call for a one-off summary and returns rather than attaching a sidecar. The other
properties, including lang, are not used and can be omitted.
action.summarize
Generate a one-off summary of the conversation and send it to a webhook. It needs a sidecar already attached to the call: without one the request fails, and no sidecar is started.
summarize.webhook
The webhook URL the summary is sent to. Defaults to the sidecar’s configured url.
summarize.prompt
The prompt used to write the summary. Defaults to the sidecar’s
params.transcribe_prompt; when that is not set either, built-in summary instructions are
used. ai_summary_prompt applies only to the end-of-call summary.
Webhook callbacks
As it observes the call, the sidecar reports each step of its activity as a structured callback on two paths:
- Relay topic
calling.ai.sidecar— always fires. Subscribe with the SignalWire Relay or browser SDK to consume callbacks in real time. - Webhook — fires only when
urlis set, as an HTTPPOSTto that URL with the callback body wrapped undersidecar_event.
Both paths carry the same payload, and each callback fires exactly once on the relay topic regardless of whether a webhook is configured — the relay always fires, the webhook is opt-in.
Callback body shape
Each callback is POSTed to your url with the event wrapped under sidecar_event, alongside a call_info envelope describing the call. project_id and space_id are included when available:
The actual event is under sidecar_event — unwrap that in your code before reading type and the event’s fields. Every callback also carries type, ts (microsecond timestamp), tick_id, and a channel_data object (call_id, plus caller_id_name / caller_id_number / destination_number when available).
See the AI sidecar callback webhook page for the full payload reference. The per-type fields are listed below.
Callback types
Each callback carries a type field identifying what occurred, along with type-specific fields.
On the callbacks that come from a single evaluation (request, thought, tool_call, tool_result, insight),
iter counts the steps within that evaluation, starting at 0, and total_iters on the insight is how many steps it
took in total. While the sidecar answers an ai_sidecar.ask, its request,
thought, tool_call, and tool_result callbacks, and the final ask_answer, carry that ask’s ask_id and
triggered_by: "ask", so you can match them to the question you sent. The ask_request callback carries the
ask_id only. Other callbacks, such as action and error, don’t carry either field.
Stop reasons
The final callback carries a stop_reason indicating why the sidecar stopped.
Error reasons
The error callback carries an error_reason describing the failure.
Tool webhook contract
When the model calls one of your SWAIG functions, the platform sends an HTTP POST to that function’s web_hook_url. Your webhook runs the function and returns the result. Both the request and the response are plain JSON.
Request
The platform sends the POST body shown below. The sidecar sends a smaller set of fields than the
regular ai method’s
SWAIG webhook, and it groups the
caller details under channel_data.
See the AI sidecar SWAIG tool webhook webhook page for the full field reference.
Response
Your webhook returns the result as its response body. The whole body, exactly as returned, becomes the tool result the
model reads on its next step. Returning a JSON object with a response string and any actions to take is the
recommended shape:
response
The result, in words the model can use. The sidecar does not extract it: the model reads the whole response body, including this field.
action
Optional. One action object, or an array of them, for the sidecar to perform. Each is keyed by an action name — see Supported SWAIG actions. When act_on_channel is true, the actions take effect on the call; otherwise they are reported as callbacks only. This is the only field the sidecar reads from the response.
Supported SWAIG actions
Each entry in the action array is an object keyed by the action name. The sidecar supports the following:
action[].user_event
Emits a user_event carrying your topic and any payload fields — the primary way to surface UI alerts and intent navigation to your application.
action[].set_global_data
A key-value object merged into global_data. Emits a global_data_change callback and persists across sessions on the same call leg.
action[].unset_global_data
The key to remove from global_data, or an array of keys to remove. Array entries that are not strings are ignored.
action[].set_meta_data
A key-value object of session metadata.
action[].unset_meta_data
The metadata key to remove from the function’s metadata, or an array of keys to remove. Array entries that are not strings are ignored.
action[].transfer
Transfers the call to a destination. Ends the sidecar and transcription.
transfer.dest
The destination — a phone number, SIP URI, or SWML URL.
action[].hangup
When true, hangs up the call. Ends the sidecar and transcription.
action[].stop
When true, stops the sidecar only; transcription continues.
action[].say
Report-only in sidecar mode. The sidecar has no voice, so instead of speaking it fires an action callback with spoken: false and reason: "no_tts_in_sidecar".
action[].toggle_functions
Enables or disables functions mid-conversation. Each entry sets a function’s active state.
toggle_functions[].function
The name of the function to toggle.
toggle_functions[].active
Whether to enable or disable the function.
action[].settings
Accepted when swaig_allow_settings is true, but does not change the sidecar’s
evaluations: they keep using the sidecar’s model and default model settings.
action[].SWML
A SWML document to run on the call. Gated by swaig_allow_swml. The transfer: true variant ends the sidecar.
action[].user_input
Injects a message into the conversation and triggers a new evaluation — use it to send the sidecar a question or instruction mid-call.
action[].back_to_back_functions
Accepted but has no effect on the sidecar. How many model steps an evaluation can take is set by
max_iters_per_tick, and the
anti-loop guard still applies.
action[].extensive_data
Accepted but has no effect on the sidecar: its tool webhook requests always carry the same fields.
Setting act_on_channel to false makes all of these report-only — they still fire as callbacks but are not applied to the call.
Built-in sidecar_skip tool
The sidecar provides one built-in tool, sidecar_skip, that you do not declare. The model calls it when the latest customer turn needs no advice.
The tick then ends silently — no insight callback fires — and a skip callback is emitted with the model’s reason.
Instruct the model in your prompt to call sidecar_skip when there is nothing useful to say — otherwise it will tend to fill silence with low-quality advice.
The name sidecar_skip is reserved; do not define a function with that name.
Limitations
- One sidecar per call. Starting a second
ai_sidecarwhile one is attached is rejected. The sidecar’s transcription is separate fromlive_transcribe, which can run on the same call. For transcription without a model, see the transcribe calls guide. - Both legs required. A single-leg
direction(for example, onlyremote-caller) is rejected. - Turn detection is tuned for Deepgram.
deepgramis the default speech engine, and turn-end detection is calibrated for it. saydoesn’t speak. The sidecar has no voice on the call, so asayaction is reported as a callback instead of being spoken aloud.- The sidecar stops with the call. When the call hangs up or transcription stops, the sidecar’s
finalcallback fires and it stops. sidecar_skipis reserved. Do not define a function with that name.- Tool loops are capped. If the model calls tools in a runaway loop — too many calls in one tick, the same call repeated, or a tool that isn’t registered — the sidecar stops calling tools for that tick and returns an
errorcallback witherror_reason: tool_loop. Make sure your tools return clear, useful results and your prompt tells the model what to do after a tool returns.
Example
Answer the call, attach the sidecar with a prompt, language, webhook URL, hints, and a single SWAIG function,
then bridge the call with connect and hang up.