Register two separate transit callbacks on the RoutingModel: one returning arc distance in meters and one returning arc duration in seconds
Set the primary cost evaluator to the distance callback via SetArcCostEvaluatorOfAllVehicles so the solver minimizes total distance
Add a duration dimension using AddDimensionWithVehicleCapacity, passing a per-vehicle list of maximum route durations in seconds as the capacity argument
To apply a soft maximum, call duration_dimension.SetSpanCostCoefficientForAllVehicles with a penalty coefficient instead of a hard cap; this converts overtime into a penalty added to the objective
Add a global span cost via routing.AddConstantDimensionWithSlack if balancing workload across vehicles is also desired, then set its cost coefficient lower than the duration penalty
Solve, then post-process the solution to compute actual route duration for each vehicle and flag any that exceed the hard operational limit even if they passed the soft penalty
Known gotchas
Mixing a hard capacity on one dimension with a soft penalty on a second dimension that shares the same transit callback can cause the solver to double-count arc costs; always use separate registered callbacks for each dimension
SetSpanCostCoefficientForAllVehicles penalizes the difference between earliest and latest CumulVar across the route, not the sum of all arc durations; for routes starting at different depots this may undercount actual driven time
The solver objective is integer-valued; if distance is in meters and penalty coefficient is set too low relative to meter-scale distances, the penalty will be effectively zero and overtime constraints will be ignored
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp