Skip to content

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.AgentChatLiveLiveView on / (authenticated home):
  • on_mount hook sets FinnestCore.Tenant context from session
  • Starts an agents.sessions row on mount; stores session_id in socket assigns
  • Renders: message history, input textarea, send button (keyboard Enter submits)
  • On send: FinnestAgents.Orchestrator.start_session/2 if 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, :error events
  • 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 :token messages during streaming
  • FinnestAgents.Session GenServer (from F-012) extended: broadcasts to agent:<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.sessions table 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_info to receive PubSub messages; must handle reconnect gracefully (LV terminate/2 + reconnect with session_id preserved)
  • Token streaming rendering: append to the currently-streaming message; use Phoenix.LiveView.stream_insert/3 for 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_id comes from socket.assigns (set by UserSocket.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.messages with 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 boundary all 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