Voyager Lockton Deliverable Prompts HackKU 2026: https://youtu.be/T8WdTHjkbW8 Voyager Demo Video: https://youtu.be/sjrUUUdxNwo
Inspiration
We're Carlos, Gage, and Om. All three of us have almost missed a flight.
Carlos nearly missed a Tokyo to Chicago leg and sprinted through Narita wondering if he'll be able to check in to his flight. Om almost missed a flight to India for a cousin's wedding, the kind of miss where the consequences aren't expense lines, they're family. Gage almost missed an early morning flight where the airport itself had barely opened, nothing to buy, nobody at the counter, nothing to do but watch the clock.
In none of those three moments did any of us actually know, with confidence, what our travel benefits covered. We had a vague sense of "there's probably something" and a very sharp sense of "this is on me right now." That gap, between travel policy existing on paper and a traveler being able to use it in the thirty seconds that matter is the thing Voyager is aimed at.
So when we read the Lockton Track brief of building a Copilot that supports a business traveler through Planning → Booking → Approval → Travel → Issues → Return, we already knew the shape of the problem. It's not that enterprise travel tools don't work. It's that they were designed for the finance team, so the traveler tabs over to Google Flights anyway. The Lockton track guidelines said "Kelli isn't a travel expert, she just wants the trip to go smoothly." We took that literally, because we are Kelli at the gate with a boarding group that already started.
Voyager is what we built in thirty six hours for the Kelli in all of us.
What it does
Voyager is a web dashboard plus a companion iOS case study (app.html). Five tabs on the web: Home, Calendar, Booking, Expenses and a floating Copilot dock that lives on every one of them. The Copilot is an on-device LLM running Qwen3.5 7B via Ollama. The booking flow hits real Duffel. The approval loop is a real file on disk. The whole product is designed around one conviction: Kelli has forty seconds, not forty minutes.
Here's how Voyager covers the six stages in the Lockton brief.
A · Before the trip — "Help me prepare"
The Booking tab is the hero surface. Kelli types a route (SFO → LHR), picks dates, and sees real Duffel fares laid out with tradeoffs in plain English — refundability, layover math, and a copper pill under each offer that reads in policy · §4.2b · Lockton or out of policy · $312 over cap. The policy check is real: lib/policy.js parses policies/flights.txt (a plain-text corporate travel policy) and runs checkFare(offer, trip) → { ok, violations } against every fare.
Next to the fare list, Kelli gets:
- A 3D Mapbox arc drawn between origin and destination via Turf, so the trip feels concrete before anything is booked.
- Live airport intelligence — TSA wait times from SFO's own portal when we have the key, falling back through LHR, then MyTSA, then an empirical hourly blend we hand-curated. Plus curb-to-gate minutes, lounge eligibility by card and ground transport options.
- Weather (Open-Meteo) rendered as an animated scene per condition for all the days at the destination.
- A traveler checklist that voyager auto-generates based on the current state of your travel package.
B · Approvals — guided, not bureaucratic
If a fare is out of policy, the Book button doesn't refuse. It changes into a request approval button. Thus, the state machine branches:
Ready → ExceptionDraft → AwaitingApproval → Booked
The Copilot drafts the approval request for Kelli — pre-fills the reason, the delta over policy, the justification — and posts it to POST /api/approvals. The approval is written to a real file (data/approvals-local.json) so it survives a refresh. Status shows up in plain English: "Waiting on your manager · typical response 4h · you'll get a notification here." Demo mode auto-approves after 10s; in real life POST /api/approvals/:id/decide flips the state the instant a manager clicks.
If an approval is denied, the Copilot explains why (which clause, what the gap was) and suggests the fix, usually a specific alternate fare from the same search.
C · During the trip — "I'm on the road"
Once a booking exists, the Home tab rebinds to it. "Your [Really Cool Destination Name Here] trip is in motion." Live flight arc, current weather at destination, terminal and gate as they update. The Copilot dock on every tab can answer situational questions — "my flight is delayed, what are my options," "where's the lounge," "is the Heathrow Express running tonight" — and calls tools (find_places via Foursquare, web_search via Tavily) when the answer isn't already in trip context.
The iOS case study (app.html) shows the ground experience as ten vertical screens: Home, Plan, Calendar, Booking, Copilot, Country Briefing, Approval, Flight, Rescue, Wrap.
D · Issues — "something went wrong"
The Copilot is explicitly designed to know when to stop. If Kelli types "I missed my connection in Frankfurt and my phone is at 4%", the Copilot doesn't write an essay. It returns a three-line summary of her situation, two concrete next steps, and — in copper, at the bottom — "if this isn't enough, call the travel desk: +1-913-…" with a tel: link. Escalation is a first-class primitive, not a fallback.
E · After the trip — "close the loop"
The Expenses tab automatically injects a row the moment a booking confirms, via the window.VoyagerBookings bus that every page subscribes to. The Copilot prompts for receipt capture, closes the approval item, and summarizes the trip in one editorial card ready to drag into Slack. The Calendar view's copper overlay of the booking disappears into history the moment it's past.
Privacy & safety (core to our design)
What the Copilot uses. The current trip context (origin, destination, dates, fare under consideration) — passed to a local LLM on Kelli's own machine via Ollama. Never sent to a third-party model provider. Tool-call outputs only when the model explicitly asks for them, proxied through our local server so Foursquare and Tavily see only the query — never her name or itinerary.
What the Copilot does not store. No conversation history leaves the browser; chat state lives in React state and reloading clears it. No travel history is persisted to a cloud service we own — the VoyagerBookings bus is pure localStorage, per-device. No Copilot telemetry is phoned home; the "28 tok/s" under every answer comes from Ollama's local /api/ps, not from us.
How sensitive data is minimized. The passenger profile stays on the proxy, not in the browser, and is only attached to Duffel requests at the moment of booking. API keys (Duffel, SFO, LHR, Foursquare, Tavily) live in .env.local on the proxy; the browser never sees them. Out-of-policy approvals are file-backed locally — no shared database means no cross-traveler leakage by default.
Basic safeguards include
- Encouraging users not to input sensitive personal or financial data.
- Structuring prompts to focus on travel logistics rather than personal identity.
- Limiting stored fields in Firebase to non-sensitive attributes.
- Hiding API keys in .env file for Ollama model, map interface, Tavily (web search), and FourSquare (geospatial data)
- Copilot requesting permission to add to calendar.
Enterprise boundaries. The policy file is a plain-text artifact (policies/flights.txt) that Lockton or any other broker could drop in without a code change. The parser is regex today and swappable for an on-device classifier tomorrow. Policy never leaves the customer's environment.
Firebase Storage (Current State) Used for lightweight storage of user and trip data. The data is stored in a centralized cloud database. The only data that is accessed from this database are trips, expenses, bookings, and user ID. Access is currently controlled at a basic level (hackathon prototype stage).
Future Security Improvements To make this production-ready, the following enhancements are planned:
- Implement strict Firebase security rules (user-scoped access)
- Add password authentication (e.g., OAuth / SSO integration)
- Add audit logging and anomaly detection
- Implement role-based access for approvals and enterprise workflows
Sample prompts for the Copilot
- What do I need for my trip to London next week?
- Find me hotels around London that you think I would like to stay at
- Look book me a meeting with Mark for X:XX time after I land in London.
- Because of my interest in botany, find me events to do with plants around London. Add the one closest to my hotel to my calendar.
- My flight just got delayed — what should I do?
- Based on the weather in London, is my flight likely to get delayed?
- I missed my connection and the next flight is tomorrow — what are my options?
- My flight just delayed from X:XX to Y:YY. Reflect these changes on my calendar.
- I’m back from my trip; summarize my business meetings that I attended and activities I did.
How we built it
Three processes, zero build step. No webpack, no Vite, no watcher.
server.py— a Python static server that setsCache-Control: no-store. React 18 and Babel Standalone load from CDN; JSX compiles in the browser. Edit any.jsxor CSS, refresh, done.server.js— an Express proxy on:3001that hides the Duffel, SFO, and LHR tokens and composes real trips from Nominatim plus a hand-rolledairports.json. Every endpoint has a JSON fixture fallback, so with zero tokens the app still renders convincingly.ollama server— the on-device LLM runtime.lib/llm.jsis a thin streamingfetchwrapper around/api/chat. That's the whole integration.
Three data planes share state without coordinating:
- Firestore for realtime calendar events and expenses (shared across devices).
- A localStorage booking bus (
window.VoyagerBookings) that Home, Calendar, and Expenses all subscribe to so a real booking propagates everywhere without a shared backend. - File-backed approvals in
data/approvals-local.jsonthat the proxy writes on the first out-of-policy fare.
The window.VoyagerUI contract
Every Booking-screen component (FlightList, Map3D, BookFlightButton, CountryBrief, …) is registered on window.VoyagerUI with a frozen prop shape defined in lib/ui-contracts.js. When the Copilot decides to render a flight card, it mounts the same FlightList the Booking screen uses — same pixels, same behavior. That single decision is what makes the agent feel like part of the product instead of a chat wrapper grafted on top.
Challenges we ran into
- Script load order in
index.htmlis load-bearing. Everylib/*.jsattaches to awindow.Voyager*singleton that laterpages/*.jsxread. One out-of-order<script>tag, and the Copilot silently fails. No bundler means no bundler to save us — we had to learn to treat the<head>like a dependency graph. - Real airport APIs are inconsistent. SFO's wait-time portal has paused registration (they reply by email, eventually). LHR's Heathrow Developer portal needs an Azure subscription key. MyTSA has no CORS. The proxy ended up with a four-tier waterfall (
sfo-direct → lhr-direct → mytsa → empirical) just to render one trustworthy integer. The waterfall caught two wrong terminal numbers during our own QA passes. - Editorial typography at 60fps. Fraunces 72pt italic is not cheap to animate. Arc draws, stamp landings, and flight-card morphs all had to stay on the compositor; no layout thrash. We spent more time than we expected making the motion feel calm instead of cheap.
- Policy parsing is deceptively hard. Corporate travel policies are written for lawyers, not regexes. Our parser handles Lockton §4.2b cleanly, but the moment we threw a second fake policy at it, it fell over. We documented the gap and scoped the v2 (a small on-device LLM classifier) into "what's next."
Accomplishments that we're proud of
- The Copilot is actually local. Not "we proxy to OpenAI and call it private." Qwen2.5 7B runs on Kelli's laptop via Ollama, streams tokens over
/api/chat, calls tools through our proxy, and displays its own GPU/CPU split live. For the Lockton "Privacy by Design" criterion, it's not a slide — it's the architecture. - The Book button actually books. It's a real state machine that talks to Duffel's sandbox, returns a real PNR, generates an ICS calendar file with zero dependencies (
lib/ics.js), and persists to a bus that Home, Calendar, and Expenses all read. - The approval loop is end-to-end. Draft → submit → await → decide → book. File-backed so it survives a refresh; auto-approves after 10s for demo continuity;
POST /api/approvals/:id/decideflips it instantly when a human intervenes. - The shared
VoyagerUIcontract. One flight card, one map, one country brief — rendered by the Booking screen and by the Copilot bubble at identical pixels. This is the move we'd repeat on the next project. - A design system we refused to compromise. We committed to Fraunces + IBM Plex Mono, warm ink on cream, copper accent, grain on every surface — and held the line. Voyager looks like itself.
- Fixture-first APIs. Every Duffel and airport endpoint has a fixture fallback. The Booking screen rendered convincingly in the first hour, before any real keys existed. This is the reason we had time to polish.
What we learned
- On-device AI is a feature, not a compromise. Qwen2.5 7B at Q4_K_M runs at 20–80 tok/s on commodity laptops and handles tool calls reliably enough to ship. For corporate travel, where the trip is the sensitive data, local inference is a real argument — not a demo-day line.
- A shared component contract is what makes an agent feel native. When the Copilot shows a flight card, it's the same flight card the Booking screen uses. Same pixels. That's the whole trick.
- Policy is a parser problem, not a product problem. Keeping
checkFare(offer, trip) → { ok, violations }stable means the brain can get smarter without the UI moving. Regex today. Classifier tomorrow. Same surface. - "Silence is the default" is a design principle you have to enforce in code. Every pixel earns its place. Chips before cards. Glyphs before chips. If a surface can be a color, make it a color. We cut a lot of well-intentioned UI this weekend and the product got better each time.
- Three people, one
windownamespace, surprisingly few merge conflicts. TheVoyagerUI+ singletons pattern turned into a coordination layer for us, not just for the agent. Each of us owned our own registered component and almost never touched each other's files.
What's next for Voyager
- Wire the existing
ToolCall/MiniMap/CardBonusSurfacesurfaces into Ollama's function-calling so the Copilot can book the flight end-to-end, not just describe it. - Replace the regex policy parser with a small on-device LLM classifier. Same signature, smarter brain.
- Ship the iOS case study (
app.html) as a real SwiftUI app using the sameVoyagerUIcontract, translated one-to-one. - A manager-side approval view that doesn't revert to a grey table the moment you cross a permission boundary.
- Add password authentication for users (e.g., OAuth / SSO integration).
- More robust integration of company data into agent without losing the ability to find content outside of the company data.
- Real corporate-pilot customer. Lockton, if you're reading — we'd love to hear which clause of §4.2b we got wrong.
In case we don't see you again, good afternoon, good evening, and good night. Thanks, HackKU.
— Carlos, Gage, Om
Built With
- babel
- css
- duffel
- express.js
- firebase
- firestore
- foursquare
- fraunces
- heathrow-api
- html
- javascript
- jsx
- localstorage
- mapbox
- mapbox-gl
- mytsa
- node.js
- nominatim
- ollama
- open-meteo
- python
- qwen2.5
- react
- sfo-api
- tavily
- turf.js
Log in or sign up for Devpost to join the conversation.