One sentence in. Five bookable trips out. Never books anything.
Every run ships a self-contained HTML viewer: ranked cards, budget bars, day-by-day itineraries, and a map pinning each hotel.
Windward is an agentic, budget-bounded, plan-only trip planner. You give it one natural-language request:
windward "vacation for 2, budget 3k total, anytime in august, want insane landscapes"It answers with 4β5 complete, priced, bookable trip plans β the exact flight, the exact hotel, a day-by-day itinerary, a budget breakdown against your ceiling, and deep links to book each piece. The last click is always yours: windward never books, pays, or touches PII. That's a design decision, not a roadmap gap.
Under the hood it's four small LLM agents wrapped around a fully deterministic pipeline β the interesting parts (pruning, costing, ranking) are plain, testable code.
- πΈ Budget is a hard constraint, not a suggestion. Plans are assembled bottom-up β flights + lodging + per-diem + buffer β and must clear your ceiling with margin. A critic agent re-verifies every sum before you see it.
- π€ LLM at the edges, code in the middle. Parse β brainstorm β write β critique are agents; prune β cost β rank is deterministic code. Cheap, reproducible, and it won't melt a provider's rate limit.
- π Zero API keys to start. Free Google Flights data via
fli, keyless hotel data viatrvl, agents on your existing Claude subscription. - πΊοΈ A real deliverable. Each run writes Markdown, JSON, and a self-contained HTML viewer with a map β artifacts you can save, share, or diff.
- π₯ Deal detection. Standout-cheap fares get flagged in every run for free; an opt-in mode hunts fares below their historical range.
- π Taste profiles. Ingest your Beli restaurant lists via vision into per-person and group taste profiles that can flavor itineraries.
- β»οΈ Reproducible runs. Run directories are keyed by a hash of the brief; provider responses cache to SQLite with a TTL. Same request, same data.
- π§’ Hard caps everywhere. Max provider calls, concurrency, quota ceilings β
config.pybounds spend and API pressure by design.
prompt
β [LLM] brief parser structure the request, infer origin (cheap)
β [gate] elicit ask: origin? long-haul? ceiling? (pre-spend)
β [LLM] brainstormer ~40 in-season, vibe-matched destinations
β [code] coarse prune 1 flight call each, drop pricey β ~12
β [code] cost assembly + hotel + per-diem buffer, keep β€ budget β ~6
β [code] rank vibe Γ headroom Γ season β top 5
β [code] exact pricing lock specific flights + hotels
β [LLM] itinerary writer day-by-day plans
β [LLM] critic verify sums β€ budget, no hallucinations
β present ranked plans + booking deep links
Only four nodes are LLM calls β the two ends. The spine is deterministic on purpose.
π Repository layout
src/windward/
βββ orchestrator.py the deterministic state machine
βββ cli.py entry points (windward, windward-deals, windward-beli)
βββ models.py typed contracts between every stage
βββ agents/
β βββ runtime.py claude -p wrapper: env scrub + billing guard
β βββ llm_agents.py the four agents
β βββ prompts/*.md their prompts + JSON contracts
βββ engine/ coarse prune, cost assembly, rank, budget, taste, deals
βββ providers/ swappable adapters behind base.py interfaces
π¦ Run artifacts
Each run is saved to runs/<month><vibe><pax>pax_<hash>/ (local only, gitignored):
run.jsonβ the structured brief + metadatasummary.mdβ ranked index of all optionsplan-NN-<dest>.{json,md}β each trip as a saveable fileindex.htmlβ the self-contained visual viewer (Leaflet map, budget bars, itineraries)
The directory is keyed by a hash of the brief, so re-running the same scenario overwrites in place instead of piling up copies.
# 1. Claude Code, logged in with a Pro/Max subscription (NOT an API key)
npm install -g @anthropic-ai/claude-code
claude login
# 2. Install
git clone https://github.com/sohan-shingade/windward && cd windward
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
# 3. (Optional but recommended) keyless hotel data
go install github.com/MikkoParkkola/trvl/cmd/trvl@latest
# or: brew install MikkoParkkola/tap/trvl
# 4. Confirm you're on subscription auth, not API billing
./scripts/preflight.sh
# 5. Plan a trip
windward "vacation for 2, budget 3k total, anytime in august, want insane landscapes"Structured flags override the parsed brief; multi-origin checks every airport and keeps the cheapest:
windward "adventurous, insane landscapes, backpacking" \
--pax 2 --budget 2000 --month 2026-08 --nights 4 --origins SFO,OAK,SJC --open--open launches the HTML viewer when the run finishes.
| Layer | Default | Alternatives |
|---|---|---|
| Flights | fli β free Google Flights, no key |
SerpApi google_flights (needs SERPAPI_API_KEY) |
| Hotels | trvl β free, keyless CLI |
SerpApi google_hotels β labeled flat-rate estimate |
| Agents | claude -p on your subscription |
implement AgentRuntime for API-key billing |
Hotel preference is trvl β SerpApi β flat estimate; force one with WINDWARD_HOTELS=trvl|serpapi|flat. One SerpApi key (free tier ~250 searches/mo) powers both engines β copy .env.example to .env and set SERPAPI_API_KEY if you want it.
Note
Provider responses cache to a local SQLite DB (~/.cache/windward/providers.db) with a TTL, so identical requests reproduce the same flight and hotel data. Force fresh data with WINDWARD_CACHE=off. The optional trvl binary is PolyForm Noncommercial β fine for personal, plan-only use.
Windward is free, and it's built to run at zero marginal cost:
- Flight and hotel data is free. The default providers (
fli,trvl) need no API key and charge nothing. SerpApi is optional, and its free tier covers normal use. - The AI agents use the Claude subscription you already have. They run through Claude Code (
claude -p) on your existing Pro/Max login β the same flat plan you use for chatting or coding. No per-token API charges, no separate account, no credit card.
You are never billed by windward for anything. The only requirement is a Claude Pro/Max subscription for the agent steps (if you don't have one, you can implement the AgentRuntime interface to point the agents at any API instead).
Two built-in guards make sure agent calls can't accidentally land on metered API billing:
- The runtime strips
ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN, andANTHROPIC_BASE_URLfrom every agent subprocess, so a key sitting in your shell can't silently switch you from subscription to pay-per-token. - The runtime reads
total_cost_usdfrom every agent result and aborts the run if it is ever positive (fail_on_api_billing=True) β a belt-and-suspenders check against CLI billing quirks.
Tip
First run? ./scripts/preflight.sh confirms you're on subscription auth (in an interactive claude session, /status should show CLAUDE_CODE_OAUTH_TOKEN).
Important
Running claude -p on your own subscription for your own local use is fine under the ToS. Subscription OAuth may not power a product served to other people β if you productize this, swap ClaudeCLIRuntime for an API-key runtime.
-
Relative (free, always on): within a normal run, any fare β€65% of the median gets tagged
π₯ dealin the output, summary, and viewer. No extra API calls. -
Absolute (opt-in):
windward-dealsscans aspirational destinations from your origins for fares below their typical historical range (SerpApiprice_insights) β real error-fare hunting. It spends metered quota, so it's off by default:windward-deals --origins SFO,OAK --month 2026-10 --nights 7 --enable
A hard cap (
deal_hunt_max_calls, default 24) clampstargets Γ originsso a scan can't blow your quota. Results save toruns/deals_*/.
Turn your Beli restaurant lists into a taste profile that can flavor trip planning. Record a slow scroll of a list (or grab screenshots); a vision agent extracts places and cuisine tags, deduplicates, and scores them by your rating pattern.
# ingest your "been" list from a screen recording
windward-beli alice --video recordings/beli_been.mov --list-type been
# alice: 47 places -> 12 cuisines, 0 on wishlist
# wrote profiles/alice/beli_snapshot.json + taste.json
# second pass for the wishlist β appends to the same snapshot, dedup handles overlap
windward-beli alice --video recordings/beli_wants.mov --list-type want_to_try
# screenshots instead of video
windward-beli alice --shots shots/ --list-type been
# merge profiles into a group (consensus scores, shared wants, union of dislikes)
windward-beli group --merge alice,bob,charlieProfiles land in profiles/ (local only, gitignored). Video ingestion needs the ffmpeg system binary; scroll slowly with brief pauses so mpdecimate can dedupe frames.
pytest # FAST: offline, no network or claude. Deterministic logic + provider
# parsers run against recorded real responses in tests/fixtures/.
pytest -m live # LIVE: genuine end-to-end (real fli + SerpApi + claude -p). Skips
# unless creds + network are present; keep ANTHROPIC_API_KEY unset.Regenerate fixtures with python scripts/record_fixtures.py (needs creds + network).
PRs welcome! Ground rules (see CLAUDE.md for the full contributor context):
- π« Plan-only, forever. No booking, payments, or PII capture.
- π§ Keep the spine deterministic. LLM calls belong in
agents/only. - π Vendor isolation. Concrete providers stay behind the
providers/base.pyinterfaces. - π§’ Don't raise the caps quietly. The limits in
config.pyprotect rate limits and spend. - β
Add tests next to new engine logic;
pytestmust pass offline with no credentials.
Released under the MIT License.
The optional trvl hotel binary is separately licensed (PolyForm Noncommercial) and is not distributed with this project.