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_inafter a network hiccup, the receipt should returnalready_checked_in=Trueinstead 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
| Tool | Mutates? | Auth | Consent | Returns |
|---|---|---|---|---|
get_flight_details(pnr, last_name) | no | user | – | FlightDetails |
get_available_seats(pnr, last_name) | no | user | – | list[Seat] |
request_consent(action, pnr, last_name, seat_number?) | no | user | – | ConsentTicket |
confirm_check_in(pnr, last_name, consent_token) | yes | user | required | CheckInReceipt |
select_seat(pnr, last_name, seat_number, consent_token) | yes | user | required | SeatConfirmation |
All five pass platools doctor flight_checkin.tools green out of the
box.
The consent flow
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:
- User asks: “Can you check me in for PNR ABC123, last name Smith?”
- Agent calls
get_flight_details("ABC123", "Smith")and shows the user the flight summary. - Agent calls
request_consent(action="confirm_check_in", pnr="ABC123", last_name="Smith"). The tool returns aConsentTicketwith a single-use token and ahuman_readablestring like “Check in booking ABC123 for passenger Smith.” - UI surfaces the
human_readablesummary as a confirmation prompt. User clicks Approve. - 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(notselect_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.
| PNR | Last name | Flight | Route | Departure (UTC) | Gate | Status |
|---|---|---|---|---|---|---|
ABC123 | Smith | BA245 | LHR -> JFK | tomorrow 09:00 | B42 | NOT_CHECKED_IN |
XYZ789 | Patel | AA100 | SFO -> LAX | tomorrow 11:30 | C7 | CHECKED_IN (demo idempotency) |
DEF456 | Garcia | UA500 | EWR -> DEN | tomorrow 14:15 | A12 | NOT_CHECKED_IN |
GHI012 | Chen | DL770 | ATL -> SEA | tomorrow 16:45 | D5 | NOT_CHECKED_IN |
JKL345 | Johnson | LH441 | MUC -> FRA | tomorrow 18:30 | 22 | NOT_CHECKED_IN |
MNO678 | Rodriguez | IB6253 | MAD -> BCN | tomorrow 20:00 | 15 | NOT_CHECKED_IN |
Running it
Local - uv (recommended)
cd examples/flight-checkincp .env.example .envuv sync
uv run platools doctor flight_checkin.tools # ship gateuv run pytest tests/ -v # functional testsuv run python -m flight_checkin.main --list-toolsuv run python -m flight_checkin.main --list-pnrsuv run python -m flight_checkin.main # connect outboundUnder docker-compose
The service is behind the examples profile so it does not start on
a bare docker compose up:
# Bring up the platform firstdocker 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-mockTesting in the Playground
docker compose up platos-api platos-web(ormake up).- Open the dashboard, go to Settings -> SDK keys, generate a secret, copy it into
.envasFLIGHT_CHECKIN_SDK_SECRET. docker compose --profile examples up flight-checkin-mock. Watch forconnected to platformin the logs.- In the dashboard, create a new agent, assign it all five flight-checkin tools.
- Open the Playground -> pick the new agent -> type: “Can you check me in for PNR ABC123 last name Smith?”
- 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_detailsandget_available_seatsnever 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_inon an already-checked-in booking returnsalready_checked_in=Trueinstead 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 forselect_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
python-billing-agent- simpler single-write example.typescript-support-agent- equivalent pattern in TypeScript.local-dev-loop- running any example locally viaplatools serve.