Skip to content

ADR-005: Widget Builder via LangGraph Clarifier

Status: Accepted Source: prd.md §19

Context

PRD §11.1 originally listed Add Widget as a no-op visual stub. The pitch (prd.md §20) leans heavily on the pattern reusability story — "the same Event → Context → AI → Action → Feedback architecture extends to fraud, inventory, retention, product quality." A chat-driven widget builder makes that pattern tangible during the demo: the executive creates a new dashboard slice in 30 seconds without engineering involvement, and the same Spine-Clarifier pattern that ships in sdlc-agent-swarms/packages/agents-clarifier (LangGraph StateGraph + interrupt_before HITL + SSE + typed Zod artifacts) is what powers it.

Decision

  1. The Add Widget button opens a split-panel modal. The left pane runs a LangGraph clarifier with a Pydantic-discriminated WidgetSpec union (kpi | chart | table). The right pane renders a live preview of the spec.
  2. The clarifier uses the same node names and topology as the Spine Clarifier: contextLoader → intentExtractor → gapDetector → questionPrioritizer → specSynthesizer → critic → specUpdater, with interrupt_before=['specSynthesizer'] so the user answers questions between rounds. Resume via graph.update_state({"human_responses": [...]}) then graph.invoke(None, config).
  3. The user-facing terminal state is preview, not complete — we explicitly require an Add to dashboard click before the widget is persisted into the new widgets Postgres table. This matches the chosen "preview-then-confirm" UX (see plan §"Widget creation scope decisions").
  4. Bedrock is the LLM backend (matching ADR-002 production target), called via boto3 bedrock-runtime with JSON mode via tool-use so the spec is structurally validated by the discriminator and not by free-text parsing. A deterministic MockLlm (no network) is used when use_bedrock=False or the call times out at widget_llm_timeout_s (default 4s) — same fallback shape as ADR-003. Note: the silent-substitution semantics this point introduced were superseded by ADR-008. In live mode, Bedrock failures now surface as BuilderModeError; MockLlm is reserved for explicit BUILDER_MODE=offline.
  5. No RAG. The widget catalog (renderer types, accents, Recharts chart kinds) and design tokens are injected inline by contextLoader. The "evolution mode" RAG path of the Spine Clarifier (Voyage + Qdrant + Cohere rerank) is explicitly out of scope for v1.
  6. Live data binding is a separate user story. Each WidgetSpec carries a data_intent block (entity, metric, dimensions, filters, refresh_seconds) that the next story will resolve into a real query against the event stream. Today the preview renders the LLM-generated mock_data block. (Part C is the slice that proves data binding for 2–3 metrics; see ADR-PROTO-005.)

Consequences

  • Header button changes from no-op to wired entry point. PRD §3.2 ("Non-Goals") loses the Add Widget no-op line; §5.1 gains row 15 marking the feature P1.
  • Adds two backend dependencies: langgraph and boto3. langchain-core is pulled transitively; we deliberately do not add langchain-aws to keep the dep tree small (call Bedrock directly via boto3.client("bedrock-runtime")).
  • New Postgres table widgets (db/init.sql); reset_demo() truncates it so make demo-reset clears chat-built widgets.
  • New SSE contract documented in docs/widget-builder.md; event names match the Spine Clarifier (stage | gaps | questions | result | error).
  • During an extended outage of Bedrock, the demo still runs against MockLlm in BUILDER_MODE=offline — questions and the final spec are deterministic and obviously canned, but the UX flow is identical. Per ADR-008, live-mode demos do NOT silently substitute MockLlm.

Cross-references