Tutorial: Build a GenUI AI Full-Stack System
Why this tutorial matters
Reading about GenUI, AG-UI, A2UI, RAG, MCP, A2A, agents, and edge personalization is not enough. These ideas only become clear when you build a system that has real boundaries:
- user intent
- generated UI
- deterministic rendering
- agent workflow state
- retrieval and citations
- tool calls
- approvals
- observability
- evals
- deployment and rollback
This tutorial is an orientation path for building a full learning system. It is not a copy-paste starter template. It is a guided architecture lab that teaches how the pieces fit together and where production risk lives.
The final system is an AI Experience Studio: a web app where a user can ask an agent to generate a personalized landing-page or customer-workflow experience, inspect sources, review generated UI, approve actions, and publish a static variant that can be served through edge routing.
What you will build
Build a system with four surfaces:
| Surface | Purpose |
|---|---|
| Intent intake | User describes campaign, audience, offer, or workflow goal |
| Agent workspace | Streams progress, retrieval, tool calls, and generated UI proposals |
| Review and approval | Human edits, rejects, approves, or publishes generated experiences |
| Published experience | Static or hybrid-rendered page with small interactive island |
The app teaches the "new full stack" by forcing every layer to be explicit:
React/TanStack/Next UI
-> GenUI component registry
-> AI interaction event stream
-> agent workflow
-> RAG/context retrieval
-> tool layer
-> approval and audit
-> static variant generation
-> edge routing
-> evals and observability
Recommended learning stack
Use tools you can actually run and inspect. The exact choices can vary, but the architecture should remain the same.
| Layer | Suggested choice | Why |
|---|---|---|
| App framework | TanStack Start or Next.js | Teaches routing, server functions, streaming, and hybrid rendering |
| UI | React + design-system primitives | GenUI needs a controlled component registry |
| Validation | Zod or JSON Schema | Generated UI payloads must be validated |
| Agent workflow | LangGraph, CrewAI Flow, or simple typed workflow first | Start deterministic, then add agent behavior |
| Model gateway | Server-side provider wrapper | Avoid direct browser-to-model calls |
| RAG store | Postgres + pgvector, Pinecone, or Weaviate | Teaches retrieval, metadata, citations |
| Tool layer | Internal typed tools; MCP-style interface later | Learn tool safety before protocol abstraction |
| Experimentation | Simple variant assignment first; Statsig-style platform later | Learn cache-safe decisions |
| Observability | Structured logs + traces + RUM | Debug across UI, workflow, model, tools |
| Evals | Fixture-based test runner | Catch regressions in prompts, schema, retrieval, and tools |
Do not start with every framework at once. The correct learning sequence is: deterministic workflow, typed events, generated UI registry, retrieval, tools, approvals, then agent autonomy.
Target architecture
Browser
React app
Generated UI renderer
Event stream client
RUM instrumentation
Application server
Auth/session
AI interaction endpoint
Workflow state store
Component schema registry
Model gateway
Retrieval service
Tool service
Approval service
Audit log
Data and context
Source documents
Embeddings/vector index
Metadata and permissions
Published variants
Experiment assignments
Delivery
Static variant build
CDN cache
Edge middleware
Small form island/API submit
The system has one rule: the model may propose, but deterministic code decides what renders, what data is allowed, what tool can execute, and what gets published.
Milestone 1: Create the deterministic shell
Before adding AI, build the app shell:
/studiofor the agent workspace/studio/:workflowIdfor resumable workflows/preview/:variantIdfor generated experience preview/publish/:variantIdor internal static route for published variants- server-side auth/session
- route-level error boundaries
- baseline design-system components
- RUM instrumentation for route load and interaction
Minimum data models:
type Workflow = {
id: string;
userId: string;
status: "draft" | "running" | "waiting_for_approval" | "failed" | "completed";
goal: string;
createdAt: string;
updatedAt: string;
};
type AuditEvent = {
id: string;
workflowId: string;
actor: "user" | "agent" | "system";
type: string;
payload: Record<string, unknown>;
createdAt: string;
};
Acceptance criteria:
- A workflow can be created and reopened.
- State survives refresh.
- Audit events can be listed.
- The UI has deterministic loading, empty, error, and terminal states.
Milestone 2: Define the GenUI component registry
Create a small registry. Keep it deliberately constrained.
Start with:
| Component | Purpose |
|---|---|
summary_panel | Explain generated recommendation |
source_list | Show retrieved evidence |
editable_draft | Let user edit generated copy |
comparison_table | Compare generated options |
approval_action | Require explicit human approval |
published_preview | Show final experience preview |
Registry contract:
type RegistryEntry = {
type: string;
version: string;
propsSchema: unknown;
risk: "display" | "input" | "approval";
requiresCitations?: boolean;
events: string[];
};
Example generated UI payload:
{
"id": "draft-hero",
"type": "editable_draft",
"version": "1",
"props": {
"title": "Auto Loan Debt Holding You Back?",
"body": "See how much you could save with a personalized relief plan.",
"cta": "Check Your Savings"
},
"citations": ["ad:facebook:auto-loan-01", "policy:claims-2026"]
}
Renderer pipeline:
parse payload
-> validate envelope
-> check component allowlist
-> normalize version
-> validate props
-> check permissions and citations
-> render component or fallback
-> log validation result
Acceptance criteria:
- Unknown components are rejected.
- Invalid props render a fallback.
- User-edited drafts cannot be overwritten by agent patches.
- Every component emits stable analytics names.
Milestone 3: Add an event stream
Use Server-Sent Events, WebSocket, or framework streaming. The transport matters less than the event contract.
Core events:
type AIEvent =
| { type: "workflow.started"; workflowId: string }
| { type: "retrieval.started"; workflowId: string; query: string }
| { type: "retrieval.completed"; workflowId: string; sourceIds: string[] }
| { type: "tool.started"; workflowId: string; toolName: string }
| { type: "component.proposed"; workflowId: string; component: unknown }
| { type: "approval.requested"; workflowId: string; approvalId: string }
| { type: "workflow.failed"; workflowId: string; reason: string }
| { type: "workflow.completed"; workflowId: string };
Frontend reducer operations:
| Operation | Use |
|---|---|
| append | New message or generated component |
| patch | Progress update |
| replace | Regenerate unedited draft |
| remove | Remove temporary UI |
| commit | Mark approved output durable |
Acceptance criteria:
- The stream can be replayed from audit events.
- A disconnected client can reopen workflow state.
- Cancellation stops backend work.
- Terminal states are explicit.
Milestone 4: Build the first workflow without agents
Do not start with autonomy. Start with a deterministic workflow that calls the model only at controlled steps.
Workflow:
capture_goal
-> retrieve_context
-> generate_copy_candidates
-> generate_ui_payloads
-> wait_for_human_review
-> publish_variant
The first model call should be boring:
Given the campaign goal, audience, source snippets, and allowed
components, generate three landing-page copy candidates. Return JSON
matching the provided schema. Do not invent claims. Every factual claim
must include a source id.
Acceptance criteria:
- The model output is schema validated.
- Invalid model output triggers repair or fallback.
- Generated copy is editable.
- Nothing is published without approval.
Milestone 5: Add RAG and citations
Create a small source corpus:
- ad examples
- product messaging
- compliance constraints
- FAQ/policy content
- prior winning page examples
- brand voice rules
Each chunk needs metadata:
type SourceChunk = {
sourceId: string;
chunkId: string;
title: string;
text: string;
sourceType: "ad" | "policy" | "brand" | "faq" | "example";
tenantId: string;
visibility: "public" | "internal" | "restricted";
updatedAt: string;
};
RAG states in the UI:
| State | UI behavior |
|---|---|
| strong context | show generated answer with citations |
| partial context | show coverage warning |
| conflicting context | show source disagreement |
| no context | ask a clarifying question |
| unauthorized context | explain access limitation |
| stale context | show freshness warning |
Acceptance criteria:
- Retrieval filters by tenant/role before model context.
- Claims can link to source ids.
- Sources render in
source_list. - Evals include malicious source text and unauthorized source cases.
Milestone 6: Add tools and approvals
Start with safe tools:
| Tool | Risk | Approval |
|---|---|---|
draft_landing_page | draft only | no |
create_variant_preview | internal write | yes or role-based |
publish_variant | public change | yes |
send_test_event | analytics test | no |
archive_variant | destructive/public impact | yes |
Tool schema example:
{
"name": "publish_variant",
"input": {
"variantId": "string",
"approvedPayloadHash": "string"
},
"sideEffect": "public_publish",
"requiresApproval": true,
"idempotencyKey": "required"
}
Approval rule:
- the user approves the exact payload they saw
- approval includes payload hash
- server verifies user, role, workflow, approval id, and hash
- publishing uses idempotency key
- audit log records the action
Acceptance criteria:
- The model cannot publish directly.
- Approval can be rejected or edited.
- Duplicate publish requests do not duplicate side effects.
- Audit log shows who approved what.
Milestone 7: Add static publishing and edge routing
Now connect GenUI to hybrid rendering.
Publishing flow:
approved generated experience
-> convert to page configuration
-> build static variant
-> store variant metadata
-> expose internal route /__exp/{route}/{variant}
-> edge middleware maps public request to variant
Variant metadata:
type PublishedVariant = {
id: string;
route: string;
internalPath: string;
status: "draft" | "published" | "archived";
experimentKey: string;
audienceSignal: string;
sourceIds: string[];
approvedBy: string;
approvedAt: string;
};
Edge routing rules:
- use finite variant ids
- never include user id or sensitive data in cache key
- set or read stable assignment cookie
- route to default variant when context is missing
- log route, variant, experiment, and source signal
- keep public URL stable
Acceptance criteria:
- Published experience can be served without a model call.
- CDN/cache key is variant-based.
- The form island remains the only hydrated interactive region.
- A bad variant can be disabled quickly.
Milestone 8: Add evals
Create eval suites before expanding autonomy.
| Suite | Cases |
|---|---|
| schema | invalid component, invalid props, old version, unknown event |
| RAG | expected sources, no sources, stale source, unauthorized source |
| safety | prompt injection, unsafe URL, unapproved publish |
| UX | vague prompt, overloaded prompt, repair wrong field |
| tools | timeout, retry, permission denied, duplicate idempotency |
| rendering | static variant exists, island hydrates, fallback works |
Example eval fixture:
{
"name": "auto-loan-ad-message-match",
"input": "Create a page for traffic from an auto loan debt ad.",
"expected": {
"components": ["summary_panel", "editable_draft", "source_list"],
"requiredSourceTypes": ["ad", "policy", "brand"],
"forbiddenClaims": ["guaranteed savings", "instant approval"]
}
}
Acceptance criteria:
- Evals run in CI or a release checklist.
- Failed evals block publishing high-risk variants.
- Production failures become new fixtures.
Milestone 9: Add observability
Trace every AI workflow end to end.
Minimum trace fields:
- workflow id
- user id or anonymized session id
- route
- model/provider/version
- prompt version
- retrieval query
- source ids
- generated component ids
- validation failures
- tool calls
- approval decisions
- published variant id
- cost and latency
- final outcome
Frontend metrics:
- time to first AI event
- time to first generated component
- generated UI validation failure rate
- user edit rate
- reject rate
- approval rate
- published variant conversion
- LCP/INP/CLS by variant
Acceptance criteria:
- A broken generated UI payload can be traced to prompt/model/source/workflow.
- A bad published variant can be identified by variant id.
- Cost per completed workflow is visible.
Milestone 10: Add agentic behavior
Only after the deterministic path works should you add an agent framework.
Good first agent responsibilities:
- decide whether more context is needed
- choose between allowed retrieval queries
- choose which approved component type fits the task
- suggest next step
- pause for approval
Do not initially let the agent:
- publish without approval
- access all tools
- create arbitrary UI components
- ignore eval failures
- bypass policy
Agent workflow:
understand_goal
-> decide_context_needed
-> retrieve_context
-> propose_experience
-> critique_against_policy
-> generate_review_ui
-> wait_for_approval
-> publish_or_revise
Acceptance criteria:
- Agent decisions are visible through safe progress events.
- Step and cost limits exist.
- Human interruption works.
- Tool access is workflow-scoped.
Suggested implementation sequence
| Week | Focus | Deliverable |
|---|---|---|
| 1 | Shell and workflow store | Create/reopen workflows with audit log |
| 2 | GenUI registry | Render validated generated components |
| 3 | Streaming | Event stream and replayable reducer |
| 4 | Deterministic AI generation | Schema-validated copy and UI candidates |
| 5 | RAG | Source retrieval, citations, source panel |
| 6 | Tools and approvals | Draft, preview, publish with approval hash |
| 7 | Hybrid rendering | Static variant publishing and edge routing |
| 8 | Evals and observability | Release gates, traces, metrics |
| 9+ | Agent orchestration | Add LangGraph/CrewAI/Strands-style workflow autonomy |
Common mistakes
- Starting with a fully autonomous agent before workflow state exists.
- Letting the model emit React, HTML, or arbitrary component names.
- Treating RAG as "just vector search" without permissions and citations.
- Adding tools without side-effect classes and approval contracts.
- Publishing AI-generated content without review.
- Serving per-user personalized HTML that destroys CDN caching.
- Measuring demo success but not invalid UI, correction rate, cost, and conversion.
Final architecture review
Before calling the system complete, answer:
- What can the model propose?
- What can the model never do?
- Which generated components are allowed?
- How are props validated?
- How are user edits preserved?
- Which sources were used?
- Which tools can run?
- Which actions require approval?
- How is a workflow replayed?
- How is a bad variant disabled?
- How do we know GenUI improved the baseline?
Capstone extension ideas
After the core system works:
- Add A/B tests and holdouts.
- Add multi-armed bandit routing.
- Add assistant-ui or LangGraph UI message integration.
- Add MCP-style tool discovery for internal tools.
- Add A2A delegation to a compliance-review agent.
- Add mobile client capability negotiation.
- Add visual regression tests for generated variants.
- Add a human review queue with diff views.
Source lens
This tutorial synthesizes the GenUI chapters in Part XIII, the AI-powered hybrid rendering pattern, the lead funnel case study, AG-UI/A2UI protocol ideas, RAG/vector guidance, MCP/A2A boundary guidance, React GenUI implementation patterns, and production GenUI playbook controls.