{"slug":"call-routing-strategies","title":"Call Routing Strategies — Round-Robin, Skill-Based, Geo, Time-of-Day","tags":["call-routing","hunt-group","round-robin","skill-based","ring-strategy","signalwire"],"agent_summary":"Seven primary inbound call routing strategies: simple forward, ring-all, hunt group (sequential), round-robin (load-balanced), skill-based, geo-based, and time-of-day. Each has distinct SWML/LaML patterns and tradeoffs in answer rate, agent fairness, and customer experience. Skill-based requires external state (CRM or DB); the others can run purely in SWML.","trigger_phrases":["call routing strategies","ring strategy","hunt group SWML","round robin call routing","skill based routing","geo routing voice","call queue strategy"],"runnable":true,"markdown":"\n# Call Routing Strategies\n\nThe decision tree from \"phone rings\" to \"human answers\" has 7 canonical strategies. Each has different math, different agent fairness, and different customer-experience trade-offs.\n\n## 1. Simple forward\n\nOne number forwards to one destination. The simplest case.\n\n```yaml\n- connect:\n    to: +13105551111\n    timeout: 30\n```\n\n**When to use:** single-agent operations, small businesses with one phone line, after-hours forwarding to on-call. **Trade-off:** zero redundancy — if the destination doesn't answer, the call fails.\n\n## 2. Ring all (simultaneous)\n\nRing every agent at once. First to answer wins.\n\n```yaml\n- connect:\n    to:\n      - +13105551111\n      - +13105552222\n      - +13105553333\n    timeout: 30\n```\n\n**When to use:** small teams (3-5 agents), urgent service categories where first-available wins (emergency plumbing, locksmiths). **Trade-off:** disruptive — every phone rings every call. Agents annoyed. Higher answer rate than hunt group but worse for agent focus.\n\n## 3. Hunt group (sequential / find-me-follow-me)\n\nTry agent 1, then agent 2, then agent 3 in sequence. Each gets the full ring timeout before falling through.\n\n```yaml\n- connect:\n    to: +13105551111\n    timeout: 15\n- connect:\n    to: +13105552222\n    timeout: 15\n- connect:\n    to: +13105553333\n    timeout: 15\n- play: say:All agents are busy, please leave a message\n- record_call: {}\n```\n\n**When to use:** strict seniority hierarchy (senior agent gets first shot), or after-hours escalation chains (try office → try cell → try backup). **Trade-off:** unfair — agent 1 takes all the calls, agents 2-3 starve.\n\n## 4. Round-robin (load balanced)\n\nCycle through agents so each gets roughly equal call volume. Requires state (which agent was last called).\n\n### Stateful pattern with external store\n\n```yaml\n- request:\n    url: https://your.api/get-next-agent?queue=sales\n    method: GET\n    save: routing\n- connect:\n    to: \"${routing.next_agent}\"\n    timeout: 30\n- request:\n    url: https://your.api/release-agent\n    method: POST\n    body:\n      agent: \"${routing.next_agent}\"\n      call_id: \"${call.id}\"\n```\n\nYour API tracks the rotation and returns the next agent in line. On call end, mark the agent available again.\n\n### Variation: weighted round-robin\n\nAgents with higher capacity get more calls per cycle:\n\n```python\nagents = [\n    (\"+13105551111\", 3),  # gets 3 of every 6 calls\n    (\"+13105552222\", 2),  # gets 2 of every 6 calls\n    (\"+13105553333\", 1),  # gets 1 of every 6 calls\n]\n```\n\n**When to use:** call centers with fungible agent skills, fairness matters. **Trade-off:** requires external state, slightly slower routing decision.\n\n## 5. Skill-based routing\n\nMatch the caller to an agent with relevant expertise (language, certification, product knowledge). Requires caller-intent capture and agent-skill metadata.\n\n### Capture intent via IVR or AI\n\n```yaml\n- prompt:\n    play: say:Press 1 for billing, 2 for technical support, 3 for new sales\n    max_digits: 1\n- request:\n    url: https://your.api/route?skill=${prompt_value}\n    method: GET\n    save: route\n- connect:\n    to: \"${route.agent}\"\n    timeout: 30\n```\n\n### Capture intent via AI receptionist\n\n```yaml\n- ai:\n    prompt:\n      text: |\n        Identify the caller's reason. Call route_to_agent with one of:\n        billing, technical, sales, retention.\n    SWAIG:\n      functions:\n        - function: route_to_agent\n          parameters:\n            type: object\n            properties:\n              skill: { type: string, enum: [\"billing\", \"technical\", \"sales\", \"retention\"] }\n          data_map:\n            webhooks:\n              - url: https://your.api/route\n```\n\n**When to use:** multi-product or multi-language operations, complex L1/L2/L3 support tiers. **Trade-off:** highest ops complexity, requires accurate skill tagging in the agent directory.\n\n## 6. Geo-based routing\n\nRoute by caller's location — area code, IP/ANI lookup, or stated location.\n\n```yaml\n- request:\n    url: https://your.api/geo-route?phone=${call.from}\n    method: GET\n    save: geo\n- connect:\n    to: \"${geo.regional_office}\"\n    timeout: 30\n```\n\nThe API does an area-code-to-region lookup or queries an LRN database for higher accuracy.\n\n**When to use:** multi-office businesses where local presence matters, regulated industries needing in-state agents (insurance, real estate). **Trade-off:** area code is unreliable for mobile users; LRN lookups cost money.\n\n## 7. Time-of-day routing\n\nDifferent destinations during different hours. See [business hours logic](/topic/business-hours-logic) for the full pattern.\n\n```yaml\n- cond:\n    when: \"is_business_hours()\"\n    then:\n      - connect:\n          to: +13105551111\n    else:\n      - connect:\n          to: +13105559999  # after-hours service\n```\n\n**When to use:** any operation with non-24/7 hours.\n\n## Hybrid strategies\n\nReal production routers combine strategies. Common hybrids:\n\n### Time + skill + round-robin\n\n```\ninbound call\n  ├─ if after hours → voicemail\n  ├─ if business hours\n  │    └─ IVR captures skill\n  │         └─ round-robin among agents with that skill\n  │              └─ if no skill match → general queue\n```\n\n### Geo + skill\n\n```\ninbound call\n  └─ geo-route to regional office\n       └─ skill-based routing within that office\n            └─ overflow to nearest neighboring office\n```\n\n### Ring-all-then-hunt\n\n```\ninbound call\n  └─ ring-all 3 senior agents (15 sec timeout)\n       └─ if no answer, hunt through junior agents\n            └─ if no answer, voicemail\n```\n\n## Comparison matrix\n\n| Strategy | Setup complexity | Answer rate | Agent fairness | Customer experience |\n|---|---|---|---|---|\n| Simple forward | Trivial | Low | N/A | Mediocre (no redundancy) |\n| Ring all | Low | Highest | Poor (everyone disrupted) | Good (fastest answer) |\n| Hunt group | Low | Medium | Very poor | OK (with reasonable timeouts) |\n| Round-robin | Medium | Medium-High | Excellent | Good |\n| Skill-based | High | Medium-High | Configurable | Excellent |\n| Geo-based | Medium | Medium | Variable | Good (local touch) |\n| Time-of-day | Low | N/A | N/A | Critical for hours-of-operation |\n\n## Per-call decision time\n\nRouting decision time impacts customer experience. Targets:\n\n| Pattern | Decision overhead |\n|---|---|\n| SWML inline cond | < 50ms |\n| SWML + external API | 100-500ms |\n| SWML + AI classification | 2-5 seconds |\n| Multi-step IVR + API | 5-15 seconds |\n\nCaller perception: silence under 1 second feels normal; 1-3 seconds feels like a hiccup; 3+ seconds feels broken unless filled with \"one moment\" TTS.\n\n## State management for stateful strategies\n\nRound-robin and skill-based need state. Three storage options:\n\n| Option | Latency | Durability | Complexity |\n|---|---|---|---|\n| Redis | < 5ms | Volatile | Low |\n| Supabase | 30-100ms | Durable | Low |\n| SignalWire global_data | inline | Per-call only (not persistent across calls) | Trivial |\n| In-memory cache | < 1ms | Lost on restart | Low |\n\nFor round-robin, a Redis counter with atomic increment is the cleanest pattern. For skill-based, durable state (Supabase) so agent skills survive restarts.\n\n## SignalWire-specific: AI agent as router\n\nUse an AI agent as the routing layer:\n\n```yaml\n- ai:\n    prompt:\n      text: |\n        You are a phone receptionist. Identify the caller's needs and route them.\n        Use route_caller to direct them based on their intent.\n    SWAIG:\n      functions:\n        - function: route_caller\n          parameters:\n            type: object\n            properties:\n              department: { type: string, enum: [\"sales\", \"support\", \"billing\"] }\n              urgency: { type: string, enum: [\"normal\", \"urgent\"] }\n          data_map:\n            webhooks:\n              - url: https://your.api/ai-route\n```\n\nThe AI handles disambiguation, language detection, and intent capture — much better than DTMF IVR for complex routing.\n\n## Common pitfalls\n\n- **No fallback** — every strategy needs a fallback (voicemail, queue, general line). When the primary fails, dead air is the worst outcome.\n- **Ring timeout too long** — 30 seconds feels like an eternity when nothing happens. 12-18 seconds is the sweet spot.\n- **Round-robin without release** — agents marked busy after a call but never released, queue clogs.\n- **Skill-based with stale skills** — agent skills change, the routing table doesn't. Add an admin UI for self-service skill updates.\n- **Geo-based on mobile-only callers** — area code accuracy is poor for cell users. Ask the caller for location during the IVR.\n\n## Related patterns\n\n- [Business hours logic](/topic/business-hours-logic) — time-of-day routing\n- [Warm transfer](/topic/warm-transfer) and [cold transfer](/topic/cold-transfer)\n- [Callback scheduling](/topic/callback-scheduling)\n- [SWML connect verb](/topic/swml-connect-verb)\n- [SignalWire call flow builder](/topic/signalwire-call-flow-builder)\n\n## References\n\n- SignalWire SWML `connect` verb documentation\n- ACD (Automatic Call Distributor) literature — standard call-center routing models\n","html":"<h1>Call Routing Strategies</h1>\n<p>The decision tree from \"phone rings\" to \"human answers\" has 7 canonical strategies. Each has different math, different agent fairness, and different customer-experience trade-offs.</p>\n<h2>1. Simple forward</h2>\n<p>One number forwards to one destination. The simplest case.</p>\n<pre><code class=\"language-yaml\">- connect:\n    to: +13105551111\n    timeout: 30\n</code></pre>\n<p><strong>When to use:</strong> single-agent operations, small businesses with one phone line, after-hours forwarding to on-call. <strong>Trade-off:</strong> zero redundancy — if the destination doesn't answer, the call fails.</p>\n<h2>2. Ring all (simultaneous)</h2>\n<p>Ring every agent at once. First to answer wins.</p>\n<pre><code class=\"language-yaml\">- connect:\n    to:\n      - +13105551111\n      - +13105552222\n      - +13105553333\n    timeout: 30\n</code></pre>\n<p><strong>When to use:</strong> small teams (3-5 agents), urgent service categories where first-available wins (emergency plumbing, locksmiths). <strong>Trade-off:</strong> disruptive — every phone rings every call. Agents annoyed. Higher answer rate than hunt group but worse for agent focus.</p>\n<h2>3. Hunt group (sequential / find-me-follow-me)</h2>\n<p>Try agent 1, then agent 2, then agent 3 in sequence. Each gets the full ring timeout before falling through.</p>\n<pre><code class=\"language-yaml\">- connect:\n    to: +13105551111\n    timeout: 15\n- connect:\n    to: +13105552222\n    timeout: 15\n- connect:\n    to: +13105553333\n    timeout: 15\n- play: say:All agents are busy, please leave a message\n- record_call: {}\n</code></pre>\n<p><strong>When to use:</strong> strict seniority hierarchy (senior agent gets first shot), or after-hours escalation chains (try office → try cell → try backup). <strong>Trade-off:</strong> unfair — agent 1 takes all the calls, agents 2-3 starve.</p>\n<h2>4. Round-robin (load balanced)</h2>\n<p>Cycle through agents so each gets roughly equal call volume. Requires state (which agent was last called).</p>\n<h3>Stateful pattern with external store</h3>\n<pre><code class=\"language-yaml\">- request:\n    url: https://your.api/get-next-agent?queue=sales\n    method: GET\n    save: routing\n- connect:\n    to: \"${routing.next_agent}\"\n    timeout: 30\n- request:\n    url: https://your.api/release-agent\n    method: POST\n    body:\n      agent: \"${routing.next_agent}\"\n      call_id: \"${call.id}\"\n</code></pre>\n<p>Your API tracks the rotation and returns the next agent in line. On call end, mark the agent available again.</p>\n<h3>Variation: weighted round-robin</h3>\n<p>Agents with higher capacity get more calls per cycle:</p>\n<pre><code class=\"language-python\">agents = [\n    (\"+13105551111\", 3),  # gets 3 of every 6 calls\n    (\"+13105552222\", 2),  # gets 2 of every 6 calls\n    (\"+13105553333\", 1),  # gets 1 of every 6 calls\n]\n</code></pre>\n<p><strong>When to use:</strong> call centers with fungible agent skills, fairness matters. <strong>Trade-off:</strong> requires external state, slightly slower routing decision.</p>\n<h2>5. Skill-based routing</h2>\n<p>Match the caller to an agent with relevant expertise (language, certification, product knowledge). Requires caller-intent capture and agent-skill metadata.</p>\n<h3>Capture intent via IVR or AI</h3>\n<pre><code class=\"language-yaml\">- prompt:\n    play: say:Press 1 for billing, 2 for technical support, 3 for new sales\n    max_digits: 1\n- request:\n    url: https://your.api/route?skill=${prompt_value}\n    method: GET\n    save: route\n- connect:\n    to: \"${route.agent}\"\n    timeout: 30\n</code></pre>\n<h3>Capture intent via AI receptionist</h3>\n<pre><code class=\"language-yaml\">- ai:\n    prompt:\n      text: |\n        Identify the caller's reason. Call route_to_agent with one of:\n        billing, technical, sales, retention.\n    SWAIG:\n      functions:\n        - function: route_to_agent\n          parameters:\n            type: object\n            properties:\n              skill: { type: string, enum: [\"billing\", \"technical\", \"sales\", \"retention\"] }\n          data_map:\n            webhooks:\n              - url: https://your.api/route\n</code></pre>\n<p><strong>When to use:</strong> multi-product or multi-language operations, complex L1/L2/L3 support tiers. <strong>Trade-off:</strong> highest ops complexity, requires accurate skill tagging in the agent directory.</p>\n<h2>6. Geo-based routing</h2>\n<p>Route by caller's location — area code, IP/ANI lookup, or stated location.</p>\n<pre><code class=\"language-yaml\">- request:\n    url: https://your.api/geo-route?phone=${call.from}\n    method: GET\n    save: geo\n- connect:\n    to: \"${geo.regional_office}\"\n    timeout: 30\n</code></pre>\n<p>The API does an area-code-to-region lookup or queries an LRN database for higher accuracy.</p>\n<p><strong>When to use:</strong> multi-office businesses where local presence matters, regulated industries needing in-state agents (insurance, real estate). <strong>Trade-off:</strong> area code is unreliable for mobile users; LRN lookups cost money.</p>\n<h2>7. Time-of-day routing</h2>\n<p>Different destinations during different hours. See <a href=\"/topic/business-hours-logic\">business hours logic</a> for the full pattern.</p>\n<pre><code class=\"language-yaml\">- cond:\n    when: \"is_business_hours()\"\n    then:\n      - connect:\n          to: +13105551111\n    else:\n      - connect:\n          to: +13105559999  # after-hours service\n</code></pre>\n<p><strong>When to use:</strong> any operation with non-24/7 hours.</p>\n<h2>Hybrid strategies</h2>\n<p>Real production routers combine strategies. Common hybrids:</p>\n<h3>Time + skill + round-robin</h3>\n<pre><code>inbound call\n  ├─ if after hours → voicemail\n  ├─ if business hours\n  │    └─ IVR captures skill\n  │         └─ round-robin among agents with that skill\n  │              └─ if no skill match → general queue\n</code></pre>\n<h3>Geo + skill</h3>\n<pre><code>inbound call\n  └─ geo-route to regional office\n       └─ skill-based routing within that office\n            └─ overflow to nearest neighboring office\n</code></pre>\n<h3>Ring-all-then-hunt</h3>\n<pre><code>inbound call\n  └─ ring-all 3 senior agents (15 sec timeout)\n       └─ if no answer, hunt through junior agents\n            └─ if no answer, voicemail\n</code></pre>\n<h2>Comparison matrix</h2>\n<p>| Strategy | Setup complexity | Answer rate | Agent fairness | Customer experience |\n|---|---|---|---|---|\n| Simple forward | Trivial | Low | N/A | Mediocre (no redundancy) |\n| Ring all | Low | Highest | Poor (everyone disrupted) | Good (fastest answer) |\n| Hunt group | Low | Medium | Very poor | OK (with reasonable timeouts) |\n| Round-robin | Medium | Medium-High | Excellent | Good |\n| Skill-based | High | Medium-High | Configurable | Excellent |\n| Geo-based | Medium | Medium | Variable | Good (local touch) |\n| Time-of-day | Low | N/A | N/A | Critical for hours-of-operation |</p>\n<h2>Per-call decision time</h2>\n<p>Routing decision time impacts customer experience. Targets:</p>\n<p>| Pattern | Decision overhead |\n|---|---|\n| SWML inline cond | &#x3C; 50ms |\n| SWML + external API | 100-500ms |\n| SWML + AI classification | 2-5 seconds |\n| Multi-step IVR + API | 5-15 seconds |</p>\n<p>Caller perception: silence under 1 second feels normal; 1-3 seconds feels like a hiccup; 3+ seconds feels broken unless filled with \"one moment\" TTS.</p>\n<h2>State management for stateful strategies</h2>\n<p>Round-robin and skill-based need state. Three storage options:</p>\n<p>| Option | Latency | Durability | Complexity |\n|---|---|---|---|\n| Redis | &#x3C; 5ms | Volatile | Low |\n| Supabase | 30-100ms | Durable | Low |\n| SignalWire global_data | inline | Per-call only (not persistent across calls) | Trivial |\n| In-memory cache | &#x3C; 1ms | Lost on restart | Low |</p>\n<p>For round-robin, a Redis counter with atomic increment is the cleanest pattern. For skill-based, durable state (Supabase) so agent skills survive restarts.</p>\n<h2>SignalWire-specific: AI agent as router</h2>\n<p>Use an AI agent as the routing layer:</p>\n<pre><code class=\"language-yaml\">- ai:\n    prompt:\n      text: |\n        You are a phone receptionist. Identify the caller's needs and route them.\n        Use route_caller to direct them based on their intent.\n    SWAIG:\n      functions:\n        - function: route_caller\n          parameters:\n            type: object\n            properties:\n              department: { type: string, enum: [\"sales\", \"support\", \"billing\"] }\n              urgency: { type: string, enum: [\"normal\", \"urgent\"] }\n          data_map:\n            webhooks:\n              - url: https://your.api/ai-route\n</code></pre>\n<p>The AI handles disambiguation, language detection, and intent capture — much better than DTMF IVR for complex routing.</p>\n<h2>Common pitfalls</h2>\n<ul>\n<li><strong>No fallback</strong> — every strategy needs a fallback (voicemail, queue, general line). When the primary fails, dead air is the worst outcome.</li>\n<li><strong>Ring timeout too long</strong> — 30 seconds feels like an eternity when nothing happens. 12-18 seconds is the sweet spot.</li>\n<li><strong>Round-robin without release</strong> — agents marked busy after a call but never released, queue clogs.</li>\n<li><strong>Skill-based with stale skills</strong> — agent skills change, the routing table doesn't. Add an admin UI for self-service skill updates.</li>\n<li><strong>Geo-based on mobile-only callers</strong> — area code accuracy is poor for cell users. Ask the caller for location during the IVR.</li>\n</ul>\n<h2>Related patterns</h2>\n<ul>\n<li><a href=\"/topic/business-hours-logic\">Business hours logic</a> — time-of-day routing</li>\n<li><a href=\"/topic/warm-transfer\">Warm transfer</a> and <a href=\"/topic/cold-transfer\">cold transfer</a></li>\n<li><a href=\"/topic/callback-scheduling\">Callback scheduling</a></li>\n<li><a href=\"/topic/swml-connect-verb\">SWML connect verb</a></li>\n<li><a href=\"/topic/signalwire-call-flow-builder\">SignalWire call flow builder</a></li>\n</ul>\n<h2>References</h2>\n<ul>\n<li>SignalWire SWML <code>connect</code> verb documentation</li>\n<li>ACD (Automatic Call Distributor) literature — standard call-center routing models</li>\n</ul>\n"}