Lucy & Room Service — how the AI takes orders
Lucy handles room-service ordering directly — there's no special "food" assistant to delegate to. Three tools, added in v3.20.0, mirror the proven activity-booking pattern.
What guests can say
Lucy responds to almost anything food-shaped:
| Guest says | Lucy does |
|---|---|
| "What's on the menu?" | Calls fetch_menu_items and presents items in a condensed list grouped by category. |
| "What's vegan?" | Fetches the menu, filters by dietary_tags, and lists matches. |
| "I'd like room service" | Asks the guest what they'd like, then walks through items + quantities. |
| "Order me a coffee" | Confirms which coffee from the menu, then calls place_room_service_order. |
| "Two mojitos and a ceviche" | Resolves each item against the menu, confirms total, then places the order. |
When in doubt, Lucy confirms before charging — the typical exchange is "Mojito ($12) and ceviche mixto ($22) — total $34. Add to your room?" Yes / No / Edit.
The three tools
hl_machine/utils/tools.py (graph repo):
fetch_menu_items(listing_id, category=None)— callsGET /hilucy/v1/listings/{id}/menu. Returns up to 200 items. Use thecategoryfilter for narrow asks like "what's for breakfast?".get_menu_item_details(item_id)— callsGET /hilucy/v1/menu-items/{id}for a single item. Useful when Lucy wants to confirm price or dietary tags before placing.place_room_service_order(items, listing_id, guest_name?, guest_phone?, guest_room?, special_requests?)— callsPOST /hilucy/v1/menu-orders. Returns aCommandupdating five state fields:room_service_order_id(folio_id or wc_order_id)room_service_status(on_folio/per_charge/awaiting_payment/failed)room_service_checkout_url(set when no folio is open)selected_menu_items(cached for context)pending_room_service_order(last placed)
When Lucy returns a checkout link vs charges to the room
The decision is made server-side in
HiLucy_Menu_Order_Service::place_order — Lucy doesn't choose. Her
ToolMessage just relays the result:
charge_status: 'on_folio'→ "Done — your order has been added to your room. Total: $X.YZ. The kitchen has been notified."charge_status: 'per_charge'→ same message; the difference is invisible to the guest.charge_status: 'awaiting_payment'→ "Your order is ready to confirm — total $X.YZ. Open this secure link to pay and the kitchen will start prep: <url>"
The web chat surfaces that URL as a tappable Pay & confirm button (rich card). WhatsApp guests get the URL inline.
Listing context — how Lucy knows which menu
Lucy reads listing_id from state.listing_id, which is populated by
the fetch_user_info entry node from the configurable.listing_id
config. Every chat thread is bound to one listing.
If a guest somehow lands in chat with no listing_id, Lucy will
politely ask which property they're at instead of guessing. Staff
can set the listing by sending the guest a fresh arrival link.
Guardrails
- Lucy will not call
place_room_service_orderwithout a guest name and phone. The tool itself returns "Please ask the guest for their name" if either is missing — Lucy follows up with the guest. - Lucy never invents items. If the guest asks for something not on
the menu, she escalates to staff via the existing
set_potential_provider_contact+ToHotelConciergeAssistantpattern (see activities docs). - Lucy doesn't auto-substitute. "We're out of mojitos" → she'll suggest similar drinks rather than swap silently.