Inspiration

Every day, thousands of young people in Monterrey earn their income driving for DiDi, Rappi, and Uber. The app shows a stream of orders, and the courier has only seconds to accept or skip each one. Take the wrong order and you burn gas and time crossing the city for a few pesos; skip too much and you earn nothing. During surge pricing or rain, the math behind that decision changes every minute — but the app never tells the courier the smart move, it just shows the next ping.

Infosys' "The Courier" challenge at HackMTY 2026 put that problem in front of us directly: build an AI agent that plays a full courier shift and earns more than a human would on instinct alone. We were drawn to it because it's not a toy optimization problem — it's the same dynamic routing problem that logistics companies solve at scale, compressed into something we could simulate, test, and demo in a weekend.

What it does

Bright-Courier simulates an entire courier shift in Monterrey and hands the wheel to an AI dispatch agent. As delivery offers, traffic, road closures, and surge pricing stream in, the agent:

  • Decides in real time whether to accept or skip each offer, in milliseconds, weighing pay against detour cost, remaining shift time, and historical demand for that zone.
  • Optimizes the route in the background, batching nearby drop-offs and improving the plan without disrupting the leg the courier is already driving.
  • Reacts safely to the unexpected — when a road closes or traffic spikes, the agent recalculates without throwing away a route the courier has already committed to.
  • Explains every decision in plain language ("Rejected: 15 min detour for $20 in a cold zone"), so a judge — or a real courier — can see exactly why the agent did what it did.

A live dashboard shows the agent's route, earnings, and decision feed side by side with a simple rule-based baseline, so the AI agent's edge is visible and measurable on the exact same simulated shift.

How we built it

The system is split into six modules with a clear boundary between them: an Event Simulator (fixed-seed offer/traffic/surge stream), a State Manager (single in-memory source of truth for the courier's route, earnings, and backpack), a Decision Engine (real-time accept/skip logic), a Global Optimizer (background route re-optimization), a Road Network engine, and a Demo Interface.

  • Backend: FastAPI, chosen specifically for native WebSocket support — no extra libraries or monkey-patching needed to stream live state to the dashboard.
  • Real-time decisions: a cheapest-insertion heuristic estimates the marginal cost of adding an offer to the active route in milliseconds, instead of re-solving the full routing problem on every single ping.
  • Global optimization: Google OR-Tools (VRPTW), run in a background thread and triggered only on a new batch of orders, a natural idle moment, or an emergency road closure.
  • Road network: OSMnx + NetworkX over the real street graph of Monterrey, with on-demand shortest-path queries for travel time and distance, and edge-weight adjustments to simulate closures and traffic.
  • Simulation data: the Solomon VRPTW benchmark and Kaggle food-delivery datasets, to give the offer stream realistic timing and demand patterns.
  • Frontend: a Next.js/React dashboard for the live map, metrics, and explainability feed.

Four of us worked in parallel from day one. Before splitting up, we agreed on shared data contracts (Offer, RoadEvent, RouteStop, CourierState) so each module could be built and unit-tested independently and plugged together at the end without surprises.

Challenges we ran into

  • Real-time speed vs. optimization quality. Our first instinct was to run OR-Tools on every incoming offer. It quickly became clear that a full VRP solve isn't fast enough to decide "in seconds," which is a hard constraint of the challenge itself. We split the problem into a cheap real-time heuristic for individual decisions and a slower, periodic global re-optimization in the background.
  • Two threads, one truth. The real-time decision thread and the background optimizer thread both touch the same courier state. We hit a real risk of the optimizer overwriting the state with a route computed against a backpack that no longer existed, and solved it with optimistic concurrency: a version counter that lets the optimizer's proposal be discarded and retried if the state changed while it was working.
  • Route thrashing. Early on, the optimizer would find a "better" route every few seconds — mathematically correct, but the equivalent of telling a real driver to do a U-turn mid-block. We added a frozen horizon (the leg already in progress is untouchable), controlled triggers (batch/idle/emergency only), and an improvement threshold (a new route only replaces the old one if the gain is real), with a hard override that bypasses all of it the moment a road closure makes the current plan unsafe.
  • Environment friction. OSMnx's nearest_nodes silently assumes OSM-style integer node IDs, which broke our first synthetic test graph until we switched to numeric IDs. Road closures are modeled as infinite-weight edges rather than removed ones, so our tests had to check for inf travel times instead of an exception. None of it was hard once diagnosed, but it cost real debugging time.
  • Coordinating four people building in parallel. More than once, an interface assumption (like whether distances would be a precomputed matrix or an on-demand function) had to be renegotiated mid-build. Agreeing on contracts early avoided the worst of it, but not all of it.

Accomplishments that we're proud of

  • A courier State Manager that is genuinely thread-safe under concurrent access, with a full unit test suite covering the versioning and stale-write-rejection logic.
  • A Road Network engine built and validated against the real street graph of Monterrey — not a toy grid — with both fast synthetic-graph unit tests and integration tests against real coordinates (Macroplaza to Tec de Monterrey).
  • A route-stability design (frozen horizon + controlled triggers + hysteresis) that we designed, argued through, and refined as a team before writing a line of the optimizer's trigger logic — and that directly targets the "Judgment" and "Feasibility" criteria the challenge is scored on.
  • Every decision the agent makes is explainable in plain language by construction, not bolted on afterward.
  • Four people building six interdependent modules in parallel without ever blocking on each other, thanks to contracts we fixed on day one.

What we learned

  • Dynamic, real-time vehicle routing is a genuinely different problem from the static VRP most of us had worked with before — the right architecture is a fast heuristic for now-decisions plus periodic global re-optimization, not one solver trying to do both jobs.
  • Mathematically optimal isn't the same as useful: a plan that changes every few seconds is worse for a real driver than a slightly suboptimal one that holds steady, and that trade-off has a name (route/plan stability) and known techniques (frozen horizon, hysteresis) worth knowing.
  • Concurrency bugs in a two-thread system are easy to miss until you think through the exact interleaving — optimistic versioning turned out to be a small amount of code for a lot of safety.
  • For a hackathon timeline, resisting the urge to over-engineer the infrastructure (we cut Redis, Celery, and a hosted database in favor of a single in-memory process) freed up time for the part that actually gets judged: the agent's behavior.

What's next for Bright-Courier

  • Finish wiring the Decision Engine, Global Optimizer, Event Simulator, and dashboard into the same end-to-end loop the State Manager and Road Network already run in.
  • Add proactive positioning: right now the agent only reacts to offers it receives; the challenge itself calls for a courier that moves toward historically busy zones while idle, which we see as the next real capability gap.
  • Replace the static demand-percentile signal with a lightweight model learned from more delivery data, instead of a fixed historical estimate.
  • Extend the simulation to multiple couriers at once, turning it into a small fleet-dispatch problem rather than a single-agent one.
  • Explore whether the core engine could be adapted into an actual assistive tool for gig couriers, not just a competition demo.
Share this project:

Updates