Skip to content

flight-checkin

A runnable Python example that walks a realistic multi-step mutation flow: look up a flight, browse seats, select one, and confirm check-in. Two of the five tools change state and are gated behind a short-lived, single-use consent token - the pattern a Platos agent should use for any destructive action that needs human approval.

Why this example exists

Most SDK samples stop at one or two read-only tools. Flight check-in is the smallest realistic use case where:

  • The agent has to chain calls. get_flight_details -> get_available_seats -> select_seat -> confirm_check_in. Four tools, shared PNR, shared flight state.
  • Two of the calls mutate live state. The agent must not execute either one without the user’s explicit approval.
  • Idempotency matters. If the agent retries confirm_check_in after a network hiccup, the receipt should return already_checked_in=True instead of failing or double-charging.

Architecture

┌─────────────────────────┐ ┌───────────────────────────┐
│ Platos platform │ WSS │ flight-checkin-mock │
│ (apps/api) │◄────────┤ (this example) │
│ │ │ │
│ ┌───────────────────┐ │ │ ┌─────────────────────┐ │
│ │ MCP gateway │ │ │ │ Platools() registry │ │
│ └─────────┬─────────┘ │ │ │ ├ get_flight_... │ │
│ │ │ │ │ ├ get_available... │ │
│ ┌─────────▼─────────┐ │ │ │ ├ request_consent │ │
│ │ Agent runtime │ │ │ │ ├ confirm_check_in │ │
│ │ (Pydantic AI) │ │ │ │ └ select_seat │ │
│ └───────────────────┘ │ │ ├─────────────────────┤ │
│ │ │ │ mock_data (in-proc) │ │
└─────────────────────────┘ │ └─────────────────────┘ │
└───────────────────────────┘

The example runs as a single Python process with no inbound ports. It opens one outbound WebSocket to platos-api:8000 and parks on it; when the agent calls one of the five tools, the platform relays the call over that socket, the SDK dispatches it to the decorated function, and the return value streams back. Exactly the Phase A contract from PRD §5.2.

The tools

ToolMutates?AuthConsentReturns
get_flight_details(pnr, last_name)nouserFlightDetails
get_available_seats(pnr, last_name)nouserlist[Seat]
request_consent(action, pnr, last_name, seat_number?)nouserConsentTicket
confirm_check_in(pnr, last_name, consent_token)yesuserrequiredCheckInReceipt
select_seat(pnr, last_name, seat_number, consent_token)yesuserrequiredSeatConfirmation

All five pass platools doctor flight_checkin.tools green out of the box.

The Platools SDK does not yet ship a first-class consent primitive, so the example uses an in-process HMAC-signed token store. The flow is five steps, and the agent runs them all in one conversational turn:

  1. User asks: “Can you check me in for PNR ABC123, last name Smith?”
  2. Agent calls get_flight_details("ABC123", "Smith") and shows the user the flight summary.
  3. Agent calls request_consent(action="confirm_check_in", pnr="ABC123", last_name="Smith"). The tool returns a ConsentTicket with a single-use token and a human_readable string like “Check in booking ABC123 for passenger Smith.”
  4. UI surfaces the human_readable summary as a confirmation prompt. User clicks Approve.
  5. Agent calls confirm_check_in("ABC123", "Smith", consent_token=...) with the token from step 3. The tool:
    • Verifies the HMAC signature.
    • Confirms the token is for confirm_check_in (not select_seat).
    • Confirms the params hash matches the current arguments.
    • Checks the token hasn’t expired (TTL = 120s).
    • Pops the token from the store - single-use, no replay.
    • Executes the mutation and returns a CheckInReceipt.

The select_seat flow is identical except step 3 passes the target seat number as an extra argument so the hash covers it.

[user] -> "Check me in for ABC123 Smith"
[agent] -> get_flight_details("ABC123", "Smith")
[tool] ← FlightDetails(flight="BA245", gate="B42", status="NOT_CHECKED_IN")
[agent] -> "Your flight is BA245 LHR->JFK, gate B42. Confirm check-in?"
[agent] -> request_consent("confirm_check_in", "ABC123", "Smith")
[tool] ← ConsentTicket(token="...", human_readable="Check in ABC123 for Smith.")
[ui] -> (modal) "Check in ABC123 for Smith?" [Approve] [Cancel]
[user] -> Approve
[agent] -> confirm_check_in("ABC123", "Smith", consent_token="...")
[tool] ← CheckInReceipt(seat="14A", boarding_time="08:00", already_checked_in=False)

Mock PNRs

All six PNRs are curated sample content - obviously-fake booking references so nothing here could be mistaken for real PII. Use them to demo the flow in the Playground or via platools serve.

PNRLast nameFlightRouteDeparture (UTC)GateStatus
ABC123SmithBA245LHR -> JFKtomorrow 09:00B42NOT_CHECKED_IN
XYZ789PatelAA100SFO -> LAXtomorrow 11:30C7CHECKED_IN (demo idempotency)
DEF456GarciaUA500EWR -> DENtomorrow 14:15A12NOT_CHECKED_IN
GHI012ChenDL770ATL -> SEAtomorrow 16:45D5NOT_CHECKED_IN
JKL345JohnsonLH441MUC -> FRAtomorrow 18:3022NOT_CHECKED_IN
MNO678RodriguezIB6253MAD -> BCNtomorrow 20:0015NOT_CHECKED_IN

Running it

Terminal window
cd examples/flight-checkin
cp .env.example .env
uv sync
uv run platools doctor flight_checkin.tools # ship gate
uv run pytest tests/ -v # functional tests
uv run python -m flight_checkin.main --list-tools
uv run python -m flight_checkin.main --list-pnrs
uv run python -m flight_checkin.main # connect outbound

Under docker-compose

The service is behind the examples profile so it does not start on a bare docker compose up:

Terminal window
# Bring up the platform first
docker compose up -d platos-api platos-web
# Generate an SDK secret via the dashboard (Settings -> SDK keys),
# paste it into .env as FLIGHT_CHECKIN_SDK_SECRET, then:
docker compose --profile examples up flight-checkin-mock

Testing in the Playground

  1. docker compose up platos-api platos-web (or make up).
  2. Open the dashboard, go to Settings -> SDK keys, generate a secret, copy it into .env as FLIGHT_CHECKIN_SDK_SECRET.
  3. docker compose --profile examples up flight-checkin-mock. Watch for connected to platform in the logs.
  4. In the dashboard, create a new agent, assign it all five flight-checkin tools.
  5. Open the Playground -> pick the new agent -> type: “Can you check me in for PNR ABC123 last name Smith?”
  6. Confirm the agent walks the five-step flow above.

What to learn from it

  • Consent gates go in front of mutations, not reads. get_flight_details and get_available_seats never require a token - they’re cheap, idempotent, and the agent should be free to call them on its own.
  • Mutation tools must be idempotent. confirm_check_in on an already-checked-in booking returns already_checked_in=True instead of failing.
  • Bind approval to parameters. The consent token carries an HMAC over the exact action + params so a token for select_seat(ABC123, 14A) cannot be reused for select_seat(ABC123, 15B).
  • Pop tokens on consume. Replay protection matters even within the TTL - a single-use token is the difference between a safe retry and an accidental double mutation.

Source: examples/flight-checkin/

See also