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.

Customer first Any idle agent Ratio pacing Shared AMI_CALLERID

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.

  1. Ready idle agents (panel + presence + SIP + wrap clear)
  2. Pace: idle × ratio, capped by max_lines (ringing + hold + connecting)
  3. Claim the next due row from master (no employee pin)
  4. Originate Local/{0number}@from-internal with MusicOnHold
  5. No answer → Not Connected. Answer → pick longest-waiting idle agent
  6. Agent free → AMI Bridge with shared AMI_CALLERID. Else hold until any idle agent is free or the customer hangs up (Abandoned)
  7. Decline keeps the caller on hold; that extension is blocked for decline_cooldown_seconds
  8. 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.

1
Abandoned first
Any Abandoned row sorts above other statuses.
2
Priority number
Lowest priority_no first.
3
Dial status
Pending, then Callback, then Not Connected.
4
Age
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

HangupStored status
Answered and talkedReceived
Answered, customer left before an agent talkedAbandoned
Cancel by customer, invalid destinationRejected
No answer, busy, congestion, chanunavailNot 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

SettingWhereMeaning
dialer_typehold_dialer_configSet to Predictive
buffer_timehold_dialer_configWrap after a connected call
predictive_ratiohold_dialer_configCustomer legs per idle agent (default 1.20)
decline_cooldown_secondshold_dialer_configSeconds a declined agent stays blocked for this call (default 30)
max_lineshold_dialer_configCap on ringing + hold + connecting customer legs
max_trieshold_dialer_configHard cap on Not Connected tries (8)
retry_secondshold_dialer_configSame-day gap after the first NC in a round (not Abandoned)
retry_patternhold_dialer_configRounds of tries, default [2,2,2,2]
work_start / work_endhold_dialer_configOriginate window, default 09:30–18:30 IST
work_dayshold_dialer_configISO weekdays, default Mon–Sat [1,2,3,4,5,6]
AMI_OUTBOUND_CHANNEL.envTrunk identity, e.g. PJSIP/9001
AMI_AGENT_CHANNEL.envDefault PJSIP/{extension}
PREDICTIVE_CONNECT_TIMEOUT_MS.envAgent ring / answer window (default 30s)
CALL_API_URL.envNot used