OPINION · September 15, 2026 · 27 min read

Why Do Agent UI Libraries Require a Runtime?

Vercel AI SDK, TanStack AI, assistant-ui, CopilotKit, and Threadplane compared: where the agent loop runs, who holds the key, and what each design trades away.

Most agent UI libraries ask you to deploy a server of their own before the chat box works.

I wanted to understand why, so I read the docs and source of the four libraries I see most often, the Vercel AI SDK, TanStack AI, assistant-ui, and CopilotKit, and compared them with Threadplane on twenty-four questions. This post is that comparison.

I think these are all great libraries. Each one made a real architectural choice about where the agent loop runs, and each choice buys something and costs something. My goal is to make those trades visible, including the ones Threadplane makes.

tl;dr

  • A vendor runtime is a server the UI library ships that sits between your browser and your agent.
  • The Vercel AI SDK and TanStack AI run their agent loop inside a route you deploy. assistant-ui adapts to whichever runtime you pick.
  • CopilotKit is the one library here that requires a runtime in production. In exchange it gets threads, an inspector, agent routing, and a deep catalog of server-side generative UI middleware in one place.
  • Threadplane runs the loop in your agent. The adapters call your LangGraph or AG-UI server from the browser.
  • We still recommend an endpoint that you own in front of your agent, for auth and credentials. The difference is whose code runs in it.
  • We ship middleware that runs inside your graph, not a box in front of it.
  • The trade is real: no hosted inspector, no multi-agent routing, no server-side stream middleware from us, and a smaller surface than the bigger projects.

What "runtime" means here

The word is overloaded, so I want to pin it down first.

In this post a runtime is a server component shipped by the UI vendor that sits in the request path between the browser and your agent. You deploy it. Your browser talks to it. It talks to your agent.

It is not your agent's own runtime. LangGraph Platform is a runtime in the ordinary sense, and so is any server that speaks AG-UI. I have written about choosing between those two elsewhere. This post is about the extra hop.

The comparison

Every cell in the table below comes from the library's own docs, npm registry data, or source, checked on 2026-09-15, and the sources are listed at the end. Where I could not verify a cell, I say so in the section for that row rather than guess.

ThreadplaneVercel AI SDKTanStack AIassistant-uiCopilotKit
📜 LicenseMITApache-2.0MITMITMIT
💳 Paid tiernonenonenoneoptional cloudhosted threads; enterprise label on the direct prod path
⭐ GitHub stars6926.8k3.1k12.2k37.4k
🧱 Vendor server in the path❌ none❌ none⚠️ optional, full features need it❌ none✅ required in prod
🔁 Where the agent loop runsyour agentits code, your routeits code, your serveryour route or agentits runtime, or the agent behind it
🔌 Browser to any AG-UI endpoint⚠️ dev-only or enterprise
🧭 Browser to LangGraph Platform✅ key in browservia its runtime
🤝 Agent frameworksLangGraph native; any AG-UI serverLangChain, LlamaIndex officialnone; AG-UI on the wire9 runtimes incl. LangGraph, AG-UI, A2A, ADK13 via AG-UI
🧠 LLM providersthe agent's43 official16 officialvia your backendv1 adapters; v2 on the AI SDK
📡 ProtocolsLangGraph API, AG-UIown stream, MCPAG-UI, MCPAG-UI, A2A, MCPAG-UI, MCP, A2A
🅰️ Angular package✅ 20 to 22⚠️ no guide✅ headless only✅ 22 only
🖼️ Prebuilt chat UI✅ plus headless❌ separate React kit⚠️ unstyled, not Angular✅ shadcn plus headless✅ plus headless
🎨 Generative UIA2UI, json-rendertool parts, MCP Appstool parts, MCP Appstool UI, present, A2UI, MCP Apps6 approaches incl. A2UI, MCP Apps
✋ Approval pause lives inyour agentyour routeits server loopyour routebrowser or agent
🛠️ Browser-executed tools✅ via your route✅ via its server✅ via runtime
🔄 Shared agent state
🧬 Subagents⚠️ pattern only
🧵 ThreadsLangGraph, or your store❌ you write ityour storecloud or your DBhosted cloud
⏯️ Resume an in-flight run✅ LangGraph✅ with Redis✅ hosted
📎 Attachments and voice⚠️ slot only
🔍 Debug panel✅ local, LangGraph✅ local⚠️ not Angular✅ local✅ local
📈 ObservabilityLangSmith or yours✅ OTel✅ OTel✅ cloud⚠️ hosted tier
🧪 Testing utilities✅ three layers✅ mocks
🕸️ Multi-agent routing✅ in runtime

The rest of this post walks through the rows and tries to say, for each one, what the design buys you and what it trades away.

📜 Project facts

All five are open source, and I want to start there because it is good news.

What a tier buys you is a funded team, and every one of these projects ships faster because of how it is funded.

🧱 Who sits in the request path

Threadplane has nothing of ours in the path. provideAgent() in @threadplane/langgraph uses the LangGraph SDK client to call your deployment. provideAgent() in @threadplane/ag-ui posts RunAgentInput to your endpoint.

The Vercel AI SDK, TanStack AI, and assistant-ui do not require a vendor box. What they ask for instead is a route that you deploy, running their code, which the next section covers.

CopilotKit requires Copilot Runtime in production. The provider throws in a production build unless you give it a runtime URL, a public API key, a license key, or a locally registered agent. The docs describe the runtime as the backend layer that connects your frontend to your agents, and call it "the recommended way."

What you gain is one place for everything: key custody, auth, agent discovery and routing, a middleware pipeline that runs where the client cannot tamper with it, and the door to hosted threads and the inspector. What you trade is a second server on the hot path, on their release schedule, and a direct path that is labelled either dev-only or enterprise.

We do recommend a server endpoint in front of your agent, and it should be one you own and control. The LangGraph adapter has no apiKey option at all, because a deployment credential passed from Angular would ship in the bundle, and it constructs the SDK client with an explicit null key so nothing is read from the environment either. What it does accept is defaultHeaders, for a per-user session token that your deployment's custom auth handler validates. The production shape the deployment guide recommends is a same-origin rewrite, one line of hosting config, that adds the credential server-side.

So there is still a hop. The difference from a vendor runtime is who owns the code running in it.

That is why the homepage says "no cloud" and not "no proxy." We changed that wording on purpose.

🔁 Where the agent loop runs

The agent loop is the code that calls the model, sees a tool call, runs or forwards it, and calls the model again. Somebody's code has to do that, and where it runs decides almost everything else in the table.

  • Threadplane: in your agent. On the LangGraph adapter, LangGraph Platform runs your graph. On the AG-UI adapter, the loop belongs to whichever framework built the server, and @threadplane/middleware runs inside it when that framework is LangGraph behind the AG-UI bridge, which is how the client-tools example is built.
  • Vercel AI SDK: ToolLoopAgent or streamText inside your API route, twenty steps by default. A DirectChatTransport runs it in-process for tests and desktop apps.
  • TanStack AI: its chat() server function, five iterations by default. Server tools, approvals, persistence, and MCP all live inside that call.
  • assistant-ui: wherever you point it. LocalRuntime runs your adapter in the client and calls your backend from there; the LangGraph, AG-UI, A2A, and ADK runtimes hand the loop to the agent server.
  • CopilotKit: in the external framework the runtime forwards to over AG-UI, or in a BuiltInAgent that runs in-process inside the runtime.

For me, this is the actual design decision, and I think each answer is defensible.

Running the loop in your route, the Vercel and TanStack shape, means the library controls the whole experience end to end, and features like approvals and persistence can be first-class because the library owns both ends. That is a genuine advantage. The trade is vendor code on the hot path, on the vendor's release schedule, and an agent loop that is separate from the one your agent framework already has.

Running the loop in the agent, the Threadplane shape and the assistant-ui framework-runtime shape, means one loop, owned by the agent framework you already chose. The trade is that the UI can only show what the agent server actually sends. If a framework's AG-UI bridge never emits a state delta, no adapter can invent one. That is why the adapter docs test each runtime and publish the result cell by cell, instead of claiming every feature works everywhere.

🔌 Talking AG-UI from the browser

AG-UI defines the agent as the endpoint. The protocol is a POST with a RunAgentInput body and a stream of events back. The architecture document names an optional secure proxy as a place for "additional capabilities," not as a required component.

  • Threadplane: yes. That is the whole AG-UI adapter, and it consumes the state, subagent, and custom events too.
  • Vercel AI SDK: no. It has its own UI Message Stream protocol over SSE, and the only AG-UI integration is a server-side wrapper that turns an AI SDK model into an AG-UI agent.
  • TanStack AI: yes. Since May 2026 the client posts an AG-UI 0.0.52 RunAgentInput, and the docs say it can hit any AG-UI server with no translation layer in between. I think this was the most important move in the space this year. Its documented event list does not include the state snapshot and delta events.
  • assistant-ui: yes, through @assistant-ui/react-ag-ui over the standard HttpAgent, with a published table that maps state, subagent, custom, and activity events.
  • CopilotKit: the free direct path is agents__unsafe_dev_only, and the docs say of that name, "The name is intentionally loud." The production direct path is selfManagedAgents, covered above. CopilotKit created AG-UI, so its runtime speaks it natively.

🧭 Talking to LangGraph Platform

LangGraph Platform ships custom authentication on every plan. You write an authenticate handler, the browser sends its own session token, and the platform enforces per-user access on threads and runs. Without that handler the platform sees only the API-key owner, which is usually the developer, so this is worth setting up on day one.

  • Threadplane: yes, with the same-origin proxy recommended for the credential and defaultHeaders for the session token.
  • Vercel AI SDK: yes, a LangSmithDeploymentTransport that connects from the browser and takes the API key as an option. I could not find a warning about that on the page, so treat it as a development convenience.
  • TanStack AI: no adapter. It integrates with model providers and coding-agent harnesses, not agent frameworks.
  • assistant-ui: yes, react-langgraph and a react-langchain runtime at feature parity, and the quickstart tells you to proxy in production so the key never reaches the client.
  • CopilotKit: through Copilot Runtime, which has first-class LangGraph Python and TypeScript integrations.

🤝 Agent frameworks, providers, and protocols

This group is where the libraries differ most in kind, so I want to spell it out.

Which agent frameworks connect.

  • Threadplane: LangGraph natively, and any AG-UI server. The adapter docs measure LangGraph, AWS Strands, Microsoft Agent Framework, and Mastra cell by cell, and list CrewAI, Pydantic AI, and AG2 as AG-UI backends.
  • Vercel AI SDK: official adapters for LangChain and LangGraph, and for LlamaIndex. Mastra and Pydantic AI ship their own adapters to its stream protocol.
  • TanStack AI: none by name. Interop is AG-UI on the wire. What it has instead is a set of harness adapters for coding agents such as Claude Code, Codex, and OpenCode.
  • assistant-ui: nine runtimes, including AI SDK, LangGraph, LangChain, AG-UI, A2A, Google ADK, and Claude Managed Agents. Mastra goes through the AI SDK runtime.
  • CopilotKit: thirteen named integrations, all over AG-UI: LangGraph, Deep Agents, Google ADK, Mastra, CrewAI, Pydantic AI, Claude Agent SDK, Agno, AG2, LlamaIndex, AWS Strands, and Microsoft Agent Framework.

Which LLM providers connect. Threadplane has no provider list, because the model key belongs to the agent and the UI never sees it. The Vercel AI SDK lists 43 official providers and 55 community ones. TanStack AI lists 16 official adapters plus an OpenAI-compatible one. assistant-ui reaches providers through your backend. CopilotKit's v1 runtime has service adapters for OpenAI, Anthropic, Google, Groq, and Bedrock, and its v2 built-in agent runs on the Vercel AI SDK.

Which protocols. Threadplane speaks the LangGraph API and AG-UI. The Vercel AI SDK speaks its own UI Message Stream and is an MCP client. TanStack AI speaks AG-UI and does MCP server-side. assistant-ui speaks AG-UI, A2A, and MCP. CopilotKit speaks AG-UI, MCP, and A2A through a middleware.

For me, the provider list is the cleanest statement of the two philosophies: a library with one expects to call the model, and a library without one expects your agent to.

🅰️ Angular

I build in Angular, so this is the row that decides the table for me.

  • Threadplane: Angular 20, 21, and 22, tested in CI. Angular is the only framework.
  • Vercel AI SDK: @ai-sdk/angular exists and is current, with Chat, Completion, and StructuredObject classes on signals. There is no getting-started guide for it, an open issue asks for one, and the useObject hook and the AI Elements component kit are React only.
  • TanStack AI: @tanstack/ai-angular is official and signals-based, with an injectChat family and a headless createChatHook. There are no prebuilt Angular components, no Angular devtools, and MCP Apps rendering is deferred for Angular.
  • assistant-ui: no Angular package. It is a React library, with React Native and a terminal renderer, and Vue and Svelte packages in the repo that are not published.
  • CopilotKit: @copilotkit/angular is current and requires Angular 22. Its feature page lists 41 supported items, including A2UI, Open Generative UI, MCP Apps, shared state, interrupts, and attachments.

Two of the four have first-class Angular support with docs. One has the package without the docs. One is React only.

🖼️ Prebuilt UI

🎨 Generative UI

"Generative UI" hides four different mechanisms behind one phrase, so I want to separate them before comparing anyone.

  1. Tool-call rendering. The model calls a tool, and you map the tool name to a component. Everyone does this.
  2. Declarative specs. The model authors a UI tree in a standard format, and the client renders it from a catalog. The two open specs here are A2UI and json-render.
  3. MCP Apps. An MCP server returns a ui:// resource, and the client mounts it in a sandboxed iframe.
  4. Vendor-specific trees. A library defines its own component vocabulary and a tool that emits it.

A2UI is becoming the shared vocabulary: Threadplane, assistant-ui, and CopilotKit all speak it, and CopilotKit's Angular package does too.

✋ Approvals

Human-in-the-loop is a pause, and what matters is where the pause lives, because that is where the decision is enforced.

  • Threadplane: your agent. LangGraph interrupts on one adapter, AG-UI interrupts on the other, one neutral submit({ resume }) on both.
  • Vercel AI SDK: toolApproval in your route, resumed with a client response. The docs note approvals cannot be used inside subagents, and say nothing about surviving a reload.
  • TanStack AI: its server loop emits an approval request and closes the stream. The browser holds the decision and resumes, no database required, and client persistence rehydrates a pending prompt after a reload.
  • assistant-ui: your route or its local runtime, with allow-once and allow-always options and free-text answers. On the AG-UI runtime, interrupts are still marked experimental.
  • CopilotKit: two mechanisms. A browser-side tool that pauses in the UI, or an agent interrupt that pauses in the graph. Not supported on its built-in agent.

I think the pause belongs in the agent, because it is the only place that cannot be skipped by a client that chooses not to render the button. The route-based designs get a simpler story for stateless deployments in exchange.

🛠️ Browser-executed tools

Client tools run in the browser, so the model needs to know they exist, and somebody has to route the call back to the browser.

  • Threadplane: yes. On AG-UI the tools ride in the protocol's tools field. On LangGraph, @threadplane/middleware runs inside your graph, binds the browser-declared tools onto your model, and routes client-tool turns back to the browser.
  • Vercel AI SDK: yes, onToolCall and addToolOutput, with automatic resubmission through your route.
  • TanStack AI: yes, a .client() handler, with the round trip going through its server function as a resume batch.
  • assistant-ui: yes, a frontend tool is any tool whose execute starts with "use client", and there are human tools that wait for a person.
  • CopilotKit: yes, through Copilot Runtime and a middleware on the agent that forwards the tool schemas. I could not find documentation for client tools on the selfManagedAgents path.

That middleware of ours is the closest thing we have to a runtime, so I want to be precise about it. It runs in your graph, in your process, on your deploy. Nothing of ours handles the request in flight.

🔄 Shared state and subagents

Both of these depend on whether the UI can see the agent's state, not just its messages.

Shared state. Threadplane exposes a state() signal fed by LangGraph values or AG-UI snapshots and deltas. assistant-ui has LangGraph state hooks and useAgUiState. CopilotKit has useAgent with setState, predictive state updates, and an Angular injectAgentStore. The Vercel AI SDK and TanStack AI have structured output, which is a different thing: a typed object the model produces, not a state the agent maintains.

Subagents. Threadplane renders subagent cards from AG-UI SUBAGENT_* events and LangGraph subgraph namespaces. assistant-ui nests a tool call's child messages and reads them from the AI SDK, AG-UI, LangChain, and OpenCode. CopilotKit routes between agents in its runtime and renders a delegation log. The Vercel AI SDK documents subagents as a pattern, an agent invoked as a tool. TanStack AI has no subagent feature.

🧵 Threads and resume

This is where CopilotKit's runtime earns its keep. Threads, a thread drawer, and an inspector come through that door, and they are polished. If that is what you want, the runtime is the price, and I think it is a fair one.

📎 Attachments and voice

Threadplane has an attachments slot on the chat input and nothing for voice. The Vercel AI SDK sends files with a message and has transcription, speech, and image generation. TanStack AI has multimodal message parts and hooks for image, audio, speech, video, and realtime voice. assistant-ui has attachment adapters, dictation, speech synthesis, and realtime voice. CopilotKit has an attachments config with a 20 MB default and Whisper transcription in the runtime, on Angular too.

🔍 Debug, observability, and testing

Debug panels. Every library has a local one, and none of them needs an account. Ours is a <chat-debug> panel for LangGraph checkpoints and state, from a debug-only entry point. TanStack's devtools do not ship for Angular, and the others are @ai-sdk/devtools, @assistant-ui/react-devtools, and the CopilotKit Inspector.

Observability. The Vercel AI SDK and TanStack AI both emit OpenTelemetry with the GenAI semantic conventions. assistant-ui and CopilotKit route observability through their hosted tiers, with the Vercel side listing 21 integrations. Threadplane leans on LangSmith or whatever your agent server already does, plus an opt-in telemetry package that points at infrastructure you control.

Testing utilities. This is one place where Threadplane is ahead, and I am a little proud of it. We ship three layers: a mockAgent() for component tests, provideFakeAgent() for adapter tests with canned wire events, and recorded LLM fixtures replayed through the whole stack. The Vercel AI SDK ships mock language models and a stream simulator. I found no documented test helpers for TanStack AI, assistant-ui, or CopilotKit.

🕸️ Multi-agent routing

Nobody treats this as a feature except CopilotKit, and Threadplane does not either. One provideAgent(), one agent. If you need several, you compose that yourself.

CopilotKit does it automatically, in its runtime, with a default agent and per-request routing. It is a real feature, and it is one of the better arguments for having a runtime at all.

So why do they require a runtime?

I think the answer has two halves, and both are reasonable.

The runtime is where the features live. Threads, an inspector, server-side middleware that renders A2UI and MCP Apps, automatic routing. Those are features somebody built, and the runtime is where they run. CopilotKit's maintainers put it plainly: key protection, auth and tenant scoping, input validation, context isolation, and one chokepoint for audit. Every item on that list is a real production concern.

The runtime is where the product lives. A hosted layer for threads and observability needs a door, and the runtime is the door. That funds the project, and I am glad the project is funded.

The route-based designs answer the same question differently. The Vercel AI SDK and TanStack AI get end-to-end control without a vendor box. assistant-ui gets to be agnostic about it. Each of those is a fair answer.

What Threadplane does instead

Threadplane has no runtime. What it has is a recommendation and a package.

The recommendation is a server endpoint that you own and control in front of your agent. That is where auth, credentials, rate limits, and any inspection of the stream belong. You already know how to authenticate it, log it, and deploy it, because it is the same kind of thing as the rest of your backend.

The package is @threadplane/middleware. It runs inside your LangGraph graph and handles the one thing the browser genuinely needs from the backend: client tools. It ships for both LangGraph.js and Python LangGraph, and it works the same whether the browser reaches that graph through the LangGraph API or through an AG-UI endpoint, because the graph binds the browser's tool catalog and ends the turn either way. On other AG-UI backends the tool catalog still rides in the protocol's tools field, and the framework's own bridge decides whether to bind it.

Everything the runtimes do still has to be done. Here is where each item goes.

ConcernWhere it lives
🔑 Key custodyYour endpoint, or the agent server that already holds the key
🪪 AuthYour endpoint. LangGraph custom auth, or whatever guards your AG-UI server
🧹 Input and output controlYour endpoint. It is your code
🧭 RoutingYou. One provideAgent() per agent
🧩 Trusted middlewareInside your graph, via @threadplane/middleware, over the LangGraph API or AG-UI
🧵 ThreadsLangGraph Platform. On AG-UI, a store you own
📋 AuditYour agent server, LangSmith, or your own tracing

"No runtime" does not mean no server. It means no server of ours.

What you give up

I would rather list it than let you discover it.

No hosted thread list or inspector. LangGraph Platform gives you threads and LangSmith gives you traces, and our debug panel runs locally only.

No server-side event middleware from us. If you want to inspect or rewrite the stream in a place the client cannot reach, that is your endpoint to write.

No multi-agent routing. One adapter, one agent, per application.

No MCP Apps, and thin multimodal. Four of the five render MCP Apps and we do not. Attachments are a slot, not a pipeline, and there is no voice.

Angular only, two protocols. If your backend speaks neither the LangGraph API nor AG-UI, nothing here helps you.

A small project. Sixty-nine stars against tens of thousands. The bigger projects have more hands, more integrations, and more eyes on bugs.

You can still get it wrong. There is no field named apiKey, but defaultHeaders will carry whatever string you put in it. The docs say what it is for. The code does not inspect the value. I think that is the right posture for a library, but it moves the responsibility to you.

Conclusion

Agent UI libraries require a runtime because the runtime is where their features live, and because a hosted product needs a door. The engineering reasons are real: key custody, auth, input control, trusted middleware, persistence, audit.

My position is simple. Those concerns belong to the agent server, which you already run, and to an endpoint you own in front of it. A vendor's box in the path, or a vendor's loop in your route, is a second place to solve a problem you already had one place to solve.

Threadplane is built on that position. It costs you a hosted inspector, routing, server-side stream middleware, and a few features the larger projects have and we do not yet.

Sources

Checked 2026-09-15. Versions and stars from the npm registry and the GitHub API on that day.

Vercel AI SDK

TanStack AI

assistant-ui

CopilotKit

Protocols and platforms