Load-balanced assignment
- Introduction
- Approach 1: Round-robin
- Approach 2: Least-loaded
- Choosing an approach
- Worker selection examples
- Using the result
- Next steps
Introduction
Load-balanced assignment distributes work fairly across team members. This guide covers two approaches:
- Round-robin - Cycles through workers in order using a persistent counter
- Least-loaded - Assigns to the worker with the fewest open tickets
Choose the approach that best fits your workflow based on whether your worker list is fixed or changes dynamically.
Approach 1: Round-robin
Round-robin assignment cycles through a list of workers in order. Each new assignment goes to the next worker in the rotation.
This approach works best when:
- The worker list is fixed (e.g. all members of a group)
- You want strict rotation order
- Workers are expected to handle similar volumes
How it works
- Query workers - Get a list of worker IDs from a fixed pool
- Increment the counter - Use a metric to maintain a persistent counter
- Cycle through workers - Pass the counter to cycle() to select the next worker
Create the metric
First, create a custom metric to track assignment invocations.
Navigate to Search » Metrics » (+).
Configure the metric:
| Field | Value |
|---|---|
| Name | example.roundRobin.counter |
| Type | counter |
| Description | Round-robin assignment counter |
Click Save Changes.
The automation
start:
# Get the list of workers from a fixed pool
record.search:
output: workers
inputs:
record_type: worker
record_query: isDisabled:no group:(name:"Support") sort:id
# Extract worker IDs into an array
set/init:
worker_ids@csv: {{workers|column('id')|join(',')}}
# Increment the counter atomically (realtime)
metric.increment:
inputs:
metric_name: example.roundRobin.counter
values: 1
is_realtime@bool: yes
output: metric_result
# Query the current counter value
data.query:
output: counter_data
inputs:
query@text:
type:metrics.timeseries
period:day
range:today
series.total:(
metric:example.roundRobin.counter
function:sum
)
format:dictionaries
set:
# Get the total count from the metric
counter@int: {{counter_data.data|first.value|default(0)}}
return:
# Select the next worker using cycle
assigned_worker_id@int: {{cycle(worker_ids, counter)}}Policy
commands:
record.search:
deny/type@bool: {{inputs.record_type is not record type ('worker')}}
allow@bool: yes
metric.increment:
allow@bool: yes
data.query:
allow@bool: yesApproach 2: Least-loaded
Least-loaded assignment gives work to the worker with the fewest currently open tickets.
This approach works best when:
- Worker availability changes dynamically (on/off shift, busy, etc.)
- You want to balance current workload rather than historical assignments
- Some workers may handle tickets faster than others
How it works
- Query available workers - Get workers who are currently available
- Count open tickets - Use worklist.subtotals to count each worker's open tickets
- Select least loaded - Assign to the worker with the minimum count
The automation
start:
# Get available workers
record.search:
output: workers
inputs:
record_type: worker
record_query: isDisabled:no isAvailable:yes group:(name:"Support") sort:id
on_success:
outcome/empty:
if@bool: {{0 == workers|length}}
then:
return:
# Count open tickets per available worker
data.query:
output: assignment_counts
inputs:
query@text:
type:worklist.subtotals
of:ticket
by:owner~1000
query:(status:o owner.id:[{{workers|column('id')|join(',')}}])
format:dictionaries
# Build a sorted dictionary of workers with their open ticket counts
set/loads:
worker_loads@json:
{% set counts = array_combine(
assignment_counts.data|column('owner_id'),
assignment_counts.data|column('count')) %}
{{
workers|map((worker) => 0+counts[worker.id] ?: 0)
|sort
|json_encode
}}
return:
assigned_worker_id@int: {{worker_loads|keys|first}}Policy
commands:
record.search:
deny/type@bool: {{inputs.record_type is not record type ('worker')}}
allow@bool: yes
data.query:
allow@bool: yesChoosing an approach
| Approach | Best for |
|---|---|
| Round-robin | Fixed worker lists, strict rotation order, equal distribution over time |
| Least-loaded | Dynamic availability, balanced current workload, varying worker capacity |
You can also combine approaches. For example, use round-robin for the initial assignment, then reassign based on least-loaded when a worker goes off shift.
Worker selection examples
You can customize the worker query based on your requirements:
Workers active in the past 15 minutes
Assign work only to workers who have been active recently:
record.search:
output: workers
inputs:
record_type: worker
record_query: isDisabled:no lastActivity:"-15 mins" sort:idWorkers available per calendar
Assign work only to workers whose calendar shows them as available:
record.search:
output: workers
inputs:
record_type: worker
record_query: isDisabled:no isAvailable:yes sort:idWorkers in a specific group
Assign work only to members of a specific group:
record.search:
output: workers
inputs:
record_type: worker
record_query: isDisabled:no group:(name:"Support") sort:idCombining criteria
You can combine multiple criteria:
record.search:
output: workers
inputs:
record_type: worker
record_query: isDisabled:no isAvailable:yes group:(name:"Support") lastActivity:"-30 mins" sort:idUsing the result
After getting the assigned worker ID, you can use it to update a record:
record.update:
inputs:
record_type: ticket
record_id: {{ticket_id}}
fields:
owner_id: {{assigned_worker_id}}Next steps
- Create separate metrics or queries for different assignment pools (e.g. Support, Sales)
- Add error handling for when no workers are available
- Combine with automation timers for scheduled assignment
- Use mail routing to trigger assignment on new tickets