Inspiration

A just-in-time automaker keeps no spare parts, so one late battery shipment can stall a plant, push back hundreds of builds and leave dealers and customers waiting without an explanation. In practice someone reads an email that says "the Huntsville line is waiting on battery packs…" and then spends hours across spreadsheets working out which orders, dealers and owners are affected.

We wanted to see if a team of AI agents could do that work: read the messy message, find exactly who is affected, take the right corrective actions and show the reasoning for each one. We also wanted it to run on a single laptop with no cloud services.

What it does

The Wacky Wheels Control Tower reads a stream of operational events and handles each one from start to finish:

  • Supplier delays: affected builds move to the earliest open slot, or transfer to another plant if the home plant is full.
  • Plant outages: capacity-aware rescheduling and transfers for every order in the outage window.
  • Quality holds: the defective lot is held, unsold vehicles are stopped from sale and owners of sold vehicles are contacted.
  • Noise gets no action. Unconfirmed rumors get escalated to a human and change nothing else.
  • Compound messages are split into separate incidents that share one slot board and one loaner pool, so nothing is booked twice.

Each dealer gets one notification that lists only their affected orders. Eligible customers get loaner cars in priority order. Every event comes with a graph path and reasoning trace shown in a control-tower UI: event feed, graph view, action log and audit trail.

Result: 15 events, 909 tool calls, 0 failures.

How we built it

A message passes through six stages:

  1. The wire: one raw message, which may contain two incidents, a rumor or nothing.
  2. The LLM reads it: Llama 3.2 3B, running locally through Ollama, proposes the incident type, the entities and the date phrases.
  3. The graph binds it: aliases, the working calendar and the ontology resolve the proposal to exactly one shipment, plant or lot.
  4. Coordinator: dispatches the specialists, ignores noise or escalates.
  5. Specialists: walk the ontology and return a plan, not a tool call.
  6. The gateway: checks every argument, and only then are the enterprise tools called.
Agent Responsibility
Coordinator Splits messages, routes incidents and is the only component allowed to call tools
Supply Chain Planner Finds the earliest feasible slot and transfers only when the home plant is full
Quality Sentinel Holds lots, stops sales and contacts owners
Dealer & Customer Desk Sends dealer notices and assigns loaners by priority

The knowledge graph

We modeled the company as an ontology with 7,000 customers, 5,348 vehicles, 3,263 customer orders, 540 production orders, 364 shipments and lots, 50 dealers and 6 plants, plus 158 aliases for nicknames. Each incident type follows its own path:

  • Supplier delay: Shipment → Part → Model (BOM) → ProductionOrder → CustomerOrder → Dealer / Customer
  • Plant outage: Plant → ProductionOrder → CustomerOrder → Dealer / Customer
  • Quality hold: Lot → ProductionOrder → Vehicle → Dealer / Customer

Scheduling and priority

For an affected order at plant p, the planner picks the earliest working day with free capacity:

$$ d^* = \min {\, d \ge d_0 \;:\; \text{load}(p, d) < \text{cap}(p, d) \,} $$

where $d_0$ is the new shipment ETA or the morning after the outage ends. If no such day exists at $p$, the order transfers to the earliest open day at another plant that can build that model.

Loaners are assigned in lexicographic priority order:

$$ \text{priority} = (\text{tier},\ \text{promise date},\ \text{order id}), \quad \text{Platinum} \succ \text{Gold} $$

Assignments are capped by the pool limit, and Standard-tier customers don't receive loaners.

Before touching the live server, run.py replays the whole plan on a private copy of the enterprise system. It checks capacity, notices, loaners, held lots, stopped vehicles, owner contacts, escalations, and that no unaffected order moved.

Challenges we faced

  • Messy human language. Events used nicknames ("the Huntsville line"), relative dates ("next Wednesday") and vague wording. We resolved these with an alias table and a working-day calendar instead of trusting the model's guesses.
  • A small model that sometimes gets it wrong. The 3B model agreed with the ontology reading on 12 of 15 events. On the other three its proposal didn't bind to a real record, so the deterministic reader stayed in control and the actions didn't change. That's why the model proposes and the code decides.
  • Compound events. Two incidents in one message compete for the same production slots and loaner cars. We used one shared slot board and loaner pool per event so the agents can't double-book.
  • Precision. Acting on an unaffected order is as bad as missing an affected one. Every argument passes through a validation gateway, and a rejected call is logged but never retried blindly.

What we learned

  • LLMs are best at understanding, and code is best at deciding. The model never picks an order, a VIN or a slot. Policy, capacity and every tool argument are deterministic, which makes the system reliable and explainable.
  • The knowledge graph does the heavy lifting. Once the ontology was right, finding everything an event affects became a graph traversal.
  • Guardrails are a feature. Doing nothing on noise and escalating rumors to a human mattered as much as the corrective actions.
  • A small local model is enough when it sits inside a well-designed system.

What's next

  • A larger local model to resolve the three events the 3B model missed, still without letting it choose slots.
  • Live event streaming instead of batch replay.
  • Richer human-in-the-loop approvals inside the control-tower UI.

Built With

Share this project:

Updates

Submission history