Start from a prefab AI agent
A complete receptionist or survey agent runs from a prefab class and a short configuration block.
Also called conversational IVR, AI IVR, AI receptionist, intent routing
The claim
The SDK ships complete agents as classes. You pass configuration, not a prompt. The prefab writes its prompt sections and registers its tools with their handlers. The receptionist also sets its voice and wires its transfer. ReceptionistAgent takes a list of departments; SurveyAgent takes a list of typed questions.
Why it holds
What you configure becomes enforcement. The department names become an enum on the transfer tool’s department argument, and the handler refuses a value that is not on the list. A rating question’s scale becomes the bound the validator applies.
How it works
ReceptionistAgent(
departments=[
{"name": "sales", "description": "Pricing, availability and new orders",
"number": "+15551230001"},
{"name": "workshop", "description": "Repairs, servicing and appointments",
"number": "+15551230002"},
],
greeting="Ridgeline Cycles, how can I help?",
)The rendered document carries two tools you did not write, collect_caller_info and transfer_call, and a prompt you did not write. It sets the voice rime.spore and transfer_summary in params. The departments sit in global_data. The transfer tool’s department parameter is {"enum": ["sales", "workshop"]}. On a transfer the handler emits a connect verb for the department’s number with transfer: "true" and post_process: true.
SurveyAgent(
survey_name="Workshop follow-up",
questions=[{"id": "rating", "type": "rating", "scale": 5,
"text": "From one to five, how would you rate the work?"}, ...],
)The survey registers validate_response and log_response, sets a post_prompt, and rejects a rating of seven on a five-point scale. The constructor accepts four question types: rating, multiple_choice, yes_no and open_ended. It fills in a scale of five for a rating that has none. It raises for a multiple choice question with no options, and carries one with options into global_data as written.
PREFAB chooses which one this process serves. app.py builds both so the verifier can prove both.
The TypeScript prefabs take the same configuration in camel case (surveyName, brandName) and render the same tools, enum and voice. The survey registers three more tools for reading progress: answer_question, get_current_question and get_survey_progress.
Limitations
A prefab is a starting point with opinions. The receptionist takes voice as a constructor argument, but its prompt text and tool descriptions are its own. SurveyAgent has no voice argument at all. When the configuration surface stops fitting, write the agent directly; the prefabs are short and readable in signalwire/prefabs/.
The receptionist hands the configured number to connect unchanged, so the verifier proves the transfer for a phone number and nothing else.
The platform posts a tool’s arguments as argument: {parsed: [args], raw}. The TypeScript SDK’s /swaig route in 2.0.5 hands argument to the prefab’s handlers as posted, so they would read parsed and raw instead of the caller’s name. The surface subclasses each prefab and overrides onFunctionCall to unwrap the documented shape before dispatch.
What to change first
Add {"name": "legal", ...} to DEPARTMENTS and run the verifier. The enum assertion fails, which is the point: the enum comes from your list, and the transfer tool’s valid arguments are exactly the departments you configured.