Inspiration

Anyone who's waited half a day for a plumber knows the bottleneck isn't the repair — it's the dispatch. When a pipe bursts, a human coordinator has to do five things at once under a ticking SLA: figure out what's actually wrong, find the nearest technician who has the right skills, confirm the parts are on the truck, reschedule conflicts, and reassure a panicking customer — often in their own language.

We wanted to see if an agent could do more than answer questions about this — we wanted one that could reason, plan, and take the actions, while keeping a human firmly in control. Field-service dispatch turned out to be the perfect proving ground: it's messy, time-critical, and every decision depends on search over live operational data — exactly where a plain LLM falls short and Elasticsearch shines.

What it does

FieldForce is an autonomous dispatch console for a home/field-services business. A new emergency arrives — "Maria Flores, burst pipe under the kitchen sink, flooding fast, customer panicking" — and with one click, a crew of agents turns it into a ready-to-approve dispatch plan in seconds:

  • Diagnoses the likely root cause by recalling the most similar past incidents.
  • Geo-ranks technicians by real travel distance and skill match, surfacing the nearest available pro and an ETA.
  • Checks parts inventory and reserves them.
  • Drafts the customer message in their language and a brief for the technician.
  • Proposes four concrete actions — which execute only after a human clicks Approve, then persist to the system of record and a live mission timeline.

The agents propose; the dispatcher commits.

How we built it

FieldForce is a true orchestrator–worker multi-agent system, built with the Google Agent Development Kit (ADK) — Google Cloud Agent Builder's code-first SDK — running Gemini 3 on Vertex AI, and deployed on Cloud Run.

             Coordinator (Gemini 3 orchestrator)
   ┌──────────────┬───────────────┬──────────────┐
   Operations      Diagnostics     Resourcing       Comms
   Analyst (MCP)   (ELSER)         (geo + parts)    (multilingual)
       └────────────── Elasticsearch ───────────────┘
                          │  proposed_actions
                 HUMAN APPROVAL GATE → execute

Elasticsearch is the agents' superpower — the things a chatbot can't do:

  • geo_distance ranking to find the closest available technician, turned into an ETA:

ETA = (d / v) × 60

  • Multi-constraint filtering (skills + availability + service type in one query).

  • semantic_text / ELSER recall of similar past incidents to ground the diagnosis.

  • Aggregations for live SLA-risk triage.

Our partner integration is load-bearing, not decorative: the Operations Analyst agent has no native tools — it does its entire job (live job-load and technician-availability checks) by issuing search calls to Elasticsearch through Elastic's official MCP server (@elastic/mcp-server-elasticsearch), attached to the crew via ADK's McpToolset. The other specialists use typed native Elasticsearch tools.

The frontend is a vanilla-JS dispatch console (Leaflet map + job queue + plan card + mission timeline) served by a FastAPI backend in the same container.

Challenges we ran into

  • ELSER on a trial cluster. Semantic search needs the ELSER model deployed, but it kept hanging on "Deploying" — the trial's ML autoscaling cap was below what ELSER's default allocation wanted. The fix was deploying it with a single allocation at the lowest vCPU tier so it fit the node.

  • The MCP server couldn't reach Elastic — but only from Node. Python connected fine; the Node-based MCP server timed out. The cluster resolved to both an IPv4 and an IPv6 (NAT64) address, and Node's Happy-Eyeballs kept racing to the unreachable IPv6. Passing --dns-result-order=ipv4first --no-network-family-autoselection to the MCP process fixed it.

  • Latency vs. cost. Our first build used Gemini 3 Pro with default "thinking" on — a single dispatch made ~15 sequential model calls and took ~90s. We measured it: thinking tripled per-call latency (4.3s → 1.3s) for no quality gain on this orchestration/retrieval task. Switching to gemini-3.1-flash-lite with thinking disabled cut latency to ~40s and dropped cost ~8× — a big lesson in matching the model to the job rather than reaching for the biggest one.

  • Hallucinated parts. The model occasionally invented a part that wasn't in inventory. We made the backend resolve every part against Elasticsearch and drop anything that doesn't exist — so the plan always reflects real stock.

What we learned

  • Multi-agent design has to be justified. Each specialist earns its place by owning a genuinely distinct job; the win is clean separation plus a human-approval gate, not agents for their own sake.

  • An MCP integration is most convincing when it's load-bearing — when an agent literally can't function without it.

  • "Keep the human in control" is a design pattern, not a slogan: separating propose from execute made the whole system safer and more demoable.

  • Picking the right model tier (and turning off thinking) is one of the highest-leverage decisions for both UX and budget.

What's next

Real integrations (Twilio SMS, calendar booking), a Scheduling agent that auto-reshuffles lower-priority jobs to free a technician for an emergency, and an adversarial "Critic" agent that stress-tests each plan against SLA, parts, and skill constraints before it reaches the dispatcher.

Built With

Share this project:

Updates