Threadplane is an open-source Angular framework that runs LangChain agents in the browser and binds their execution state to Angular Signals. It combines AG-UI protocol adapters, generative UI primitives (json-render and A2UI), and Angular’s dependency injection to wire agent loops directly into reactive components.
The project addresses a specific pain point: most agent frameworks treat the frontend as a dumb terminal for streaming text. Threadplane makes the browser a first-class orchestration environment with native Angular patterns for state management, tool progress, interrupts, and generative UI rendering.
Architecture: Signals as Agent State Containers
Threadplane’s core abstraction is injectAgent(), a dependency injection function that returns a Signal-based handle to an agent runtime. When you call it, you get reactive primitives for messages, tool calls, status updates, and thread branching.
State flow:
- Agent runtime (LangChain, LangGraph, or any AG-UI backend) emits events over a stream.
- AG-UI adapter parses events into structured updates: token deltas, tool invocations, status changes.
- Angular Signals update automatically, triggering component re-renders.
- Generative UI components (json-render or A2UI) render agent outputs as Angular components from a trusted catalog.
The Signal contract means you don’t manually subscribe to streams or manage cleanup. Angular’s reactivity handles it. Tool progress, interrupts, and branching all flow through the same Signal graph.
Key components:
- Runtime adapters (MIT licensed): Connect LangChain/LangGraph to AG-UI protocol.
- AG-UI parser: Converts streamed events into typed Signal updates.
- json-render integration: Maps agent JSON outputs to pre-registered Angular components.
- A2UI renderer: Interprets Google’s declarative UI protocol for portable component generation.
- Thread persistence layer: Stores conversation history and branching state.
Security Boundaries: Client-Side Execution Shifts Responsibility
Running agents in the browser changes where credentials live and what tools can access.
Server-side agent (traditional):
- API keys stay in environment variables.
- Tools can access databases, file systems, internal APIs.
- User input is validated before reaching the agent.
Browser-side agent (Threadplane):
- API keys must be passed from the client or proxied through a backend endpoint.
- Tools are limited to browser APIs: fetch, localStorage, IndexedDB.
- Agent logic runs in user-controlled JavaScript, so you can’t trust tool outputs without validation.
Threadplane doesn’t solve credential management for you. If your agent needs an OpenAI key, you either:
- Proxy LLM calls through your backend (adds latency, keeps keys safe).
- Pass keys from the client (fast, but exposes keys to the user).
- Use a token exchange flow (OAuth-style, more setup).
Tool access is similarly constrained. A browser agent can’t read your Postgres database or shell out to Python scripts. It can call HTTP APIs, manipulate the DOM, and use Web APIs. If your agent needs server-side tools, you run a hybrid architecture: browser handles UI state, backend handles privileged operations.
Generative UI: Two Rendering Paths
Threadplane supports two approaches for turning agent outputs into UI components.
json-render (Vercel’s approach)
The agent returns JSON that maps to a fixed catalog of Angular components. You register components ahead of time:
const componentCatalog = {
'weather-card': WeatherCardComponent,
'chart': ChartComponent,
'data-table': DataTableComponent
};
const agent = injectAgent({
runtime: langchainAdapter,
generativeUI: {
renderer: 'json-render',
catalog: componentCatalog
}
});
When the agent emits { "component": "weather-card", "props": { "temp": 72 } }, Threadplane instantiates WeatherCardComponent with those props. This is safe because you control the catalog. The agent can’t inject arbitrary code.
A2UI (Google’s declarative protocol)
A2UI lets agents describe UI structure in a portable format (similar to React Server Components’ wire format). The agent sends a tree of primitives: boxes, text, buttons, lists. Threadplane renders them using your design system’s base components.
const agent = injectAgent({
runtime: langchainAdapter,
generativeUI: {
renderer: 'a2ui',
primitiveMap: {
'box': ContainerComponent,
'text': TextComponent,
'button': ButtonComponent
}
}
});
A2UI is more flexible (agents can compose novel layouts) but requires careful sanitization. You’re trusting the agent to generate safe UI trees. Threadplane doesn’t sandbox A2UI output by default, so you need to validate props and structure before rendering.
Deployment and Scaling Trade-Offs
| Dimension | Browser-Side Agent | Server-Side Agent |
|---|---|---|
| Latency | No backend hop for agent loop, but LLM calls still hit the network. Faster for local tools (DOM manipulation, client state). | Backend processes agent loop, then streams to client. Adds one round-trip per tool call. |
| Token costs | Each user runs their own agent loop. Parallel users = parallel LLM calls. No batching. | Backend can batch requests, cache tool results, share memory across users. |
| Offline capability | Agent loop can run offline if tools don’t need network. LLM calls still require connectivity unless you use a local model (WebLLM). | Requires backend connection for every interaction. |
| Multi-user coordination | Hard. Each browser runs independently. Shared state requires WebSockets or polling. | Natural. Backend owns shared state, coordinates tool calls, enforces consistency. |
| Credential security | API keys must be client-accessible or proxied. User can inspect all agent logic. | Keys stay server-side. Agent logic is opaque to users. |
| Scaling cost | Compute moves to client devices. Server only handles API proxying and persistence. | Server runs all agent loops. Scales with user count and agent complexity. |
Threadplane works best for single-user workflows where the agent manipulates client-side state (forms, visualizations, local data). It’s a poor fit for multi-user collaboration or agents that need privileged backend access.
Observability: What You Can and Can’t See
Threadplane exposes agent state through Signals, so you can log every update in the browser console. Tool calls, token counts, and status changes are all observable.
What you lose:
- Centralized tracing: Each user’s agent runs in their own browser. You can’t aggregate traces without sending telemetry to a backend.
- Replay and debugging: Server-side agents can replay execution from logs. Browser agents require client-side persistence (IndexedDB) and manual export.
- Cost tracking: Token usage is scattered across clients. You need to instrument LLM proxy calls to track spend.
Threadplane includes a thread persistence layer that stores conversation history in IndexedDB. You can export threads for debugging, but there’s no built-in observability backend.
Code Example: Wiring an Agent with Tool Progress
import { Component, inject } from '@angular/core';
import { injectAgent } from '@threadplane/angular';
import { langchainAdapter } from '@threadplane/langchain';
@Component({
selector: 'app-agent-chat',
template: `
<div class="messages">
@for (msg of agent.messages(); track msg.id) {
<div class="message">{{ msg.content }}</div>
}
</div>
@if (agent.toolCalls(); as tools) {
<div class="tool-progress">
@for (tool of tools; track tool.id) {
<span>{{ tool.name }}: {{ tool.status }}</span>
}
</div>
}
<input
[value]="agent.input()"
(input)="agent.setInput($event.target.value)"
(keyup.enter)="agent.send()"
/>
`
})
export class AgentChatComponent {
agent = injectAgent({
runtime: langchainAdapter({
apiUrl: '/api/agent',
tools: ['search', 'calculator']
}),
generativeUI: {
renderer: 'json-render',
catalog: { /* component map */ }
}
});
}
The agent object is a Signal graph. messages(), toolCalls(), and input() are all reactive. When the agent emits a new token or starts a tool, Angular re-renders automatically.
Failure Modes
Stream interruption: If the WebSocket or SSE connection drops mid-agent-loop, Threadplane doesn’t automatically resume. You need to implement reconnection logic and resend the last user message.
Tool call timeouts: Browser-side tools can hang (slow API calls, unresponsive third-party services). Threadplane doesn’t enforce timeouts by default. You need to wrap tool functions with timeout logic.
Memory leaks: Long-running agents with large message histories can bloat browser memory. Threadplane’s thread persistence helps, but you need to prune old messages or paginate history.
Generative UI injection: If you use A2UI without sanitizing props, a malicious or buggy agent can inject unsafe HTML or trigger XSS. json-render is safer because you control the component catalog.
API key exposure: If you pass LLM keys from the client, users can extract them from DevTools. Proxy calls through a backend or use short-lived tokens.
Technical Verdict
Use Threadplane when:
- You’re building single-user agent workflows in Angular.
- The agent manipulates client-side state (forms, visualizations, browser APIs).
- You want reactive agent state without manual stream management.
- You need generative UI and want to use json-render or A2UI.
- You’re okay with client-side execution trade-offs (credential exposure, limited tool access).
Avoid Threadplane when:
- Your agent needs privileged backend access (databases, file systems, internal APIs).
- You’re building multi-user collaboration features (shared agent state, coordination).
- You need centralized observability and cost tracking across users.
- You want to batch LLM calls or cache tool results server-side.
- You’re not using Angular (the framework is tightly coupled to Angular’s DI and Signals).
Threadplane is a complete solution for browser-native agent orchestration in Angular. It’s not a thin wrapper around LangChain. It’s a full framework with opinions about state management, UI generation, and thread persistence. If those opinions match your architecture, it eliminates a lot of plumbing. If they don’t, you’ll fight the framework.