Building enterprise AI customer support

This is the first of eight posts where I build a complete customer support agent — a database, company policies, guardrails and tests, the parts a demo usually skips. I build it for a fictional UK transport company, UKConnect, which means every piece is inspectable and also that nothing here has faced a real customer.

This part is about the architecture: how the pieces fit, and why I didn't build it as one big agent.

Why bother — what's wrong with normal support

Start with the problem. Phone-and-email support has the same issues almost everywhere:

  • Long wait times (average 8 minutes)
  • Inconsistent service quality across different agents
  • High operational costs (£40K+ per agent annually)
  • Limited availability (business hours only)
  • Scaling difficulties (linear cost increase)
Comparison of the failure modes of phone-and-email support (long waits, inconsistent quality, cost per agent, business hours only, cost scaling with volume) against the properties a multi-agent AI design aims for (fast consistent replies, logged conversations, round-the-clock availability)

How I built it: one coordinator, two specialists

Here's the shape. One coordinator, two specialists. Each agent does one job well, and the coordinator decides who handles what.

Multi-agent architecture: a customer query reaches the Master Agent (orchestrator), which routes to a Policy Agent (RAG over a vector database) or a Ticket Agent (SQLite database and tools), then synthesises the reply

The three agents

The Master Agent — the coordinator

The Master Agent reads the question and decides what to do with it. It doesn't answer policy questions or touch the database itself — its job is to analyse the query, route it to the right specialist, and turn the result into a reply. It's a Gemini model with careful prompting, nothing more.

The Policy Agent — answers from documents

The Policy Agent handles anything that lives in the company's documentation: refund rules, terms and conditions, fares. It retrieves the relevant policy text first, then answers from that — grounded in what the company actually says, not guessed. Under the hood that's a vector database and RAG, which I cover properly in part 3.

The Ticket Agent — does the work

The Ticket Agent does the actual work on bookings — search, book, change, cancel — by calling tools against the database. This is the agent that changes things, so it's the one I keep on the tightest leash.

Three things this design gets right

Splitting the work this way isn't decoration. It buys three things.

Routing. The Master Agent reads intent and sends each request to the specialist that can actually handle it, so the customer never has to know there's more than one agent behind the reply.

Context. The customer's state and the conversation history are kept across every hand-off, so a multi-step request — cancel this, then tell me the refund — doesn't fall apart halfway through.

Focus. Each agent has one job. That makes it simpler to build, easier to test, and more accurate than one big agent trying to do everything at once.

What it actually buys you

An honesty note first: this is a project I built for a fictional company, so I can't put a real cost figure on it. I haven't run it for an actual support desk and measured the savings, and anyone who quotes you a tidy "60% cost reduction" off a demo is guessing. What I can talk about is what the architecture genuinely gives you.

The answers are consistent — the same question gets the same answer every time, which a room full of people can't promise. There are no wait times, it handles as many conversations at once as arrive, and it's available around the clock. And every interaction is logged, which a phone line never gives you, so you can see what customers actually ask and where the system struggles. Those are properties of the design, not numbers I made up.

The stack

Nothing exotic. The intelligence is Google's Gemini, with a vector database and embeddings for semantic search over the policy documents. The backend is Python, with SQLite for the operational data here (you'd want PostgreSQL if this ever ran for real) and FastAPI for the API. The agents are wired together with Google's ADK; data wrangling is pandas and NumPy, and the early vector search uses scikit-learn.

If you're weighing frameworks, I later rebuilt the whole thing on LangGraph — the ADK vs LangGraph comparison walks through what changed and why.

What it can actually do

In practice it handles four kinds of request: policy questions (refunds, terms, fares), booking operations (search, book, modify, cancel), general service (accounts, complaints, problems), and mixed requests that need a policy lookup and a database change in one go — those are the ones a single-purpose bot usually fails.

It also adapts how it talks: formal and detailed for a corporate client, casual for a younger audience, short for someone reading on a phone. Same facts, different register.

What a request actually looks like

Two examples make the routing concrete. A simple one stays with a single specialist:

A simple policy question

Customer: "What's your refund policy?"

Master Agent: [Analyzes] → Identifies as policy-related query

Policy Agent: [RAG Search] → Retrieves relevant refund policy sections

Master Agent: [Synthesizes] → Delivers formatted, personalized response

A harder one needs both specialists, in order — the database does the cancellation, the Policy Agent works out the fee, and the Master Agent stitches it into one answer:

A booking change that needs both agents

Customer: "Cancel my booking UKC005 and tell me the refund amount"

Master Agent: [Analyzes] → Identifies booking + policy requirements

Ticket Agent: [Database] → Cancels booking, calculates base refund

Policy Agent: [RAG] → Applies refund rules and applicable fees

Master Agent: [Synthesizes] → "Booking cancelled, £67.50 refunded"

Why this design would hold up

Three reasons, all of them practical rather than theoretical. Because the agents are separate and stateless, I can scale the busy ones independently and run them across several servers without sharing memory. Because each agent owns one concern, I can update or fix one without taking the others down, and I can test each in isolation. And because adding a capability means adding an agent — not rewriting the existing ones — the system grows without a redesign. Moving it to a new industry is mostly new prompts and new tools, not new architecture.

How fast and how accurate

These are from my own testing on the project's scenarios, not a production deployment. Simple questions come back in under a second; multi-step operations that touch the database take one to three seconds, and the heaviest multi-agent flows two to five. Routing lands on the right agent the large majority of the time, and policy answers come back grounded in the right document. I'd trust it — but I'd measure it properly against real traffic before claiming anything firmer.

It's not just for transport

I built this for a transport company — rail and bus bookings, fares, refund rules. But the shape is industry-agnostic. Swap the policies and the tools and it's a healthcare desk doing appointments, patient queries and insurance; or e-commerce doing orders, returns and shipping; or hospitality doing reservations and guest services. The coordinator-plus-specialists pattern doesn't change. Only the documents and the database behind it do.

Next in the series

Next I build the data layer the agents run on: the database schema, the hybrid storage, and the RAG setup with the vector database and embeddings. That's where the Policy Agent gets something real to retrieve from.

That's the foundation. Everything in the rest of the series sits on top of it. The point of splitting the work across specialist agents isn't elegance — it's that each piece stays small enough to build, test, and trust on its own.


This is the kind of system I build at twentytwotensors. If you want one for your business, get in touch.