STORY-F-017: Agent Chat LiveView + Phoenix Channels streaming¶
Epic: Agent UI Priority: Must Have Story Points: 3 Status: Not Started Assigned To: Unassigned Created: 2026-04-17 Sprint: 4
User Story¶
As an authenticated user, I want a LiveView chat interface on the home screen with token-by-token response streaming via Phoenix Channels, so that the Phase 0 deliverable ("login → org setup → agent chat working → Cmd+K navigates") is complete and subsequent Scout + Verify sprints have the agent UI to build on.
Description¶
Background¶
PRD E8.1 + architecture Part 6 commit to Phoenix Channels (not SSE) for agent streaming — mobile uses Channels for notifications anyway, so reuse. LiveView hosts the chat interface; Channels carry the streaming tokens.
This story wires the end-to-end path: user types a message → LiveView commits user message → Orchestrator routes (F-012) → MCP tool or Claude (F-013/F-014) → tokens stream back via Channel → LiveView renders. The sample tool from F-014 ("who am I") proves Tier-1 pattern-match path; Tier-2 Claude path proves streaming.
Scope¶
In scope:
FinnestWeb.AgentChatLive— LiveView on/(authenticated home):on_mounthook setsFinnestCore.Tenantcontext from session- Starts an
agents.sessionsrow on mount; storessession_idin socket assigns - Renders: message history, input textarea, send button (keyboard Enter submits)
- On send:
FinnestAgents.Orchestrator.start_session/2if not already; cast message to session GenServer - Subscribes to
agent:<session_id>PubSub topic; renders incoming tokens as they stream - Handles
:token,:tool_call,:complete,:errorevents - Shows tool invocations inline (e.g. "Checking your user info...") so the agent's work is visible
- Styled with DaisyUI: message bubbles for user vs assistant, tool-call cards, loading dots
FinnestWeb.UserSocket+FinnestWeb.AgentChannel:- Channel topic
agent:<session_id> - Auth: JWT from LiveView session; rejects subscriptions outside authenticated session_id
- Server pushes
:tokenmessages during streaming FinnestAgents.SessionGenServer (from F-012) extended: broadcasts toagent:<session_id>PubSub topic during generation- Integration with F-013
AnthropicDirect.stream/3: tokens consumed from Enumerable and broadcast per token - Integration with F-014 MCP tools: Tier-1 pattern-match routing works end-to-end (e.g. "who am I" returns user info without calling Claude)
- Persistence: user + assistant messages written to
agents.messages(stub schema ok if real schema not yet landed; create minimal schema in this story if needed) agents.sessionstable minimal schema (if not yet exists): id UUID, org_id, user_id, started_at, ended_at, metadata JSONB- Graceful degradation: if AnthropicDirect returns
:budget_exceeded, render friendly error and suggestion (E7.4 AC failover)
Out of scope:
- Cmd+K command bar (F-018 — separate story)
- Tier-2 Workflow agents (Scout+Verify sprints)
- Tier-3 Autonomous agents (Migration Phases)
- Agent-as-tutor (per B09; Migration Phase 1+)
- L2/L3 memory retrieval (defer)
- Response caching ETS (defer to Scout+Verify sprints)
- Full domain MCP tools (Scout+Verify sprints add real ones)
Technical Notes¶
- LiveView uses
handle_infoto receive PubSub messages; must handle reconnect gracefully (LVterminate/2+ reconnect with session_id preserved) - Token streaming rendering: append to the currently-streaming message; use
Phoenix.LiveView.stream_insert/3for incremental DOM updates (avoids full re-render per token) - Session idle timeout (10 min from F-012) — on LiveView, show a "session ended" indicator and allow resume via new session creation
- Accessibility (FE-06/14): chat messages have proper ARIA labels; streaming text live region (
aria-live="polite"); keyboard-only operable; screen reader friendly (announce tool invocations as they happen) - UI polish: typing indicator while waiting for first token; subtle animation on tool card expansion
- DaisyUI components:
chat-bubble chat-bubble-primary(user),chat-bubble(assistant),card card-compact(tool invocations) - Security: every socket message's
org_idcomes from socket.assigns (set byUserSocket.connect/3), never from client payload (AI-03) - Correlation ID propagation: generate new correlation_id per user message; propagate through session → orchestrator → tool/LLM call → back to LV
- Module namespace: modules live under
FinnestCore.*/FinnestAgents.*/FinnestWeb.*(flat). Boundary library constraint per F-003 precedent; same as Sprint 3 D4 resolution.
Dependencies¶
- Blocked by: STORY-F-006 (auth — need authenticated user on home), STORY-F-012 (Orchestrator + Session), STORY-F-013 (AnthropicDirect for LLM path), STORY-F-014 (sample MCP tool for pattern-match path), STORY-F-016 (event store for message audit events)
Acceptance Criteria¶
- Authenticated user navigates to
/sees agent chat UI (styled, accessible, input focused) - User types "who am I", presses Enter → message appears immediately as user bubble → agent response appears as streaming bubble → final content shows current user's info
- Tier-1 pattern-match path (cost $0): "who am I" completes via MCP tool call without hitting Claude (confirmed via
agents.tool_audit— no LLM call row) - Tier-2 LLM path: "summarise what Finnest does" streams tokens from Claude; final message rendered correctly
- Tool invocation visible: clicking to expand a tool-call card shows input + output
- Streaming works via Phoenix Channels (not SSE); confirmed via browser dev tools WebSocket frames
- Session persists: refresh browser mid-chat → session_id re-hydrated; message history restored from
agents.messages - Idle 10 min → session end indicator; sending new message starts fresh session
- Budget-exceeded path: set org budget to 0 → user sees graceful error, not crash
- Accessibility: keyboard-only user can complete full chat (Tab to focus, Enter to send); screen reader announces streaming via live region
- User/assistant messages persisted to
agents.messageswith correlation_id - LiveView p95 mount <300 ms (PF-01)
- Agent first token <1s p95 (Channel confirm)
- Cross-tenant leak test: user A's session cannot subscribe to user B's
agent:<session_id>channel → rejected -
mix format,credo --strict,dialyzer,mix boundaryall green
Testing Requirements¶
- Unit: AgentChatLive mount, handle_event (send message), handle_info (token stream)
- Integration (Phoenix.LiveViewTest): full send→stream→render flow with MockProvider
- Integration (ChannelTest): subscribe, send token, verify push
- Security: subscription to foreign session_id rejected
- Accessibility (axe-core): live region present; form labels correct; colour contrast AA
- Performance: 10 concurrent chat sessions on one node; p95 token latency <200 ms
References¶
- PRD
../prd/prd-scout-verify-golive.mdE8.1, E8.2, E8.3 ../architecture/architecture.mdPart 6 §API Design, Part 13 §Three paths to every action../architecture/agents.md§Tier 1 Conversational, §Memory System../10-GUARDRAILS.mdAI-03, AI-04, FE-10, PF-01, OP-03