Predictive engine
Customers ring first. Agents join only on answer.
Same hold_dialer_master_data and hold_dialer_priority pool as Progressive. Originate is AMI for both engines. Lead ingest lives in the API reference.
End-to-end flow
A 1 second pacer counts idle agents on this module’s hold_dialer_employee roster, claims the next due lead from master, and originates the customer on a Local channel. Skills are unused. Master has no agent pin. AMI ignores non-roster extensions. The agent panel stays on waiting until a customer answers and is bridged. PSTN inbound is off.
- Ready idle agents (panel + presence + SIP + wrap clear)
- Pace: idle × ratio, capped by max_lines (ringing + hold + connecting)
- Claim the next due row from master (no employee pin)
- Originate Local/{0number}@from-internal with MusicOnHold
- No answer → Not Connected. Answer → pick longest-waiting idle agent
- Agent free → AMI Bridge with shared AMI_CALLERID. Else hold until any idle agent is free or the customer hangs up (Abandoned)
- Decline keeps the caller on hold; that extension is blocked for decline_cooldown_seconds
- Talk → record attempt. Hangup → settle. Agent wrap buffer_time
Master has no agent_id. Connected agent is written on hold_dialer_call_attempts at settle, never on the lead row. Agent phones never see the customer number.
Pacing
target = min(max(idle, floor(idle × predictive_ratio)), max_lines). Extra legs are target − (ringing + hold + connecting). Ratio is at least 1. There is no abandon-rate brake. After a connected call the agent is not idle until buffer_time wrap finishes. Dashboard On hold is answered customers still on MusicOnHold (outbound only). See Settle & retry.
Claim order
Same SQL as Progressive: FOR UPDATE SKIP LOCKED, then Calling and reserved. Connected agent is written on hold_dialer_call_attempts at settle, not on master. call_count does not increment on claim — only when settle maps to Not Connected.
Any Abandoned row sorts above other statuses.
Lowest
priority_no first.Pending, then Callback, then Not Connected.
Never attempted first, then oldest attempt, then oldest push, then lowest id.
Received, Rejected, Calling, and reserved rows are skipped. Not Connected waits on next_retry_at ([2,2,2,2] + working hours). Abandoned waits a fixed 5 minutes. Claim refuses originate outside the work window except for a due callback. Callback priorities (is_callback = 1, seeded as id 2) wait on callback_scheduled_at. Time-gated callbacks are documented in the Callback Request guide.
AMI originate
Customer originate is Local/{0number}@from-internal/n with Application: MusicOnHold and OLI_ID. AMI_OUTBOUND_CHANNEL (for example PJSIP/9001) identifies the trunk in AMI events; it is not the Originate channel. 10-digit numbers get a leading 0.
When a customer answers, the engine originates PJSIP/{extension} with Application: Bridge and shared AMI_CALLERID to the longest-waiting idle agent.
Hold until agent
If no agent is free, or the agent is already on another predictive call, the extra customer stays on MusicOnHold. Only one bridge originate is sent to an agent at a time. If that agent declines (SIP deny), the customer stays on hold. That extension is written to hold_dialer_agent_declines and blocked for this call for decline_cooldown_seconds (default 30). When the window ends, the engine offers any idle agent. Hold is unlimited — there is no hold timeout. The customer is settled Abandoned only if they hang up while waiting or connecting.
Hangup results
| Hangup | Stored status |
|---|---|
| Answered and talked | Received |
| Answered, customer left before an agent talked | Abandoned |
| Cancel by customer, invalid destination | Rejected |
| No answer, busy, congestion, chanunavail | Not Connected |
Not Connected uses [2,2,2,2]: 1st of a round waits retry_seconds; 2nd waits 1 / 7 / 14 days. After the first pair, priority_id becomes 2. At 8 NCs the row is copied to hold_dialer_disposed and deleted from master. Abandoned always retries in 5 minutes and ignores those knobs. Received and Rejected are not claimed again.
Engine config
| Setting | Where | Meaning |
|---|---|---|
dialer_type | hold_dialer_config | Set to Predictive |
buffer_time | hold_dialer_config | Wrap after a connected call |
predictive_ratio | hold_dialer_config | Customer legs per idle agent (default 1.20) |
decline_cooldown_seconds | hold_dialer_config | Seconds a declined agent stays blocked for this call (default 30) |
max_lines | hold_dialer_config | Cap on ringing + hold + connecting customer legs |
max_tries | hold_dialer_config | Hard cap on Not Connected tries (8) |
retry_seconds | hold_dialer_config | Same-day gap after the first NC in a round (not Abandoned) |
retry_pattern | hold_dialer_config | Rounds of tries, default [2,2,2,2] |
work_start / work_end | hold_dialer_config | Originate window, default 09:30–18:30 IST |
work_days | hold_dialer_config | ISO weekdays, default Mon–Sat [1,2,3,4,5,6] |
AMI_OUTBOUND_CHANNEL | .env | Trunk identity, e.g. PJSIP/9001 |
AMI_AGENT_CHANNEL | .env | Default PJSIP/{extension} |
PREDICTIVE_CONNECT_TIMEOUT_MS | .env | Agent ring / answer window (default 30s) |
CALL_API_URL | .env | Not used |