Trip Planner
SkillDev toolsEnd-to-end international trip planning: turns "I want to go to X for N days" into a verified, bookable plan — route skeleton across cities, flight price scans (international + domestic legs), train-vs-fly decisions, hour-by-hour daily timelines with opening hours, dwell times, holiday collisions and tappable turn-by-turn map links (小时级行程+地图导航+离线KML), hotel shortlists by neighborhood, budget rollup, and a booking checklist with deep links. Use this whenever the user asks to plan a trip, vacation, itinerary or honeymoon, compare flight dates/prices, pick between cities or routes, schedule a travel day hour by hour, fill a spare block of time ("I'm near X with 2 free hours"), turn a finished plan into a designed page (eight themed renders: illustrated / clay / noir / glass / journal / zine / splash / portal — 插画/黏土/夜航/玻璃/手账/Zine/闪屏/穿越版), or asks 旅行规划/行程安排/机票比价/去某国玩N天怎么安排/现在有空档干嘛/把行程做成好看的网页 — even if they only mention one piece (just flights, just hotels, or just navigation), the playbook and verification rules here still apply.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Trip Planner skill
What this skill tells your AI
The instructions your AI receives, as published by skywain/trip-planner-skill in SKILL.md and read by ahel’s review.
Turn a fuzzy trip idea into a plan the user can book link-by-link. The deliverable is verified and bookable, not inspirational: every price and opening time carries a source + as-of date, or an explicit "verify at link" flag. AI travel tools fail on stale data, not on prose — fixing exactly that is this skill's job, so verification IS the work.
Hard rules
- Never book, pay, hold, or enter personal data anywhere. Produce deep links and a checklist; the human books. This is what keeps the skill safe to run autonomously.
- Prices and hours come from tools, never from memory. Model memory is fine for geography and "what's worth seeing"; anything bookable or closable gets checked. A missing price is written "—, check link", never guessed.
- Cheap before expensive: bundled script + keyless APIs first (see references/data-sources.md), browser automation second and only for what scripts can't get (OTA hotel prices, LCC fares, odd venues). Never curl OTA/airline sites — they bot-block instantly; browser pane only. Pace requests like one polite human.
- Search budgets are real: ~25 web searches for your own orchestration work (visa, flights, holidays, hotels, assembly) — separate from, not inclusive of, the ≤8 written into each parallel city subagent's prompt. Unbounded research agents hang and burn money, so the cap goes in the prompt every time. Budget exhausted → ship with the least-verified items flagged rather than digging further.
- Reply in the language the user asked in. Report money in the user's home currency
(infer from origin), stating the FX rate + date used once. FX source:
frankfurter.dev first — but it only carries ~30 major currencies, and closed or
minor ones (MAD / VND / EGP …) are not "unsupported", they are silently dropped
from a 200 response (
symbols=VND,USDcomes back with USD alone). For those usehttps://open.er-api.com/v6/latest/<BASE>and check the returned object has the key you asked for; the plan states which source it used (data-sources.md §FX). - Track the phases as todos (whatever task/todo tool the harness has; none → a short checklist at the top of your working notes) so a long plan survives interruptions and stays visible.
Interaction contract
Three moments at most, usually two: (0) one intake message, only if a core fact is missing and can't be inferred (Phase 0 — most requests need none); (a) after Phase 2 — present 2-3 route skeletons, get a pick; (b) final delivery. Everything else runs without questions. If the user says "一次到位 / don't ask, just plan" or the session is clearly headless, skip (0) and (a): assume, pick the best skeleton yourself and state every assumption prominently at the top of the output.
Quick modes (no full pipeline)
- Gap filler — "I'm near X with 2 free hours": offer 2-3 options within a 15-min radius, one per energy level (a sight / food / a sit-down), each with walk time, a map link, a turn-back deadline, and — the one thing worth a search — confirmation that it is open right now. ≤3 searches; answer in minutes, not a report.
- Single day — "we have one day in Rome, what do we do?": run Phase 1's holiday + festival check, Phase 4 for that one day, and the Phase 6 self-check. Skip route skeletons, flights and hotels entirely; read scheduling.md and navigation.md and leave the rest closed. This is the most common request that is not a whole trip.
- Live replan — "missed the train / it's pouring": rebuild only the affected day
from its degradation tags (
[skippable]/[swap→…]/late_cut line) instead of re-planning the trip.[pinned]blocks hold;[opener]may move but costs a queue; re-verify only the hops that changed.
Phase 0 — Intake (one message, or none)
Read references/phase-0-intake.md now, before you decide whether to ask the user
anything — it is the whole procedure for this phase (what counts as a core fact, how
origin is inferred, the intake message format and its rules, what goes into prefs,
the picture-capability check, the style line and the plan language, the exit
criteria). This section is only the contract; do not compose an intake message from
memory of the format.
Inputs: the user's request and anything said earlier. Outputs: the plan's top-level
prefs block and lang, prefs.pictures (native | key | stock), the assumptions block
for checkpoint (a) — and at most one intake message.
Gates — these decide pass/fail and do not move into the reference file:
- One message, or none. Ask only when a core fact (origin · destination · when / how long · page style) is missing and cannot be inferred; ask for everything in ONE message in the intake format (core first, optional after, each optional line with its default, one "all defaults" line); anything the user already stated is settled and never re-asked; never a follow-up "just one more thing".
- Origin is inferred and stated, not asked — from the conversation language, the locale / timezone or earlier messages, as that city's own international airport — or, when only a language / locale is known, that country's largest international gateway (phase-0-intake.md lists the common hubs; São Paulo is GRU, not GIG) — unless it is genuinely unguessable, which makes it the one core question.
- The picture-capability check runs silently before styles are mentioned; never
ask for a key in chat, never read, print or copy
themes/.auth_header; with no generator the page still ships in a theme on the stock kit (illustrated or clay only; the other six need generated pictures — offer illustrated) — a plain text page is never the deliverable. langfollows the language the user asked in — it drives the page chrome only (--langoverrides); every content string in the plan is written in the user's language too.
Phase 1 — Country brief (once per destination)
Read references/phase-1-brief.md now, before any fact about the destination is
written — it is the whole procedure for this phase (where each fact comes from; the
visa / holiday / event / weather / money / insurance / safety lines; the advisory line,
the emergency card, the health line with the yellow-fever audit, the hazard line; the
exit criteria). This section is only the contract; never answer a Phase 1 fact from
memory of the procedure or of the country. In chat, Phase 1 is ≤ 10 lines — the
brief cards themselves follow output-template.md §Brief templates.
Inputs: destination(s), dates, the traveller's passport and origin, the skeleton
candidates. Outputs: the brief cards in canonical order, the Phase 1 checklist rows
(visa lead time · travel-clinic consult · yellow-fever vaccine + ICVP · insurance ·
hazard gate · copies + registration), and the facts later phases inherit.
Gates — these decide pass/fail and do not move into the reference file:
- Every Phase 1 fact is the assembler's alone — visa / entry, advisory, health, hazard, insurance: city agents never decide them, and anything they say is overwritten.
- Official sources only, never memory — government, embassy and foreign-ministry pages, CDC / TravelHealthPro / WHO, the insurer's schedule — each line stamped source + as-of; nothing found → "n/a — see advisory", not a guess. The plan never doses or prescribes: it writes the travel-clinic consult date and the agenda.
- The advisory level drives the plan: a base, leg or day trip in a "do not travel" / Level 4 / 暂勿前往 area stops the pipeline and asks the user; Level 3 / "avoid all but essential" / 谨慎前往 goes to the user with the line in front of them; regional "avoid" areas are checked base by base, leg by leg.
- Transit counts: the visa audit and the yellow-fever certificate audit both run over every transit airport before "no visa needed" is written anywhere — and the yellow-fever audit starts from the departure country, not the passport.
- A hazard-season hit means a gate: season card, hazard gate on the checklist and
in the
.ics, insurance deadline NOW, exposed bookings kept refundable. - User-named events are verified before anything else is planned.
Phase 2 — Route skeleton → checkpoint (a)
- Longlist cities/areas scored against the user's ranked interests and
prefs.scenery(nature / city / beach / forest / lake / mountain); shortlist by geography — order as a line or loop, never a star with backtracking.prefs.travel_styleshapes the legs: self-drive → a rental leg and park/countryside bases (Phase 3 §Driving legs); group tour → the tour's own schedule is the spine (Phase 4). - Nights allocation: ≥2 nights per base (each 1-night stay burns a half day on packing and transit); prefer "base + day-trips" over hotel-hopping when the day-trip is <90 min each way. 10-15 days ≈ 8-13 usable days ≈ 2-4 bases, and 2-3 beats 4.
- Day-count honesty: landing before 15:00 = half a sightseeing day, later = zero sightseeing days for the count — the evening still gets one free, walkable, unticketed block near the hotel (scheduling.md §Arrival day); departure day = zero unless the flight leaves after 18:00.
- Decide open-jaw now (fly into the first base, out of the last) — on multi-city routes it usually beats round-trip because it refunds a backtracking day. Check both jaw directions in Phase 3; prices are asymmetric.
- Present 2-3 skeletons (e.g. classic / nature-lean / relaxed): city order, nights per base, intercity legs with rough mode + duration, one-line pace verdict. Recommend one.
Phase 3 — Flights & intercity legs
Read references/phase-3-legs.md now, before the first flight scan — it is the
whole procedure for this phase (the plan shape, the international price-source ladder,
multi-airport and LCC arithmetic, the separate-tickets audit, intercity rail vs fly,
driving legs, what every leg row records, the exit criteria). This section is only the
contract; do not price a leg from memory of the procedure.
Inputs: the chosen skeleton (Phase 2), prefs.travel_style, the Phase 1 visa / entry
facts (transit countries included). Outputs: legs[] — one pick + one backup per leg —
the checklist rows for flights, date-locked rail and rentals, their budget rows, and the
baggage walkthrough for multi-leg trips.
Gates — these decide pass/fail and do not move into the reference file:
assets/plan.example.jsonis the single source of truth for the plan's shape — open it before writing a field; a wrong shape does not fail loudly — the renderers WARN and print an empty section.- Every international pick and backup is priced in ≥ 2 sources (flight_scan /
Google + Skyscanner / Kayak / Trip.com / the carrier's site);
legs.notenames them with the as-of date; a > 10 % disagreement prints as a band; no browser pane → Google alone and the note says so. "Price unverified" only when every source fails. - Separate tickets across a foreign hub are a visa trap: the audit runs before "no visa needed" is written anywhere — including tickets the user already holds.
- Rail wins under ~5 h station-to-station; price on the operator's site. A park without a car is decided with the user, never by default.
- Every leg row carries price + currency + as-of, the checked-bag fee, the refund / change class and a deep link; one pick + one backup per leg.
Phase 4 — City day-plans
Read references/phase-4-days.md now, before any city is planned or any city agent
is launched — it is the whole procedure for this phase (the city-agent contract, the
six per-city steps, the route_tools order, the sun / check rules, and the exit
criteria). This section is only the contract; do not plan a city from memory of the
procedure, and build every city-agent prompt from that file's §City-agent contract
(paste its lines, or pass the file's absolute path — the agent never sees SKILL.md).
Inputs: the chosen skeleton (Phase 2), the legs table (Phase 3), the Phase 1 brief
facts and prefs. Outputs: per city, plan-JSON day objects insertable verbatim into
days[] (output-template.md §city-block) — stops, hour-level timeline,
hop_links, sun, rain_alt, ribbon — plus the city's checklist_items.
Gates — these decide pass/fail and do not move into the reference file:
- City agents never make visa / entry / health / advisory / hazard / insurance calls. Those facts are the assembler's Phase 1 job and override anything a city block says; a city agent's prompt carries search budget ≤ 8, an explicit "do not run geocoding" line, the plan language, the §city-block return format, and the visa / entry hard rule as its last line.
- Hour-level timelines are the default deliverable; day-level only when the user asks for a rough cut.
route_tools.py checkexits 0 before rendering — a BROKEN or SUSPICIOUS hop is fixed in the plan, never explained away in prose. A SUSPICIOUS hop has exactly two fixes: a vehicle really runs it (fly / drive / boat / train / bus) → declare thatmodeon the arriving stop and give it itslegs[]row (add the row if the leg has none); nothing runs it (a 250 km hop inside one city is a mis-geocoded stop) → fix the stop, never themode.sun --writeruns before any sunrise / golden-hour / dark-start prose, after the stops carry coordinates; a plan that crosses timezones stamps every day'stzfirst.- Every day has its rain alternative, its food area and its
ribbon; anchors are chosen per interest-fit, ≤ pace + 1 optional per day.
Phase 5 — Hotels
Per base: pick 1-2 neighborhoods with reasons (near the rail hub actually used, safe
after dark, luggage-friendly), in the lodging type and band from prefs.lodging
(default mid-range hotel; a ryokan/onsen or B&B habit changes which properties you list). Browser spot-check Google Hotels/Booking with the real
dates for a price band, then list 2-3 concrete properties: name, area, band per night,
deep link with dates baked in (recipes in data-sources.md). Advise: book refundable
now, re-shop 2-3 weeks out.
Phase 6 — Assemble, self-check, deliver
Read references/phase-6-assemble.md now, before the final plan.geo.json is
written — it is the whole procedure for this phase (assembly order, cover title, the
adversarial self-check list, delivery, the themed-render flow incl. stock mode, and the
exit criteria). This section is only the contract; do not assemble or render from memory
of the procedure.
Inputs: plan.geo.json assembled per references/output-template.md — the single
editable source — plus Phase 0's prefs.theme / prefs.pictures / plan.lang.
Outputs — three things every time, handed over through the harness's artifact / file
tool: (1) the chat summary — route one-liner, total budget, the 3 biggest decisions
made for the user, and in stock mode the picture notice; (2) trip-<theme>.html;
(3) trip.kml — plus the gates .ics, always: the pre-departure ladder rows are
date-locked gates (output-template.md §Pre-departure re-check ladder).
Gates — these decide pass/fail and do not move into the reference file:
- The deliverable is a themed page, never a plain text one. The plain
render_plan.pypage is an extra: on request for a printable version, or as the last resort after one honest fix attempt of the theme renderer — and then the summary says so. - The adversarial self-check runs in full before delivery, fixes what it catches,
and is recorded as "self-checked: N issues found and fixed" in
meta.self_checkand the lastdecisions[]row. The list lives in the reference file; a skipped item is a defect, not a shortcut. - Acceptance bars are exit codes and eyes, not prose:
route_tools checkandscripts/plan_lint.py --strictexit 0 before rendering (the only tolerated FAIL: a polar day'ssun, PLN-11),themes/qc.pyexits 0 after, and the export-probe PNG or the page in a browser was actually looked at — none available → say so in the summary. plan.geo.jsonstays the single editable source: a later "move day 3 to Nara" is a JSON edit plus geocode → check → links → kml → render, never a rewrite.- Cover title comes from references/cover-titles.md — never a literal placeholder, never a blacklisted cliché.
When things fail
- flight_scan.py errors twice (or cannot be installed — data-sources.md §Flights has
the PEP 668 variants) → browser Google Flights; that blocked too → the second price
source (Skyscanner / Kayak, data-sources.md §Flights → Second price source); only
when every source fails do deep links go out marked "price unverified", keep moving.
A harness with no browser pane is the one case a single source is acceptable — then
legs.notesays "single source — no browser"; a web search is never the rung after a failed scan while a browser exists. - A venue's hours survive 2 searches unverified → schedule it flagged "confirm on arrival"; don't burn more budget.
- Anything still unverified at delivery gets a ⚠️ in the plan — visible honesty beats quiet confidence.
Bundled resources
Paths below are relative to the skill root (the directory holding this SKILL.md) — resolve it once and call the scripts by absolute path, because a subagent's working directory is not the skill directory and shell cwd does not persist between calls.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 38
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
trip-planner-skywain- Source
- github.com/skywain/trip-planner-skill