Map SIP usernames on one domain to different AI agents and return the selected agent's call flow.
Also called SIP URI routing, dial a voice AI agent from a PBX
sip
The claim
register_routing_callback(fn, path) on an agent mounts path under the agent’s route. When the platform POSTs there, the SDK calls fn(request, body). A returned route becomes a 307 redirect; None means “carry on and serve this agent”. SWMLService.extract_sip_username(body) reads the username out of call.to. It takes the part before @ of a sip: URI, the value after tel:, or the bare value. A dictionary from username to route is the whole router.
Why it holds
In 3.0.1 you must register the callback before server.register(agent), because that call copies the agent’s routes into the app once. The server’s own setup_sip_routing() registers its callback after that copy and mounts no /sip endpoint, which the verifier’s design works around and Limitations records.
RFC 9110 defines 307 so that a client following it must not change the request method. The section is https://www.rfc-editor.org/rfc/rfc9110#section-15.4.8. The verifier does that step by hand: it re-POSTs the same body to Location verbatim, and the support agent serves its SWML. The routes in USERNAMES end in a slash on purpose. An AgentBase serves its root only at /route/, so a Location of /support lands on a 404. A username the map does not know returns None from the callback, and the agent that received the request serves its own document.
Limitations
AgentServer.setup_sip_routing() exists in 3.0.1 but mounts nothing: it registers its callback after server.register() has already copied each agent’s routes. Register the callback on the agent first, as this recipe does.
Routing is by username only. The router ignores the domain part of the URI, so two domains sharing a username share an agent.
What to change first
Add "billing": "/sales/" to USERNAMES and run the verifier. It fails at once, because it asserts its own ROUTES equals the app’s map. Add the same entry to ROUTES and it passes again. The two maps grow together, and the dictionary is the router. To see the 3.0.1 ordering rule instead, register the callback after server.register(). Every /sip POST becomes a 404.