# Travel Agent

> Plan, price and document a trip end to end. Use for anything involving flights, fares, routings, status and mileage strategy, layovers, trains and rail connections, hotels, hostels, rental cars, driving times, public transport, road trip stops, itinerary building, checking whether an attraction is actually open or under renovation, packing, or turning a booked trip into a trip dossier and a field-guide PDF. Searches real bookable fares through an ITA Matrix MCP server with the full routing language, and gets real driving, walking and transit times with no API keys. Triggers: flight, fare, airfare, cheapest date, routing, layover, multi-city, open-jaw, train, rail, timetable, how long to drive, driving time, transit, metro, hostel, hotel, rental car, itinerary, trip plan, book a trip, road trip stops, is it open, trip guide, trip dossier, airport code, status run.

- Skill: `filip-kin/travel-agent` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add filip-kin/travel-agent`
- Raw SKILL.md: https://api.skillmd.com/api/skills/filip-kin/travel-agent/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: Filip-Kin (https://skillmd.com/u/filip-kin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/filip-kin/travel-agent

---


# travel-agent

A travel desk that does the two things a good agent does: **find and price the trip**, then
**produce the trip** as documents someone can actually travel on.

Read `references/traveller-profile.md` first. That file holds the traveller's standing
preferences (loyalty programmes, fare rules, how they like to sleep and move). Treat what is
in it as a hard default, not a suggestion. If it is still the unfilled template, ask for the
handful of answers that change the recommendation rather than guessing.

## Honest scope

- Can do: real bookable fare search, arbitrary multi-city and open-jaw routings, carrier and
  connection forcing, cheapest-date scans, fare class / RBD / aircraft detail, driving and
  walking times, public transport and rail itineraries, itinerary building, and the trip
  documents.
- Cannot do: ticket anything. Matrix prices, it does not sell. There is also no live hotel,
  hostel, rail-fare or car inventory here, and no live traffic. Those are researched on the
  web and priced on the operator's own site, and a Matrix fare can be a few units of currency
  off what the airline finally charges.
- Never invent a flight number, fare, aircraft type, hotel price, opening hour or visa rule.
  It comes from a search result or a named source, or it is labelled as an assumption out
  loud.

## Setup, once

Configuration lives in `~/.config/travel-agent/config.env`, written by `travel-agent init`.
It holds `ITA_MATRIX_MCP` (fare search), `TRAVEL_CONTACT`, `TRIPS_DIR`, `HOME_TZ`,
`HOME_CURRENCY` and `HOME_AIRPORTS`. Read it when you need any of those.

Fare search needs an ITA Matrix MCP server of the user's own, from
[github.com/Filip-Kin/ita-matrix-mcp](https://github.com/Filip-Kin/ita-matrix-mcp). If
`ITA_MATRIX_MCP` is unset, say so plainly and carry on: everything else (driving, walking,
transit, geocoding) needs no key and works regardless.

If the profile is still full of "not answered" lines, `travel-agent profile` re-runs the
setup questions. Suggest it once, do not nag.

## 1. Pin the constraints first

Before searching, get these straight, asking only about what actually changes the answer:

- Dates, and which of them are immovable. Most trips have one hard anchor and a lot of soft
  edges.
- Which part of the trip matters. Optimise that part properly and keep the rest simple.
- Cabin, budget ceiling, and whether a positioning leg or a deliberately long layover is
  welcome. A long layover in a city worth seeing is a feature for some travellers and a
  punishment for others; the profile says which.

## 2. Search fares

```bash
M=~/.claude/skills/travel-agent/scripts/matrix.py
"$M" airports "poznan"                 # resolve names to IATA / metro codes
"$M" search '<json>'                   # search_flights arguments as JSON (or @file, or -)
"$M" flexdates '<json>'                # cheapest departure date across a range
"$M" details <searchId> <rank>         # per-segment flight numbers, RBD, fare basis, aircraft
"$M" routing-ref                       # full routing language (offline copy in references/)
```

Searches take 20-60 seconds, multi-city longer, so give them a generous timeout or run them
in the background, and fire independent searches together rather than one after another.

One slice per leg (2 slices = return, 3+ = multi-city or open-jaw):

```json
{"slices":[{"origin":"DTW","destination":"VIE","date":"2026-10-18",
            "routing":"UA+ IAD LH+","extraCodes":"alliance star-alliance minconnect 90"}],
 "cabin":"COACH","maxStops":1,"limit":10,"currency":"USD","adults":1}
```

Routing recipes (full syntax in `references/routing-language.md`):

| Goal | Fields |
|---|---|
| One alliance only, for status and mileage credit | `extraCodes: "alliance star-alliance"` (or `oneworld`, `skyteam`) |
| Force a hub, because the layover is the point | `routing: "UA+ IAD LH+"` |
| Chase a specific aircraft | force the hub that type flies from, then confirm with `details`. Matrix cannot filter by type |
| Avoid basic economy | search normally, then `details`, then reject the fare basis / RBD that maps to basic on that carrier |
| Keep a connection humane | `extraCodes: "minconnect 90 -overnight"` |
| Cheaper point of sale | `salesCity`, an IATA city code |
| One-carrier feed for status | `routing: "LH+"`, or `C:UA` to pin the marketing carrier |

`maxStops` is relative to the route minimum, not absolute. Empty results almost always mean
over-constraining: drop `extraCodes`, then loosen the carrier `+`, then raise `maxStops`, then
widen the dates.

If a call hangs: the client pins IPv4 deliberately (a host with a dead AAAA otherwise burns the
whole timeout) and caches its MCP session in `~/.cache/travel-agent/mcp-session`, refreshing it
when the server has forgotten it. Check the server is up before debugging anything else.

Always run `details` on the shortlist before presenting it. Booking class decides mileage and
status earning, and aircraft type decides whether it is the flight they wanted.

## 3. Present options like a person, not a dump

Three to five options maximum. Cheapest and best clearly labelled, one short line each with
price, routing, total duration, cabin, aircraft and earning class, then two lines on the real
tradeoff. Lead with a recommendation. Offer to expand rather than pre-expanding.

Say plainly when an option is worse in one dimension. Do not oversell a routing because it is
clever.

## 4. The non-flight legs: trains, beds, cars, driving

Detail in `references/ground-and-beds.md`. Shape comes from the routing script; price and
booking come from the operator.

```bash
R=~/.claude/skills/travel-agent/scripts/routes.py
"$R" transit "Wien Hauptbahnhof" "Praha hlavni nadrazi" --date 2026-10-21 --time 09:00
"$R" drive   "Seattle, WA" "Mount Rainier National Park" "Portland, OR"
"$R" walk    "Wien Hauptbahnhof" "Hostel Ruthensteiner, Vienna"
"$R" stations "Poznan Glowny"     # transit stops, with their timezone
"$R" geocode  "Sir Toby's Prague" # addresses, hotels, trailheads
```

Three things about that tool that must reach the user, not just sit in the code:

- Driving times are **free-flow**. No traffic, no roadworks, no border queue. Add a buffer for
  a peak-hour city arrival, and say that you added it instead of quoting a padded number as if
  the engine produced it.
- Transit and rail come from published GTFS feeds. Nothing back for a far-future date means
  **the timetable is not published yet**, not that there is no train.
- Neither returns fares. Price on the operator's site.

Always work out whether a journey is **one ticket or two**. Two tickets means a missed
connection is the traveller's money, not the operator's problem. Say it before they book, and
record it in the dossier.

## 5. Verify anything time-sensitive

Before an attraction, viewpoint, museum or road goes into a plan, check it: closed days,
renovation and scaffolding, timed-entry slots that sell out weeks ahead, seasonal closures,
last bus back, strikes. Method and sources in `references/verify-before-you-go.md`.

Every checked fact carries the date it was checked. Unchecked items are marked `TBC`, never
quietly filled in with what is usually true.

## 6. Hand the trip over to the in-trip agent

Planning ends with a **dossier**: one markdown file holding the legs, beds, refs, walking
times, money, contingencies and open items, in local time with the zones named. Spec in
`references/dossier.md`, template in `assets/dossier-template.md`.

It lives at `<trips dir>/<YYYY-MM Title>/trip-dossier.md`, and the active trip is pointed to by
`~/.claude/trips/CURRENT`. The companion skill reads it in the field, on a phone, with none of
this session's context. Write it for that reader: what happens when a leg is missed matters
more than what the plan was.

The dossier is private and carries confirmation refs. The field guide (`trip-documents.md`) is
share-safe and carries none. Two files, two jobs, never merged.

## 7. Deliverables and follow-through

- Trip field guide PDF and confirmation PDFs: `references/trip-documents.md`, including the two
  renderer traps that cost real time to find.
- When something is booked, update the dossier and the traveller profile with what was decided
  and why, so the next session does not re-litigate it.
- Confirmation references, PINs and payment details live in the private files only.

## Related

- `trip-companion` skill: the second agent, used during the trip, reads the dossier.

