Building the AI travel agent at Kitt
Four architectural decisions, and the design work each one demanded. An AI agent that booked flights, hotels and cars on production infrastructure, shipped in beta.
- Role
- Co-founder and CPO. Product, design, and frontend engineering
- Company
- Kitt, B2B corporate travel for GCC enterprise clients
- Contributors
- Imad, Prajna, Kunal - Founding team
Kitt's self-serve flows were the product people used daily. This was the bet on where it was heading: an AI agent that booked flights, hotels and cars on the same production stack, in beta, built by a four-person team.
Book a flight, find a hotel, arrange a car, check where a teammate is travelling. One conversational surface over everything the platform could already do.
Asking for flights on the left, the trip being assembled on the right
Why we built it
Before
Translating a sentence into a form
GCC companies booked travel the way they had for a decade: email an agent, wait for a quote, approve over WhatsApp, reconcile invoices by hand at month end. Self-serve fixed most of that, but people still arrived knowing exactly what they wanted in sentence form and had to translate it into fields.
After
Asking, and getting an answer
The agent removed the translation step. It could also answer things a form never could: who from the team is travelling this week, what trips are coming up. A travel platform holds all of that data; the agent made it reachable in one line.
What it could do
Roughly thirty tools across four domains.
| Domain | Tools | Filter surface |
|---|---|---|
| Flights | Search, recommend, compare, look up a fare | ~40 dimensions |
| Hotels | Search, recommend, fetch rooms, compare rooms, book a rate | ~45 keys, widest of the four |
| Cars | Search, recommend, deep-link to booking | Own set, plus an unwired evaluator |
| Trips | List trips, get trip details | None, ran server-side straight to the model |
The architecture, in plain terms
Four decisions shaped everything after them.
- 01
The browser runs the search
A flight search fans out to three suppliers and takes thirty seconds or more, results arriving the whole time. That doesn't fit the tidy ask-and-answer shape a model expects from a tool. So the agent announces the search, and the browser runs it, rendering results as they land while the model keeps talking.
- 02
The model sees the screen
The browser holds the results, so the model never does. Every message carries a small description of what's on screen instead: top flights, airline count, cheapest direct one, price spread. Around 2KB. The model answers from your screen rather than a data dump it might misread.
- 03
Names that survive re-sorting
"Book the second one" breaks the moment you re-sort by price. So every flight, hotel and car gets a handle derived from its bookable identity, checked against what's actually on screen before anything renders. Nothing the model invents can reach the booking path.
- 04
Context expires on a clock
Most chat products trim history by token count. This trims by time, on a ten-minute window, because in travel the data itself goes stale. A fare discussed forty minutes ago may not exist at that price anymore.
The agent decides the search, the browser runs it, the model sees what came back
The layout: a chat you never leave
Fifty flights don't fit in a conversation column. A separate page loses the thread you were pulling on. What shipped: a two-panel shell with a stack of sheets on top of it.
Sheets open over the chat and close back into it
The composer
Small surface, most-used surface.
A rich text editor, not a plain input. Typing @ opens a people picker: it sets who the booking is for and scopes whose travel data the message can read, re-checked server-side. You cannot send at all until a passenger is selected.
Flights, hotels, cars - type @ for travellers
Default composer
New booking
Add-a-flight template with fillable chips
Recording…
Voice recording — waveform loops while listening
Designing a thirty-second wait
A three-provider flight search takes over thirty seconds. A spinner dies at about three. The loading experience had to be a sequence, not a state.
Every state a search moves through, from thinking to results
Flights in the conversation
The recommended strip is what most people saw first, so its job was to be scannable at a glance.
- 01
One card per flight, not per fare
Suppliers return the same departure under different fare families. The card collapses them and leads with the cheapest.
- 02
Layovers spelled out
Direct or not sits under the times, with layover airports and duration shown rather than buried behind a stops count.
- 03
Next-day arrivals called out in red
The detail people skim past and then have to rebook around.
- 04
Policy as a small shield
Loud on none of the cards, present on all of them.
One card per flight, cheapest direct first
The all-flights browser
Every flight we found, with the same filters as the rest of Kitt
Hotels on a map
Hotels broke the card-strip pattern, because the question you're actually asking about a hotel is where is it.
A hotel question answered with a map and cards, not paragraphs
Trips in a split view
Trip questions are the ones a form could never answer, so they got the layout with the most room. Asking about a trip renders a compact card: a calendar tile, the trip name, the traveller's avatar, one button, "View". Pressing it splits the layout rather than opening a modal, because the whole point of asking in chat is that you're mid-thought.
Asking about a teammate's trips, then opening one without leaving the chat
Where the beta stood when we stopped
Being clear about the unfinished edges is part of the story.
- Hotel booking in chat was real, with confirmed, approval-pending, failed and timed-out states. Flight booking was simulated, resolving after a fixed 1.8-second delay, with real bookings routed to the self-serve flow. Car booking deep-linked to the rental page.
- The reference guardrail fails silently for flights only. An unresolvable reference drops the card, but the model's prose can still describe a flight that's not on screen. Hotels and rooms got explicit fallback messages.
- A conditional filter evaluator for cars, 179 lines, complete and tested, was never wired in. The event handler that should call it is a stub with a TODO.
- The clarifying-question flow for ambiguous locations ("Which Springfield?") was built in three of the four places it needed to exist. The wire connecting the last two was never plugged in.
- Neither surface had a mobile layout. Internal-first beta on desktop, responsive work sequenced after the interaction model settled.