The seven-line server
FastAPI exposes WebSocket through the @app.websocket decorator. The handler is a coroutine that takes a single WebSocket argument. Three things must happen, in order: await websocket.accept() to complete the handshake, then await websocket.receive_*() in a loop, then await websocket.send_*() to reply.
accept() is not optional
Until you call accept(), the handshake is not complete and any send will throw. You can choose to not accept — that is how you reject a connection during auth checks. After accept, the connection is fully open and frames flow.
Running and testing
Spin up the server with uvicorn; test from the command line with websocat or wscat. cwkPippa runs Uvicorn without --reload on purpose (Pippa's adapter is the live brain stem; reload kills sessions). For learning, use --reload.