Progressive engine

One free agent. One next lead. No overdial.

This guide is only the calling brain. Lead ingest lives in the API reference. Overdial and customer-first originate are in the Predictive guide. Both engines originate through AMI.

Panel active Presence ready AMI idle Callback Schedule waits on time

End-to-end flow

The engine does not poll on a global timer. Every AMI patch, panel join/leave, or ready/pause updates that extension and may schedule a claim. Inbound on that desk blocks outbound. So does an overdue missed activity (outbound_blocked); the only outbound allowed then is a redial of the agent's own missed OLI.

  1. Agent socket + AMI state (panel active, presence ready, SIP idle)
  2. Wait buffer_time after the last connected call
  3. Claim next skill-matched lead (Callback → Pending → Abandoned → Not Connected; NC uses Fresh Assign + call_count)
  4. AMI Originate to PJSIP/{extension} into from-internal
  5. Talk → Received. Miss → Not Connected. Then wrap and claim again

When an agent dials

FlagSourceMust be
panelAgent page connectedactive
presenceReady / pause on the panel (sales_dialer_employee.panel_status)ready
amiAsterisk device stateidle

Also required: no call already in flight, employee loaded from this module’s sales_dialer_employee roster, and at least one skill in sales_dialer_employee.skills (JSON list matched to lead form_type). Ready/pause writes sales_dialer_employee.panel_status. AMI and the dashboard only track roster extensions. After a call, the engine waits buffer_time seconds, then claims again. If no lead matches, it retries every 10 seconds.

Claim order

Two agents cannot take the same row. Claim uses FOR UPDATE SKIP LOCKED, then sets Calling and reserves the lead (isReserved = 1, reserved_at). first_attempt_at (first dial only) and last_attempt_at are stamped right before the real originate, so a claim released before dialing leaves no attempt history. call_count does not increment on claim.

1
Pool match (both)
Skill: form_type ∈ agent skills. Priority: priority_id ∈ agent priority_ids (empty list = all priorities).
2
Dial status tier
Callback (due), then Pending, then Abandoned, then Not Connected last.
3
Within tier
Callback / Pending / Abandoned: lowest priority_no. Not Connected: sooner next_retry_at, then Fresh Assign, then lowest call_count.
4
Age
Never attempted first, then oldest attempt, then oldest push, then lowest id.

Unassigned leads must match form_type to the agent’s skills and priority_id to the agent’s priority_ids allowlist (empty / null = all priorities). Master has no agent_id pin. Received, Rejected, Calling, and reserved rows are skipped by auto-claim. Socket redial can reopen a settled row (call_type = redial; Settle & retry) with skill check only — no priority gate. Claim also waits on next_retry_at (Abandoned requires a due next_retry_at; other statuses allow null) and, only when sales_dialer_config.outbound_obey_work_hours = 1, refuses originate outside work_start–work_end (including due callbacks). With the default 0, auto-dial runs whenever a ready agent matches; next_retry_at is still clamped to the window (Wrap and working hours).

ScenarioPick?
Skill matches, priority_ids emptyYes (all priorities)
Skill matches, lead priority_id in allowlistYes
Skill matches, lead priority_id not in allowlistNo — next eligible lead / agent
Skill miss (any priority)No
Predictive: idle union has one “all priorities” agentClaim SQL skips priority filter; bridge still requires that agent’s skill+priority
Inbound hold → offerSame skill + priority gate at pickSkilledAgent
Redial / ondemandSkill only (no priority allowlist)

Callback Request

priority_id 1 (Callback Schedule) honors callback_scheduled_at. Any idle agent with matching skill and priority allowlist may claim it when due. Full behaviour (time gate, retry) is in the Callback Request guide. How you send the fields is in the API reference.

1
Lead is waiting
Future callback_scheduled_at → skip. Other priorities keep dialing.
2
Time is due
Any matching skill + priority agent may claim it.
3
No time sent
Priority 1–3 without a datetime is Pending and dials like any other skill+priority-matched lead.

Hangup results

HangupStored status
ANSWERReceived
Cancel by customer, invalid destinationRejected
No answer, busy, congestion, chanunavailNot Connected

Received, Rejected, and Disposed are not claimed again (unless socket redial or a new CRM push). Not Connected uses sequential retry_pattern buckets (currently [4,4,3,2,1,1,1,1,1,1,1]) until max_tries (20). Leftover tries in the current bucket carry to the next working day; finishing a bucket waits until the next work_start before the next number. retry_seconds (3600) is the same-day gap for Not Connected. Abandoned is not on that cycle — it only waits retry_seconds when next_retry_at was empty; a slot after work_end snaps to the next working morning. call_count increments only on Not Connected. Auto-dial also stops when isMaintainance = 1.

Engine config

SettingWhereMeaning
dialer_typesales_dialer_configProgressive or Predictive
buffer_timesales_dialer_configSeconds between calls per agent
max_triessales_dialer_configHard cap on Not Connected tries (currently 20)
retry_secondssales_dialer_configSame-day gap after Not Connected (currently 3600)
retry_patternsales_dialer_configSequential buckets; leftover carries (currently [4,4,3,2,1,1,1,1,1,1,1])
work_start / work_endsales_dialer_configRetry window (currently 09:30–18:30 IST); also the originate window when outbound_obey_work_hours = 1
work_dayssales_dialer_configISO weekdays (currently [1,2,3,4,5,6,7])
outbound_obey_work_hourssales_dialer_config1 = auto-dial only inside the work window. Default 0. Inbound and redial are unaffected
disposed_priority_idsales_dialer_configFinal Disposal — never claimed
isMaintainancesales_dialer_config1 stops new originates and redial
MISSED_ACTIVITY_ENABLED / MISSED_ACTIVITY_GRACE_MINUTES.envMissed-activity outbound block on/off (default on) and grace minutes after a bridged call (default 10)
AMI_AGENT_CHANNEL.envDefault PJSIP/{extension}
AMI_CALLERID.envOutbound CLI
DIAL_ORIGINATE_TIMEOUT_MS.envWait for AMI on_call (default 60s)