---
title: "Building a Dispatch Agent: Intelligent Route Planning and Driver Assignment"
description: "Learn how to build an AI dispatch agent that optimizes delivery routes, matches drivers to orders based on constraints like location and capacity, and handles real-time changes to the delivery schedule."
canonical: https://callsphere.ai/blog/building-dispatch-agent-intelligent-route-planning-driver-assignment
category: "Learn Agentic AI"
tags: ["Dispatch", "Route Optimization", "Driver Assignment", "Logistics AI", "Python"]
author: "CallSphere Team"
published: 2026-03-17T00:00:00.000Z
updated: 2026-05-06T20:25:48.180Z
---

# Building a Dispatch Agent: Intelligent Route Planning and Driver Assignment

> Learn how to build an AI dispatch agent that optimizes delivery routes, matches drivers to orders based on constraints like location and capacity, and handles real-time changes to the delivery schedule.

## The Dispatch Optimization Problem

Dispatch is one of the hardest problems in logistics. Given a set of delivery orders with time windows, a fleet of drivers with different locations and capacities, and real-time traffic conditions, a dispatcher must assign orders to drivers and sequence stops to minimize total distance while meeting every delivery window.

Human dispatchers juggle this with experience and intuition, but they struggle as order volume grows. An AI dispatch agent combines route optimization algorithms with conversational tools, letting dispatchers interact naturally while the agent handles the computational heavy lifting.

## Modeling Orders, Drivers, and Routes

Start with data models that capture the dispatch domain:

```mermaid
flowchart LR
    CALLER(["Client"])
    subgraph TEL["Telephony"]
        SIP["Twilio SIP and PSTN"]
    end
    subgraph BRAIN["Salon AI Agent"]
        STT["Streaming STT
Deepgram or Whisper"]
        NLU{"Intent and
Entity Extraction"}
        TOOLS["Tool Calls"]
        TTS["Streaming TTS
ElevenLabs or Rime"]
    end
    subgraph DATA["Live Data Plane"]
        CRM[("CRM and Notes")]
        CAL[("Calendar and
Schedule")]
        KB[("Knowledge Base
and Policies")]
    end
    subgraph OUT["Outcomes"]
        O1(["Appointment booked"])
        O2(["Reschedule completed"])
        O3(["Stylist handoff"])
    end
    CALLER --> SIP --> STT --> NLU
    NLU -->|Lookup| TOOLS
    TOOLS  CRM
    TOOLS  CAL
    TOOLS  KB
    NLU --> TTS --> SIP --> CALLER
    NLU -->|Resolved| O1
    NLU -->|Schedule| O2
    NLU -->|Escalate| O3
    style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
    style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
    style O1 fill:#059669,stroke:#047857,color:#fff
    style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
    style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
```

```python
from dataclasses import dataclass, field
from datetime import datetime, time
from typing import Optional
import math

@dataclass
class DeliveryOrder:
    order_id: str
    pickup_address: str
    delivery_address: str
    pickup_lat: float
    pickup_lng: float
    delivery_lat: float
    delivery_lng: float
    weight_lbs: float
    window_start: time
    window_end: time
    priority: str = "standard"
    status: str = "pending"

@dataclass
class Driver:
    driver_id: str
    name: str
    current_lat: float
    current_lng: float
    vehicle_capacity_lbs: float
    current_load_lbs: float = 0.0
    active_orders: list[str] = field(default_factory=list)
    status: str = "available"
    shift_end: time = time(18, 0)

def haversine_miles(lat1: float, lng1: float, lat2: float, lng2: float) -> float:
    """Calculate distance between two coordinates in miles."""
    R = 3959
    dlat = math.radians(lat2 - lat1)
    dlng = math.radians(lng2 - lng1)
    a = (math.sin(dlat / 2) ** 2 +
         math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
         math.sin(dlng / 2) ** 2)
    return R * 2 * math.asin(math.sqrt(a))
```

## Driver Matching Tool

The matching algorithm scores each driver against an order based on proximity, remaining capacity, and schedule fit:

```python
from agents import function_tool

DRIVERS = [
    Driver("DRV-01", "Alex Rivera", 37.7749, -122.4194, 2000.0, 350.0,
           ["ORD-101"], "active"),
    Driver("DRV-02", "Priya Sharma", 37.3382, -121.8863, 1500.0, 0.0,
           [], "available"),
    Driver("DRV-03", "Carlos Mendez", 37.5485, -122.0574, 3000.0, 1200.0,
           ["ORD-102", "ORD-103"], "active"),
]

ORDERS = [
    DeliveryOrder("ORD-201", "Warehouse A", "123 Market St", 37.5600, -122.0700,
                  37.7850, -122.4100, 250.0, time(10, 0), time(14, 0)),
    DeliveryOrder("ORD-202", "Warehouse B", "456 Mission St", 37.3400, -121.8900,
                  37.7600, -122.4300, 180.0, time(9, 0), time(12, 0), "express"),
    DeliveryOrder("ORD-203", "Warehouse A", "789 Howard St", 37.5600, -122.0700,
                  37.7820, -122.3950, 500.0, time(11, 0), time(16, 0)),
]

@function_tool
def find_best_driver(order_id: str) -> str:
    """Find the best available driver for a delivery order based on proximity and capacity."""
    order = next((o for o in ORDERS if o.order_id == order_id), None)
    if not order:
        return f"Order {order_id} not found."

    candidates = []
    for driver in DRIVERS:
        remaining_capacity = driver.vehicle_capacity_lbs - driver.current_load_lbs
        if remaining_capacity  str:
    """Optimize delivery sequence for a driver using nearest-neighbor routing."""
    driver = next((d for d in DRIVERS if d.driver_id == driver_id), None)
    if not driver:
        return f"Driver {driver_id} not found."

    orders = [o for o in ORDERS if o.order_id in order_ids]
    if not orders:
        return "No valid orders provided."

    # Nearest-neighbor heuristic
    route = []
    current_lat, current_lng = driver.current_lat, driver.current_lng
    remaining = list(orders)
    total_distance = 0.0

    while remaining:
        nearest = min(
            remaining,
            key=lambda o: haversine_miles(
                current_lat, current_lng, o.pickup_lat, o.pickup_lng
            ),
        )
        pickup_dist = haversine_miles(
            current_lat, current_lng, nearest.pickup_lat, nearest.pickup_lng
        )
        delivery_dist = haversine_miles(
            nearest.pickup_lat, nearest.pickup_lng,
            nearest.delivery_lat, nearest.delivery_lng,
        )
        total_distance += pickup_dist + delivery_dist

        route.append(
            f"  {len(route)+1}. Pickup {nearest.order_id} at {nearest.pickup_address} "
            f"({pickup_dist:.1f} mi) -> Deliver to {nearest.delivery_address} "
            f"({delivery_dist:.1f} mi)"
        )
        current_lat, current_lng = nearest.delivery_lat, nearest.delivery_lng
        remaining.remove(nearest)

    lines = [
        f"Optimized route for {driver.name}:",
        *route,
        f"\nTotal distance: {total_distance:.1f} miles",
        f"Estimated time: {total_distance / 25 * 60:.0f} minutes (avg 25 mph city)",
    ]
    return "\n".join(lines)
```

## Order Assignment Tool

```python
@function_tool
def assign_order(order_id: str, driver_id: str) -> str:
    """Assign a delivery order to a specific driver."""
    order = next((o for o in ORDERS if o.order_id == order_id), None)
    driver = next((d for d in DRIVERS if d.driver_id == driver_id), None)

    if not order:
        return f"Order {order_id} not found."
    if not driver:
        return f"Driver {driver_id} not found."

    remaining = driver.vehicle_capacity_lbs - driver.current_load_lbs
    if order.weight_lbs > remaining:
        return (
            f"Cannot assign: {order.weight_lbs} lbs exceeds "
            f"{driver.name}'s remaining capacity of {remaining} lbs."
        )

    driver.current_load_lbs += order.weight_lbs
    driver.active_orders.append(order_id)
    driver.status = "active"
    order.status = "assigned"

    return (
        f"Order {order_id} assigned to {driver.name}. "
        f"New load: {driver.current_load_lbs}/{driver.vehicle_capacity_lbs} lbs. "
        f"Active orders: {len(driver.active_orders)}"
    )
```

## Assembling the Dispatch Agent

```python
from agents import Agent, Runner

dispatch_agent = Agent(
    name="Dispatch Coordinator",
    instructions="""You are an intelligent dispatch assistant. You can:
    1. Find the best driver for each order based on proximity and capacity
    2. Assign orders to drivers
    3. Optimize delivery routes for assigned orders
    Prioritize express orders. Always explain your driver recommendations.""",
    tools=[find_best_driver, assign_order, optimize_route],
)

result = Runner.run_sync(
    dispatch_agent,
    "I have three new orders: ORD-201, ORD-202, and ORD-203. Assign them to the best drivers and optimize routes."
)
print(result.final_output)
```

## FAQ

### Why use nearest-neighbor instead of a more optimal routing algorithm?

Nearest-neighbor is a greedy heuristic that runs in O(n squared) time and produces routes within 20-25 percent of optimal. For real-time dispatch where decisions must be made in seconds, it strikes a good balance. For batch optimization, use Google OR-Tools or the OSRM Trip API which implement more sophisticated algorithms like branch-and-bound or Lin-Kernighan.

### How do I handle real-time order changes (cancellations, additions)?

Build a re-optimization tool that the agent calls whenever the order set changes. The tool takes the driver's current position and remaining orders, re-runs the routing algorithm, and returns an updated sequence. Use webhooks or polling to detect order state changes and trigger re-optimization automatically.

### Can the agent handle multi-stop pickups where one warehouse has multiple orders?

Yes. Group orders by pickup location before routing. The tool should recognize when multiple orders share the same warehouse and batch them into a single pickup stop. This significantly reduces total distance by avoiding redundant trips to the same location.

---

#Dispatch #RouteOptimization #DriverAssignment #LogisticsAI #Python #AgenticAI #LearnAI #AIEngineering

---

Source: https://callsphere.ai/blog/building-dispatch-agent-intelligent-route-planning-driver-assignment
