← All Tutorials

ViciDial Call Distribution: Agent Rank & Load Balancing

Monitoring & Observability Intermediate 17 min read #100 Published

Uneven agent workload and call abandonment happen when rank distribution is misconfigured: fix queue strategy, agent status routing, and load-balancing weights to match your team size and call volume.

Call distribution in ViciDial depends on three overlapping systems: agent rank (a numeric priority assigned to each agent), queue strategy (how Asterisk selects the next agent), and load balancing (how calls are weighted across groups). When rank is set incorrectly, calls queue up behind low-skill agents while senior agents sit idle, or they distribute randomly instead of fairly. The fix involves setting rank values correctly in the database, choosing the right queue strategy in extensions, and validating agent status flow through the inbound group configuration.

Tested on ViciDial 2.14 (SVN 3555+), Asterisk 16/18, MariaDB 10.5+, with SIP trunks and local agent registration.

Prerequisites

Understanding agent rank in ViciDial

Agent rank is a numeric field (0-9999) stored in the vicidial_users table. Lower numbers are called first. Rank is not tied to role or permission; it is purely a call-distribution priority within a specific inbound group.

Open a terminal and check current rank assignments:

mysql -u root -p asterisk -e \
  "SELECT user_id, full_name, user_rank, inbound_group FROM vicidial_users WHERE user_rank IS NOT NULL ORDER BY inbound_group, user_rank;"

Output looks like:

| user_id | full_name      | user_rank | inbound_group |
|---------|----------------|-----------|---------------|
| 1001    | Alice Senior   | 1         | SALES         |
| 1002    | Bob Support    | 2         | SALES         |
| 1003    | Carol New      | 99        | SALES         |
| 2001    | Dave Tech      | 1         | SUPPORT       |
| 2002    | Eve Tech       | 2         | SUPPORT       |

Rank 1 agents in SALES get calls first. When Alice is READY, she answers. When she goes PAUSED or INCALL, rank 2 (Bob) becomes the next candidate. Rank 99 (Carol, new) only gets calls if Alice and Bob are not available.

The inbound_group column links each agent to a phone queue. Agents in different groups do not compete for the same calls.

Setting up rank for balanced distribution

For a team of five agents handling the same queue, a fair setup assigns ranks 1 through 5:

UPDATE vicidial_users
SET user_rank = 1
WHERE user_id = '1001' AND inbound_group = 'SALES';

UPDATE vicidial_users
SET user_rank = 2
WHERE user_id = '1002' AND inbound_group = 'SALES';

UPDATE vicidial_users
SET user_rank = 3
WHERE user_id = '1003' AND inbound_group = 'SALES';

UPDATE vicidial_users
SET user_rank = 4
WHERE user_id = '1004' AND inbound_group = 'SALES';

UPDATE vicidial_users
SET user_rank = 5
WHERE user_id = '1005' AND inbound_group = 'SALES';

Commit the changes. Do not use gaps (rank 1, 3, 5, 10). Asterisk iterates through each rank in order; unused rank values waste cycles.

For a tiered support model (senior handling overflow):

UPDATE vicidial_users SET user_rank = 10 WHERE user_id = '1001' AND inbound_group = 'SALES';
UPDATE vicidial_users SET user_rank = 11 WHERE user_id = '1002' AND inbound_group = 'SALES';
UPDATE vicidial_users SET user_rank = 12 WHERE user_id = '1003' AND inbound_group = 'SALES';
UPDATE vicidial_users SET user_rank = 1 WHERE user_id = '2001' AND inbound_group = 'SALES';

Here, the senior agent (rank 1) gets first call, then three junior agents. This ensures skill routing without complex queues.

Queue strategy and load balancing

Asterisk offers five queue strategies. ViciDial uses them in the extensions-vicidial.conf file. The choice affects how agents are selected at each call.

Strategy selection: what works for ViciDial

ringall: Rings all available agents in rank order simultaneously. The first to answer gets the call. Good for small teams (3-5 agents). Higher answer rate, but agents get ring storms.

leastrecent: Rings the agent least recently called, then the next least recent, in rank order. Distributes calls more evenly. Recommended for most setups.

fewest: Rings the agent with the fewest current calls. Useful if agents handle multiple calls at once (rare in ViciDial).

rrmemory: Round-robin with memory; cycles through agents in rank order and remembers the last agent called. Fair, stable. Good for equal-skill teams.

linear: Rings agents one at a time, in strict rank order, waiting for timeout before moving to the next. Slow for high volume. Avoid.

For a standard ViciDial inbound group with 5-20 agents of similar skill, use leastrecent or rrmemory. For senior + junior teams, use ringall with rank separating tiers.

Open the inbound group configuration file:

cat /etc/asterisk/extensions-vicidial.conf | grep -A 50 "exten => 6001"

Look for the Queue application line:

exten => 6001,1,Queue(SALES|t|${DIALPLAN_VARS}|5)

The queue name is SALES. The third parameter sets strategy. Modify the queues.conf file:

grep -A 30 "^\[SALES\]" /etc/asterisk/queues.conf

Output:

[SALES]
music = default
strategy = leastrecent
timeout = 15
retry = 5
weight = 10
member => Local/1001@vicidial
member => Local/1002@vicidial
member => Local/1003@vicidial

To change to rrmemory:

sed -i 's/strategy = leastrecent/strategy = rrmemory/' /etc/asterisk/queues.conf

Reload Asterisk:

asterisk -rx "module reload app_queue.so"

Verify the change took effect:

asterisk -rx "queue show SALES"

Output includes:

Strategy: Round Robin with Memory

Load balancing across inbound groups

If you have multiple inbound groups (SALES, SUPPORT, BILLING), and agents belong to multiple groups, load balancing ensures each agent is not overloaded across all groups.

In ViciDial, an agent can have a primary inbound_group and secondary groups. Rank applies to each group independently. However, if an agent is assigned to two groups, they will receive calls from both based on their rank in each.

To prevent an agent from being overwhelmed, use the agent's max_concurrent_calls setting in vicidial_users:

SELECT user_id, full_name, inbound_group, max_concurrent_calls
FROM vicidial_users
WHERE user_id = '1001';

Set max concurrent calls to 1 for single-call handling:

UPDATE vicidial_users
SET max_concurrent_calls = 1
WHERE user_id = '1001' AND inbound_group = 'SALES';

This is enforced at the dial-plan level in extensions-vicidial.conf. Look for the agent's Local channel:

exten => 1001,1,Dial(SIP/1001@default,,TtHhkK)

The second parameter (empty above) can be filled with options. Asterisk does not natively limit concurrent calls per extension; this is managed by ViciDial's state tracking in the vicidial_agent_log table.

ViciDial tracks agent state (READY, INCALL, etc.) in memory and database:

SELECT user_id, status, calls_today, last_call_time
FROM vicidial_agent_log
WHERE user_id = '1001' AND campaign_id = 'SALES' LIMIT 1;

The Asterisk dial-plan consults this table before ringing an agent. If the agent's status is not READY or RING, they are skipped.

Configuring inbound groups and rank assignment

Log into the web admin console at /vicidial/admin.php. Go to Inbound > Manage Inbound Groups.

Create or edit a group (e.g., SALES):

Save. Then go to Manage Inbound Group Members. Add agents:

Agent ID Rank Status
1001 1 Active
1002 2 Active
1003 3 Active

The web interface updates vicidial_users.user_rank for each agent. Verify via CLI:

mysql -u root -p asterisk -e \
  "SELECT user_id, user_rank, inbound_group FROM vicidial_users WHERE inbound_group = 'SALES';"

Testing call distribution

Start with two agents logged in to the same inbound group.

asterisk -rx "agent show 1001"

Output should show:

Agent/1001 (Alice) is logged in

Repeat for agent 1002:

asterisk -rx "agent show 1002"

Now, send an inbound call to the group's DID. Use a test call or generate one via a curl script:

curl -X POST "http://localhost:8088/stasis/applications/vicidial_inbound/subscribe?channel=SIP/trunk%2F5551234567" \
  -H "Authorization: Bearer YOUR_TOKEN"

Or, if you have an existing inbound trunk, dial the number manually.

Watch Asterisk console in real time:

asterisk -c -f 2>&1 | grep -E "(Queue|Agent|Member)"

If agent 1001 is READY, they ring first. If they go INCALL, watch for agent 1002 to ring next. If rank is wrong (e.g., both at rank 1), both ring simultaneously (depending on strategy).

Check the Asterisk queue status:

asterisk -rx "queue show SALES"

Output example:

SALES has 1 calls (max unlimited) in 'rrmemory' strategy (20s holdtime, 45s talktime)
  Members:
    Local/1001@vicidial (Member) Penalty: 0 Paused: No (In use)
    Local/1002@vicidial (Member) Penalty: 0 Paused: No (Ready)
    Local/1003@vicidial (Member) Penalty: 0 Paused: No (Ready)

Member status tells you who is ringing or in call. If it says (In use), that agent's status is INCALL in the database.

Advanced: custom rank-based load balancing

For high-volume or multi-team setups, use Asterisk's "penalty" system in queues.conf to override rank. Penalty increases the time an agent is skipped before being rung.

Edit queues.conf:

[SALES]
strategy = leastrecent
timeout = 15
queue-yoursoundfile = on

member => Local/1001@vicidial,penalty=0
member => Local/1002@vicidial,penalty=0
member => Local/1003@vicidial,penalty=10
member => Local/1004@vicidial,penalty=20

Agents 1003 and 1004 have higher penalties, so they are deprioritized even if they are READY. Agent 1003 is rung only after agents 1001 and 1002 have declined or are busy. Agent 1004 is a last resort.

Reload:

asterisk -rx "module reload app_queue.so"

Verify:

asterisk -rx "queue show SALES"

Output:

Members:
  Local/1001@vicidial (Member) Penalty: 0 Paused: No
  Local/1002@vicidial (Member) Penalty: 0 Paused: No
  Local/1003@vicidial (Member) Penalty: 10 Paused: No
  Local/1004@vicidial (Member) Penalty: 20 Paused: No

Penalty works independently of rank. If you use both rank and penalty, Asterisk applies rank first (within vicidial_users), then penalty (in queues.conf) as a secondary filter.

Monitoring and tuning rank distribution

Log in to the ViciDial admin panel. Go to Reports > Agent Performance.

Filter by Inbound Group: SALES. Check the Calls Handled column for each agent over the last hour.

Expected distribution for equal-rank agents: roughly equal call counts. If one agent has 20 calls and another has 5, check:

  1. Agent availability: Is the low-call agent paused or offline?
  2. Rank: Did you apply the correct rank to all agents?
  3. Queue strategy: Is it set to leastrecent or rrmemory?
  4. Agent skill: Do some agents handle calls longer, reducing their READY time?

Example query to check agent call volume:

SELECT user_id, COUNT(*) as call_count
FROM vicidial_log
WHERE inbound_group_id = 'SALES' AND call_date > DATE_SUB(NOW(), INTERVAL 1 HOUR)
GROUP BY user_id
ORDER BY call_count DESC;

If agent 1001 has handled 15 calls and agent 1003 has handled 2, and both have equal rank, check agent 1003's inbound_group assignment:

SELECT user_id, inbound_group, user_rank FROM vicidial_users WHERE user_id = '1003';

If inbound_group is NULL or different, they are not in the same queue.

Why does ViciDial skip agents even when they are available?

Agents are skipped if their status in vicidial_agent_log is not READY. Check directly:

mysql -u root -p asterisk -e \
  "SELECT user_id, status FROM vicidial_agent_log WHERE user_id IN ('1001','1002','1003');"

Possible statuses:

If an agent shows DEAD or PAUSED when you expect READY, they were disconnected or paused. In the agent screen at /agc/vicidial.php, the agent can click Ready, or an admin can force the status in the database:

UPDATE vicidial_agent_log
SET status = 'READY'
WHERE user_id = '1003' AND campaign_id = 'SALES';

Restart the agent's Asterisk session to propagate the change:

asterisk -rx "hangup request channel SIP/1003@*"

The agent must log back into the agent screen.

Handling agent rank changes without dropping calls

If you change rank while an agent is logged in, the change takes effect on the next call offered to that agent, not immediately. Existing INCALL agents are not affected.

To change rank safely during business hours:

  1. Check current active calls:
asterisk -rx "queue show SALES"
  1. If few calls are active, update rank:
UPDATE vicidial_users SET user_rank = 5 WHERE user_id = '1003' AND inbound_group = 'SALES';
  1. Reload the queue in Asterisk:
asterisk -rx "module reload app_queue.so"
  1. Verify change took effect:
asterisk -rx "queue show SALES"

If calls drop, it means the extension or agent was in the queue at the moment of reload. This is rare but possible in high-traffic systems. During production, make rank changes at low-traffic windows (nights, weekends) or disable the inbound group temporarily:

Go to Inbound > Manage Inbound Groups, set the group status to Inactive, save. Agents will go PAUSED. Update rank. Re-enable the group.

Troubleshooting rank and load-balancing issues

Calls queue instead of ringing agents

Problem: Calls sit in queue, hold time increases, agents have zero calls.

Cause 1: Agents are offline or in wrong group.

Check:

mysql -u root -p asterisk -e "SELECT user_id, inbound_group, status FROM vicidial_agent_log WHERE campaign_id = 'SALES';"

If no rows return, no agents are logged into SALES. Ensure agents log into the correct group at /agc/vicidial.php.

Cause 2: Queue members are not properly registered.

Check extensions-vicidial.conf for the queue context:

grep -A 5 "\[vicidial\]" /etc/asterisk/extensions-vicidial.conf | head -20

Look for:

exten => _1XXX,1,Dial(SIP/${EXTEN}@default)

If the agent extension is not properly registered in SIP, Dial fails silently. Check SIP peers:

asterisk -rx "sip show peers"

Agent 1001 should appear as SIP/1001 (if registered) or SIP/1001@default (if via trunk). If not listed, they are not registered.

Cause 3: Queue strategy does not match agent load.

If using linear strategy, calls ring agents one at a time, waiting for timeout. With 20 agents and a 15-second timeout, the queue waits 15 seconds per agent before moving to the next. For the 5th agent, the caller waits 75 seconds.

Change strategy to leastrecent or rrmemory in queues.conf.

Calls always route to the same agent

Problem: Agent 1001 handles all calls. Agents 1002, 1003 never ring.

Cause 1: Rank is not set for other agents.

Check:

SELECT user_id, user_rank FROM vicidial_users WHERE inbound_group = 'SALES';

If user_rank is NULL for agents 1002 and 1003, they have no rank and are not in the queue.

Fix: Assign rank to all agents:

UPDATE vicidial_users SET user_rank = 2 WHERE user_id = '1002' AND inbound_group = 'SALES';
UPDATE vicidial_users SET user_rank = 3 WHERE user_id = '1003' AND inbound_group = 'SALES';

Reload the queue:

asterisk -rx "module reload app_queue.so"

Cause 2: Agent 1001 is the only one in the queue.

Check queues.conf:

grep -A 10 "^\[SALES\]" /etc/asterisk/queues.conf

If only one member is listed, manually add others or regenerate the file via the ViciDial admin. Go to Inbound > Manage Inbound Groups, select SALES, and confirm all agents are listed. Save and check queues.conf again.

Cause 3: Other agents have a higher penalty or invalid penalty syntax.

Check:

asterisk -rx "queue show SALES"

If agents show Penalty: 999 or Penalty: infinity, they are effectively disabled. Edit queues.conf and set penalty to a numeric value (0-999):

member => Local/1001@vicidial,penalty=0
member => Local/1002@vicidial,penalty=0
member => Local/1003@vicidial,penalty=0

Agent is rung twice for one call

Problem: Agent 1001 gets two simultaneous rings for a single inbound call.

Cause: Agent is listed twice in queues.conf or the member list was not reloaded.

Check:

grep "1001@vicidial" /etc/asterisk/queues.conf

If the line appears twice, remove the duplicate:

sed -i '0,/member => Local\/1001@vicidial/!b; /member => Local\/1001@vicidial/{x;d;}' /etc/asterisk/queues.conf

(This removes the first occurrence; adjust if you need to remove a different one.)

Reload:

asterisk -rx "module reload app_queue.so"

One agent's calls have long hold times despite low call volume

Problem: Agent 1002 gets calls with 5-minute hold times. Agent 1001 has zero hold time.

Cause 1: Agent 1002 has a higher penalty and is being skipped.

Check:

asterisk -rx "queue show SALES" | grep 1002

If Penalty is high (>10), lower it.

Cause 2: Agent 1001's phone is ringing all calls but agent is not answering.

Check the agent's extension registration and call logs. Is agent 1001 repeatedly rejecting calls or letting them time out?

Look at recent calls for agent 1001:

SELECT call_id, start_time, duration, status FROM vicidial_log WHERE user_id = '1001' ORDER BY start_time DESC LIMIT 10;

If duration is 0 and status is ABANDONED, the call was not answered. Check if agent 1001's phone is registered and online in SIP:

asterisk -rx "sip show peers" | grep 1001

If unreachable or offline, agent will not answer calls. Ask agent to log in via SIP client or check their phone.

Frequently asked questions

Why does my inbound group show zero members after I assign rank to agents?

The queue members are populated from vicidial_users at module reload time. If rank is NULL or inbound_group does not match the queue name, the agent is not added. Check that inbound_group is set to the exact queue name (case-sensitive) and user_rank is a number between 0 and 9999. Then run asterisk -rx "module reload app_queue.so" and verify with queue show [groupname].

Can I assign the same rank to multiple agents?

Yes, but behavior depends on strategy. With ringall, both ring simultaneously. With leastrecent or rrmemory, the system tracks which agent was called least recently or last and picks that one. Assigning the same rank to two agents is equivalent to having no rank separation; use it only if agents have identical skill levels and you want simultaneous ring.

How do I prioritize VIP callers to a specific agent or rank?

ViciDial does not have built-in VIP queue routing by caller ID, but you can create a separate inbound group for VIP calls with a single dedicated agent, then route DID entries to that group. Alternatively, use a dialplan variable to set a higher priority in the queue by checking inbound_cid against a list and using asterisk's priorities or a custom AGI script to inject the call higher in the queue.

What happens if I delete or merge two inbound groups?

If you delete SALES inbound group in the web admin, all agents in SALES lose their inbound_group assignment (it becomes NULL), and calls to DIDs mapped to SALES will fail because the queue no longer exists. To merge SALES and SALES_BACKUP, update all agents in SALES_BACKUP to inbound_group = 'SALES' and delete SALES_BACKUP. Update the DID routing if necessary.

How do rank and penalty interact in Asterisk?

Rank is applied first by ViciDial's dial-plan; it determines which agents are in the queue and in which order they are listed. Penalty is then applied by Asterisk's queue engine to further reorder or deprioritize agents. If rank 1 has 0 penalty and rank 2 has 20 penalty, rank 1 is always called first; rank 2 is called only if rank 1 is not available. Penalty does not override rank; it supplements it.

Summary

Agent rank in ViciDial controls the order in which agents are offered calls within an inbound group. Setting rank correctly requires updating the vicidial_users table (user_rank field), choosing an appropriate queue strategy (leastrecent or rrmemory for most setups), and verifying agent inbound_group assignment matches the queue name. Load balancing is further refined using Asterisk's penalty system in queues.conf to deprioritize overflow agents or skill-based tiers. Testing is done by logging in two or more agents, sending test calls, and monitoring queue and agent status via Asterisk CLI. Common issues include NULL rank, incorrect inbound_group, offline agents, duplicate queue members, and mismatched strategies. Monitor call distribution via the agent performance report or direct SQL queries on vicidial_log to verify even load. Changes to rank take effect on the next call offered after a queue reload; high-traffic systems should make rank changes during off-peak hours or disable the group temporarily.

Stuck on something specific?

Book a free 30-minute call. I run ViciDial centers across 3 countries and can usually unblock your setup in one session — or build it for you.

Book a Free Consultation