Skip to content

Commit 3149a2b

Browse files
authored
docs: add Angular agent UI articles (#812)
1 parent 81cdc22 commit 3149a2b

2 files changed

Lines changed: 595 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
---
2+
title: 'Agentic UI in Angular: Production Patterns After the Demo'
3+
description: 'Production patterns for agentic UI in Angular: signals, tool progress, approvals, durable threads, constrained generative UI, and recovery.'
4+
date: 2026-08-09
5+
tags: [opinion, patterns, agentic-ui, ag-ui, angular, production]
6+
author: brian
7+
draft: false
8+
featured: false
9+
---
10+
11+
Agentic UI in Angular starts where the streaming chat demo ends.
12+
13+
A demo proves that tokens can reach a template.
14+
A production agent UI has to make a long-running, partially autonomous system understandable, controllable, and recoverable.
15+
16+
That difference matters.
17+
An agent can call tools, pause for a decision, change application state, and continue work after the user closes the tab.
18+
A transcript alone doesn't explain what the system is doing or give the user enough control over what happens next.
19+
20+
If you came here looking for an AG-UI Angular setup, the [fullstack AG-UI tutorial](/blog/build-fullstack-agentic-angular-apps-using-ag-ui) covers the wire-up.
21+
This post begins after that connection works.
22+
23+
For me, the production question isn't, “Can the agent stream?”
24+
It's, “Can the user understand, interrupt, resume, and trust the work?”
25+
26+
Let's look at the patterns that answer that question.
27+
28+
## When is plain chat enough?
29+
30+
Plain chat is enough more often than agent framework diagrams suggest.
31+
32+
If the experience is low-risk question answering, retrieval, or short-lived drafting, a message list and composer may be the right product.
33+
The user asks, the model responds, and a retry is an acceptable recovery path.
34+
35+
Keep it that way if you can.
36+
Every visible tool, checkpoint, approval, and generated component adds product behavior your team has to design, test, and support.
37+
38+
The boundary moves when the system starts doing work _outside_ the conversation.
39+
If it can modify data, contact another person, spend money, run for minutes, delegate work, or resume later, the UI needs more than bubbles and a spinner.
40+
41+
The spinner isn't a product model (poor spinner).
42+
43+
## Pattern 1: Put the runtime behind an Angular contract
44+
45+
The first pattern is a boring boundary, and I mean that as a compliment.
46+
47+
Your components should read messages, status, tool calls, errors, state, and interrupts from one stable interface.
48+
They should submit user intent through that same interface.
49+
They shouldn't know whether the backend emitted LangGraph stream chunks, AG-UI events, or something custom.
50+
51+
Threadplane calls this runtime-neutral boundary the [`Agent` contract](/docs/langgraph/concepts/agent-contract).
52+
Runtime adapters translate their wire format into Signals and a small action surface that chat components can consume.
53+
54+
This keeps protocol details out of your design system and route-level components.
55+
It also gives tests a clean seam: replace the contract with writable Signals instead of recreating a server stream.
56+
57+
There is a cost.
58+
A neutral contract can't pretend every runtime has the same capabilities.
59+
Checkpoint history, branching, subagents, and interrupts may be optional or adapter-specific, so feature-detect them and keep runtime-specific behavior at a deliberate edge.
60+
61+
I think that's healthier than finding AG-UI event names scattered through a dozen Angular components six months later.
62+
63+
## Pattern 2: Treat the stream as state, not text
64+
65+
Streaming text is only one projection of a run.
66+
67+
A useful read model includes the current messages, lifecycle status, active tool calls, shared state, error, and any pending interrupt.
68+
Those values change at different rates, but the template needs a coherent answer every time Angular renders.
69+
70+
Signals fit this work well.
71+
The adapter reduces runtime events into stable state, Angular tracks the parts each view reads, and `computed()` can turn those Signals into product decisions such as “can submit,” “show cancel,” or “this task is waiting for approval.”
72+
73+
The important part isn't avoiding RxJS.
74+
RxJS is still a good fit for transport streams.
75+
The important part is stopping raw event order from becoming the component API.
76+
77+
Let the adapter own accumulation, deduplication, and lifecycle transitions.
78+
Let the component read the result.
79+
The [Signals guide](/docs/langgraph/concepts/angular-signals) shows the boundary in practice.
80+
81+
The tradeoff is that normalization can hide useful runtime detail.
82+
Keep an explicit event escape hatch for information that isn't durable UI state, but don't publish messages or tool calls through two competing sources.
83+
Two sources of truth create timing bugs that are difficult to reproduce and even harder to explain to a user.
84+
85+
## Pattern 3: Make tool progress part of the product
86+
87+
Tool calls aren't developer logs.
88+
They're the part of the product that explains where the time went and what authority the agent used.
89+
90+
“Working…” tells the user almost nothing.
91+
“Searching 12 policies,” “Drafting the refund,” and “Waiting for the billing service” set an expectation and make a slow run legible.
92+
93+
Let's treat each important tool as a small state machine:
94+
95+
- what the agent intends to do;
96+
- what is running now;
97+
- what completed, with a useful result;
98+
- what failed, and whether the user can recover.
99+
100+
Raw JSON arguments usually aren't the right UI.
101+
Map high-value tools to product-specific Angular components, group repetitive background calls, and keep low-value orchestration noise out of the main reading path.
102+
Threadplane's [tool-call templates](/docs/chat/components/chat-tool-call-template) let a team replace the default card one tool at a time.
103+
104+
Custom tool UI costs more than a generic trace.
105+
Spend that effort where the result changes a user's decision, where latency is meaningful, or where a failure needs a next step.
106+
The rest can use a compact default.
107+
108+
## Pattern 4: Pause before consequential writes
109+
110+
An approval shown after a write isn't human-in-the-loop.
111+
It's a receipt.
112+
113+
For a consequential action, the backend should pause _before_ execution and persist enough state to resume from the same point.
114+
The UI should show what will change, which resource is affected, and the values the agent intends to use.
115+
Then the user can approve, reject, or edit the proposal.
116+
117+
Keep authorization on the server.
118+
An Angular approval card expresses a decision; it doesn't replace permission checks, idempotency, or an audit record.
119+
120+
Not every tool needs an interrupt.
121+
Approving every search or read turns safety into click fatigue.
122+
I prefer risk tiers: allow reversible reads, confirm sensitive or externally visible writes, and require stronger review for destructive or financial actions.
123+
124+
The interrupt and resume shape belongs on the same neutral agent boundary, while each runtime decides how to checkpoint the work.
125+
The [AG-UI approval tutorial](/blog/human-in-the-loop-ag-ui-agents-in-angular) and its [LangGraph counterpart](/blog/human-in-the-loop-langgraph-agents-in-angular) cover the implementation details.
126+
127+
## Pattern 5: Give threads durable semantics
128+
129+
A thread ID isn't just a sidebar key.
130+
It is the identity of work that may cross runs, routes, browser sessions, and deployments.
131+
132+
Decide what a thread belongs to: a user, case, project, or task.
133+
Scope access on the server, use stable identifiers, and make a reload restore the same conversation from durable backend state.
134+
135+
Let's also separate a _thread_ from a _run_.
136+
One thread can contain many attempts, tool calls, pauses, and resumptions.
137+
If those concepts collapse into one loading boolean, retry and recovery behavior becomes ambiguous.
138+
139+
Angular routing can make the active thread explicit and shareable.
140+
The URL can restore the active ID, but only the backend can restore the work behind it.
141+
The [thread-routing guide](/docs/chat/guides/thread-routing) calls out that dependency, and the [LangGraph persistence guide](/docs/langgraph/guides/persistence) covers checkpoints and thread restoration.
142+
143+
This is also where backend differences matter.
144+
The runtime-neutral `Agent` contract isn't a message database, and the AG-UI adapter doesn't currently provide LangGraph's history and time-travel APIs.
145+
Choose the adapter whose durability surface matches the product, or add an application-owned thread service instead of assuming the protocol solved persistence.
146+
147+
## Pattern 6: Let agents choose components, not invent UI
148+
149+
Generative UI gets useful when the agent can choose the right surface for the job.
150+
It gets risky when “generate a surface” means “ship arbitrary code into the application.”
151+
152+
The production pattern is a registry of approved Angular components.
153+
The agent returns a structured spec, and the frontend resolves each type against components your team owns.
154+
155+
That boundary keeps accessibility, localization, analytics, validation, and theming inside the design system.
156+
It also limits what the agent can render.
157+
An unregistered type can't instantiate an Angular component.
158+
159+
Threadplane supports this with a `ViewRegistry` for json-render and A2UI v1 surfaces.
160+
You can add, override, or remove components as the product evolves; the [generative UI guide](/docs/chat/guides/generative-ui) and [custom catalog patterns](/docs/chat/guides/custom-catalogs) show how.
161+
162+
The tradeoff is intentional constraint.
163+
A small catalog won't express every layout the model imagines, but it will produce a UI your team can test and support.
164+
For unknown or invalid specs, define a plain-text fallback and capture enough diagnostic context to fix the contract without exposing private content.
165+
166+
## Pattern 7: Design the unhappy path first
167+
168+
Agent UI failures are rarely one clean exception.
169+
A stream can stop halfway through a sentence, a tool can time out after other tools completed, an approval can outlive its session, or a saved link can point to a thread the user can't access.
170+
171+
Let's define the recovery behavior before polishing the happy path.
172+
173+
- Classify errors so the UI retries only when retrying can help.
174+
- Preserve enough completed work to explain what happened.
175+
- Give the user a safe way to stop a run.
176+
- Handle stale threads and unsupported generated components.
177+
- Decide when to fall back to plain text or a non-agent workflow.
178+
179+
The [`AgentError` model](/docs/chat/guides/error-handling) distinguishes connection, authentication, server, and interrupted failures so the UI can respond differently.
180+
User aborts settle gracefully back to idle instead of becoming errors.
181+
That is more useful than rendering `Something went wrong` for everything.
182+
183+
Testing should follow the same state model.
184+
Use a contract mock for component behavior, a fake adapter for streaming integration, and fixture replay for the small number of end-to-end paths that need the whole stack.
185+
The [AG-UI testing guide](/docs/ag-ui/guides/testing) lays out those layers.
186+
187+
Observe the transitions users feel: run duration, tool failures, interrupt wait time, retries, and thread restore failures.
188+
Keep event properties operational and out of prompt, completion, tool-input, and tool-output content unless your own policy explicitly requires otherwise.
189+
Threadplane's [browser telemetry is opt-in](/docs/telemetry/getting-started/introduction), and an app-owned sink keeps that boundary under your control.
190+
191+
## What about backend portability?
192+
193+
Backend portability is the result of these patterns, not a one-line provider swap you can assume forever.
194+
195+
If components depend on the neutral contract, tool UI depends on normalized tool state, and approvals use a common interrupt shape, then LangGraph and AG-UI backends can share most of the Angular surface.
196+
The [adapter guide](/docs/choosing-an-adapter) documents that common boundary.
197+
198+
But portability has limits.
199+
If the product depends on LangGraph checkpoint history, a runtime-specific branch model, or a custom AG-UI event, that feature needs an explicit adapter boundary and its own tests.
200+
201+
That's fine.
202+
The goal isn't to erase useful backend capabilities.
203+
It's to make the coupling visible, small, and intentional.
204+
205+
## Conclusion
206+
207+
Agentic UI in Angular isn't a more animated chat transcript.
208+
It's the product layer that turns asynchronous agent work into state a user can understand and actions a user can control.
209+
210+
Start with plain chat when plain chat is enough.
211+
When the agent gains more time, authority, or persistence, add the patterns that make those capabilities legible: a neutral contract, Signals, meaningful tool progress, approvals, durable threads, constrained components, and rehearsed recovery.
212+
213+
These are the production patterns I think are worth carrying into an Angular architecture review.
214+
If your team has found another one, I'd like to hear what made the difference.

0 commit comments

Comments
 (0)