# Todo 28: Session Turns ## Status done ## Goal Separate session and turn behavior. A WebSocket session is the ongoing conversation. A turn starts with one user message and finishes after ChatAgent has produced the visible reply, including any EventAgent handoff and final ChatAgent response. ## Root Cause The UI currently closes and recreates the WebSocket for every submit. The backend runtime also treats `done` as the end of the whole run after one user message. `round_index` is a ChatAgent call counter, so the post-event ChatAgent call appears as a second round with `events_enabled=[]`, which is expected for a one-event-loop turn but confusing without turn boundaries. ## Scope - Add a persistent runtime session entrypoint for WebSocket use. - Emit `turn_started` and `turn_completed` events. - Reset EventAgent event-loop budget for each new user turn. - Keep post-event final ChatAgent calls inside the same turn. - Keep existing one-shot `runtime.run()` behavior for tests and smoke helpers. - Reuse an open WebSocket from the frontend instead of closing it on every submit. ## Verification - Runtime test proves a second user turn gets enabled events again. - WebSocket test proves one connection can process two user messages. - Static UI test proves the browser sends `user_message` on an open socket and handles `turn_completed`. - `uv run pytest tests/test_debug_runtime.py tests/test_websocket_api.py -q` passes: 45 tests. - `uv run pytest` passes: 56 tests.