<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brian Love</title>
    <description>The latest articles on DEV Community by Brian Love (@blove).</description>
    <link>https://dev.to/blove</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F290639%2F709928e7-e5b7-47d6-811c-0156c4f1e45e.jpg</url>
      <title>DEV Community: Brian Love</title>
      <link>https://dev.to/blove</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/blove"/>
    <language>en</language>
    <item>
      <title>LangGraph Subgraphs: When to Split a Graph and When Not To</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Tue, 03 Nov 2026 16:00:41 +0000</pubDate>
      <link>https://dev.to/blove/langgraph-subgraphs-when-to-split-a-graph-and-when-not-to-1ep4</link>
      <guid>https://dev.to/blove/langgraph-subgraphs-when-to-split-a-graph-and-when-not-to-1ep4</guid>
      <description>&lt;p&gt;Most people reach for a LangGraph subgraph expecting a &lt;em&gt;state&lt;/em&gt; boundary, and what they actually get is an &lt;em&gt;observable&lt;/em&gt; one.&lt;/p&gt;

&lt;p&gt;If your question is "single agent, approval loop, or multi-agent?", that's an architecture question, and the &lt;a href="https://threadplane.ai/docs/langgraph/concepts/agent-architecture" rel="noopener noreferrer"&gt;decision matrix&lt;/a&gt; already answers it.&lt;br&gt;
This post is about the layer underneath: what a subgraph actually changes at runtime, why our own graphs got split, and what the frontend sees while a child is running.&lt;/p&gt;
&lt;h2&gt;
  
  
  What does a subgraph actually give you?
&lt;/h2&gt;

&lt;p&gt;Nested execution and namespaced stream events.&lt;br&gt;
That's the whole list.&lt;/p&gt;

&lt;p&gt;Let's start with the canonical pattern, which is small.&lt;br&gt;
Compile a child &lt;code&gt;StateGraph&lt;/code&gt;, then add the compiled graph as a node in the parent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;research_builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MessagesState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;research_builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;research_builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;research_subgraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;research_builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MessagesState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;research_subgraph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# a compiled graph, used as a node
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things change.&lt;br&gt;
The child runs as its own graph, with its own nodes and its own step sequence, rather than being flattened into the parent's.&lt;br&gt;
And LangGraph emits the child's stream events under a namespace, so a consumer can tell parent output from child output.&lt;/p&gt;

&lt;p&gt;I think state isolation gets assumed as a third thing, and it shouldn't be.&lt;/p&gt;

&lt;p&gt;If parent and child share &lt;code&gt;MessagesState&lt;/code&gt;, the child appends to the same message list the parent is building.&lt;br&gt;
Nothing about &lt;code&gt;add_node&lt;/code&gt; fenced anything off.&lt;/p&gt;

&lt;p&gt;Isolation is something you design.&lt;br&gt;
Give the child its own state schema, then map in at the boundary and map the result back out.&lt;br&gt;
That's a decision you make and maintain, not a property &lt;code&gt;compile()&lt;/code&gt; hands you.&lt;/p&gt;

&lt;p&gt;We ship one graph that does exactly that, and because it's a capability demo built to show the primitive, it's a clean look at the shape.&lt;br&gt;
Its child state schema has no &lt;code&gt;messages&lt;/code&gt; key at all.&lt;br&gt;
Parent and child share exactly two keys, &lt;code&gt;research_topic&lt;/code&gt; and &lt;code&gt;research_brief&lt;/code&gt;, so the child is handed a topic and hands back a brief.&lt;br&gt;
It can't read the transcript, and it can't append to one.&lt;/p&gt;

&lt;p&gt;That boundary is real, and none of it came from &lt;code&gt;compile()&lt;/code&gt;.&lt;br&gt;
It came from writing two &lt;code&gt;TypedDict&lt;/code&gt;s and being deliberate about what they share.&lt;/p&gt;
&lt;h3&gt;
  
  
  What about context windows and error boundaries?
&lt;/h3&gt;

&lt;p&gt;Those are real reasons to split (our own docs lean on them).&lt;br&gt;
The &lt;a href="https://threadplane.ai/docs/langgraph/guides/subgraphs" rel="noopener noreferrer"&gt;subgraphs guide&lt;/a&gt; points at per-task context windows and failure containment as reasons to reach for subagents, and the docstring on our own research child's only node calls it "a focused contractor."&lt;/p&gt;

&lt;p&gt;But let's look at where each one actually comes from.&lt;br&gt;
A narrow context window is a consequence of what you pass into the child's &lt;code&gt;ainvoke&lt;/code&gt;; you get it by handing over a topic instead of a transcript.&lt;br&gt;
An error boundary is a consequence of how the parent handles a failed child call, and a node-level retry wraps any node, plain function or compiled graph alike.&lt;br&gt;
Reuse across parents is a consequence of the child being a value you can reference twice.&lt;/p&gt;

&lt;p&gt;You can have all three without ever compiling a child graph, and you can compile a child graph and get none of them.&lt;/p&gt;

&lt;p&gt;There's one more, and it's worth stating because it looks like a counterexample.&lt;br&gt;
Wire the child in as a node under a parent that has a checkpointer, and the child's steps get checkpointed under its namespace, which is what lets you interrupt and resume at child granularity.&lt;br&gt;
That's the namespace again, doing a second job.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why do people really split?
&lt;/h2&gt;

&lt;p&gt;In our own repo, the honest answer is: so the frontend can see the delegation.&lt;/p&gt;

&lt;p&gt;That's a claim about our own graphs, not a law of the framework (and one of them splits for a different reason entirely, which I'll get to).&lt;br&gt;
But it's a natural experiment rather than a portfolio.&lt;br&gt;
Nobody wrote these to prove a point about subgraphs, and the constraint that drove them, a frontend that renders per-child progress, isn't specific to us.&lt;/p&gt;

&lt;p&gt;Here's the comment we wrote at the time, sitting above the research subagent in our canonical &lt;code&gt;examples/chat&lt;/code&gt; graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Research subagent — a small compiled child graph the parent dispatches
# via the `research` @tool. Running it as an actual subgraph (vs. inline
# logic) is what causes LangGraph to emit stream events under namespace
# prefix `tools:&amp;lt;id&amp;gt;` for the child run, which is what the @threadplane/langgraph
# SubagentTracker keys on to populate `agent.subagents()`.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a state argument.&lt;br&gt;
It's a visibility argument.&lt;/p&gt;

&lt;p&gt;The design doc for that feature is blunter still.&lt;br&gt;
Here's the alternative it rejected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plain `@tool` returning a synthesized "subagent" payload — Simpler graph
code but does not exercise the SubagentTracker code path: no `tools:`
namespace events get emitted because no subgraph runs. The card would
render empty. Rejected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;cockpit/chat/subagents&lt;/code&gt; demo makes the same call.&lt;br&gt;
Its three specialists dispatch through a real compiled child graph because the subgraph run is what emits namespace events, and the namespace events are what the tracker turns into per-child progress.&lt;br&gt;
Run the specialists as a flat in-process helper and the feature still works; the UI just can't see it working.&lt;/p&gt;

&lt;p&gt;In both of those graphs the compiled child is invoked from inside a &lt;code&gt;@tool&lt;/code&gt; body, not wired in as a plain node.&lt;br&gt;
That's deliberate: the tool call carries the identity, an id the tracker can attribute the child's stream to, and a &lt;code&gt;subagent_type&lt;/code&gt; to name it.&lt;/p&gt;

&lt;p&gt;A plain &lt;code&gt;add_node&lt;/code&gt; subgraph has an identity too, just a thinner one.&lt;br&gt;
Its namespace segment is unique per invocation and prefixed with the node name, so it registers in &lt;code&gt;subagents()&lt;/code&gt; under its namespace key the moment it first streams, named by its node.&lt;br&gt;
The subgraph is what makes the events observable; the tool call upgrades that identity from a node name to a real delegation record, with arguments a UI can render.&lt;/p&gt;
&lt;h2&gt;
  
  
  What does the frontend see while a child runs?
&lt;/h2&gt;

&lt;p&gt;Namespaced events, and nearly everything interesting downstream follows from that one fact.&lt;/p&gt;
&lt;h3&gt;
  
  
  What does the wire look like?
&lt;/h3&gt;

&lt;p&gt;The event type carries the namespace after a pipe, so the base type is the part before it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;messages                 # parent
messages|tools:&amp;lt;uuid&amp;gt;    # a child run, streaming under its own namespace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our transport requests those child streams by default: &lt;code&gt;streamSubgraphs&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; unless you turn it off.&lt;br&gt;
That's the LangGraph JS SDK's own option name, passed straight through, and it's worth knowing if you're coming from the Python API, where the in-process &lt;code&gt;graph.stream()&lt;/code&gt; equivalent is the &lt;code&gt;subgraphs=True&lt;/code&gt; kwarg.&lt;/p&gt;
&lt;h3&gt;
  
  
  What's the terminal-event hazard?
&lt;/h3&gt;

&lt;p&gt;A child graph terminates before the parent does, and a child's terminal event looks an awful lot like the parent's.&lt;/p&gt;

&lt;p&gt;Without a namespace guard, that child terminal marker gets read as "the run finished" and closes out the parent's still-streaming assistant message.&lt;br&gt;
We guard it by refusing namespaced events as top-level terminal evidence, and there's a test that feeds a namespaced terminal marker in and asserts the parent message settles with outcome &lt;code&gt;interrupted&lt;/code&gt; rather than success.&lt;/p&gt;

&lt;p&gt;If you ever write a transport against this stream yourself, that's the bug you'll hit, and it'll look like truncation rather than a namespace bug.&lt;/p&gt;
&lt;h3&gt;
  
  
  Where does child text go?
&lt;/h3&gt;

&lt;p&gt;Onto the child's stream, and nowhere else.&lt;/p&gt;

&lt;p&gt;Any consumer reading a namespaced stream has to decide what a child's tokens mean, and "append them like everything else" is the path of least resistance.&lt;br&gt;
It's also a trap, and a well-hidden one.&lt;br&gt;
Merge a child's tokens into the transcript and its internal notes render as their own chat bubble mid-stream.&lt;br&gt;
Then the parent's final &lt;code&gt;values&lt;/code&gt; event rewrites the message list from authoritative graph state, and the stray bubble disappears on its own.&lt;br&gt;
The end state looks right.&lt;br&gt;
The streaming pass didn't.&lt;/p&gt;

&lt;p&gt;So we don't make it a decision at all.&lt;br&gt;
A namespaced event belongs to its child, structurally: it feeds that child's &lt;code&gt;messages()&lt;/code&gt; on the subagent stream and never merges into the parent transcript.&lt;br&gt;
There's no opt-out flag because there's nothing to opt out of.&lt;br&gt;
What the transcript shows at settle is decided by state: a shared &lt;code&gt;messages&lt;/code&gt; key delivers the child's message through the final &lt;code&gt;values&lt;/code&gt; sync, and an isolated child schema means it never arrives.&lt;br&gt;
&lt;code&gt;transcriptNodeNames&lt;/code&gt; still exists for the genuinely separate problem of &lt;em&gt;top-level&lt;/em&gt; side-effect nodes, like routers and title generators.&lt;/p&gt;
&lt;h3&gt;
  
  
  How does a child get attributed?
&lt;/h3&gt;

&lt;p&gt;By an announced binding, and for me this is the part of the design I'd steal for any protocol.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tools:&amp;lt;uuid&amp;gt;&lt;/code&gt; namespace a child streams under is a checkpoint id, assigned independently of the parent's tool-call id.&lt;br&gt;
Nothing on the wire links the two.&lt;br&gt;
But the server knows both halves.&lt;br&gt;
So the graph announces the pair: one custom event pairing the child's checkpoint namespace with its tool-call id, emitted from inside the tool body.&lt;br&gt;
The tracker treats that binding as authoritative.&lt;br&gt;
It never overrides an established mapping, it works with any number of children in flight, and it replays any chunks that streamed before the binding arrived.&lt;/p&gt;

&lt;p&gt;There's also a description-comparison ladder for graphs that don't announce: exact match on the tool call's &lt;code&gt;description&lt;/code&gt; argument, then substring either direction, then a positional fallback that only fires when exactly one unmapped subagent is still pending or running.&lt;br&gt;
That last rung is deliberately conservative.&lt;br&gt;
With parallel children in flight, guessing would cross-wire one card's output into another, so an unattributed stream stays buffered instead.&lt;br&gt;
An empty card beats a confidently wrong one.&lt;/p&gt;

&lt;p&gt;The general point is worth carrying to any protocol.&lt;br&gt;
A consumer mapping child runs onto delegations is doing string matching unless something hands it an id.&lt;br&gt;
Here the graph hands it one, which is why the ladder is a fallback, and why it would be load-bearing in a stack that can't announce.&lt;/p&gt;

&lt;p&gt;Nesting is worth knowing about too.&lt;br&gt;
A subagent that itself delegates gets its own entry in &lt;code&gt;subagents()&lt;/code&gt;, keyed by its namespace path, the same way a plain subgraph node does.&lt;br&gt;
Each level of delegation surfaces as its own stream; the map stays flat, so there's no parent/child tree to walk.&lt;/p&gt;
&lt;h2&gt;
  
  
  When should you not split?
&lt;/h2&gt;

&lt;p&gt;When there's no observable boundary to draw and no genuinely divergent state.&lt;/p&gt;

&lt;p&gt;The cleanest evidence I have is a control group we didn't set out to build.&lt;br&gt;
Our &lt;code&gt;cockpit/ag-ui/subagents&lt;/code&gt; capability ships the same three-subagent feature as the LangGraph one.&lt;br&gt;
It's a LangGraph &lt;code&gt;StateGraph&lt;/code&gt; too: same framework, same orchestrator-plus-&lt;code&gt;task&lt;/code&gt;-tool shape, same three roles, same cards in the UI, with no subgraph anywhere.&lt;br&gt;
Its module docstring says so outright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mirrors cockpit/chat/subagents' orchestrator + `task` tool + `_run_subagent`
structure, but each dispatch emits `subagent_activity` CUSTOM events [...]
The backend's SubagentEmittingAgent expands those CUSTOM events into the
protocol's standard SUBAGENT_STARTED / TEXT_MESSAGE_* (attributed via
subagentRunId) / SUBAGENT_FINISHED / SUBAGENT_ERROR events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thing that differs is the transport.&lt;br&gt;
AG-UI already carries first-class delegation events: &lt;code&gt;SUBAGENT_STARTED&lt;/code&gt;, &lt;code&gt;SUBAGENT_FINISHED&lt;/code&gt;, and content events attributed to a child run.&lt;br&gt;
So the specialists stayed a flat &lt;code&gt;async&lt;/code&gt; helper, the tool body dispatches its progress as custom events, and a thin wrapper on the server expands those into the protocol's standard subagent events on the wire.&lt;/p&gt;

&lt;p&gt;The subgraph was never required by the feature.&lt;br&gt;
It was required by the transport.&lt;/p&gt;

&lt;p&gt;You could dispatch custom events from the LangGraph graph too.&lt;br&gt;
Nothing stops you, and &lt;code&gt;adispatch_custom_event&lt;/code&gt; is a LangChain primitive, not an AG-UI one.&lt;br&gt;
What namespaces buy is that you don't have to.&lt;br&gt;
The boundary emits its own identity for free, and a transport that reads it works against any graph rather than any graph that remembered to instrument itself.&lt;/p&gt;

&lt;p&gt;Staying flat wasn't free.&lt;br&gt;
There's no separate state schema to isolate anything into, and no child step sequence; every specialist gets the parent's shape, one LLM call wide.&lt;br&gt;
What it bought was one fewer graph for a feature that renders identically.&lt;/p&gt;

&lt;p&gt;For me, that's the test.&lt;br&gt;
If your transport already has a way to say "a child is working right now," or your UI doesn't render per-child progress at all, then a subgraph is a boundary you now have to defend: an extra state schema, mapping at both edges, and one more place to look when a message goes missing.&lt;/p&gt;

&lt;p&gt;And splitting because a region of the graph &lt;em&gt;feels&lt;/em&gt; like a separate concern isn't a reason on its own.&lt;br&gt;
A node is already a unit.&lt;/p&gt;

&lt;h3&gt;
  
  
  So when does a split earn itself?
&lt;/h3&gt;

&lt;p&gt;When the child really is a different graph.&lt;br&gt;
The repo has exactly one of those, and after arguing the other side this whole time, it's only fair to show it.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;examples/ag-ui&lt;/code&gt; demo runs on that same AG-UI transport, and its research tool reaches the frontend the same way: the protocol's standard &lt;code&gt;SUBAGENT_*&lt;/code&gt; events, with the child's messages and its own &lt;code&gt;lookup&lt;/code&gt; tool call attributed to the child run.&lt;br&gt;
So it isn't buying observability.&lt;br&gt;
It already had it.&lt;br&gt;
It compiles a child graph anyway.&lt;/p&gt;

&lt;p&gt;Let's look at what the child is, though.&lt;br&gt;
It has its own &lt;code&gt;agent → tools → agent&lt;/code&gt; loop with conditional edges and an iteration cap: a different control flow from the parent's, not a slice of it.&lt;/p&gt;

&lt;p&gt;This part took me a second read to see.&lt;br&gt;
A custom child state schema doesn't discriminate at all: the two graphs I just used as observability evidence &lt;em&gt;also&lt;/em&gt; define their own child &lt;code&gt;TypedDict&lt;/code&gt;s.&lt;br&gt;
But both of those children are one node and a straight line, so the schema is really just an argument list with a type on it.&lt;/p&gt;

&lt;p&gt;So it's the control flow, not the schema.&lt;br&gt;
A child that carries a &lt;code&gt;topic&lt;/code&gt; string is a function call wearing a graph costume.&lt;br&gt;
A child that loops until it's satisfied is a graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Split when something outside the graph needs to see the child run as its own thing: a card, a progress panel, per-child streaming.&lt;br&gt;
Split when the child has its own control flow (a loop, a branch, a stopping condition the parent doesn't have) and you're willing to own the mapping at both edges.&lt;br&gt;
Don't split for tidiness, and don't assume the split isolated state: wire a child in as a node on a shared &lt;code&gt;MessagesState&lt;/code&gt; and it appends straight to the transcript the parent is building.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://threadplane.ai/docs/langgraph/concepts/agent-architecture" rel="noopener noreferrer"&gt;architecture matrix&lt;/a&gt; covers the tiering question, the &lt;a href="https://threadplane.ai/docs/langgraph/guides/subgraphs" rel="noopener noreferrer"&gt;subgraphs guide&lt;/a&gt; has the composition and &lt;code&gt;subagents()&lt;/code&gt; wiring, and &lt;a href="https://threadplane.ai/blog/what-inject-agent-returns" rel="noopener noreferrer"&gt;What injectAgent() Actually Returns&lt;/a&gt; walks the signal surface those child streams land in.&lt;/p&gt;

&lt;p&gt;If you've split a graph for a third reason (not observability, and not a child that's genuinely its own graph), please let me know.&lt;br&gt;
Those are the two I've been able to justify, and I doubt they're the only two that exist. 🤔&lt;/p&gt;

</description>
      <category>langgraph</category>
      <category>ai</category>
      <category>angular</category>
      <category>architecture</category>
    </item>
    <item>
      <title>AG-UI vs. WebMCP: Who Owns the Agent?</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Thu, 29 Oct 2026 22:26:13 +0000</pubDate>
      <link>https://dev.to/blove/ag-ui-vs-webmcp-who-owns-the-agent-1hf1</link>
      <guid>https://dev.to/blove/ag-ui-vs-webmcp-who-owns-the-agent-1hf1</guid>
      <description>&lt;p&gt;Learn the difference between AG-UI and WebMCP, and when you'd reach for each one.&lt;/p&gt;

&lt;p&gt;Both show up in the same conversations, both mention tools, and both promise to connect agents to web apps.&lt;br&gt;
So it's easy to assume they compete, but they actually point in opposite directions.&lt;br&gt;
Once you see which way each one points, the choice gets simple.&lt;/p&gt;
&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Understand what AG-UI does.&lt;/li&gt;
&lt;li&gt;Understand what WebMCP does.&lt;/li&gt;
&lt;li&gt;Learn the one question that tells them apart.&lt;/li&gt;
&lt;li&gt;See how they work together in the same app.&lt;/li&gt;
&lt;li&gt;Learn where Threadplane fits.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What is AG-UI?
&lt;/h2&gt;

&lt;p&gt;AG-UI is an open protocol for streaming an agent's work to a user interface.&lt;/p&gt;

&lt;p&gt;Your frontend starts a run, and the agent streams back a sequence of typed events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RUN_STARTED&lt;/code&gt; and &lt;code&gt;RUN_FINISHED&lt;/code&gt; bracket the run.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TEXT_MESSAGE_START&lt;/code&gt;, &lt;code&gt;TEXT_MESSAGE_CONTENT&lt;/code&gt;, and &lt;code&gt;TEXT_MESSAGE_END&lt;/code&gt; stream the assistant's reply token by token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TOOL_CALL_START&lt;/code&gt;, &lt;code&gt;TOOL_CALL_ARGS&lt;/code&gt;, and &lt;code&gt;TOOL_CALL_END&lt;/code&gt; show the agent calling tools as it happens.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STATE_SNAPSHOT&lt;/code&gt; and &lt;code&gt;STATE_DELTA&lt;/code&gt; keep shared state in sync between the agent and the UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over HTTP, that's a stream of server-sent events that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data: {"type":"RUN_STARTED","threadId":"t_1","runId":"r_1"}
data: {"type":"TEXT_MESSAGE_START","messageId":"m_1","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"m_1","delta":"Checking flights"}
data: {"type":"TOOL_CALL_START","toolCallId":"c_1","toolCallName":"searchFlights"}
...
data: {"type":"RUN_FINISHED","threadId":"t_1","runId":"r_1"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is who is on each end.&lt;br&gt;
On one end is &lt;em&gt;your&lt;/em&gt; agent: a LangGraph graph, a Strands agent, a Mastra workflow, whatever you built.&lt;br&gt;
On the other end is &lt;em&gt;your&lt;/em&gt; UI, rendering those events as a chat, a timeline, an approval card, or a map.&lt;/p&gt;

&lt;p&gt;AG-UI is the wire between two things you own.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is WebMCP?
&lt;/h2&gt;

&lt;p&gt;WebMCP is a proposed web API that lets a page expose tools to an AI agent running in the browser.&lt;/p&gt;

&lt;p&gt;It's being incubated in the W3C Web Machine Learning Community Group, with editors from Microsoft and Google.&lt;br&gt;
It's still a draft Community Group report, not a standard, and Chrome is running it as an origin trial.&lt;/p&gt;

&lt;p&gt;The shape will feel familiar if you've built an MCP server.&lt;br&gt;
You register a tool with a name, a description, a JSON Schema for its input, and an &lt;code&gt;execute&lt;/code&gt; callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelContext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add_to_cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add a product to the shopping cart by SKU.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;consequentialHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Added &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Cart has &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; items.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;execute&lt;/code&gt; callback runs in your page, with the user's session, cookies, and app state already in place.&lt;br&gt;
The spec describes these pages as being similar to MCP servers that implement their tools in client-side script instead of on a backend.&lt;/p&gt;

&lt;p&gt;Chrome also documents a declarative flavor in its origin trial, where you add &lt;code&gt;toolname&lt;/code&gt; and &lt;code&gt;tooldescription&lt;/code&gt; attributes to an existing &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; and the browser builds the tool from its fields.&lt;br&gt;
That part isn't in the normative spec yet, so I'd treat it as experimental.&lt;/p&gt;

&lt;p&gt;A quick heads up if you've read earlier tutorials: they use &lt;code&gt;navigator.modelContext&lt;/code&gt;, while the current draft exposes the API on &lt;code&gt;document.modelContext&lt;/code&gt;.&lt;br&gt;
That's why I feature-detect rather than assume.&lt;/p&gt;

&lt;p&gt;On one end of WebMCP is &lt;em&gt;your&lt;/em&gt; page, offering tools.&lt;br&gt;
On the other end is an agent you probably &lt;em&gt;didn't&lt;/em&gt; build: the one built into the browser, or an extension, or something the user brought with them.&lt;/p&gt;

&lt;p&gt;WebMCP is the doorway you open for someone else's agent.&lt;/p&gt;
&lt;h2&gt;
  
  
  So what's the difference?
&lt;/h2&gt;

&lt;p&gt;Here's the one question I'd ask: &lt;strong&gt;who owns the agent?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you own the agent and you're building the UI for it, that's AG-UI.&lt;/li&gt;
&lt;li&gt;If the user brings the agent and you want your site to be usable by it, that's WebMCP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put another way, the arrows point in opposite directions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AG-UI&lt;/th&gt;
&lt;th&gt;WebMCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who owns the agent?&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;The user, browser, or extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where does the agent run?&lt;/td&gt;
&lt;td&gt;Usually your server&lt;/td&gt;
&lt;td&gt;Usually the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What flows over it?&lt;/td&gt;
&lt;td&gt;A stream of events: text, tool calls, state&lt;/td&gt;
&lt;td&gt;Tool definitions and tool calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who renders the conversation?&lt;/td&gt;
&lt;td&gt;Your app&lt;/td&gt;
&lt;td&gt;The agent's own UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What does your app provide?&lt;/td&gt;
&lt;td&gt;The UI&lt;/td&gt;
&lt;td&gt;The capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For me, that last row is the clearest way to hold it.&lt;br&gt;
With AG-UI, your app &lt;em&gt;is&lt;/em&gt; the interface to an agent.&lt;br&gt;
With WebMCP, your app is a set of capabilities that some other interface can drive.&lt;/p&gt;
&lt;h2&gt;
  
  
  Doesn't AG-UI have tools too?
&lt;/h2&gt;

&lt;p&gt;It does, and this is where the confusion usually starts.&lt;/p&gt;

&lt;p&gt;AG-UI supports frontend tools.&lt;br&gt;
When you start a run, you can send a list of tool definitions along with the messages.&lt;br&gt;
The agent can call them, the call streams down as &lt;code&gt;TOOL_CALL_*&lt;/code&gt; events, your frontend executes it, and the result goes back on the next run.&lt;/p&gt;

&lt;p&gt;So in both protocols, a tool can run in the browser.&lt;br&gt;
The difference is still ownership.&lt;br&gt;
AG-UI frontend tools are offered to &lt;em&gt;your&lt;/em&gt; agent, as part of a conversation your app is hosting.&lt;br&gt;
WebMCP tools are offered to &lt;em&gt;whatever&lt;/em&gt; agent the browser lets in, and your app may never see the conversation at all.&lt;/p&gt;

&lt;p&gt;I think of AG-UI frontend tools as private, and WebMCP tools as public.&lt;br&gt;
That's a simplification, since WebMCP has origin controls and a Permissions Policy gate, but it's the right instinct for how carefully you should design them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Can I use both?
&lt;/h2&gt;

&lt;p&gt;Yes, and I think a lot of apps will.&lt;/p&gt;

&lt;p&gt;The nice part is that the tool shapes are nearly the same: a name, a description, and a JSON Schema.&lt;br&gt;
So you can write the capability once and offer it through both doors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addToCart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add_to_cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add a product to the shopping cart by SKU.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Added &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Door 1: your own in-app assistant, over AG-UI.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addToCart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addToCart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addToCart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputSchema&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;

&lt;span class="c1"&gt;// Door 2: the user's browser agent, over WebMCP.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelContext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addToCart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your in-app assistant gets a rich, branded experience: streaming text, tool views, approvals, and state you control.&lt;br&gt;
A user who prefers their own agent still gets a site that works with it, instead of one it has to guess its way through by reading the DOM.&lt;/p&gt;

&lt;p&gt;If you're building that in-app assistant in Angular, the &lt;a href="https://threadplane.ai/docs/ag-ui/guides/client-tools" rel="noopener noreferrer"&gt;client tools guide&lt;/a&gt; shows the AG-UI side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should I watch out for?
&lt;/h2&gt;

&lt;p&gt;A few tradeoffs worth keeping in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebMCP is early.&lt;/strong&gt; It's an origin trial with a moving API surface. Feature-detect, and don't build a critical path on it yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You don't control the WebMCP caller.&lt;/strong&gt; Treat every input as untrusted, keep inputs narrow, and mark anything that spends money or sends a message with &lt;code&gt;consequentialHint&lt;/code&gt;. The user's agent may be reading content from other tabs, so prompt injection is a real concern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AG-UI means you run the agent.&lt;/strong&gt; That's infrastructure, model costs, and a backend to deploy. The upside is that you control the whole experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neither one replaces the other.&lt;/strong&gt; WebMCP doesn't stream your agent's reasoning into your UI, and AG-UI doesn't make your site usable by an agent you didn't build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where does Threadplane fit?
&lt;/h2&gt;

&lt;p&gt;A few questions come up whenever I talk about this, so here are the short answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Threadplane use AG-UI?
&lt;/h3&gt;

&lt;p&gt;Threadplane is a UI library for agents &lt;em&gt;you&lt;/em&gt; own, which is exactly the AG-UI side of the arrow.&lt;/p&gt;

&lt;p&gt;AG-UI gives us one adapter that reaches a lot of runtimes.&lt;br&gt;
&lt;code&gt;@threadplane/ag-ui&lt;/code&gt; works with any backend that speaks the protocol: CrewAI, Mastra, Microsoft Agent Framework, AG2, Pydantic AI, AWS Strands, and LangGraph fronted by AG-UI.&lt;br&gt;
We test that claim against real runtimes, and the results are in &lt;a href="https://threadplane.ai/docs/choosing-an-adapter" rel="noopener noreferrer"&gt;Choosing an adapter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For me, that's the appeal.&lt;br&gt;
Your Angular components shouldn't care which framework your agent team picked, and a protocol is how you keep it that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do we also use LangGraph directly?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@threadplane/langgraph&lt;/code&gt; talks to LangGraph Platform through LangGraph's own JavaScript SDK, without going through AG-UI.&lt;/p&gt;

&lt;p&gt;The reason is durability.&lt;br&gt;
Thread history, checkpoints, and time travel are things LangGraph does really well, and they're outside what the AG-UI protocol defines.&lt;br&gt;
Going direct lets the LangGraph adapter expose &lt;code&gt;history()&lt;/code&gt; and the rest of the thread surface, instead of only what fits through the event stream.&lt;/p&gt;

&lt;p&gt;The tradeoff is a second adapter to maintain.&lt;br&gt;
The upside is that you don't pay for it in your components: both adapters produce the same &lt;code&gt;Agent&lt;/code&gt; contract, so &lt;code&gt;&amp;lt;chat [agent]="agent" /&amp;gt;&lt;/code&gt; doesn't change when the import does.&lt;br&gt;
I wrote up exactly &lt;a href="https://threadplane.ai/blog/what-changes-when-the-runtime-changes" rel="noopener noreferrer"&gt;what changes when the runtime changes&lt;/a&gt; if you want the details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Threadplane support WebMCP?
&lt;/h3&gt;

&lt;p&gt;No, not today.&lt;/p&gt;

&lt;p&gt;Threadplane builds the UI for your own agent, and WebMCP is about exposing your page to someone else's.&lt;br&gt;
Those are different jobs, so nothing about WebMCP is blocked by Threadplane, and nothing in Threadplane needs it.&lt;/p&gt;

&lt;p&gt;If you want both, you can do it yourself today.&lt;br&gt;
Offer a tool to your in-app agent through Threadplane's &lt;a href="https://threadplane.ai/docs/ag-ui/guides/client-tools" rel="noopener noreferrer"&gt;client tools&lt;/a&gt;, and register the same function with &lt;code&gt;document.modelContext&lt;/code&gt; next to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In summary, AG-UI and WebMCP solve different problems.&lt;br&gt;
AG-UI connects &lt;em&gt;your&lt;/em&gt; agent to &lt;em&gt;your&lt;/em&gt; UI with a stream of events.&lt;br&gt;
WebMCP lets &lt;em&gt;your&lt;/em&gt; page offer tools to &lt;em&gt;someone else's&lt;/em&gt; agent running in the browser.&lt;/p&gt;

&lt;p&gt;When you're not sure which one you need, ask who owns the agent.&lt;br&gt;
The answer tells you which way the arrow points.&lt;/p&gt;

&lt;p&gt;I'm &lt;em&gt;really&lt;/em&gt; curious to see where WebMCP lands as it moves through the standards process.&lt;br&gt;
If you're experimenting with either one, or both, let me know what you're building.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>angular</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Three Questions to Answer Before You Ship an Agent UI</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Wed, 16 Sep 2026 05:29:13 +0000</pubDate>
      <link>https://dev.to/blove/three-questions-to-answer-before-you-ship-an-agent-ui-2ief</link>
      <guid>https://dev.to/blove/three-questions-to-answer-before-you-ship-an-agent-ui-2ief</guid>
      <description>&lt;p&gt;Most teams building an agent UI start with the chat box and work backwards.&lt;/p&gt;

&lt;p&gt;I think that is the wrong order.&lt;/p&gt;

&lt;p&gt;The decisions that shape the product are made earlier, usually by default, and usually without anyone noticing they were decisions.&lt;/p&gt;

&lt;p&gt;In this post, I want to give the three I keep coming back to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Where does the agent loop run?&lt;/li&gt;
&lt;li&gt;How does the model hand you UI?&lt;/li&gt;
&lt;li&gt;Which dimension did your tests delete?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I build in Angular and I maintain &lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;Threadplane&lt;/a&gt;, so my examples lean that way. The questions apply to any framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The agent loop runs in one of three places: a vendor's server, your route running a vendor's code, or your agent. Pick on purpose.&lt;/li&gt;
&lt;li&gt;Generative UI is not one thing. The useful choice is between a fixed spec you validate up front and a live surface the agent keeps editing.&lt;/li&gt;
&lt;li&gt;Every deterministic test harness buys determinism by deleting a dimension of the real system. Know which one yours deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Where does the agent loop run?
&lt;/h2&gt;

&lt;p&gt;The agent loop is the code that calls the model, sees a tool call, runs it, and calls the model again.&lt;/p&gt;

&lt;p&gt;Where it lives decides who holds the API key, where approvals pause, where threads persist, and whose release schedule you are on.&lt;/p&gt;

&lt;p&gt;I compared five libraries on this in September 2026. The answers sort into three shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A vendor server in the request path.&lt;/strong&gt; You deploy the library's runtime and your browser talks to it. You get key custody, auth, routing, and trusted middleware in one place. You trade a second server on the hot path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor code in your route.&lt;/strong&gt; No box to deploy, but the loop is the library's, inside an API route you own. Approvals and persistence can be first-class because the library owns both ends. You trade a loop that is separate from the one your agent framework already has.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your agent.&lt;/strong&gt; The UI talks to the agent server directly. One loop, owned by the framework you already chose. You trade the ability to show anything the server does not send.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For me, the third shape is right for most teams, and it is the one Threadplane is built on.&lt;/p&gt;

&lt;p&gt;If you are running an agent, you already have a server. It holds the model key, it already authenticates, and it already sees every tool call. A vendor's box in front of it is a second place to solve a problem you already had one place to solve.&lt;/p&gt;

&lt;p&gt;That does not mean no server at all. Put an endpoint you own in front of your agent for auth and credentials. The difference from a vendor runtime is who owns the code running in it.&lt;/p&gt;

&lt;p&gt;The full comparison, with every cell source-checked and the costs of my own choice listed, is in &lt;a href="https://threadplane.ai/blog/why-do-agent-ui-libraries-require-a-runtime" rel="noopener noreferrer"&gt;Why Do Agent UI Libraries Require a Runtime?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. How does the model hand you UI?
&lt;/h2&gt;

&lt;p&gt;"Generative UI" hides three mechanisms behind one phrase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool-call rendering.&lt;/strong&gt; The model calls a tool and you map the tool name to a component. Every library does this, and it is where most teams should start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declarative specs.&lt;/strong&gt; The model authors a UI tree in a standard format and the client renders it from a catalog you registered. The two open specs gaining ground are &lt;a href="https://a2ui.org" rel="noopener noreferrer"&gt;A2UI&lt;/a&gt; and &lt;a href="https://github.com/vercel-labs/json-render" rel="noopener noreferrer"&gt;json-render&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sandboxed apps.&lt;/strong&gt; An MCP server returns a &lt;code&gt;ui://&lt;/code&gt; resource and the client mounts it in an iframe. This is MCP Apps, and my project does not render it yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The choice that matters is inside the second bucket, and it is about contract shape.&lt;/p&gt;

&lt;p&gt;With a fixed spec like json-render, the contract is application-owned. You define the schema, validate the whole spec before it mounts, and your handlers decide what a click means.&lt;/p&gt;

&lt;p&gt;With a live protocol like A2UI, the surface is agent-owned. The agent creates it, keeps editing it across turns, and receives structured actions back.&lt;/p&gt;

&lt;p&gt;My heuristic is simple. If you can validate the entire UI before it renders, start with the fixed spec. If the surface has to live past its first render, step up to the protocol.&lt;/p&gt;

&lt;p&gt;An order summary card is a fixed spec. A three-day itinerary that fills in prices and gets rewritten when the user objects is a live surface.&lt;/p&gt;

&lt;p&gt;The walkthrough with the same card in both formats is in &lt;a href="https://threadplane.ai/blog/json-render-vs-a2ui-choosing" rel="noopener noreferrer"&gt;json-render vs A2UI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Which dimension did your tests delete?
&lt;/h2&gt;

&lt;p&gt;Agents are hard to test end to end because the model does not return the same thing twice.&lt;/p&gt;

&lt;p&gt;The usual fix is to record its responses as fixtures and replay them. We test our whole demo fleet that way, and I recommend it. Two things about that setup deserve more thought than they get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where you put the mock decides what is under test.&lt;/strong&gt; Mock at the app boundary and you have proven your component renders what you handed it. Mock at the model provider, with the real agent server running against a fake model endpoint, and your routing, streaming, and tool round-trips are all under test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every deterministic harness deletes a dimension.&lt;/strong&gt; Ours deletes time. Replayed responses arrive in one or two chunks instead of token by token, so final-DOM assertions stay stable. A handful of fixtures opt back in with tiny chunks where the progressive render is the thing under test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the bug class that disappears when you delete time. A child agent streams tokens, a client merges them into the transcript as an extra bubble, and then the run settles and the transcript is rebuilt correctly. Assert on the finished DOM and the test passes. Any assertion after an &lt;code&gt;await&lt;/code&gt; sees a settled system, and a self-correcting bug is one that settles.&lt;/p&gt;

&lt;p&gt;So the question about your harness is not whether it is green. It is which dimension you deleted, and which tests opted back in.&lt;/p&gt;

&lt;p&gt;The full accounting is in &lt;a href="https://threadplane.ai/blog/what-fixture-replay-cant-catch" rel="noopener noreferrer"&gt;What Fixture Replay Can't Catch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Each of these questions has a default answer that arrives with whatever library you install first. None of the defaults is wrong, but all of them are decisions, and I think you should make them as decisions.&lt;/p&gt;

&lt;p&gt;For me: run the loop in the agent with an endpoint you own in front of it, start with a fixed UI spec and step up per surface, and mock at the model provider while knowing what you gave up.&lt;/p&gt;

&lt;p&gt;Those are the answers &lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;Threadplane&lt;/a&gt; is built around. If you are building the same thing in another framework, I would like to hear where yours landed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>testing</category>
    </item>
    <item>
      <title>Bringing Agents to the Web with Angular: Announcing Threadplane</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Thu, 04 Jun 2026 17:38:50 +0000</pubDate>
      <link>https://dev.to/blove/bringing-agents-to-the-web-with-angular-announcing-threadplane-3d06</link>
      <guid>https://dev.to/blove/bringing-agents-to-the-web-with-angular-announcing-threadplane-3d06</guid>
      <description>&lt;p&gt;For the past year I've been building AI agents, and shipping them inside Angular apps.&lt;/p&gt;

&lt;p&gt;And every single time, the same thing happened: the backend got all the love, and the Angular frontend got the leftovers.&lt;/p&gt;

&lt;p&gt;So we built Threadplane.&lt;/p&gt;

&lt;p&gt;Threadplane is an open-source framework for building fullstack agentic applications with Angular. It connects your agent runtime — LangChain, LangGraph, anything that speaks AG-UI — to production-ready Angular surfaces: chat, streaming, tool progress, interrupts, durable threads, and generative UI. All of it expressed in Signals, dependency injection, and standalone components. The way Angular actually wants to be written.&lt;/p&gt;

&lt;p&gt;Let me tell you why we built it, what's in it, and how to try it this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.toThreadplane%20is%20an%20Angular-native%20framework%20for%20building%20agentic%20apps"&gt;threadplane.ai&lt;/a&gt;. The runtime adapters are &lt;strong&gt;MIT licensed&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It bridges &lt;strong&gt;LangChain / LangGraph&lt;/strong&gt; and &lt;strong&gt;AG-UI&lt;/strong&gt; backends into Angular Signals through one &lt;code&gt;injectAgent()&lt;/code&gt; call.&lt;/li&gt;
&lt;li&gt;Generative UI works through either &lt;strong&gt;Vercel's json-render&lt;/strong&gt; (a fixed, trusted component catalog) or &lt;strong&gt;Google's A2UI&lt;/strong&gt; (a portable declarative UI protocol) — rendered into &lt;em&gt;your&lt;/em&gt; design system.&lt;/li&gt;
&lt;li&gt;It's a complete solution: messages, status, tool calls, interrupts, branching, thread persistence, and tests. Not just stream plumbing.&lt;/li&gt;
&lt;li&gt;Grab it at &lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;https://threadplane.ai&lt;/a&gt; and on GitHub at &lt;a href="https://github.com/cacheplane/angular-agent-framework" rel="noopener noreferrer"&gt;cacheplane/angular-agent-framework&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why build this at all?
&lt;/h2&gt;

&lt;p&gt;Here's the thing nobody says out loud about agentic apps in 2026.&lt;/p&gt;

&lt;p&gt;The backend is the easy part now.&lt;/p&gt;

&lt;p&gt;That's not a dig — it's a compliment to everyone who's done the work. LangChain and LangGraph give you orchestration, tools, memory, and human-in-the-loop. Protocols like AG-UI standardize how an agent streams events to a UI. The runtime layer has matured fast.&lt;/p&gt;

&lt;p&gt;The frontend is where it still gets messy.&lt;/p&gt;

&lt;p&gt;An agent isn't a request and a response. It's a &lt;em&gt;stream&lt;/em&gt;. Tokens arrive a few at a time. A tool starts running, then finishes. The agent pauses to ask you to approve something. A subagent spins up, does its work, and reports back. The user edits an earlier message and the whole conversation branches. Then they close the tab, come back tomorrow, and expect the thread to still be there.&lt;/p&gt;

&lt;p&gt;Every one of those is a frontend state problem. And in Angular, you were on your own.&lt;/p&gt;

&lt;p&gt;For me, this came to a head on a flight-planning assistant I was building — an orchestrator agent delegating to research subagents, each grounded by retrieval over a different corpus. The agent architecture was right. The model choices were right. But wiring the stream into the Angular UI meant hand-rolling the same plumbing over and over: a service holding streaming state, a pile of &lt;code&gt;BehaviorSubject&lt;/code&gt;s, manual change detection, and a tool-call lifecycle I kept reinventing in slightly different ways across components.&lt;/p&gt;

&lt;p&gt;I'd written that code three times. That's usually my signal that it shouldn't be my code anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The React teams already had this
&lt;/h2&gt;

&lt;p&gt;Let's be honest about the landscape, because it's the whole reason Threadplane exists.&lt;/p&gt;

&lt;p&gt;If you build agentic UIs in React, you've had good tools for a while. LangGraph's official frontend integration, &lt;code&gt;useStream&lt;/code&gt;, is a single React hook that gives you thread management, message streaming, and branching out of the box. There's a mature ecosystem of generative-UI libraries. The examples, the starters, the blog posts — they assume React.&lt;/p&gt;

&lt;p&gt;If you build in Angular, none of that was yours.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useStream&lt;/code&gt; is React-only. Most of the generative-UI libraries ship React renderers first, and Angular &lt;em&gt;eventually&lt;/em&gt;, or not at all. So Angular teams did what Angular teams have always done: translate. Read the React example, mentally port the hooks to services, port the JSX to templates, port the React state to Signals, and hope you got the streaming edge cases right.&lt;/p&gt;

&lt;p&gt;I don't think that's good enough anymore.&lt;/p&gt;

&lt;p&gt;Angular has Signals. It has dependency injection that's genuinely best-in-class for this kind of problem. It has standalone components and a strong opinion about structure. An agent UI built &lt;em&gt;for&lt;/em&gt; Angular should feel like Angular — not like a React app wearing a trench coat.&lt;/p&gt;

&lt;p&gt;That's the gap. Threadplane is our attempt to close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Threadplane actually gives you
&lt;/h2&gt;

&lt;p&gt;Threadplane is a small set of focused packages. You take what you need.&lt;/p&gt;

&lt;p&gt;The center of it is &lt;code&gt;injectAgent()&lt;/code&gt;. Think of it as the Angular equivalent of &lt;code&gt;useStream&lt;/code&gt; — but projected through Angular's reactivity model instead of React's.&lt;/p&gt;

&lt;p&gt;You wire your backend up once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideAgent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@threadplane/langgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-langgraph-platform.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;assistantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you inject it into any component and bind directly to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ChatComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@threadplane/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;injectAgent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@threadplane/langgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-support-chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ChatComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;chat [agent]="chat" /&amp;gt;

    @if (chat.isLoading()) {
      &amp;lt;span&amp;gt;Streaming…&amp;lt;/span&amp;gt;
    }
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportChatComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectAgent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;chat.messages()&lt;/code&gt;, &lt;code&gt;chat.status()&lt;/code&gt;, &lt;code&gt;chat.toolCalls()&lt;/code&gt;, &lt;code&gt;chat.interrupt()&lt;/code&gt; — these are all Angular Signals. You bind them straight into your template. No subscriptions. No &lt;code&gt;async&lt;/code&gt; pipe. No zone.js required.&lt;/p&gt;

&lt;p&gt;And it's not just the happy path. You get the stuff that actually makes an agent UI production-ready:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming messages and status&lt;/strong&gt; as reactive primitives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool call progress&lt;/strong&gt; — render what the agent is doing as it does it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interrupts&lt;/strong&gt; for human-in-the-loop — pause, ask the user to approve, resume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subagents&lt;/strong&gt; — a map of the subagents spun up during a run, so you can show delegation as it happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branching and history&lt;/strong&gt; — edit a message, fork the conversation, walk back through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable threads&lt;/strong&gt; — persist a conversation and switch back to it later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;regenerate()&lt;/code&gt; and &lt;code&gt;reload()&lt;/code&gt;&lt;/strong&gt; for the retry behaviors users now expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's what I mean by &lt;em&gt;complete solution&lt;/em&gt;. The boring, necessary 80% is in the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring your own runtime
&lt;/h2&gt;

&lt;p&gt;Here's a design decision I care about: Threadplane doesn't want to own your agent.&lt;/p&gt;

&lt;p&gt;We are not a backend agent runtime. Keep LangGraph. Keep your custom service. Keep whatever you've already invested in.&lt;/p&gt;

&lt;p&gt;Threadplane talks to it through adapters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@threadplane/langgraph&lt;/code&gt; adapts a LangGraph Platform endpoint into Angular Signals. This is the &lt;code&gt;provideAgent()&lt;/code&gt; / &lt;code&gt;injectAgent()&lt;/code&gt; pair above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@threadplane/ag-ui&lt;/code&gt; bridges any AG-UI-compatible backend into the same chat surface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AG-UI, if you haven't run into it yet, is an open, lightweight, event-based protocol for connecting agents to user-facing apps. It's a small set of typed events — message chunks, tool calls, state updates, lifecycle — streamed over SSE (or whatever transport you like). It's being adopted across a lot of the ecosystem, which means an Angular adapter for it is leverage: speak AG-UI on the backend, and Threadplane renders it on the frontend.&lt;/p&gt;

&lt;p&gt;The nice part is what sits between the adapter and the UI. Both adapters resolve to the same runtime-neutral &lt;code&gt;Agent&lt;/code&gt; contract, and &lt;code&gt;@threadplane/chat&lt;/code&gt; only ever consumes &lt;em&gt;that&lt;/em&gt; contract. So swapping &lt;code&gt;@threadplane/langgraph&lt;/code&gt; for &lt;code&gt;@threadplane/ag-ui&lt;/code&gt; doesn't touch your components or your templates. The seam is in one place, on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generative UI, rendered into your design system
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most excited about, so let's slow down here.&lt;/p&gt;

&lt;p&gt;Generative UI is when the agent doesn't just send text — it sends &lt;em&gt;interface&lt;/em&gt;. A weather card instead of a paragraph about the weather. A form instead of "please reply with your address." A chart instead of a table of numbers read aloud.&lt;/p&gt;

&lt;p&gt;The scary version of this is letting a model emit arbitrary code into your app. That's a fast way to move chaos across a trust boundary. For a real product, you don't want that.&lt;/p&gt;

&lt;p&gt;There are two good answers to this in 2026, and Threadplane supports both.&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;Vercel's json-render&lt;/strong&gt;. The model generates JSON, constrained to a &lt;em&gt;fixed&lt;/em&gt; catalog of components you define. The agent can only use the components you've approved — it can't invent UI. &lt;code&gt;@threadplane/render&lt;/code&gt; is an Angular engine built on &lt;code&gt;@json-render/core&lt;/code&gt; that renders those JSON specs into real Angular components from your own library.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;Google's A2UI&lt;/strong&gt; — a framework-agnostic protocol for declaring UI &lt;em&gt;intent&lt;/em&gt;, separating what the user should see from how it's implemented. It's the more dynamic, portable end of the spectrum. &lt;code&gt;@threadplane/a2ui&lt;/code&gt; gives you the protocol types, a streaming parser, and a dynamic-value resolver in pure TypeScript.&lt;/p&gt;

&lt;p&gt;Fixed catalog or portable protocol — that's a real architectural choice, and it depends on your surface area. (I wrote a whole post on &lt;a href="https://brianflove.com/posts/2026-03-17-google-a2ui-fixed-vs-dynamic-schemas/" rel="noopener noreferrer"&gt;fixed versus dynamic schemas&lt;/a&gt; if you want to go deeper.) The point is that with Threadplane, both render into &lt;em&gt;your&lt;/em&gt; design system. Approved components, your styles, your accessibility, your tests. The agent fills in the blanks. It doesn't get to repaint the walls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open source, and why MIT
&lt;/h2&gt;

&lt;p&gt;We're shipping this open source on purpose, and I want to be straight about exactly what that means.&lt;/p&gt;

&lt;p&gt;The runtime adapters and the rendering engine — &lt;code&gt;@threadplane/langgraph&lt;/code&gt;, &lt;code&gt;@threadplane/ag-ui&lt;/code&gt;, &lt;code&gt;@threadplane/render&lt;/code&gt;, and &lt;code&gt;@threadplane/a2ui&lt;/code&gt; — are &lt;strong&gt;MIT licensed&lt;/strong&gt;. Free for any use, including commercial, with attribution. The pieces that connect your agent to Angular and render generative UI are yours to use, fork, and build on.&lt;/p&gt;

&lt;p&gt;The opinionated chat UI, &lt;code&gt;@threadplane/chat&lt;/code&gt;, is the one exception: it's free for noncommercial use and has a commercial license for production inside a for-profit company. I'd rather tell you that plainly now than surprise you in a &lt;code&gt;LICENSE&lt;/code&gt; file later.&lt;/p&gt;

&lt;p&gt;MIT for the foundation matters to me because of where this category is heading. The agentic frontend space is full of stacks that start open and quietly close, or that only really open up the framework everyone &lt;em&gt;else&lt;/em&gt; uses. Angular teams — especially in fintech, healthcare, and other places where a React rewrite is off the table — deserve a foundation they can actually depend on. No required cloud. No app telemetry on by default. No bait-and-switch.&lt;/p&gt;

&lt;p&gt;A quick, honest disclosure while we're here: I also help maintain &lt;a href="https://hashbrown.dev" rel="noopener noreferrer"&gt;Hashbrown&lt;/a&gt;, an MIT-licensed framework for agents that &lt;em&gt;run in the browser&lt;/em&gt;, for both Angular and React. People ask how the two relate, so — short version: Hashbrown is for browser-running, LLM-driven frontend agents. Threadplane is for production chat and generative UI on top of server-side runtimes like LangGraph and AG-UI. Different lanes. I'm genuinely glad both exist, and I think the Angular community is better off with more open tools in it, not fewer. 💚&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it this week
&lt;/h2&gt;

&lt;p&gt;That's the pitch. Here's the part where you go build something.&lt;/p&gt;

&lt;p&gt;Install the LangGraph adapter and the chat UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @threadplane/langgraph @threadplane/chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire &lt;code&gt;provideAgent()&lt;/code&gt;, drop in &lt;code&gt;&amp;lt;chat&amp;gt;&lt;/code&gt;, and you've got a streaming agent UI in about thirty seconds. From there, add interrupts when you need a human in the loop, add generative UI when text isn't enough, and swap to the AG-UI adapter if that's your backend.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The docs, quickstarts, and live demos are at &lt;strong&gt;&lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;threadplane.ai&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The code is on GitHub at &lt;strong&gt;&lt;a href="https://github.com/cacheplane/angular-agent-framework" rel="noopener noreferrer"&gt;cacheplane/angular-agent-framework&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building agents in Angular, I'd love to know what you ship — and what's still missing. Open an issue, tell me where it falls short, or just tell me it saved you from writing that streaming service a fourth time.&lt;/p&gt;

&lt;p&gt;Let's give Angular the agent frontend it deserves.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>angular</category>
      <category>agents</category>
    </item>
    <item>
      <title>Agentic Memory and What It Means for Web Apps</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Wed, 18 Mar 2026 23:02:39 +0000</pubDate>
      <link>https://dev.to/blove/agentic-memory-and-what-it-means-for-web-apps-2hea</link>
      <guid>https://dev.to/blove/agentic-memory-and-what-it-means-for-web-apps-2hea</guid>
      <description>&lt;p&gt;Memory is becoming one of the most important design surfaces in agentic software.&lt;/p&gt;

&lt;p&gt;Not because models suddenly became databases.&lt;br&gt;
And not because storing more transcripts is the same thing as making a system smarter.&lt;/p&gt;

&lt;p&gt;It matters because memory changes what kind of software we are building.&lt;/p&gt;

&lt;p&gt;A stateless LLM can answer.&lt;br&gt;
A system with agentic memory can improve.&lt;/p&gt;

&lt;p&gt;That is a different class of product.&lt;/p&gt;

&lt;p&gt;For me, the core idea is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory turns LLMs from stateless responders into stateful systems&lt;/li&gt;
&lt;li&gt;memory is a form of non-parametric learning&lt;/li&gt;
&lt;li&gt;the hard problem is no longer storing more context&lt;/li&gt;
&lt;li&gt;the hard problem is deciding what should be remembered, when, and in what form&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are building fullstack agentic web applications, this is the shift to pay attention to.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agentic memory is not just retrieval. It is an agent capability to store, retrieve, update, summarize, and delete knowledge over time.&lt;/li&gt;
&lt;li&gt;This gives us a practical form of continual learning without fine-tuning model weights.&lt;/li&gt;
&lt;li&gt;For web apps, memory changes architecture, not just prompting. It affects UX, data modeling, evaluation, governance, and trust.&lt;/li&gt;
&lt;li&gt;The right production model is usually typed memory, selective retrieval, background consolidation, and immutable raw history behind derived memory.&lt;/li&gt;
&lt;li&gt;Memory is becoming an agent policy surface. That means memory quality will matter as much as model quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Angular Agent Framework
&lt;/h2&gt;

&lt;p&gt;Check out &lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;threadplane - Agent UI for Angular&lt;/a&gt; at threadplane.ai for building agentic fullstack Angular applications using LangChain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why memory changes everything
&lt;/h2&gt;

&lt;p&gt;Most LLM applications started as stateless request-response systems.&lt;/p&gt;

&lt;p&gt;That was fine for summarization, classification, and one-shot chat.&lt;br&gt;
It is not enough for software that is supposed to improve over time.&lt;/p&gt;

&lt;p&gt;As soon as you want an agent to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remember user preferences&lt;/li&gt;
&lt;li&gt;reuse successful workflows&lt;/li&gt;
&lt;li&gt;avoid repeated failures&lt;/li&gt;
&lt;li&gt;carry state across sessions&lt;/li&gt;
&lt;li&gt;personalize behavior without retraining&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you need memory.&lt;/p&gt;

&lt;p&gt;And not memory in the casual sense of "we saved the conversation somewhere."&lt;/p&gt;

&lt;p&gt;You need a system where the agent can actively manage what it knows.&lt;/p&gt;

&lt;p&gt;That is what makes the memory &lt;em&gt;agentic&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What agentic memory actually is
&lt;/h2&gt;

&lt;p&gt;Agentic memory is a system where an agent can decide to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store something&lt;/li&gt;
&lt;li&gt;retrieve something&lt;/li&gt;
&lt;li&gt;update something&lt;/li&gt;
&lt;li&gt;summarize something&lt;/li&gt;
&lt;li&gt;delete something&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters.&lt;br&gt;
If the system can only append, it does not really have memory discipline.&lt;br&gt;
It has a log.&lt;/p&gt;

&lt;p&gt;This is why I think the right framing is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory is not storage. It is a control surface for reasoning.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the real shift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic memory is like cramming for a test
&lt;/h2&gt;

&lt;p&gt;One of the more important ideas here is that memory gives us a form of continual learning without changing model weights.&lt;/p&gt;

&lt;p&gt;The model does not need to be fine-tuned every time it learns something useful.&lt;br&gt;
It can improve by pulling the right memories into context at inference time.&lt;/p&gt;

&lt;p&gt;That is why I think of memory as test-time learning.&lt;/p&gt;

&lt;p&gt;Different systems approach it differently, but the common idea is the same:&lt;br&gt;
the agent gets better because it can reuse abstractions learned from prior experience.&lt;/p&gt;

&lt;p&gt;That is a much more practical path for product teams than constant retraining.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three memory types that matter
&lt;/h2&gt;

&lt;p&gt;I think it is useful to separate memory into three buckets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Semantic memory&lt;/li&gt;
&lt;li&gt;Episodic memory&lt;/li&gt;
&lt;li&gt;Procedural memory&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Semantic memory
&lt;/h3&gt;

&lt;p&gt;Semantic memory is facts, preferences, constraints, and stable knowledge.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preferred output format&lt;/li&gt;
&lt;li&gt;user role&lt;/li&gt;
&lt;li&gt;account rules&lt;/li&gt;
&lt;li&gt;domain terminology&lt;/li&gt;
&lt;li&gt;known business constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the memory type that drives correctness and personalization.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Episodic memory
&lt;/h3&gt;

&lt;p&gt;Episodic memory is past experiences.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a successful prior resolution for a similar support issue&lt;/li&gt;
&lt;li&gt;a failed workflow and the correction that fixed it&lt;/li&gt;
&lt;li&gt;a previous user interaction pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the memory type that helps reasoning by analogy.&lt;br&gt;
It is how agents get a practical form of "I have seen something like this before."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Procedural memory
&lt;/h3&gt;

&lt;p&gt;Procedural memory is behavior.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preferred prompts&lt;/li&gt;
&lt;li&gt;tool-use patterns&lt;/li&gt;
&lt;li&gt;routing rules&lt;/li&gt;
&lt;li&gt;safety policies&lt;/li&gt;
&lt;li&gt;execution instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the memory type that improves consistency.&lt;/p&gt;

&lt;p&gt;I think this separation matters because different memory types want different storage, retrieval, and evaluation strategies.&lt;/p&gt;

&lt;p&gt;If you flatten them all into one vector store, you are usually making retrieval worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for web apps
&lt;/h2&gt;

&lt;p&gt;This is the part I care about most.&lt;/p&gt;

&lt;p&gt;Agentic memory is not just an infra feature for backend agents.&lt;br&gt;
It changes how web apps should be designed.&lt;/p&gt;

&lt;p&gt;A web app with agentic memory is not just rendering model output.&lt;br&gt;
It is participating in a learning loop.&lt;/p&gt;

&lt;p&gt;The frontend becomes the place where memory is created, corrected, and validated.&lt;/p&gt;

&lt;p&gt;That has a few practical implications.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Web apps become memory surfaces
&lt;/h3&gt;

&lt;p&gt;The frontend sees things the model and backend often do not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the user accepted&lt;/li&gt;
&lt;li&gt;what they edited&lt;/li&gt;
&lt;li&gt;what they rejected&lt;/li&gt;
&lt;li&gt;how long they hesitated&lt;/li&gt;
&lt;li&gt;where they retried&lt;/li&gt;
&lt;li&gt;when they abandoned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are memory candidates.&lt;/p&gt;

&lt;p&gt;Not all of them should be stored.&lt;br&gt;
But the web app is where those signals become visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Personalization becomes a first-class product system
&lt;/h3&gt;

&lt;p&gt;Personalization used to mean feature flags, settings, and saved preferences.&lt;/p&gt;

&lt;p&gt;Now it also means memory.&lt;/p&gt;

&lt;p&gt;The agent should be able to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how a person likes information presented&lt;/li&gt;
&lt;li&gt;what defaults they repeatedly choose&lt;/li&gt;
&lt;li&gt;what kinds of actions they permit or avoid&lt;/li&gt;
&lt;li&gt;what vocabulary is normal in their context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a better product experience.&lt;br&gt;
It is also a new governance problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Multi-session coherence becomes a UX expectation
&lt;/h3&gt;

&lt;p&gt;Once users see an agent remember important context, they start expecting continuity.&lt;/p&gt;

&lt;p&gt;That means the web app needs to help answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what should persist across sessions?&lt;/li&gt;
&lt;li&gt;what should expire?&lt;/li&gt;
&lt;li&gt;what should be editable by the user?&lt;/li&gt;
&lt;li&gt;what should be visible as remembered state?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why memory is also a UX problem, not just a systems problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Context engineering becomes product infrastructure
&lt;/h3&gt;

&lt;p&gt;I think "context engineering" is one of the most useful phrases in this space.&lt;/p&gt;

&lt;p&gt;The problem is no longer just fitting more tokens into a prompt.&lt;br&gt;
The problem is selecting the right abstractions.&lt;/p&gt;

&lt;p&gt;Bad memory systems create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;context poisoning&lt;/li&gt;
&lt;li&gt;distraction&lt;/li&gt;
&lt;li&gt;token waste&lt;/li&gt;
&lt;li&gt;conflicting guidance&lt;/li&gt;
&lt;li&gt;brittle personalization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good memory systems do the opposite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;selective retrieval&lt;/li&gt;
&lt;li&gt;summarization&lt;/li&gt;
&lt;li&gt;distillation&lt;/li&gt;
&lt;li&gt;isolation by scope&lt;/li&gt;
&lt;li&gt;time-aware filtering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why I would argue:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem is no longer remembering more. It is remembering the right abstractions.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflection is where memory becomes learning
&lt;/h2&gt;

&lt;p&gt;One of the most important loops in agent systems is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;act&lt;/li&gt;
&lt;li&gt;observe&lt;/li&gt;
&lt;li&gt;critique&lt;/li&gt;
&lt;li&gt;store&lt;/li&gt;
&lt;li&gt;reuse&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is reflection.&lt;/p&gt;

&lt;p&gt;In practice, a lot of the gains in agent quality come from this loop.&lt;br&gt;
The agent does something, observes success or failure, stores the useful lesson, and applies it later.&lt;/p&gt;

&lt;p&gt;This is why grounded reflection matters so much.&lt;br&gt;
If the critique comes from real environment feedback, user behavior, or verifiable outcomes, the memory is much more useful than a purely self-generated summary.&lt;/p&gt;

&lt;p&gt;This is also why the web app matters so much.&lt;br&gt;
It is often the best place to observe the real outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture I would actually ship
&lt;/h2&gt;

&lt;p&gt;If I were building agentic memory into a web app today, I would not start with one giant memory store.&lt;/p&gt;

&lt;p&gt;I would use a layered design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Short-term thread memory&lt;/li&gt;
&lt;li&gt;Long-term typed memory&lt;/li&gt;
&lt;li&gt;Immutable raw history&lt;/li&gt;
&lt;li&gt;Background consolidation&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Short-term thread memory
&lt;/h3&gt;

&lt;p&gt;This is the active working set for the current task or session.&lt;/p&gt;

&lt;p&gt;Use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recent messages&lt;/li&gt;
&lt;li&gt;in-progress tool state&lt;/li&gt;
&lt;li&gt;temporary planning context&lt;/li&gt;
&lt;li&gt;current UI state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is hot memory.&lt;br&gt;
Fast in, fast out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Long-term typed memory
&lt;/h3&gt;

&lt;p&gt;This is where semantic, episodic, and procedural memories live separately.&lt;/p&gt;

&lt;p&gt;Use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user preferences&lt;/li&gt;
&lt;li&gt;reusable examples&lt;/li&gt;
&lt;li&gt;learned task heuristics&lt;/li&gt;
&lt;li&gt;stable operating policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where I want stronger structure and stronger retrieval rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutable raw history
&lt;/h3&gt;

&lt;p&gt;Never trust repeated summarization as your only source of truth.&lt;/p&gt;

&lt;p&gt;Summaries drift.&lt;br&gt;
Compression loses nuance.&lt;br&gt;
Derived memory can get subtly wrong over time.&lt;/p&gt;

&lt;p&gt;So I want a raw, immutable log behind the optimized memory layer.&lt;/p&gt;

&lt;p&gt;That gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auditability&lt;/li&gt;
&lt;li&gt;rollback&lt;/li&gt;
&lt;li&gt;better debugging&lt;/li&gt;
&lt;li&gt;safer reprocessing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Background consolidation
&lt;/h3&gt;

&lt;p&gt;Not every memory write should happen synchronously in the request path.&lt;/p&gt;

&lt;p&gt;Some should.&lt;br&gt;
Others should be consolidated later.&lt;/p&gt;

&lt;p&gt;That is the hot + cold model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;synchronous writes for critical immediate context&lt;/li&gt;
&lt;li&gt;asynchronous consolidation for summarization, distillation, and indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is usually the right tradeoff between latency and memory quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patterns I like
&lt;/h2&gt;

&lt;p&gt;There are a few patterns here that I think are especially practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot + cold memory
&lt;/h3&gt;

&lt;p&gt;Write immediately when the task needs it.&lt;br&gt;
Consolidate later when quality matters more than latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distilled memory
&lt;/h3&gt;

&lt;p&gt;Do not store raw transcripts as the primary memory object if what you really need is a reusable abstraction.&lt;/p&gt;

&lt;p&gt;Store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the lesson&lt;/li&gt;
&lt;li&gt;the source&lt;/li&gt;
&lt;li&gt;the timestamp&lt;/li&gt;
&lt;li&gt;the confidence&lt;/li&gt;
&lt;li&gt;the scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is much more useful than dumping an entire conversation into retrieval.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutable + derived memory
&lt;/h3&gt;

&lt;p&gt;I trust systems more when they keep both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;immutable raw events&lt;/li&gt;
&lt;li&gt;derived summaries and optimized memories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is how you keep memory systems from becoming opaque.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory graphs
&lt;/h3&gt;

&lt;p&gt;Similarity search is useful, but it is not enough.&lt;/p&gt;

&lt;p&gt;Some memories are connected by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;causality&lt;/li&gt;
&lt;li&gt;sequence&lt;/li&gt;
&lt;li&gt;dependency&lt;/li&gt;
&lt;li&gt;contradiction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Graph-shaped memory is much better at expressing that than naive top-k vector retrieval.&lt;/p&gt;

&lt;p&gt;I expect more systems to move in this direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The production risks are real
&lt;/h2&gt;

&lt;p&gt;Memory makes systems better.&lt;br&gt;
It also makes them more dangerous.&lt;/p&gt;

&lt;p&gt;At least four risks matter immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Retrieval quality
&lt;/h3&gt;

&lt;p&gt;Just because something is semantically similar does not mean it is operationally relevant.&lt;/p&gt;

&lt;p&gt;Memory retrieval often misses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;causal relevance&lt;/li&gt;
&lt;li&gt;implicit constraints&lt;/li&gt;
&lt;li&gt;temporal change&lt;/li&gt;
&lt;li&gt;contradictory updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why memory quality is usually more important than memory volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Memory drift
&lt;/h3&gt;

&lt;p&gt;If you repeatedly summarize summaries, you eventually distort the original meaning.&lt;/p&gt;

&lt;p&gt;That is why derived memory needs provenance and raw backing data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Security
&lt;/h3&gt;

&lt;p&gt;Memory injection is a real design concern.&lt;/p&gt;

&lt;p&gt;If an attacker can poison memory, they can shape future agent behavior.&lt;/p&gt;

&lt;p&gt;This means memory systems need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;trust boundaries&lt;/li&gt;
&lt;li&gt;scoped access&lt;/li&gt;
&lt;li&gt;deletion paths&lt;/li&gt;
&lt;li&gt;source attribution&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Evaluation
&lt;/h3&gt;

&lt;p&gt;A memory system can look impressive in a demo and still fail long-horizon tasks in production.&lt;/p&gt;

&lt;p&gt;We still need better evaluation for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-session behavior&lt;/li&gt;
&lt;li&gt;long-horizon execution&lt;/li&gt;
&lt;li&gt;memory usefulness over time&lt;/li&gt;
&lt;li&gt;robustness to stale or conflicting memories&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Memory governance is now part of application architecture
&lt;/h2&gt;

&lt;p&gt;This is the part I think teams will underestimate.&lt;/p&gt;

&lt;p&gt;As soon as memory affects behavior, governance matters.&lt;/p&gt;

&lt;p&gt;You need clear rules for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what gets stored&lt;/li&gt;
&lt;li&gt;who can access it&lt;/li&gt;
&lt;li&gt;how it decays&lt;/li&gt;
&lt;li&gt;how it is corrected&lt;/li&gt;
&lt;li&gt;how it is deleted&lt;/li&gt;
&lt;li&gt;how it is explained to the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is true for enterprise software.&lt;br&gt;
It is even more true for consumer software.&lt;/p&gt;

&lt;p&gt;The best systems will not just remember well.&lt;br&gt;
They will remember responsibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  My practical recommendations
&lt;/h2&gt;

&lt;p&gt;If you are building agentic memory into a web app now, this is the sequence I would use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separate semantic, episodic, and procedural memory.&lt;/li&gt;
&lt;li&gt;Keep immutable raw history behind derived memory.&lt;/li&gt;
&lt;li&gt;Prefer distilled memory objects over raw transcript retrieval.&lt;/li&gt;
&lt;li&gt;Add time, source, scope, and version to every stored memory.&lt;/li&gt;
&lt;li&gt;Use synchronous writes sparingly and background consolidation aggressively.&lt;/li&gt;
&lt;li&gt;Tune retrieval strategy by memory type instead of using one global approach.&lt;/li&gt;
&lt;li&gt;Evaluate on multi-session and long-horizon tasks, not only single-turn quality.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the difference between "we added memory" and "we built a memory system."&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Agentic memory changes the role of the model.&lt;br&gt;
It also changes the role of the web app.&lt;/p&gt;

&lt;p&gt;The web app is no longer just a place where model output gets rendered.&lt;br&gt;
It is where memory is shaped, corrected, surfaced, and governed.&lt;/p&gt;

&lt;p&gt;That is why I think memory is going to become foundational to intelligent software.&lt;/p&gt;

&lt;p&gt;Not because remembering more is inherently better.&lt;br&gt;
But because the right memory architecture lets software learn without pretending every improvement requires retraining.&lt;/p&gt;

&lt;p&gt;Memory is becoming policy.&lt;br&gt;
And policy is becoming product behavior.&lt;/p&gt;

&lt;p&gt;That is what makes this interesting.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Google A2UI: Fixed Schemas, Dynamic Schemas, and a Safe Fallback Strategy</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Wed, 18 Mar 2026 23:02:39 +0000</pubDate>
      <link>https://dev.to/blove/google-a2ui-fixed-schemas-dynamic-schemas-and-a-safe-fallback-strategy-284f</link>
      <guid>https://dev.to/blove/google-a2ui-fixed-schemas-dynamic-schemas-and-a-safe-fallback-strategy-284f</guid>
      <description>&lt;p&gt;Google's A2UI is one of the more interesting protocol ideas in agentic UI right now.&lt;/p&gt;

&lt;p&gt;Not because it lets a model generate arbitrary frontend code.&lt;br&gt;
That would be a fast way to move chaos across a trust boundary.&lt;/p&gt;

&lt;p&gt;It is interesting because it does the opposite.&lt;/p&gt;

&lt;p&gt;A2UI gives the agent a declarative way to describe UI while keeping rendering inside a trusted client-owned component system.&lt;/p&gt;

&lt;p&gt;That is the part that matters.&lt;br&gt;
Especially if you are building a real product instead of a demo.&lt;/p&gt;

&lt;p&gt;The practical question is not whether A2UI is good.&lt;br&gt;
The practical question is how to use it without turning your UI contract into a probabilistic event.&lt;/p&gt;

&lt;p&gt;For me, that comes down to one decision:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fixed schema&lt;/li&gt;
&lt;li&gt;Dynamic schema&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My recommendation is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use fixed schemas by default.&lt;/li&gt;
&lt;li&gt;Use dynamic schema overlays for the long tail.&lt;/li&gt;
&lt;li&gt;Validate everything.&lt;/li&gt;
&lt;li&gt;Retry a few times.&lt;/li&gt;
&lt;li&gt;Fall back deterministically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A2UI is a protocol for agent-driven interfaces where the client owns rendering and the model emits declarative UI messages.&lt;/li&gt;
&lt;li&gt;A2UI v0.9 is more model-friendly than earlier versions because it is flatter, more prompt-friendly, and easier to validate.&lt;/li&gt;
&lt;li&gt;Most teams should start with fixed schemas because they are easier to test, easier to operate, and easier to trust.&lt;/li&gt;
&lt;li&gt;Dynamic schemas are useful, but only when they stay grounded in renderer-approved catalogs.&lt;/li&gt;
&lt;li&gt;The right production posture is fixed by default, dynamic overlays for the long tail, and deterministic fallback when validation fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Angular Fullstack Agentic Frameowork
&lt;/h2&gt;

&lt;p&gt;Be sure to check out the &lt;a href="https://threadplane" rel="noopener noreferrer"&gt;Angular Agent Framework&lt;/a&gt; for building fullstack agentic applications using Angular. A2UI support will be landing soon!&lt;/p&gt;
&lt;h2&gt;
  
  
  Fullstack architecture sketch
&lt;/h2&gt;

&lt;p&gt;Before getting into fixed versus dynamic schema, this is the deployment shape I would use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5jas8zz4jxxpxmjwkwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5jas8zz4jxxpxmjwkwk.png" alt="Animated fullstack architecture diagram for A2UI showing the trusted web client, the API and agent backend, and the observability layer." width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Web Client
  - trusted A2UI renderer
  - advertises supported catalogs
  - may advertise inline catalogs
  - sends actions and data model updates

API / Agent Backend
  - receives client capabilities
  - selects fixed vs dynamic policy
  - builds prompt from selected catalog
  - runs schema repair loop when dynamic
  - validates final payloads
  - streams safe A2UI messages to client

Observability
  - schema validation failures
  - payload validation failures
  - fallback frequency
  - per-intent success rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why A2UI matters
&lt;/h2&gt;

&lt;p&gt;A2UI matters because it gives us a cleaner split between model behavior and frontend control.&lt;/p&gt;

&lt;p&gt;The model decides what interface should appear.&lt;br&gt;
The client decides how that interface is rendered.&lt;/p&gt;

&lt;p&gt;That is a much better contract than shipping arbitrary frontend code across the wire and pretending the security story will work itself out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwmd80wdja8ncvuhjfw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwmd80wdja8ncvuhjfw2.png" alt="Animated diagram of A2UI where the agent emits declarative messages, the backend validates the contract, and the trusted client renderer owns rendering." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why v0.9 is the interesting version
&lt;/h2&gt;

&lt;p&gt;One caveat up front:&lt;br&gt;
A2UI v0.9 is still a draft, while v0.8 is the stable production release.&lt;/p&gt;

&lt;p&gt;So this is not a "ship and forget" protocol.&lt;br&gt;
You should pin versions and test contracts carefully.&lt;/p&gt;

&lt;p&gt;But v0.9 is still the version I would watch, because it is the version that feels much more usable for agent systems.&lt;/p&gt;

&lt;p&gt;The notable shifts are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a prompt-first design&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createSurface&lt;/code&gt; replacing &lt;code&gt;beginRendering&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a flat component model with a &lt;code&gt;component&lt;/code&gt; discriminator&lt;/li&gt;
&lt;li&gt;more standardized &lt;code&gt;updateDataModel&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;swappable catalogs instead of a single hardwired shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes the protocol easier to prompt, easier to generate, and easier to validate.&lt;br&gt;
That is the real win.&lt;/p&gt;
&lt;h2&gt;
  
  
  A quick note on what "schema" means here
&lt;/h2&gt;

&lt;p&gt;When people say "fixed schema" or "dynamic schema" in the context of A2UI, they usually mean something looser than a single JSON Schema file.&lt;/p&gt;

&lt;p&gt;In practice, the protocol is made up of separate artifacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a server-to-client envelope&lt;/li&gt;
&lt;li&gt;shared common types&lt;/li&gt;
&lt;li&gt;one or more catalogs that define renderable components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the real decision is usually this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do you give the model a fixed catalog contract?&lt;/li&gt;
&lt;li&gt;or do you dynamically select, narrow, or extend that contract for a specific request?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the framing that matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  The protocol shape you are actually building around
&lt;/h2&gt;

&lt;p&gt;For a web app, the v0.9 lifecycle is fairly straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The server sends &lt;code&gt;createSurface&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The server sends &lt;code&gt;updateComponents&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The server may send &lt;code&gt;updateDataModel&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The client renders the surface and sends actions back.&lt;/li&gt;
&lt;li&gt;The server continues streaming updates as needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A minimal shape looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v0.9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"createSurface"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"surfaceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"signup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"catalogId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://a2ui.org/specification/v0_9/basic_catalog.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sendDataModel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v0.9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updateComponents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"surfaceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"signup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Column"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"# Create account"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TextField"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/user/email"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submit_label"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Continue"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"child"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submit_label"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"submitSignup"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important implementation detail is that A2UI uses a flat list of components linked by IDs.&lt;br&gt;
That is a good choice because it is easier to emit, easier to diff, and easier to validate for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate IDs&lt;/li&gt;
&lt;li&gt;missing &lt;code&gt;root&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;dangling references&lt;/li&gt;
&lt;li&gt;cycles&lt;/li&gt;
&lt;li&gt;orphaned components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are exactly the boring details that determine whether a generative UI system survives contact with production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Strategy 1: Fixed schema
&lt;/h2&gt;

&lt;p&gt;Fixed schema means your application chooses the catalog ahead of time and the model only emits valid instances of UI that conform to that contract.&lt;/p&gt;

&lt;p&gt;This is the default I would recommend for most teams.&lt;/p&gt;

&lt;p&gt;Use it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the workflow is business-critical&lt;/li&gt;
&lt;li&gt;the UI shape is predictable&lt;/li&gt;
&lt;li&gt;you want strong testability&lt;/li&gt;
&lt;li&gt;analytics and event handling need to stay stable&lt;/li&gt;
&lt;li&gt;you do not want renderer behavior drifting between requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;onboarding flows&lt;/li&gt;
&lt;li&gt;support triage&lt;/li&gt;
&lt;li&gt;approvals&lt;/li&gt;
&lt;li&gt;payments&lt;/li&gt;
&lt;li&gt;account management&lt;/li&gt;
&lt;li&gt;healthcare or compliance-heavy forms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not the places where you want model creativity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why fixed schema works
&lt;/h3&gt;

&lt;p&gt;A fixed schema gives you a stable protocol boundary.&lt;br&gt;
That buys you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompts are easier to reason about&lt;/li&gt;
&lt;li&gt;examples are easier to curate&lt;/li&gt;
&lt;li&gt;QA can build reusable fixtures&lt;/li&gt;
&lt;li&gt;regressions are easier to reproduce&lt;/li&gt;
&lt;li&gt;analytics stay more consistent&lt;/li&gt;
&lt;li&gt;frontend behavior remains predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also makes debugging much easier.&lt;br&gt;
When something breaks, you are usually debugging a bad payload, not a drifting contract and a bad payload at the same time.&lt;/p&gt;
&lt;h3&gt;
  
  
  My rule of thumb
&lt;/h3&gt;

&lt;p&gt;If the flow is revenue-critical, compliance-heavy, or operationally sensitive, start fixed.&lt;/p&gt;

&lt;p&gt;You can always loosen the system later.&lt;br&gt;
Going the other direction is usually painful.&lt;/p&gt;
&lt;h2&gt;
  
  
  Strategy 2: Dynamic schema
&lt;/h2&gt;

&lt;p&gt;Dynamic schema is where things get more interesting.&lt;br&gt;
It is also where teams get themselves into trouble.&lt;/p&gt;

&lt;p&gt;What I mean by dynamic schema is not "let the model invent whatever UI primitives it wants."&lt;/p&gt;

&lt;p&gt;That is not a schema strategy.&lt;br&gt;
That is giving up.&lt;/p&gt;

&lt;p&gt;In a production A2UI application, dynamic schema should still be renderer-owned.&lt;br&gt;
The client needs to know how to render every component the model is allowed to reference.&lt;/p&gt;

&lt;p&gt;So in practice, dynamic schema usually means one of three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dynamic catalog selection from a trusted set&lt;/li&gt;
&lt;li&gt;Dynamic use of client-advertised inline catalogs&lt;/li&gt;
&lt;li&gt;Dynamic overlays that narrow or extend a trusted base catalog for a specific request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third option is the one I would ship most often.&lt;/p&gt;
&lt;h2&gt;
  
  
  The dynamic pattern I recommend
&lt;/h2&gt;

&lt;p&gt;Do not let the model generate a brand new universe from scratch.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start from a fixed trusted base catalog.&lt;/li&gt;
&lt;li&gt;Let the model generate a task-shaped overlay.&lt;/li&gt;
&lt;li&gt;Validate that overlay.&lt;/li&gt;
&lt;li&gt;Merge it into the base contract for prompting and validation.&lt;/li&gt;
&lt;li&gt;Fall back to the base contract if the overlay keeps failing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives you flexibility without losing control.&lt;br&gt;
Most products do not need unbounded UI generation.&lt;br&gt;
They need a safe way to adapt a known renderer contract to the long tail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzbyj9x9muo6q41e10kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzbyj9x9muo6q41e10kz.png" alt="Animated diagram of the A2UI dynamic overlay loop showing fixed-by-default policy, overlay validation and repair, and deterministic fallback." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why validation is non-negotiable
&lt;/h2&gt;

&lt;p&gt;A2UI v0.9 is more prompt-friendly.&lt;br&gt;
It is not a security model.&lt;/p&gt;

&lt;p&gt;If you allow a model to shape any part of the UI contract, then validation becomes part of the product, not an implementation detail.&lt;/p&gt;

&lt;p&gt;At minimum, a serious pipeline should validate two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The schema contract itself&lt;/li&gt;
&lt;li&gt;The final A2UI payload generated against that contract&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That separation matters because a valid overlay does not guarantee a valid payload.&lt;/p&gt;

&lt;p&gt;For dynamic schema in particular, I would validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;overlay structure&lt;/li&gt;
&lt;li&gt;component and property compatibility&lt;/li&gt;
&lt;li&gt;child reference types&lt;/li&gt;
&lt;li&gt;topology integrity&lt;/li&gt;
&lt;li&gt;final payload instances against the selected catalog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this layer, dynamic schema quickly turns into "the backend forwards malformed intent and hopes the renderer is charitable."&lt;/p&gt;
&lt;h2&gt;
  
  
  The architecture I would actually ship
&lt;/h2&gt;

&lt;p&gt;If I were building a fullstack agent web app around A2UI, this is the high-level policy I would use.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Fixed by default
&lt;/h3&gt;

&lt;p&gt;Use a fixed catalog for the majority of flows.&lt;br&gt;
Keep the happy path boring.&lt;/p&gt;

&lt;p&gt;That is where stability and predictable UX come from.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Dynamic for the long tail
&lt;/h3&gt;

&lt;p&gt;Use dynamic overlays only for workflows that are genuinely exploratory, tenant-specific, or too variable for one static prompt contract.&lt;/p&gt;

&lt;p&gt;This keeps the flexible path available without making the whole system probabilistic.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Dynamic means task-shaped, not unbounded
&lt;/h3&gt;

&lt;p&gt;Restrict dynamic behavior to things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;narrowing allowed components&lt;/li&gt;
&lt;li&gt;adding task-specific examples or constraints&lt;/li&gt;
&lt;li&gt;selecting from trusted custom catalogs&lt;/li&gt;
&lt;li&gt;merging safe overlays over a base catalog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not let the model invent component semantics the client never negotiated.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Validate, repair, retry, fallback
&lt;/h3&gt;

&lt;p&gt;The loop should be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate candidate overlay&lt;/li&gt;
&lt;li&gt;Validate it&lt;/li&gt;
&lt;li&gt;If invalid, feed the error back to the model&lt;/li&gt;
&lt;li&gt;Retry a few times&lt;/li&gt;
&lt;li&gt;Fall back to fixed if it still fails&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I would cap retries at around five attempts.&lt;/p&gt;

&lt;p&gt;Past that point, you are not repairing the schema.&lt;br&gt;
You are negotiating with entropy.&lt;/p&gt;

&lt;p&gt;At a high level, the control flow should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user request
  -&amp;gt; choose fixed or dynamic policy
  -&amp;gt; if dynamic:
       generate candidate overlay
       validate overlay
       if invalid:
         return validation error to model
         retry up to N times
       if still invalid:
         fall back to fixed catalog
  -&amp;gt; generate final A2UI payload
  -&amp;gt; validate final payload
  -&amp;gt; stream to client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation should be just as explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_SCHEMA_ATTEMPTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_catalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fixed_catalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fixed_catalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_capabilities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;should_use_dynamic_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fixed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fixed_catalog&lt;/span&gt;

    &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;last_overlay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_SCHEMA_ATTEMPTS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;overlay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm_generate_overlay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;client_capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;client_capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;previous_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;last_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;broken_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;last_overlay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;selected_catalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate_overlay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_capabilities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dynamic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selected_catalog&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;last_overlay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;overlay&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback-fixed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fixed_catalog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Validate the final payload too
&lt;/h3&gt;

&lt;p&gt;Even after the schema contract passes, validate the generated A2UI payload before it reaches the client.&lt;/p&gt;

&lt;p&gt;Schema validation and instance validation are separate responsibilities.&lt;br&gt;
Treat them that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Cache successful overlays
&lt;/h3&gt;

&lt;p&gt;If a task-shaped overlay validates and performs well for a repeated intent, cache it by tenant, task family, and capability set.&lt;/p&gt;

&lt;p&gt;That reduces latency, prompt cost, and repeated schema thrash.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Treat all agent output as untrusted
&lt;/h3&gt;

&lt;p&gt;This should be obvious, but it is still worth stating directly.&lt;/p&gt;

&lt;p&gt;Agent messages, UI payloads, and model-shaped schema overlays are all untrusted input.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;sanitization&lt;/li&gt;
&lt;li&gt;strict renderer boundaries&lt;/li&gt;
&lt;li&gt;strong CSP and isolation where relevant&lt;/li&gt;
&lt;li&gt;no casual trust just because the output happens to be JSON-shaped&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A useful mental model
&lt;/h2&gt;

&lt;p&gt;I think the cleanest way to reason about A2UI is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the renderer is trusted&lt;/li&gt;
&lt;li&gt;the model is constrained&lt;/li&gt;
&lt;li&gt;the backend is the enforcer&lt;/li&gt;
&lt;li&gt;fallback is deterministic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the architecture.&lt;br&gt;
Once you see it that way, the fixed-versus-dynamic decision gets much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use which approach
&lt;/h2&gt;

&lt;p&gt;My practical rule is simple.&lt;/p&gt;

&lt;p&gt;Use fixed schema when you want reliability.&lt;br&gt;
Use dynamic overlays when variability is real and the value justifies the complexity.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixed for core trusted workflows&lt;/li&gt;
&lt;li&gt;dynamic for the long tail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Per surface, not per company.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A2UI v0.9 is a meaningful step forward for agent-driven interfaces.&lt;/p&gt;

&lt;p&gt;The flatter structure, prompt-first posture, swappable catalogs, and explicit lifecycle make it much more usable than older "generate a giant nested object and pray" approaches.&lt;/p&gt;

&lt;p&gt;But the big lesson is not that everything should become dynamic.&lt;br&gt;
The big lesson is that agent-driven UI still needs architecture.&lt;/p&gt;

&lt;p&gt;The posture I would use is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixed schemas for stable, high-confidence workflows&lt;/li&gt;
&lt;li&gt;dynamic overlays for the long tail&lt;/li&gt;
&lt;li&gt;strict validation in the middle&lt;/li&gt;
&lt;li&gt;repair loops for recoverable failures&lt;/li&gt;
&lt;li&gt;deterministic fallback when the model cannot produce a safe contract&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives you flexibility without surrendering control.&lt;br&gt;
And in agent systems, control is the thing that lets creativity survive production.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://a2ui.org/" rel="noopener noreferrer"&gt;A2UI homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/introducing-a2ui-an-open-project-for-agent-driven-interfaces/" rel="noopener noreferrer"&gt;Google Developers Blog: Introducing A2UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2ui.org/specification/v0.9-a2ui/" rel="noopener noreferrer"&gt;A2UI Protocol v0.9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2ui.org/specification/v0.9-evolution-guide/" rel="noopener noreferrer"&gt;A2UI v0.8.1 to v0.9 evolution guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2ui.org/quickstart/" rel="noopener noreferrer"&gt;A2UI quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2ui.org/guides/renderer-development/" rel="noopener noreferrer"&gt;A2UI renderer development guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2ui.org/guides/custom-components/" rel="noopener noreferrer"&gt;A2UI custom component catalogs guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2ui.org/specification/v0.9-a2ui/#validator-compliance-custom-catalogs" rel="noopener noreferrer"&gt;A2UI v0.9 validator compliance and custom catalogs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/google/A2UI/main/agent_sdks/python/agent_development.md" rel="noopener noreferrer"&gt;A2UI Python SDK agent development guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/google/A2UI/main/agent_sdks/python/README.md" rel="noopener noreferrer"&gt;A2UI Python SDK README&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Frontend Reward Loop for Agentic Software</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Wed, 18 Mar 2026 23:02:37 +0000</pubDate>
      <link>https://dev.to/blove/the-frontend-reward-loop-for-agentic-software-4cig</link>
      <guid>https://dev.to/blove/the-frontend-reward-loop-for-agentic-software-4cig</guid>
      <description>&lt;p&gt;I have been thinking about this after reading the rLLM work on post-training language agents.&lt;/p&gt;

&lt;p&gt;The big idea in that work is right: if agents are going to improve, they need a loop.&lt;br&gt;
Not just inference.&lt;br&gt;
A loop of action, feedback, and improvement.&lt;/p&gt;

&lt;p&gt;What I want to argue in this post is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The frontend is the best place to collect the heuristics for that loop.&lt;/li&gt;
&lt;li&gt;Most teams should push prompt augmentation much further before training an offline reward model.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agent quality is now a feedback-loop problem, not only a model-size problem.&lt;/li&gt;
&lt;li&gt;The frontend is the only place where intent, correction, and outcome are visible together.&lt;/li&gt;
&lt;li&gt;Product heuristics can drive real gains without starting with offline reward-model training.&lt;/li&gt;
&lt;li&gt;Prompt augmentation gets most teams very far on short and medium-horizon workflows.&lt;/li&gt;
&lt;li&gt;Move to offline RL only when prompt gains flatten or long-horizon credit assignment becomes the bottleneck.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fullstack Agentic Applications
&lt;/h2&gt;

&lt;p&gt;Be sure to check out the &lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;Angular Agent Framework&lt;/a&gt; for building fullstack agentic applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  From static intelligence to live behavior
&lt;/h2&gt;

&lt;p&gt;Reasoning models can do very well on static tasks.&lt;br&gt;
Agentic software lives in dynamic tasks.&lt;/p&gt;

&lt;p&gt;That means we care less about one-shot correctness and more about behavioral quality over a sequence of steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the system choose a sensible next action?&lt;/li&gt;
&lt;li&gt;Did the user need to undo or rewrite it?&lt;/li&gt;
&lt;li&gt;Did the workflow actually complete?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why post-training agents matter.&lt;br&gt;
And this is why product teams need to treat interaction data as first-class infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the frontend is the best reward surface
&lt;/h2&gt;

&lt;p&gt;The backend can tell us what was called.&lt;br&gt;
The model logs can tell us what token came next.&lt;br&gt;
Only the frontend can reliably tell us whether the user felt the action was useful.&lt;/p&gt;

&lt;p&gt;The frontend gives us a dense stream of behavior signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept vs reject&lt;/li&gt;
&lt;li&gt;Minor edit vs full rewrite&lt;/li&gt;
&lt;li&gt;One-shot completion vs retry loops&lt;/li&gt;
&lt;li&gt;Continue vs abandon&lt;/li&gt;
&lt;li&gt;Agent flow vs human escalation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not perfect labels.&lt;br&gt;
They are heuristics.&lt;br&gt;
But they are high signal and available immediately.&lt;/p&gt;

&lt;p&gt;In practice, this is the best early reward surface most teams have.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical heuristic set
&lt;/h2&gt;

&lt;p&gt;If I were starting today, I would implement four buckets.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Correction heuristics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Edit distance between agent output and final submitted value&lt;/li&gt;
&lt;li&gt;Undo/revert frequency after agent actions&lt;/li&gt;
&lt;li&gt;Manual overwrite rate&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Friction heuristics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Time-to-next-meaningful-action&lt;/li&gt;
&lt;li&gt;Retry count for same intent&lt;/li&gt;
&lt;li&gt;Dead-end path frequency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Outcome heuristics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Task completion rate&lt;/li&gt;
&lt;li&gt;Reopen/regression rate&lt;/li&gt;
&lt;li&gt;SLA-aware completion time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Trust heuristics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Delegation rate for repeat tasks&lt;/li&gt;
&lt;li&gt;Human escalation rate&lt;/li&gt;
&lt;li&gt;Opt-out after poor outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single metric is enough.&lt;br&gt;
A composite score is usually better than betting on one proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I want to challenge: do we need offline reward models first?
&lt;/h2&gt;

&lt;p&gt;Usually, no.&lt;/p&gt;

&lt;p&gt;I think most teams can get much further than expected with prompt augmentation before training a separate reward model offline.&lt;/p&gt;

&lt;p&gt;When I say prompt augmentation, I mean the full runtime strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better task framing&lt;/li&gt;
&lt;li&gt;Better context packing from recent user behavior&lt;/li&gt;
&lt;li&gt;Better tool constraints and checks&lt;/li&gt;
&lt;li&gt;Better fallback prompts for low-confidence cases&lt;/li&gt;
&lt;li&gt;Better routing between prompt variants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still behavior optimization.&lt;br&gt;
It is just inference-time optimization.&lt;/p&gt;

&lt;p&gt;For many product workflows, that is enough to unlock most early gains.&lt;/p&gt;

&lt;h2&gt;
  
  
  How far prompt augmentation gets us
&lt;/h2&gt;

&lt;p&gt;Prompt augmentation gets us very far on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bounded workflows&lt;/li&gt;
&lt;li&gt;Clear completion criteria&lt;/li&gt;
&lt;li&gt;Frequent user corrections&lt;/li&gt;
&lt;li&gt;Fast evaluation loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal support triage&lt;/li&gt;
&lt;li&gt;Structured drafting and form completion&lt;/li&gt;
&lt;li&gt;Actionable summarization&lt;/li&gt;
&lt;li&gt;Multi-step UI workflows with explicit completion states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where it starts to fail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long trajectories with delayed outcomes&lt;/li&gt;
&lt;li&gt;Heavy credit assignment ambiguity&lt;/li&gt;
&lt;li&gt;High cost/latency from complex inference-time strategies&lt;/li&gt;
&lt;li&gt;Persistent instability across prompt variants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is when offline trajectory training and RL become worth the investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended rollout
&lt;/h2&gt;

&lt;p&gt;If you are building agentic product features now, this is the sequence I would use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instrument frontend interaction events with stable task/session IDs.&lt;/li&gt;
&lt;li&gt;Define a small heuristic scorecard (correction, friction, outcome, trust).&lt;/li&gt;
&lt;li&gt;Use that scorecard to drive prompt and routing updates weekly.&lt;/li&gt;
&lt;li&gt;Build an eval flywheel with holdouts and regression checks.&lt;/li&gt;
&lt;li&gt;Introduce offline reward-model training only after improvements plateau.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps teams moving quickly while preserving a path to deeper optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails
&lt;/h2&gt;

&lt;p&gt;None of this works well without guardrails.&lt;/p&gt;

&lt;p&gt;At minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy-safe event collection&lt;/li&gt;
&lt;li&gt;Anti-gaming checks for proxy metrics&lt;/li&gt;
&lt;li&gt;Correctness-weighted objectives (not just speed)&lt;/li&gt;
&lt;li&gt;Human review samples to calibrate heuristic drift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we do not add guardrails, we optimize the dashboard instead of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The backend can train the policy.&lt;br&gt;
But the frontend is where the reward signal is born.&lt;/p&gt;

&lt;p&gt;If we want agents that improve from real human interaction, we need to treat frontend behavior as training infrastructure.&lt;/p&gt;

&lt;p&gt;And we should be honest about sequencing:&lt;br&gt;
Start with prompt augmentation.&lt;br&gt;
Push it hard.&lt;br&gt;
Then add offline RL when the data proves you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Angular &amp;amp; LangChain together?
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;a href="https://stream-resource.dev" rel="noopener noreferrer"&gt;the Enterprise Streaming Resource for LangChain and Angular&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;rLLM: &lt;a href="https://pretty-radio-b75.notion.site/rLLM-A-Framework-for-Post-Training-Language-Agents-21b81902c146819db63cd98a54ba5f31" rel="noopener noreferrer"&gt;A Framework for Post-Training Language Agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;rLLM Docs: &lt;a href="https://rllm-project.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;rLLM Project Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sutton and Silver: [Welcome to the Era of Experience](&lt;a href="https://storage.googleapis.com/deepmind-media/Era-of-Experience%20/The%20Era%20of%20Experience%20Paper.pdf" rel="noopener noreferrer"&gt;https://storage.googleapis.com/deepmind-media/Era-of-Experience%20/The%20Era%20of%20Experience%20Paper.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Landscape of Generative UI in 2026</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Wed, 18 Mar 2026 23:02:36 +0000</pubDate>
      <link>https://dev.to/blove/the-landscape-of-generative-ui-in-2026-ad6</link>
      <guid>https://dev.to/blove/the-landscape-of-generative-ui-in-2026-ad6</guid>
      <description>&lt;p&gt;Generative UI is no longer just "chat that can answer questions."&lt;br&gt;&lt;br&gt;
In 2026, it is a product and architecture decision.&lt;/p&gt;

&lt;p&gt;In this post, I want to give a practical framework I keep coming back to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Chat Components&lt;/li&gt;
&lt;li&gt;Component Systems&lt;/li&gt;
&lt;li&gt;Embedded Generative UI&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Generative UI is a spectrum, not a single implementation pattern.&lt;/li&gt;
&lt;li&gt;Each pattern optimizes for a different balance of control and flexibility.&lt;/li&gt;
&lt;li&gt;Most teams should mix patterns by surface area, not pick one globally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;The first wave of AI products proved that people will use chat interfaces.&lt;br&gt;
The next wave is about execution: helping users actually complete work.&lt;/p&gt;

&lt;p&gt;Text-only responses are often too slow for real workflows.&lt;br&gt;
Users need affordances to compare options, edit inputs, and take action with confidence.&lt;/p&gt;

&lt;p&gt;That is where Generative UI becomes useful: it converts intent into usable interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Generative UI is
&lt;/h2&gt;

&lt;p&gt;Generative UI is UI that is selected, composed, or embedded at runtime based on user intent and agent reasoning.&lt;/p&gt;

&lt;p&gt;It is not "markdown in a chat bubble."&lt;br&gt;
It is a system where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model interprets intent&lt;/li&gt;
&lt;li&gt;tools fetch or mutate state&lt;/li&gt;
&lt;li&gt;the UI renders clear actions and state transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'm working on.
&lt;/h2&gt;

&lt;p&gt;Before we go any further, I'd love to share that I am building &lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;threadplane - Agent UI for Angular&lt;/a&gt; at threadplane.ai&lt;/p&gt;

&lt;h2&gt;
  
  
  The spectrum
&lt;/h2&gt;

&lt;p&gt;I think about this as &lt;strong&gt;More Control -&amp;gt; More Freedom&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Chat Components&lt;/li&gt;
&lt;li&gt;Component Systems&lt;/li&gt;
&lt;li&gt;Embedded Generative UI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are complementary patterns.&lt;br&gt;
You can (and often should) use all three in one product.&lt;/p&gt;

&lt;h2&gt;
  
  
  1) Chat Components
&lt;/h2&gt;

&lt;p&gt;Chat Components are predefined UI blocks that the agent can invoke.&lt;br&gt;
The frontend team still owns the component implementation and behavior.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2llgogwyx4fgmbtdxuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2llgogwyx4fgmbtdxuv.png" alt="Animated diagram of Chat Components where agent messages trigger a fixed set of approved UI components." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why teams start here
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fastest path to a reliable production experience&lt;/li&gt;
&lt;li&gt;Strong control over brand and behavior&lt;/li&gt;
&lt;li&gt;Easier trust and security posture from explicit contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tradeoffs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;New UI shapes still require frontend releases&lt;/li&gt;
&lt;li&gt;Less flexible for novel, long-tail requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best fit
&lt;/h3&gt;

&lt;p&gt;Core, high-trust, brand-critical flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  2) Component Systems
&lt;/h2&gt;

&lt;p&gt;Component Systems use schema-driven composition.&lt;br&gt;
The model or backend provides structured payloads, and the frontend composes the screen from reusable primitives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpw4enwf2trckiuqbu48s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpw4enwf2trckiuqbu48s.png" alt="Animated diagram of Component Systems where schema rows stream into composed UI modules." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this pattern scales
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lower coupling between backend behavior and frontend rendering&lt;/li&gt;
&lt;li&gt;Better coverage for long-tail UI permutations&lt;/li&gt;
&lt;li&gt;Consistent visual system even with many generated layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tradeoffs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;More engineering investment up front&lt;/li&gt;
&lt;li&gt;Less pixel-perfect than handcrafted, one-off UI&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best fit
&lt;/h3&gt;

&lt;p&gt;Enterprise and platform surfaces with broad variability and repeatable primitives.&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Embedded Generative UI
&lt;/h2&gt;

&lt;p&gt;Embedded Generative UI is when a host app embeds external app surfaces and coordinates secure handoff between agent context and embedded execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxctym32wcpyep5okn9j0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxctym32wcpyep5okn9j0.png" alt="Animated diagram of Embedded Generative UI where a host app securely hands off context to embedded app surfaces." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why teams adopt it
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Maximum flexibility for specialized experiences&lt;/li&gt;
&lt;li&gt;Natural path to ecosystem or app-platform strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tradeoffs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Hardest developer experience&lt;/li&gt;
&lt;li&gt;Inconsistent presentation across embedded surfaces&lt;/li&gt;
&lt;li&gt;More complex security and permission design&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best fit
&lt;/h3&gt;

&lt;p&gt;Super-host products where extensibility is part of the core value proposition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is already being used (verified examples)
&lt;/h2&gt;

&lt;p&gt;The examples below are based on public product/docs updates and are accurate as of February 21, 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chat Components examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI's &lt;a href="https://openai.com/index/spring-update/" rel="noopener noreferrer"&gt;Spring Update (May 13, 2024)&lt;/a&gt; states ChatGPT users can "analyze data and create charts," which is a concrete case of model responses invoking visual components inside chat.&lt;/li&gt;
&lt;li&gt;Salesforce announced &lt;a href="https://investor.salesforce.com/press-releases/press-release-details/2025/Salesforce-Unveils-Agentforce-2dx-A-Major-Leap-Forward-in-Its-Digital-Labor-Platform/default.aspx" rel="noopener noreferrer"&gt;Agentforce Cards (March 5, 2025)&lt;/a&gt;, where agents embed Lightning Web Components in responses.&lt;/li&gt;
&lt;li&gt;Intercom's &lt;a href="https://www.intercom.com/changes/en/197926-new-card-design-for-fin-answers" rel="noopener noreferrer"&gt;Fin answer cards update (January 27, 2025)&lt;/a&gt; is another example of structured, reusable UI cards rendered inline in conversational responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Component Systems examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Copilot Studio documents &lt;a href="https://learn.microsoft.com/en-us/microsoft-copilot-studio/adaptive-cards-overview" rel="noopener noreferrer"&gt;Adaptive Cards&lt;/a&gt; as JSON-defined custom UI, including inputs and submit actions, rendered in chat surfaces.&lt;/li&gt;
&lt;li&gt;Microsoft 365 Copilot API documents &lt;a href="https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/api-plugin-adaptive-cards" rel="noopener noreferrer"&gt;Adaptive Card response templates&lt;/a&gt;, including static and dynamic templates that map cleanly to schema-driven composition.&lt;/li&gt;
&lt;li&gt;Salesforce's &lt;a href="https://developer.salesforce.com/blogs/2025/09/best-practices-for-designing-agentforce-agents-for-scale" rel="noopener noreferrer"&gt;adaptive response formats&lt;/a&gt; (for example rich choice and rich link) show a standardized response schema approach for composing predictable UI affordances.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Embedded Generative UI examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI's &lt;a href="https://developers.openai.com/apps-sdk/reference" rel="noopener noreferrer"&gt;Apps SDK reference&lt;/a&gt; describes components rendered in ChatGPT and connected through a &lt;code&gt;window.openai&lt;/code&gt; bridge, with explicit iframe and CSP controls.&lt;/li&gt;
&lt;li&gt;OpenAI's &lt;a href="https://developers.openai.com/blog/chatgpt-apps-lessons" rel="noopener noreferrer"&gt;ChatGPT apps lessons learned (February 4, 2026)&lt;/a&gt; notes they built roughly two dozen apps and discusses practical iframe architecture patterns for hosted app experiences.&lt;/li&gt;
&lt;li&gt;OpenAI's &lt;a href="https://openai.com/index/introducing-gpts/" rel="noopener noreferrer"&gt;Introducing GPTs (November 2023)&lt;/a&gt; and the &lt;a href="https://help.openai.com/en/articles/8798868-introducing-the-gpt-store" rel="noopener noreferrer"&gt;GPT Store help article&lt;/a&gt; document the host + app-store model in ChatGPT.&lt;/li&gt;
&lt;li&gt;Anthropic's &lt;a href="https://www.anthropic.com/news/introducing-artifacts" rel="noopener noreferrer"&gt;Artifacts overview&lt;/a&gt; and &lt;a href="https://support.anthropic.com/en/articles/9517075-what-are-artifacts-and-how-do-i-use-them" rel="noopener noreferrer"&gt;Artifacts support docs&lt;/a&gt; describe interactive, side-panel app-like outputs embedded directly in the Claude host experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started: Hashbrown
&lt;/h2&gt;

&lt;p&gt;If you want to implement Generative UI in a web app today with Hashbrown, the core pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stream model output from your backend.&lt;/li&gt;
&lt;li&gt;Register renderable components in the frontend.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;useUiChat&lt;/code&gt; so assistant messages can include UI trees, not just text.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The official setup in Hashbrown docs/README is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;@hashbrownai/{core,react,openai}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Wrap your app with &lt;code&gt;HashbrownProvider&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Define model-callable components with &lt;code&gt;exposeComponent&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;useUiChat({ components: [...] })&lt;/code&gt; to render those components in assistant responses.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HashbrownProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;exposeComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useUiChat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hashbrownai/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hashbrownai/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUiChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a helpful assistant that can render UI components.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nf"&gt;exposeComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FlightCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FlightCard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Show a flight option card&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Airline name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Formatted ticket price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// render messages and call sendMessage(...)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Providers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HashbrownProvider&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;HashbrownProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/liveloveapp/hashbrown" rel="noopener noreferrer"&gt;Hashbrown main README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/liveloveapp/hashbrown/blob/main/packages/react/README.md" rel="noopener noreferrer"&gt;Hashbrown React README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/liveloveapp/hashbrown/blob/main/packages/react/src/hooks/use-ui-chat.tsx" rel="noopener noreferrer"&gt;Hashbrown &lt;code&gt;useUiChat&lt;/code&gt; source&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started: CopilotKit (v2 APIs)
&lt;/h2&gt;

&lt;p&gt;For CopilotKit, a practical v2 path is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the root &lt;code&gt;&amp;lt;CopilotKit&amp;gt;&lt;/code&gt; provider to connect to your runtime.&lt;/li&gt;
&lt;li&gt;Use v2 chat components (for example &lt;code&gt;CopilotChat&lt;/code&gt; or &lt;code&gt;CopilotSidebar&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Register v2 frontend tools with &lt;code&gt;useFrontendTool&lt;/code&gt; and a &lt;code&gt;render&lt;/code&gt; function for in-chat UI.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CopilotKit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@copilotkit/react-core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;CopilotChat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ToolCallStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useFrontendTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@copilotkit/react-core/v2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@copilotkit/react-core/v2/styles.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ToolUIs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useFrontendTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;showFlightCard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Display a flight option card in the chat UI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;ToolCallStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading card...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FlightCard&lt;/span&gt; &lt;span class="na"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;airline&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CopilotKit&lt;/span&gt; &lt;span class="na"&gt;runtimeUrl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/api/copilotkit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToolUIs&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CopilotChat&lt;/span&gt; &lt;span class="na"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"travel-agent"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;CopilotKit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes for v2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep hooks/components from &lt;code&gt;@copilotkit/react-core/v2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Import the provider from &lt;code&gt;@copilotkit/react-core&lt;/code&gt; (as the v2 docs specify).&lt;/li&gt;
&lt;li&gt;Use Zod schemas for v2 tool parameters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/CopilotKit/CopilotKit/blob/main/docs/content/docs/reference/v2/index.mdx" rel="noopener noreferrer"&gt;CopilotKit v2 API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CopilotKit/CopilotKit/blob/main/docs/content/docs/reference/v2/components/CopilotChat.mdx" rel="noopener noreferrer"&gt;CopilotChat (v2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CopilotKit/CopilotKit/blob/main/docs/content/docs/reference/v2/hooks/useFrontendTool.mdx" rel="noopener noreferrer"&gt;useFrontendTool (v2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CopilotKit/CopilotKit/tree/main/examples/v2" rel="noopener noreferrer"&gt;CopilotKit v2 example app&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choosing the right pattern
&lt;/h2&gt;

&lt;p&gt;My practical rule: choose per surface, not per company.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;Chat Components&lt;/strong&gt; for trusted core workflows.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Component Systems&lt;/strong&gt; for scalable, long-tail generation.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Embedded Generative UI&lt;/strong&gt; only where ecosystem value clearly outweighs complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture notes that matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep UI contracts owned by frontend.&lt;/li&gt;
&lt;li&gt;Keep tool boundaries explicit (read, write, side effects).&lt;/li&gt;
&lt;li&gt;Keep agent behavior portable across model providers.&lt;/li&gt;
&lt;li&gt;Match security by pattern:&lt;/li&gt;
&lt;li&gt;Chat Components: strict, typed contracts.&lt;/li&gt;
&lt;li&gt;Component Systems: schema validation plus policy checks.&lt;/li&gt;
&lt;li&gt;Embedded Generative UI: sandboxing, origin boundaries, and explicit permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation progression I recommend
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start with Chat Components for one critical workflow.&lt;/li&gt;
&lt;li&gt;Add Component Systems for breadth and speed.&lt;/li&gt;
&lt;li&gt;Add Embedded Generative UI selectively for ecosystem scenarios.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep one reference workflow (for example, flight booking) across all three stages.&lt;br&gt;
It makes tradeoffs concrete and easier to explain to stakeholders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Generative UI is not just a model capability.&lt;br&gt;
It is a design and systems decision.&lt;/p&gt;

&lt;p&gt;In 2026, the strongest strategy is intentional composition:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start constrained.&lt;/li&gt;
&lt;li&gt;Expand with structure.&lt;/li&gt;
&lt;li&gt;Embed only where leverage is clear.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>OpenClaw Setup, Configuration, and Key Takeaways</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Wed, 18 Mar 2026 23:02:35 +0000</pubDate>
      <link>https://dev.to/blove/openclaw-setup-configuration-and-key-takeaways-5ce8</link>
      <guid>https://dev.to/blove/openclaw-setup-configuration-and-key-takeaways-5ce8</guid>
      <description>&lt;p&gt;I set up OpenClaw on macOS with Telegram as my primary channel. The initial setup was quick, but I hit pairing and health-check failures that caused the first message to hang. This post captures what worked, what broke, and the operating policy I now use by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;macOS desktop app + CLI&lt;/li&gt;
&lt;li&gt;Node &lt;code&gt;v22.14.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;OpenClaw &lt;code&gt;2026.2.19-2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Telegram bot channel enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Baseline setup
&lt;/h2&gt;

&lt;p&gt;I started with the gateway and channel checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw gateway probe
openclaw status &lt;span class="nt"&gt;--deep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That verified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gateway reachable on &lt;code&gt;ws://127.0.0.1:18789&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Telegram configured and healthy&lt;/li&gt;
&lt;li&gt;Agent session available&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Pairing the macOS app
&lt;/h2&gt;

&lt;p&gt;My first blocker was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gateway closed (1008): pairing required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I inspected pending and paired devices and approved the app request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw devices list &lt;span class="nt"&gt;--json&lt;/span&gt;
openclaw devices approve &amp;lt;request-id&amp;gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensured the macOS app had the expected auth state and roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Debugging "health check failed" and hanging sends
&lt;/h2&gt;

&lt;p&gt;After pairing, the app still showed a failed health check and initial sends hung. The logs showed repeated health RPC failures tied to role auth behavior, which degraded responsiveness under load.&lt;/p&gt;

&lt;p&gt;What helped most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comparing app behavior with app process stopped vs running&lt;/li&gt;
&lt;li&gt;Watching gateway logs during connection attempts&lt;/li&gt;
&lt;li&gt;Validating roles/tokens in device state before changing anything else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key lesson: trust the logs over UI symptoms.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Stabilizing and re-validating
&lt;/h2&gt;

&lt;p&gt;After correcting pairing/auth behavior, I re-ran validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw gateway restart
openclaw gateway probe
openclaw status &lt;span class="nt"&gt;--deep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final checks I care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gateway RPC succeeds consistently&lt;/li&gt;
&lt;li&gt;Telegram status is OK&lt;/li&gt;
&lt;li&gt;No recurring pairing-upgrade loop&lt;/li&gt;
&lt;li&gt;No repeating unauthorized health errors&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Default security policy (what OpenClaw should always/never do)
&lt;/h2&gt;

&lt;p&gt;This is the policy I now use as my default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ask before risky actions; state what/why first.&lt;/li&gt;
&lt;li&gt;Show exact files/commands changed.&lt;/li&gt;
&lt;li&gt;Run relevant tests/lint before declaring done.&lt;/li&gt;
&lt;li&gt;Redact secrets/tokens from output.&lt;/li&gt;
&lt;li&gt;Ask clarifying questions when requirements are ambiguous.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Never (without explicit approval)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Destructive ops (&lt;code&gt;rm -rf&lt;/code&gt;, &lt;code&gt;git reset --hard&lt;/code&gt;, DB deletes, force-push).&lt;/li&gt;
&lt;li&gt;Sending external messages (Telegram/email/webhooks) on my behalf.&lt;/li&gt;
&lt;li&gt;Installing/updating system tools, packages, or services.&lt;/li&gt;
&lt;li&gt;Accessing or exporting secrets/credentials.&lt;/li&gt;
&lt;li&gt;Spending money or invoking paid APIs/services.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'm working on.
&lt;/h2&gt;

&lt;p&gt;With openclaw I am working on &lt;a href="https://threadplane.ai" rel="noopener noreferrer"&gt;threadplane - Agent UI for Angular&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pairing roles must match real app behavior, not just initial connect success.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;openclaw devices list --json&lt;/code&gt; is the fastest way to spot role mismatches.&lt;/li&gt;
&lt;li&gt;Health checks are a real signal; if they fail, message send paths can stall.&lt;/li&gt;
&lt;li&gt;Re-verify after every change with &lt;code&gt;gateway probe&lt;/code&gt; and &lt;code&gt;status --deep&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Strong boundaries reduce accidental risk while still keeping automation useful.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>tooling</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building Web Applications We Can Trust - The Imperative of SRE</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Sun, 11 Jun 2023 03:00:14 +0000</pubDate>
      <link>https://dev.to/blove/building-web-applications-we-can-trust-the-imperative-of-sre-3ohk</link>
      <guid>https://dev.to/blove/building-web-applications-we-can-trust-the-imperative-of-sre-3ohk</guid>
      <description>&lt;h1&gt;
  
  
  Building Web Applications We Can Trust - The Imperative of SRE
&lt;/h1&gt;

&lt;p&gt;In this article, we explore the critical role of Site Reliability Engineering (SRE) for different types of SaaS owners and web developers, ranging from those overlooking reliability issues, those fighting to escape 'reliability hell', and those aware of the importance but lacking the resources to implement it.&lt;br&gt;
We emphasize the necessity of SRE not just as a problem-solving method but as a culture, fostering a more reliable, trustworthy digital future.&lt;br&gt;
We also introduce &lt;a href="https://getpolaris.ai" rel="noopener noreferrer"&gt;Polaris, an AI-powered tool designed to support and simplify the journey towards improved reliability&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In today's digital era, web application and Software-as-a-Service (SaaS) platforms dominate our interactions, dictating how we work, communicate, shop, and more.&lt;br&gt;
As a result, reliability and trust have emerged as the most crucial features of these platforms.&lt;br&gt;
In the throes of digital transformation, if there is one thing that every SaaS owner or web developer must focus on, it's Site Reliability Engineering (SRE).&lt;br&gt;
Why? Let's explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unresolved Reliability: Time Bomb or Opportunity?
&lt;/h2&gt;

&lt;p&gt;The first group of SaaS owners and software engineers are those who are aware of reliability issues but opt to overlook them.&lt;br&gt;
Their reasons may vary, from cost implications to rapid growth that seems to dwarf the need for reliability.&lt;br&gt;
This can be a deceptive comfort zone, the mirage of a thriving business masking an impending reliability catastrophe.&lt;br&gt;
Yes, you might have strong consumer lock-in, and your business might be on a steep growth curve.&lt;br&gt;
However, remember this: trust, once lost, is hard to regain.&lt;/p&gt;

&lt;p&gt;Consider what's at stake: your reputation, customer loyalty, and ultimately, your bottom line.&lt;br&gt;
The need for reliability isn't a business choice —- it's a business imperative.&lt;br&gt;
Don't wait for the time bomb to detonate; use the ticking to turn it into an opportunity.&lt;br&gt;
By implementing SRE best practices now, you can preemptively address potential reliability issues and strengthen user trust, providing you with a competitive edge in the market.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Long Journey Out of Reliability Hell: A Crucible for Change
&lt;/h2&gt;

&lt;p&gt;The second group comprises SaaS owners and engineers painfully aware that they are in what we call "reliability hell".&lt;br&gt;
You may be experiencing high churn rates, customer complaints, and even plummeting profits.&lt;br&gt;
Getting out of reliability hell is indeed a Herculean task.&lt;br&gt;
It takes time, effort, and significant investment in SRE best practices, tools, and processes.&lt;br&gt;
Yet, it's a task worth undertaking, a crucible that can forge a more resilient and trusted business.&lt;/p&gt;

&lt;p&gt;By embracing SRE, you are not just rectifying issues, but more importantly, you're establishing a culture of reliability within your organization.&lt;br&gt;
This culture is a game-changer.&lt;br&gt;
It ensures that reliability is built into the DNA of your applications, not bolted on as an afterthought.&lt;br&gt;
It fosters an environment where every code, every deployment, every update is geared towards enhancing reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledging the Need for SRE: A Call to Arms
&lt;/h2&gt;

&lt;p&gt;Lastly, there are those SaaS owners and developers who are acutely aware of the need for reliability but find themselves unprepared to take it on.&lt;br&gt;
The challenges of knowledge, skills, and resources can indeed be daunting.&lt;br&gt;
But remember this: admitting the problem is the first step to the solution.&lt;/p&gt;

&lt;p&gt;Fortunately, you're not alone in this journey.&lt;br&gt;
Tools like Polaris, an AI-powered site reliability tool, can be invaluable.&lt;br&gt;
Polaris is designed to detect outages and incidents in your web application in real time, helping you to navigate the complexities of SRE, even with limited resources.&lt;/p&gt;

&lt;p&gt;Our vision for Polaris is simple: &lt;strong&gt;to help you build applications that people trust&lt;/strong&gt;.&lt;br&gt;
Because in a world reliant on digital, trust and reliability are the true currencies of success.&lt;/p&gt;

&lt;p&gt;If you're ready to steer your web application or SaaS business towards a more reliable, more trusted future, we invite you to explore Polaris.&lt;br&gt;
Polaris is currently in beta and we'd love for you to be part of our journey.&lt;br&gt;
Feel free to sign up for Polaris, or contact us to learn more.&lt;/p&gt;

&lt;p&gt;Together, we can build a world of reliable and trusted web applications.&lt;/p&gt;

</description>
      <category>sre</category>
      <category>saas</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Embracing Site Reliability Engineering</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Wed, 10 May 2023 03:38:41 +0000</pubDate>
      <link>https://dev.to/blove/embracing-site-reliability-engineering-17ll</link>
      <guid>https://dev.to/blove/embracing-site-reliability-engineering-17ll</guid>
      <description>&lt;p&gt;Site Reliability Engineering (SRE) is transforming the way software teams approach reliability and performance in modern web applications.&lt;br&gt;
With an increasing number of frontend-focused engineers, it's essential to understand the fundamentals of SRE and implement a culture of reliability within your team and organization.&lt;br&gt;
This blog post will introduce you to the basics of Site Reliability Engineering, its importance for software engineers and managers, and provide some best practices to kickstart your journey towards a more reliable web application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Site Reliability Engineering?
&lt;/h2&gt;

&lt;p&gt;Site Reliability Engineering is a discipline that combines aspects of software engineering and operations to build and maintain scalable and reliable systems.&lt;br&gt;
Pioneered by Google, SRE focuses on automating infrastructure management, ensuring service reliability, and optimizing performance.&lt;br&gt;
SRE emphasizes the use of Service Level Objectives (SLOs) and Error Budgets as a means to balance feature development, system stability, and user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Should Software Engineers and Managers Be Concerned with Reliability?
&lt;/h2&gt;

&lt;p&gt;Reliability is crucial for the success of any web application.&lt;br&gt;
A reliable application ensures a positive user experience, leading to higher user retention, satisfaction, and ultimately, revenue.&lt;br&gt;
As a software engineer or manager, it's your responsibility to ensure that your application is stable, performant, and consistently meeting the needs of your users.&lt;br&gt;
Embracing SRE principles can help you identify and address performance bottlenecks and system failures, reducing downtime and improving overall system resilience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Building a Culture of Reliability
&lt;/h2&gt;

&lt;p&gt;First, &lt;strong&gt;define clear Service Level Indicators (SLIs) and Service Level Objectives (SLOs)&lt;/strong&gt;. SLIs are metrics that quantify your system's performance, while SLOs are target values for those metrics.&lt;br&gt;
Establishing clear SLIs and SLOs helps your team set realistic goals for system reliability and provides a measurable way to track progress.&lt;br&gt;
Ensure that these metrics are aligned with your users' expectations to maintain a high-quality user experience.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;implement monitoring and observability&lt;/strong&gt;.&lt;br&gt;
Monitoring and observability are crucial for understanding your system's health and performance.&lt;br&gt;
Invest in tools that provide real-time insights into your application's performance and enable you to identify potential issues quickly.&lt;br&gt;
Polaris, an AI-powered site reliability platform, is an excellent example of a solution that helps you effectively monitor and gain observability into your web applications' performance and reliability.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;automate incident response and management&lt;/strong&gt;.&lt;br&gt;
Automate processes for detecting, triaging, and resolving incidents to minimize the impact on your users.&lt;br&gt;
Integrating your monitoring tools with incident management solutions, such as PagerDuty or Jira, can help streamline the process and reduce time to resolution.&lt;/p&gt;

&lt;p&gt;Fourth, &lt;strong&gt;embrace a blameless culture&lt;/strong&gt;.&lt;br&gt;
Encourage a culture of learning and continuous improvement by treating failures as opportunities to learn rather than assigning blame.&lt;br&gt;
Conduct blameless postmortems to identify the root causes of incidents and implement preventive measures to avoid recurrence.&lt;/p&gt;

&lt;p&gt;Fifth, &lt;strong&gt;continuously iterate and improve&lt;/strong&gt;.&lt;br&gt;
Continuously evaluate and refine your processes, tools, and infrastructure.&lt;br&gt;
Keep your team informed about best practices, and encourage open communication and collaboration to foster a culture of learning and improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Site Reliability Engineering is a powerful approach to ensuring the reliability and performance of your web applications.&lt;br&gt;
By understanding its fundamentals and adopting SRE best practices, you can build a culture of reliability within your team and organization, ultimately leading to more satisfied users and a successful web application.&lt;br&gt;
Start your journey towards a more reliable web application today by embracing the principles of Site Reliability Engineering and leveraging the power of tools like Polaris to gain observability and insights into your system's performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polaris
&lt;/h2&gt;

&lt;p&gt;Building a culture of site reliability is critical to the success of an organization, especially if you are a SaaS product. But, it can be hard to climb out of reliability hell.&lt;/p&gt;

&lt;p&gt;My team is building an &lt;a href="https://getpolaris.ai" rel="noopener noreferrer"&gt;AI-powered Site Reliability Platform&lt;/a&gt; that helps SaaS companies build apps people trust by increasing the reliability and performance of your web apps. We're currently in a closed-beta. However, don't fret, you can sign up for our beta and help us build a platform that changes the way we think about site reliability.&lt;/p&gt;

</description>
      <category>sre</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Typed Forms in Angular</title>
      <dc:creator>Brian Love</dc:creator>
      <pubDate>Thu, 20 Oct 2022 18:47:47 +0000</pubDate>
      <link>https://dev.to/blove/typed-forms-in-angular-334c</link>
      <guid>https://dev.to/blove/typed-forms-in-angular-334c</guid>
      <description>&lt;p&gt;Angular version 14 is a feature-packed release that brings new APIs, functionality, and a developer experience.&lt;br&gt;
This includes one of the most requested feature improvements in Angular: typed forms.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to migrate to the new strongly-typed forms API in Angular version 14&lt;/li&gt;
&lt;li&gt;Typed a &lt;code&gt;FormControl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Typed a &lt;code&gt;FormGroup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Typed a &lt;code&gt;FormArray&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;FormRecord&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Mixing typed and untyped controls&lt;/li&gt;
&lt;li&gt;Non-nullable controls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;
&lt;h2&gt;
  
  
  Typed Forms API FTW 🎉
&lt;/h2&gt;

&lt;p&gt;If you have been using Angular's forms API, whether template-based or reactive forms, you likely have noticed that prior to Angular 14 much of the API surface had a &lt;em&gt;lot&lt;/em&gt; of &lt;code&gt;any&lt;/code&gt; types.&lt;/p&gt;

&lt;p&gt;Angular (version 2, not AngularJS) was released in September of 2016 (after Flash died but before build tools took the world by storm). Not long after the release, developers started to note that the API was both not type-safe (or null-safe).&lt;br&gt;
In fact, &lt;a href="https://github.com/angular/angular/issues/13721" rel="noopener noreferrer"&gt;the most upvoted and commented on issue&lt;/a&gt; in the GitHub repository was created December of 2016 to report this problem.&lt;/p&gt;

&lt;p&gt;Angular 14 introduces reactive form APIs that are both type-safe and null-safe. This is a big win for using reactive forms in Angular.&lt;br&gt;
Unfortunately, the template-driven forms API is currently not strongly typed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Migrating to Angular 14
&lt;/h2&gt;

&lt;p&gt;To get started, you need to update your project to use Angular 14 to take advantage of the new APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng update @angular/cli @angular/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your project is updated, you'll not that instance of &lt;code&gt;FormControl&lt;/code&gt; and &lt;code&gt;FormGroup&lt;/code&gt; have been migrated to the untyped classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FormControl&lt;/code&gt; is migrated to &lt;code&gt;UntypedFormControl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FormGroup&lt;/code&gt; is migrated to &lt;code&gt;UntypedFormGroup&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Typed &lt;code&gt;FormControl&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In Angular 14, the &lt;code&gt;FormControl&lt;/code&gt; class now has a TypeScript generic type of &lt;code&gt;TValue&lt;/code&gt; whose default type assignment is &lt;code&gt;any&lt;/code&gt;. What does this mean?&lt;/p&gt;

&lt;p&gt;Prior to Angular 14, when accessing properties such as &lt;code&gt;value&lt;/code&gt; the type was &lt;code&gt;any&lt;/code&gt;, and when using the method &lt;code&gt;setValue&lt;/code&gt;, it would accept &lt;code&gt;any&lt;/code&gt; argument value.&lt;/p&gt;

&lt;p&gt;Here are just a few of the class members that have been updated to use the &lt;code&gt;TValue&lt;/code&gt; generic type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setValue(value: TValue)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patchValue(value: TValue)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reset(formState: TValue)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getRawValue(): TValue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value: TValue&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a big win for preventing runtime exceptions and regressions due to type errors!&lt;/p&gt;

&lt;p&gt;It's important to note that the &lt;code&gt;TValue&lt;/code&gt; generic type is inferred by TypeScript when newing-up a new &lt;code&gt;FormControl&lt;/code&gt; instance, so it's not necessary to specify the generic type.&lt;br&gt;
Let's look at an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mike&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note, we did not specify the &lt;code&gt;TValue&lt;/code&gt; generic type above.&lt;br&gt;
Rather, the type is inferred by the &lt;code&gt;value&lt;/code&gt; argument specified to constructor function when creating a new instance of the &lt;code&gt;FormControl&lt;/code&gt; class.&lt;/p&gt;
&lt;h2&gt;
  
  
  Typed &lt;code&gt;FormGroup&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The purpose of Angular's &lt;code&gt;FormGroup&lt;/code&gt; is to group form controls logically in order to track their state (value, validity, etc.) together.&lt;br&gt;
Angular 14 also introduces updated typings for the &lt;code&gt;FormGroup&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;Let's look at an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SignUpForm&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signUpFormGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignUpForm&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's quickly review the code above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we define a new &lt;code&gt;SignUpForm&lt;/code&gt; interface.&lt;/li&gt;
&lt;li&gt;The interface is &lt;em&gt;only necessary if we have optional form controls&lt;/em&gt;. In this example, the &lt;code&gt;subscribe&lt;/code&gt; control can be dynamically added and removed from the group.&lt;/li&gt;
&lt;li&gt;Next, we create a new &lt;code&gt;FormGroup&lt;/code&gt; instance, and since we have an optional control, we specify the generic type to the &lt;code&gt;SignUpForm&lt;/code&gt; interface.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; control's value is typed: &lt;code&gt;string | null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;email&lt;/code&gt; control's value is typed: &lt;code&gt;string | null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;subscribe&lt;/code&gt; control value is typed: &lt;code&gt;string | null | undefined&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;signUpFormGroup&lt;/code&gt; value is typed: &lt;code&gt;Partial&amp;lt;{ name: string | null; email: string | null; subscribe: string | null | undefined }&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may have noticed that the control values can be &lt;code&gt;null&lt;/code&gt; or, if optional, &lt;code&gt;undefined&lt;/code&gt;.&lt;br&gt;
This is because Angular's form APIs allow for null values.&lt;br&gt;
For example, if we invoke the &lt;code&gt;reset()&lt;/code&gt; method on a control, the value is not set to the original/seed value, rather, it is set to &lt;code&gt;null&lt;/code&gt;.&lt;br&gt;
We'll show how to override this behavior below.&lt;/p&gt;

&lt;p&gt;It should also be noted that the &lt;code&gt;TValue&lt;/code&gt; for each control is also inferred as we previously learned.&lt;/p&gt;
&lt;h2&gt;
  
  
  Typed &lt;code&gt;FormArray&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Currently, TypeScript arrays are homogeneous.&lt;br&gt;
This means that TypeScript expects that every item in an array is of the same type (including &lt;code&gt;any&lt;/code&gt;).&lt;br&gt;
For the sake of clarity, this is not to be confused with tuples which can have unambiguous types.&lt;/p&gt;

&lt;p&gt;With that said, typed &lt;code&gt;FormArray&lt;/code&gt; instances require that all controls are homogeneously typed.&lt;br&gt;
If a &lt;code&gt;FormArray&lt;/code&gt; requires heterogeneous types then the use of the &lt;code&gt;UntypedFormArray&lt;/code&gt; is recommended.&lt;/p&gt;

&lt;p&gt;In this example we'll create a new &lt;code&gt;FormArray&lt;/code&gt; with an initial control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormArray&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because we have defined the &lt;code&gt;FormArray&lt;/code&gt; to include a control whose &lt;code&gt;TValue&lt;/code&gt; is a &lt;code&gt;string&lt;/code&gt;, the type of the array is inferred as: &lt;code&gt;FormArray&amp;lt;AbstractControl&amp;lt;string, string&amp;gt;&amp;gt;&lt;/code&gt;.&lt;br&gt;
The compiler now expects that each item in the array is a control whose &lt;code&gt;TValue&lt;/code&gt; is a &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we attempt to add a new control to the array whose &lt;code&gt;TValue&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; a string, the compiler will throw an exception indicating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler exception indicates that there is an error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Argument of type 'FormControl&amp;lt;number&amp;gt;' is not assignable to parameter of type 'AbstractControl&amp;lt;string, string&amp;gt;'.
Types of property 'value' are incompatible.
Type 'number' is not assignable to type 'string'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a good thing! 😌&lt;/p&gt;

&lt;p&gt;Ok, but what if our array of controls is initially empty?&lt;br&gt;
In that case, we can specify the generic type of the controls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;lineItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AbstractControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we use the &lt;code&gt;AbstractControl&lt;/code&gt; type and specify the &lt;code&gt;TValue&lt;/code&gt; to be of type &lt;code&gt;string&lt;/code&gt; as we expect every control's value to be a &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  New &lt;code&gt;FormRecord&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As we learned previously the &lt;code&gt;FormGroup&lt;/code&gt; in Angular 14+ supports specifying a group of controls whose &lt;code&gt;TValue&lt;/code&gt; types are known when creating the group, even when creating optional controls within the group.&lt;br&gt;
However, what about a group of controls whose &lt;code&gt;TValue&lt;/code&gt; is not known when creating the group.&lt;br&gt;
To meet this use case, Angular 14 ships with a new &lt;code&gt;FormRecord&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Let's look at an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AbstractControl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have created the group of controls using the &lt;code&gt;FormRecord&lt;/code&gt; class, we can start to add (and/or remove) controls from the group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;street&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code works as expected.&lt;br&gt;
A new control is added to the group that meets the type definition.&lt;br&gt;
However, what happens if we attempt to add a new control whose &lt;code&gt;TValue&lt;/code&gt; is a &lt;code&gt;number&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the compiler throws an exception indicating that we cannot add this control to the group due to the type constraints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Argument of type 'FormControl&amp;lt;number&amp;gt;' is not assignable to parameter of type 'AbstractControl&amp;lt;string, string&amp;gt;'.
Types of property 'value' are incompatible.
Type 'number' is not assignable to type 'string'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;UntypedFormGroup&lt;/code&gt; class supports the user requirements in the event that we need a group of controls whose values are heterogeneous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed Types
&lt;/h2&gt;

&lt;p&gt;We can declare mixed types of controls when working with a group of controls of heterogeneous types as long as we declare the controls when creating the group.&lt;br&gt;
Further, we can use the &lt;code&gt;UntypedFormControl&lt;/code&gt; class to declare a control whose &lt;code&gt;TValue&lt;/code&gt; type is &lt;code&gt;any&lt;/code&gt; (under the hood, this is a type whose generic type is preset to &lt;code&gt;any&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Let's look at an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;formGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;no&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;postalCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UntypedFormControl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;street&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postalCode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postalCode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ABC123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This all works as we expect.&lt;br&gt;
We have a few controls, each with their own &lt;code&gt;TValue&lt;/code&gt;, including the untyped control whose &lt;code&gt;TValue&lt;/code&gt; is &lt;code&gt;any&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Non-nullable Controls
&lt;/h2&gt;

&lt;p&gt;If you recall from earlier, we mentioned that resetting a form control state will set the value to &lt;code&gt;null&lt;/code&gt;, not the initial/seed value as we might expect.&lt;br&gt;
With non-nullable form controls, we can explicitly instruct the compiler that the value is reset to the initial value when invoking the &lt;code&gt;reset()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Let's look at an example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;formGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;street&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;nonNullable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;street&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;street&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// empty string, NOT null&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;There are a few key takeaways when using Angular 14's updated forms API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;FormControl&lt;/code&gt;, &lt;code&gt;FormGroup&lt;/code&gt;, and &lt;code&gt;FormArray&lt;/code&gt; signatures have been updated to support TypeScript generics.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;TValue&lt;/code&gt; type is inferred where possible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FormGroup&lt;/code&gt; supports enumerated, homogeneous, and optional controls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FormRecord&lt;/code&gt; supports non-enumerated (dynamic) and homogenous controls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UntypedFormControl&lt;/code&gt; is a non strongly-typed &lt;code&gt;FormControl&lt;/code&gt;. Or, in other words, the &lt;code&gt;TValue&lt;/code&gt; type is preset to &lt;code&gt;any&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UntypedFormGroup&lt;/code&gt; is a non strongly-typed &lt;code&gt;FormGroup&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FormArray&lt;/code&gt; supports homogeneously typed controls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UntypedFormArray&lt;/code&gt; is a non strongly-typed &lt;code&gt;FormArray&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;nonNullable&lt;/code&gt; supports resetting a control state to the initial/seed value.&lt;/li&gt;
&lt;li&gt;Through strict types and template type checking, we can avoid runtime exceptions and reduce regressions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Need Help?
&lt;/h2&gt;

&lt;p&gt;Do you have questions? We can help! &lt;a href="https://liveloveapp.com/services/angular-development" rel="noopener noreferrer"&gt;LiveLoveApp provides angular consulting&lt;/a&gt;. We can answer your questions, perform an architecture review of your application and help you to implement best practices to improve your software development performance.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
