Try it

  1. Browse (read-only): "find a waterproof trail shoe under $170": the agent calls search_products. It's annotated readOnlyHint: true, so the gate auto-approves it (no bubble) and matching cards light up.
  2. Inspect (read-only): "tell me about SHOE-005": the agent calls view_product; the card flashes and scrolls into view. Still read-only → still no bubble.
  3. Add (mutating): "add SHOE-001 to my cart": add_to_cart is mutating, so Persona's native approval bubble opens; approve it and the Cart updates.
  4. Parallel (the headline): "add SHOE-001 and SHOE-007 at the same time": the model emits two add_to_cart calls in one turn. Each gets its own approval bubble; approve both and the widget batches the two results into a single /resume, keyed by per-call id (runtypelabs/core#3878). Watch the orange batched /resume row in the wire log show both ids at once.
  5. Promo & remove (mutating): "apply code TRAIL10 and drop the socks": apply_promo + remove_from_cart, both gated. The cart shows the discount line and the item leaves.

The gate policy splits read-only from mutating: the storefront auto-approves its read-only tools (search_products, view_product) so they run with no friction, and routes every mutating call to the approval bubble. The page is the authority on which of its own tools mutate state.