Page actions

Introduction

@threadplane/ag-ui is protocol-first: it consumes the AG-UI event vocabulary rather than any one runtime's SDK. That makes "any AG-UI backend plugs in" a claim that can be tested instead of asserted.

This section documents what happened when it was tested. On 2026-08-31 the adapter was run against three runtimes that have nothing to do with LangGraph, in two languages, with no adapter changes for messages, tool calls, or state. Each runtime has a standalone Angular example, a real backend, and a committed transcript of its wire traffic.

Note: What this section is not

These pages document runtimes as backends measured against the adapter. They are not a substitute for each vendor's own documentation, and Threadplane does not maintain any of the upstream AG-UI bridges described here.

How it fits

CHAT UI@threadplane/chat<chat> · <chat-message-list> · <chat-input>Agent contract · signals + events$ADAPTER@threadplane/langgraphADAPTER@threadplane/ag-uiLangGraph PlatformCrewAI · Mastra · Agent Fwk · Strands · …
The same Angular surface above the seam — these pages record what three non-LangGraph runtimes did when the backend beneath it changed.

The runtimes

Measured support

RuntimeMessagesTool callsStateInterruptsSubagents
LangGraph (via the AG-UI bridge)YesYesYesYesYes
AWS Strands (Python)YesYesPartialYesYes
Microsoft Agent Framework (Python)YesYesYesYesYes
Mastra (TypeScript)YesYesYesYesYes

Every gap in that table is caused by an upstream integration, not by the AG-UI protocol and not by a defect in @threadplane/ag-ui. The full cause analysis lives in Choosing an adapter.

What is the same everywhere

The Angular side does not change between these three runtimes. Each example binds through the same two calls, provideAgent() and injectAgent(). In your own application the provider takes the endpoint URL directly:

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/ag-ui';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideAgent({ url: '/agent' }),
  ],
};
import { Component } from '@angular/core';
import { ChatComponent } from '@threadplane/chat';
import { injectAgent } from '@threadplane/ag-ui';
 
@Component({
  selector: 'app-root',
  imports: [ChatComponent],
  template: `<chat [agent]="agent" />`,
})
export class App {
  protected readonly agent = injectAgent();
}

The examples in this repository wrap that same provideAgent() call in a factory, because they resolve their endpoint at runtime rather than hard-coding it, and each one renders a shared-state panel and an approval card around <chat>. Neither difference reaches the adapter: the configuration it receives is still a single url.

What changes is the backend, its hosting lane, and the wire conventions it happens to use. The How It Connects page for each runtime records those conventions as they were measured.

What is different everywhere

Three differences turned up repeatedly, and each runtime page returns to them.

Interrupts arrive by two different conventions. AWS Strands and Microsoft Agent Framework signal an interrupt only through the protocol-standard RUN_FINISHED outcome. The LangGraph bridge signals it only through a CUSTOM event named on_interrupt. Mastra emits both. The adapter accepts either, and within a single run the first signal wins.

Resume payloads are not portable. The adapter derives the wire shape from how the interrupt arrived, so application code passes one neutral submit({ resume }) regardless of runtime.

Subagents now stream on every runtime measured here — AWS Strands, Microsoft Agent Framework, and Mastra. Each backend ships a small emitter that translates its native delegation signals into the protocol's SUBAGENT_* events, which @threadplane/ag-ui consumes directly. The per-runtime pages describe the emitter and link the wire capture behind each cell.

Further reading

Looking for something specific?