Skip to main content

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:

SurfacePurpose
Intent intakeUser describes campaign, audience, offer, or workflow goal
Agent workspaceStreams progress, retrieval, tool calls, and generated UI proposals
Review and approvalHuman edits, rejects, approves, or publishes generated experiences
Published experienceStatic 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

Use tools you can actually run and inspect. The exact choices can vary, but the architecture should remain the same.

LayerSuggested choiceWhy
App frameworkTanStack Start or Next.jsTeaches routing, server functions, streaming, and hybrid rendering
UIReact + design-system primitivesGenUI needs a controlled component registry
ValidationZod or JSON SchemaGenerated UI payloads must be validated
Agent workflowLangGraph, CrewAI Flow, or simple typed workflow firstStart deterministic, then add agent behavior
Model gatewayServer-side provider wrapperAvoid direct browser-to-model calls
RAG storePostgres + pgvector, Pinecone, or WeaviateTeaches retrieval, metadata, citations
Tool layerInternal typed tools; MCP-style interface laterLearn tool safety before protocol abstraction
ExperimentationSimple variant assignment first; Statsig-style platform laterLearn cache-safe decisions
ObservabilityStructured logs + traces + RUMDebug across UI, workflow, model, tools
EvalsFixture-based test runnerCatch 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:

  • /studio for the agent workspace
  • /studio/:workflowId for resumable workflows
  • /preview/:variantId for generated experience preview
  • /publish/:variantId or 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:

ComponentPurpose
summary_panelExplain generated recommendation
source_listShow retrieved evidence
editable_draftLet user edit generated copy
comparison_tableCompare generated options
approval_actionRequire explicit human approval
published_previewShow 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:

OperationUse
appendNew message or generated component
patchProgress update
replaceRegenerate unedited draft
removeRemove temporary UI
commitMark 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:

StateUI behavior
strong contextshow generated answer with citations
partial contextshow coverage warning
conflicting contextshow source disagreement
no contextask a clarifying question
unauthorized contextexplain access limitation
stale contextshow 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:

ToolRiskApproval
draft_landing_pagedraft onlyno
create_variant_previewinternal writeyes or role-based
publish_variantpublic changeyes
send_test_eventanalytics testno
archive_variantdestructive/public impactyes

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.

SuiteCases
schemainvalid component, invalid props, old version, unknown event
RAGexpected sources, no sources, stale source, unauthorized source
safetyprompt injection, unsafe URL, unapproved publish
UXvague prompt, overloaded prompt, repair wrong field
toolstimeout, retry, permission denied, duplicate idempotency
renderingstatic 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

WeekFocusDeliverable
1Shell and workflow storeCreate/reopen workflows with audit log
2GenUI registryRender validated generated components
3StreamingEvent stream and replayable reducer
4Deterministic AI generationSchema-validated copy and UI candidates
5RAGSource retrieval, citations, source panel
6Tools and approvalsDraft, preview, publish with approval hash
7Hybrid renderingStatic variant publishing and edge routing
8Evals and observabilityRelease gates, traces, metrics
9+Agent orchestrationAdd 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.