Agentic UI Implementation Patterns: How to Design Agent Trust with Plan-and-Execute, Human-in-the-Loop, and Real-Time Streaming
When Gartner predicted that 40% of enterprise applications would integrate task-specialized AI agents by the end of 2026, I thought, "No way." Seeing that number — which was under 5% in early 2025 — skyrocket at this pace makes it feel less like a distant future. More striking than the speed of change is how it's changing.
Honestly, when I first heard "agents build the UI," I was skeptical. I thought, "Isn't this just a smarter chatbot?" But when I actually looked into CopilotKit and LangGraph-based projects, I could feel the paradigm itself shifting. Where traditional AI would dump text at you saying "here's the output," Agentic UI has the agent directly rendering forms, generating buttons, and updating state to complete the task.
This article focuses on the React + TypeScript environment, but it's directly relevant to agent backend developers as well. Agentic UI isn't just a frontend story — it's a problem of redesigning the entire communication layer between agent and UI. In this article, we'll look at three core design principles of Agentic UI and implementation patterns validated in production, complete with code.
Core Concepts
The Difference Between AI That "Acts" and AI That "Suggests"
The difference between traditional generative AI and Agentic UI can be summed up in one sentence:
Generative AI says "Let me tell you how to do it," while Agentic UI just does it.
Technically, this means a unidirectional request-response model is replaced by a long-running event stream. The agent doesn't end with a single response — it executes multi-step tasks while updating the UI in real time.
| Category | Traditional Gen AI | Agentic UI |
|---|---|---|
| Interaction model | Request → text response | Long-running streaming session |
| UI role | Fixed chat interface | Dynamically generated and patched by the agent |
| User role | Director | Collaborator (approve, modify, delegate) |
| Executor | Human | Agent (with user oversight) |
The Agentic UI Lifecycle: Breaking It Into 3 Stages
The process by which an agent performs work can be divided into three stages. Clarifying this distinction in practice makes it much clearer where to apply which UI patterns.
| Stage | Purpose | Key Patterns |
|---|---|---|
| Pre-Action | Establish user consent and scope | Plan visualization, approval requests |
| In-Action | Provide real-time execution context | Expose reasoning, display confidence signals |
| Post-Action | Ensure safety and recovery | Execution audit log, Undo, escalation |
The key in the Pre-Action stage is having the agent first ask, "Here's what I'm going to do — is that okay?" Skipping this stage makes it hard for users to trust the agent, and in practice this is the single biggest source of complaints after deployment. Similarly, including Undo and audit logs as default features in Post-Action design from the start is equally important — errors are inevitable, so recovery mechanisms need to be there from the beginning.
The Protocol Landscape in 2026
There is fierce competition to standardize how agents and frontends communicate. I was initially confused seeing AG-UI, A2UI, MCP, and A2A all at once, but once you understand the role of each protocol, it becomes quite clear.
- MCP (Anthropic): The tool integration layer for agents calling external APIs and databases
- A2A (Google): The agent-to-agent communication protocol for multiple agents collaborating
- AG-UI (open source, initial implementation led by CopilotKit): The real-time event streaming standard between agents and frontends
- A2UI (Google, 2026): A declarative data format for agents to describe UI appropriate to the conversational context
3-layer production stack: It helps to think of the layers as MCP (tool integration) + A2A (agent collaboration) + AG-UI (agent ↔ frontend).
AG-UI handles text streaming, tool calls, state synchronization, and execution control via 17 event types. Major agent frameworks including LangChain, CrewAI, Mastra, and PydanticAI have official support, and several cloud vendors are also moving toward adding AG-UI endpoint support.
A2UI is an open-source project released by Google. Its security design differentiator is that it describes UI as a data format rather than executable code, structurally blocking UI injection attacks. More on what UI injection is later.
The Generative UI Spectrum
There are three modes depending on how freely an agent can generate UI. None is definitively the right answer, and in actual production, all three often coexist within a single product.
| Mode | Control Level | Approach | Representative Tools |
|---|---|---|---|
| Controlled Generative UI | High | Agent selects and renders pre-built components | AG-UI, useFrontendTool |
| Declarative Generative UI | Medium | Agent describes components via declarative data | A2UI, Open-JSON-UI |
| Open-ended Generative UI | Low | Agent generates UI completely freely | MCP Apps, Custom UI |
Now let's look at how to implement these concepts with real code. I've prepared three examples, each corresponding to one of the three lifecycle stages above.
Practical Application
Example 1: Plan-and-Execute Pattern (Pre-Action)
This is the most battle-tested pattern in practice. The agent shows the user a step-by-step execution plan before starting work, and execution begins only after the user modifies or approves it.
Using CopilotKit's useCopilotAction, when the agent backend sends a plan, the frontend can immediately render a custom component and relay the user's response back to the agent. The interesting part is renderAndWaitForResponse — unlike the traditional useState + fetch pattern, this option automatically pauses agent execution the moment the UI is rendered. The backend agent waits in place until the user calls respond().
import { useCopilotAction } from "@copilotkit/react-core";
interface PlanStep {
id: string;
description: string;
estimatedDuration?: string;
}
type PlanResponse = {
approved: boolean;
steps?: PlanStep[];
rejectionReason?: string;
};
function AgentWorkspace() {
useCopilotAction({
name: "presentExecutionPlan",
description: "실행 계획을 사용자에게 제시하고 승인을 기다립니다",
parameters: [
{
name: "steps",
type: "object[]",
description: "실행할 단계 목록",
attributes: [
{ name: "id", type: "string" },
{ name: "description", type: "string" },
{ name: "estimatedDuration", type: "string", required: false },
],
},
{
name: "taskSummary",
type: "string",
description: "전체 작업 요약",
},
],
renderAndWaitForResponse: ({
steps,
taskSummary,
respond,
}: {
steps: PlanStep[];
taskSummary: string;
respond: (response: PlanResponse) => void;
}) => (
<PlanApprovalCard
steps={steps}
taskSummary={taskSummary}
onApprove={(modifiedSteps) =>
respond({ approved: true, steps: modifiedSteps })
}
onReject={(reason) =>
respond({ approved: false, rejectionReason: reason })
}
/>
),
});
return <div>{/* 에이전트 작업 공간 UI */}</div>;
}| Code Point | Role |
|---|---|
renderAndWaitForResponse |
Pauses agent execution at the same moment the UI renders |
respond: (response: PlanResponse) => void |
Typed callback — relays user response to the agent backend |
modifiedSteps |
Gives the user the ability to directly modify the plan before approving |
Example 2: Displaying Agent Progress in Real Time with AG-UI Event Stream (In-Action)
Transparently showing the user "what's happening right now" while the agent executes a multi-step task is the key to building trust. You can handle events by directly using the AG-UI protocol.
When reflecting agent internal state to the UI in real time via StateDelta events, JSON Patch operations are required. Here we use applyPatch from the fast-json-patch library.
// npm install fast-json-patch @ag-ui/client
import { applyPatch } from "fast-json-patch";
import { AbstractAgent, EventType } from "@ag-ui/client";
import { useState } from "react";
function useAgentStream(agentUrl: string) {
const [streamingText, setStreamingText] = useState("");
const [activeToolCall, setActiveToolCall] = useState<string | null>(null);
const [agentState, setAgentState] = useState<Record<string, unknown>>({});
const [error, setError] = useState<string | null>(null);
const runAgent = async (userMessage: string) => {
setError(null);
const agent = new AbstractAgent({ url: agentUrl });
// 텍스트 스트리밍: 에이전트 응답을 실시간으로 출력
agent.on(EventType.TextMessageContent, (event) => {
setStreamingText((prev) => prev + event.delta);
});
// 툴 콜: 에이전트가 외부 API를 호출할 때 인디케이터 표시
agent.on(EventType.ToolCallStart, (event) => {
setActiveToolCall(event.toolCallName);
});
agent.on(EventType.ToolCallEnd, () => {
setActiveToolCall(null);
});
// StateDelta: JSON Patch 형식으로 에이전트 상태를 점진적으로 동기화
// applyPatch는 document를 in-place 변경하므로 먼저 clone
agent.on(EventType.StateDelta, (event) => {
setAgentState((prev) => {
const cloned = structuredClone(prev);
applyPatch(cloned, event.delta);
return cloned;
});
});
agent.on(EventType.RunFinished, () => {
setStreamingText("");
});
try {
await agent.run({
messages: [{ role: "user", content: userMessage }],
});
} catch (err) {
const message =
err instanceof Error
? err.message
: "에이전트 실행 중 오류가 발생했습니다";
setError(message);
setActiveToolCall(null);
}
};
return { streamingText, activeToolCall, agentState, error, runAgent };
}With this setup, when the agent queries a database or calls an external API, you can provide real-time feedback like "Fetching stock data now..." through the activeToolCall state. From the user's perspective, the uncertainty of "Did my request even go through?" disappears.
One practical tip: due to the SSE (Server-Sent Events)-based nature of AG-UI streaming, if you don't explicitly close the connection when a component unmounts, you can get memory leaks. It's recommended to add agent.abort() or equivalent connection teardown logic in the useEffect cleanup function.
Example 3: Human-in-the-Loop — Approval Gate Before High-Risk Actions (Before Entering Post-Action)
Actions that are hard to reverse — payments, sending emails, external API calls — must have a user approval step. CopilotKit provides this pattern as a built-in feature by integrating with LangGraph's interrupt node.
type ApprovalResponse = {
approved: boolean;
reason?: string;
};
useCopilotAction({
name: "requestHighRiskApproval",
description: "고위험 작업 실행 전 사용자 명시적 동의를 요청합니다",
parameters: [
{ name: "actionType", type: "string" }, // "payment" | "email" | "api_call"
{ name: "actionDetails", type: "string" }, // 사람이 읽을 수 있는 설명
{ name: "riskLevel", type: "string" }, // "medium" | "high" | "critical"
{ name: "reversible", type: "boolean" }, // 실행 취소 가능 여부
],
renderAndWaitForResponse: ({
actionType,
actionDetails,
riskLevel,
reversible,
respond,
}: {
actionType: string;
actionDetails: string;
riskLevel: string;
reversible: boolean;
respond: (response: ApprovalResponse) => void;
}) => (
<ApprovalGate
actionType={actionType}
actionDetails={actionDetails}
riskLevel={riskLevel}
isReversible={reversible}
onApprove={() => respond({ approved: true })}
onDeny={(reason) => respond({ approved: false, reason })}
/>
),
});interface ApprovalGateProps {
riskLevel: string;
isReversible: boolean;
actionDetails: string;
onApprove: () => void;
onDeny: (reason: string) => void;
}
function ApprovalGate({
riskLevel,
isReversible,
actionDetails,
onApprove,
onDeny,
}: ApprovalGateProps) {
return (
<div className={`approval-gate risk-${riskLevel}`}>
<RiskBadge level={riskLevel} />
<p>{actionDetails}</p>
{!isReversible && (
<WarningBanner message="이 작업은 실행 후 되돌릴 수 없습니다" />
)}
<div className="actions">
<button onClick={onApprove}>승인하고 실행</button>
<button onClick={() => onDeny("사용자가 거부")}>거부</button>
</div>
</div>
);
}Surfacing the reversible flag explicitly in the UI may seem like a small detail, but it has a real impact on user trust. Visually distinguishing "this can be undone" from "this cannot be reversed" makes users more comfortable delegating authority to the agent.
Pros and Cons
Advantages
Honestly, the biggest benefit of Agentic UI isn't productivity gains or adaptive UX — it's that "the experience of waiting itself disappears." Thanks to streaming events, while the agent processes a 5-minute multi-step task, the user can watch progress in real time, and that directly translates to trust.
| Item | Detail |
|---|---|
| Productivity gains | Agents handle repetitive, multi-step tasks on the user's behalf, dramatically reducing workload |
| Adaptive UX | UI changes dynamically with context, providing an experience optimized for each user and scenario |
| Framework independence | AG-UI-based architecture is not locked to a specific vendor and connects with any agent backend |
| Real-time visibility | Streaming events immediately display agent progress, eliminating the uncertainty of waiting |
Disadvantages and Caveats
According to PwC and CIO research, only 8% of users trust fully autonomous agents. That number was quite a shock to me as well — no matter how advanced the technology gets, without trust, users ultimately won't use agents. All of the caveats below are connected to this trust problem.
| Item | Detail | Mitigation |
|---|---|---|
| Lack of trust | Only a minority of users feel comfortable with fully autonomous agents | Apply a gradual delegation model; always place HITL gates on high-risk actions |
| Black box problem | Opaque reasoning means even accurate results won't be trusted | Expose agent reasoning in the UI during the In-Action stage |
| Security vulnerabilities | Risks of UI injection, prompt injection, and data exfiltration | Use declarative data formats (A2UI), strengthen input validation |
| Unpredictability | The same input can produce different execution paths | Design UI for non-deterministic behavior; provide error recovery paths by default |
| Governance complexity | As agent count grows, traditional IT oversight methods become unmanageable | Build execution audit logs in by default; design escalation paths |
UI Injection: An attack where a malicious prompt manipulates the UI itself through the agent. For example, during a document-reading process, instructions embedded in that document alter agent behavior to render fake buttons or forms. This is exactly why A2UI uses a data format instead of executable code.
The Most Common Mistakes in Practice
-
Deprioritizing Post-Action design: Assuming "the agent will handle it fine" and tacking on Undo functionality or audit logs as an afterthought leads to situations where, when the agent does something unintended, there's no recovery mechanism. Since errors are inevitable, it's far better to include recovery paths as default features from the start.
-
Hiding the agent's reasoning process: Deciding that "users don't need to see the internal logic" and not exposing why the agent took a given action in the UI means that even technically perfect results won't be trusted. A single line like "Currently querying customer database" makes a significant difference in perceived trustworthiness.
-
Jumping straight to fully autonomous mode: It's much smoother to start with the agent at a suggestion level, and then gradually increase the autonomy level as the user builds trust. If you give full permissions from the first deployment and users develop resistance, the distrust that forms is very hard to reverse.
Closing Thoughts
The core of Agentic UI lies not in how smart the agent is, but in how much users can trust it. Once you've actually gone through deployment, you come to feel that transparency and control design separate user reactions far more dramatically than technical completeness. "What is the agent doing right now?" and "Can I cancel this action?" — whether the UI can answer these two questions clearly is what ultimately determines real adoption. The Plan-and-Execute pattern, Human-in-the-Loop gates, and real-time event streaming are the concrete methods for designing that trust at the UI level.
Three steps you can start with right now:
-
Experience your first Agentic UI with CopilotKit: Run
npx create-copilotkit-app@latestto generate a project and you'll get a starter withuseCopilotActionalready set up. Try building a simple approval UI withrenderAndWaitForResponsethere — you'll quickly get a feel for how Agentic UI works. -
Layer the Plan-and-Execute pattern onto an existing LLM feature: If you already have a project using LLMs, you can start by adding a single plan confirmation step just before a multi-step task using
useCopilotAction. If you're using LangGraph, you can implement the same pattern at the backend level using aninterruptnode. Without major architectural changes, you'll be able to see user trust shift. -
Browse the AG-UI event type documentation: Looking through the 17 event types in the AG-UI official docs will give you a mental model for "how does an agent communicate with UI?" In particular,
StateDeltaandToolCallStart/Endare the events most frequently used in real implementations.
References
- Designing For Agentic AI: Practical UX Patterns | Smashing Magazine
- Beyond Generative: The Rise Of Agentic AI And User-Centric Design | Smashing Magazine
- AG-UI Overview | Agent User Interaction Protocol 공식 문서
- Introducing A2UI: An open project for agent-driven interfaces | Google Developers Blog
- The Developer's Guide to Generative UI in 2026 | CopilotKit Blog
- The State of Agentic UI: Comparing AG-UI, MCP-UI, and A2UI | CopilotKit
- Understanding AG-UI: The Standard for Agentic User Interfaces | ANGULARarchitects
- Generative UI: Understanding Agent-Powered Interfaces | CopilotKit
- Agentic AI Design Patterns: Enterprise Guide | AuFait UX
- Agentic AI UX Design: 5 UX Patterns That Work | Onething Design
- Production-Grade Agentic Apps with AG-UI: Real-Time Streaming Guide 2026 | Medium
- Building interactive agentic applications using ADK and AG-UI | Google Cloud Blog
- Secrets of Agentic UX: Emerging Design Patterns | UX Magazine
- Agentic AI has big trust issues | CIO
- The rise and risks of agentic AI | PwC
- CopilotKit GitHub Repository