Skip to content

feat(rooms): Phase 11 — chat, wired into existing AI personas - #21

Merged
withrvr merged 2 commits into
masterfrom
phase-11-chat-and-personas
Sep 14, 2026
Merged

withrvr merged 2 commits into
masterfrom
phase-11-chat-and-personas

Conversation

@withrvr

@withrvr withrvr commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 11: chat. Personas and AI-side chat generation (AgentController.say(), persona-driven voice) already existed since Phase 5 — this phase's actual gap was narrower than the name suggests: humans had no way to send a chat message at all, and AgentController.hear() existed but nothing ever called it, so an AI never learned about anyone else's chat either.

  • RoomService.send_chat + POST /rooms/{room_id}/chat: own-seat-only, rate limited (RATE_LIMIT_CHAT, an already-scaffolded-but-unused setting), works whether or not a game is running (chat never touches game state, unlike /moves). Feeds a running game's AI seats via hear() and tags the event with that game's own correlation.game_id so it archives alongside the game — matching how an agent's own chat already does.
  • Chat.tsx: message list (seat badge showing an AI's persona instead of a bare seat number) + compose box, gated on having a seat. Wired into the room detail page for everyone — chat is public.
  • Fixed a real type inaccuracy found while building this: seat was typed number | null in the hand-written chat event types; the real schema never allows null.
  • Found and fixed while documenting the route: GET /rooms/{id}/events's since pattern was still the old ^\d+-\d+$, missed when the websocket route's identical pattern was widened during PR feat(frontend): Phase 10 — lobby, live game, focus view, admin #20's review round — breaking transport parity. Widened to match. Also fixed a stale doc row still showing the pre-review-fix token flow.

Deliberately not fixed: AI-to-AI hear() fan-out (one agent's chat reaching a sibling agent's recent_chat) remains unwired — a separate, older Phase-5 gap, found while wiring the human side, out of scope for this phase.

Test plan

  • Backend suite: 772 tests green (765 + 7 new)
  • ruff check/ruff format --check/mypy app/ty check app clean
  • bun run build/bun run lint clean
  • Full Playwright suite: 57/57 green, including a real two-human chat exchange (both directions, both pages, confirmed live over the websocket)
  • prek run --all-files clean

https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW

Personas and AI-side chat generation (AgentController.say, persona-driven
voice) already existed since Phase 5. The actual gap: humans had no way
to send a chat message at all, and AgentController.hear() existed but
nothing ever called it, so an AI never learned about anyone else's chat.

- RoomService.send_chat + POST /rooms/{room_id}/chat: own-seat-only,
  rate limited (RATE_LIMIT_CHAT, an already-scaffolded-but-unused
  setting), works whether or not a game is running (chat never touches
  game state, unlike /moves). Feeds a running game's AI seats via
  hear() and tags the event with that game's own correlation.game_id so
  it archives alongside the game, matching how an agent's own chat
  already does.
- Chat.tsx: message list (seat badge showing an AI's persona instead of
  a bare seat number) + compose box, gated on having a seat. Wired into
  the room detail page for everyone -- chat is public.
- Fixed a real type inaccuracy found while building this: seat was typed
  number | null in the hand-written chat event types; the real schema
  never allows null.
- Found and fixed while documenting the route: GET /rooms/{id}/events'
  since pattern was still the old ^\d+-\d+$, missed when the websocket
  route's identical pattern was widened during PR #20's review round --
  breaking transport parity. Widened to match. Also found and fixed a
  stale WS route doc row (still showed the pre-review-fix token flow).

AI-to-AI hear() fan-out remains unwired -- a separate, older Phase-5 gap,
found while wiring the human side, deliberately not fixed here.

New tests: backend/tests/rooms/test_chat.py (6), a hear()-fan-out test in
test_room_service.py, and a real two-human chat-exchange Playwright test.

Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW
- Chat.tsx: reset draft on room switch (same bug class as useRoomSocket),
  add aria-live/role=log to the message list, key on event_id instead of
  ts+seat
- send_chat: rename text param (shadowed sqlalchemy's text()), name the
  200-char cap _MAX_CHAT_CHARS

Claude-Session: https://claude.ai/code/session_01UbiPa1rKCyqhqufgwJWkeW
@withrvr
withrvr merged commit 60c091a into master Sep 14, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant