Warum das Konsumieren von Agenten eine echte Client-Bibliothek braucht – und wie der Einstieg in wenigen Minuten gelingt.
Die Server-Seite von Agentic AI boomt: Agent-Frameworks, Tool Calling, Memory, Orchestrierung. Doch in dem Moment, in dem eine Angular-Anwendung einen Agenten einbinden soll – seine Antworten darstellen, clientseitige Tools aufrufen, echte UI statt Textwüsten rendern –, wird es im Ökosystem überraschend still. CopilotKit für Angular füllt genau diese Lücke. Dieser Artikel zeigt anhand eines Beispiels, wie man diese populäre Bibliothek zusammen mit offenen Standards nutzt.
Wir brauchen eine Bibliothek zum Konsumieren von Agenten
Mit einem Agenten zu sprechen ist in der Theorie einfach: Frage senden, Antwort empfangen. In Wirklichkeit muss der Client einen Strom an Events verarbeiten. Beispiele dafür sind Textfragmente, Tool-Aufrufe samt Argumenten und Ergebnissen, Freigaben durch den Benutzer oder Fehlersignale.
Das ist Infrastrukturcode – und Infrastrukturcode hat in Anwendungscode nichts verloren. Was wir brauchen, ist eine Bibliothek, die die genannten Details übernimmt, damit sich der Anwendungscode auf das konzentrieren kann, was das Produkt besonders macht.
AG-UI: Kein Lock-in beim Server-Stack
Die zweite Anforderung ist Unabhängigkeit. Ob das Backend-Team seine Agenten mit LangGraph oder LangChain, mit Mastra, mit Spring AI oder mit dem Microsoft Agent Framework baut, sollte für das Frontend keine Rolle spielen.
Genau das liefert AG-UI: ein offenes, leichtgewichtiges Protokoll zwischen Agenten und Benutzeroberflächen. Der Client bekommt einen standardisierten Strom an Events – Anfrage (Run) gestartet, Tool-Aufruf, Textfragment, Anfrage (Run) beendet – und muss nicht wissen, welches Framework diese Events erzeugt.
Drei Standards, ein Frontend
AG-UI ist nicht der einzige Standard, um den sich das Frontend kümmern muss. Direkt daneben entstehen zwei weitere, die jeweils ein komplementäres Anliegen abdecken:
- A2UI lässt einen Agenten Benutzeroberflächen deklarativ beschreiben, sodass er seine Antwort aus echten UI-Komponenten statt aus Prosa zusammensetzen kann.
- MCP Apps erweitern das Model Context Protocol, damit Dritt-Tools eigene Visualisierungen zusammen mit ihren Fähigkeiten mitliefern können.
Diese Standards widersprechen einander auch nicht, sondern lassen sich kombinieren: ein über AG-UI gestreamter Agent, der mit A2UI-Oberflächen antwortet und MCP Apps von Dritten einbettet. Jemand muss diese Teile in ein kohärentes Programmiermodell bringen – und dieser Jemand sollte nicht jedes Projektteam für sich sein.
Standards allein genügen nicht
Es gibt eine zweite Lücke: Ease of Use und Developer Experience (DX). Die offiziellen SDKs zu den genannten Standards sind ein solider erster Schritt, aber bewusst low-level. Sie liefern zwar Konzepte wie Event-Streams oder Renderer, die Verdrahtung bleibt aber bei uns. Das würde Infrastrukturcode in der Anwendung nötig machen – genau das wollen wir vermeiden.
Genau hier setzt CopilotKit an. Entwickelt vom selben Team, das AG-UI initiiert hat, verpackt es die genannten Standards in ein Frontend-SDK mit zwei Betriebsmodi: eine ausgereifte Chat-Komponente für schnelle Erfolge sowie einen Headless-Modus, der direkten Zugriff auf die empfangenen Events gewährt und somit mehr Freiheiten gibt. Mit @copilotkit/angular kommt all das als Angular-natives Paket: Provider, Signals und Komponenten, die sich wie der Rest der Anwendung anfühlen.
Kurz gesagt: AG-UI, A2UI und MCP Apps standardisieren die Kommunikation mit dem Agenten; CopilotKit liefert eine bequeme Möglichkeit, Agenten über diese Protokolle einzubinden, und stellt somit den Missing Link für Agentic UI mit Angular dar.
Eine Demo erstellen
Die Demo besteht aus einer regulären Angular-Anwendung mit einem Mastra-basierten Agenten direkt daneben. Mastra ist ein populäres TypeScript-Framework zum Bauen von Agenten. Zwei Befehle scaffolden beide Teile:
ng new angular-copilot-demo
cd angular-copilot-demo
npx mastra@latest init
Der Mastra-Wizard fügt einen Ordner src/mastra mit einem funktionierenden Beispiel-Agenten hinzu und fragt unterwegs nach LLM-Provider und API-Key, den er in einer .env-Datei ablegt.
Wir verwenden hier Mastra, weil es von Grund auf für TypeScript gebaut wurde und erfrischend leichtgewichtig ist. Für unsere Zwecke spielt die Wahl allerdings kaum eine Rolle: Das Frontend wird immer nur AG-UI sehen. Jeder Agent, der AG-UI spricht – gebaut etwa mit LangGraph, CrewAI, Pydantic AI, LlamaIndex oder dem Microsoft Agent Framework –, ließe sich auf exakt dieselbe Weise anbinden.
Der serverseitige Agent
Der gescaffoldete Mastra-Agent ist ein Wetter-Assistent mit einem einzigen Tool, das das Wetter für eine Stadt nachschlägt – gewissermaßen das „Hello World“ der Agentic AI. Die Datei weather-agent.ts bringt alles zusammen, was ein Agent braucht – Prompt, Modell, Tools und Memory:
// src/mastra/agents/weather-agent.ts
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { weatherTool } from '../tools/weather-tool';
export const weatherAgent = new Agent({
id: 'weather-agent',
name: 'Weather Agent',
instructions: `You are a helpful weather assistant [...]`,
model: 'openai/gpt-5.6-terra',
tools: { weatherTool },
memory: new Memory(),
});
In den instructions steckt der System-Prompt, der Rolle und Verhalten des Agenten festlegt. Beim model genügt ein einziger Bezeichner aus Provider und Modellname – darum kümmert sich Mastras Model-Router, der den zugehörigen API-Key als Umgebungsvariable erwartet.
Über die konfigurierte Memory-Instanz bleibt der Gesprächsverlauf am Server, sodass der Client immer nur die neueste Nachricht senden muss. Das von mastra init generierte Grundgerüst nutzt dafür eine lokale SQLite-Datenbank.
Mit Schemas, die auf der populären Bibliothek Zod basieren, beschreibt das registrierte weatherTool seine Parameter und Ergebnisse:
// src/mastra/tools/weather-tool.ts
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';
export const weatherTool = createTool({
id: 'get-weather',
description: 'Get current weather for a location',
inputSchema: z.object({
location: z.string().describe('City name'),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
location: z.string(),
[...]
}),
execute: async (inputData) => {
return await getWeather(inputData.location);
},
});
Aus diesen Schemas ergeben sich zum einen die TypeScript-Typen für den Programmcode. Zum anderen leitet Mastra zur Laufzeit aus dem inputSchema ein JSON-Schema ab und schickt es zusammen mit dem Namen und der description an das Modell. Anhand dieser Angaben entscheidet das Modell selbst, wann es das Tool aufruft und welche Argumente es übergibt. Das outputSchema bleibt hingegen am Server: Es validiert und typisiert das Tool-Ergebnis.
Die Methode execute enthält die Tool-Implementierung, die im betrachteten Fall lediglich mit getWeather an eine öffentliche Wetter-API delegiert.
Den Agenten via AG-UI veröffentlichen
Bisher spricht der Agent nur Mastra. Die Brücke zu AG-UI ist der Adapter MastraAgent aus @ag-ui/mastra: Er führt den Mastra-Agenten aus und übersetzt dessen Streaming-Events in AG-UI-Events. Eine eigene Route macht das per HTTP verfügbar – der folgende, leicht vereinfachte Handler veröffentlicht jeden registrierten Agenten unter /ag-ui/:agentId und streamt die AG-UI-Events als Server-sent Events zurück:
// src/mastra/server/ag-ui-route.ts
import { registerApiRoute } from '@mastra/core/server';
import { MastraAgent } from '@ag-ui/mastra';
import type { RunAgentInput } from '@ag-ui/core';
import { streamSSE } from 'hono/streaming';
import { concatMap, lastValueFrom } from 'rxjs';
export const agUiRoute = registerApiRoute('/ag-ui/:agentId', {
method: 'POST',
handler: async (c) => {
const mastra = c.get('mastra');
const agent = mastra.listAgents()[c.req.param('agentId')];
const input = (await c.req.json()) as RunAgentInput;
const aguiAgent = new MastraAgent({ agent, resourceId: 'anonymous' });
return streamSSE(c, async (sse) => {
const send = (data: unknown): Promise<void> =>
sse.writeSSE({ data: JSON.stringify(data) });
await lastValueFrom(aguiAgent.run(input).pipe(concatMap(send)), {
defaultValue: undefined,
});
});
},
});
Der Handler liest den RunAgentInput – die Payload, die AG-UI-Clients senden, inklusive Thread-Id und neuer Nachrichten – und übergibt ihn dem Adapter, dessen run-Methode ein Observable mit AG-UI-Events zurückgibt. Der Rest ist angenehm unspektakulär: Da Mastras Server auf Hono aufbaut, kümmert sich dessen Helfer streamSSE um SSE-Header und -Framing. concatMap leitet die Events der Reihe nach durch send weiter, und lastValueFrom hält den Callback – und damit den Stream – offen, bis das Observable abgeschlossen ist.
Die zentrale Mastra-Instanz registriert den Agenten sowie die Route und aktiviert CORS per Middleware:
// src/mastra/index.ts
import { Mastra } from '@mastra/core/mastra';
import { weatherAgent } from './agents/weather-agent';
import { agUiRoute } from './server/ag-ui-route';
export const mastra = new Mastra({
agents: { weatherAgent },
server: {
apiRoutes: [agUiRoute],
cors: { origin: '*' },
},
[...]
});
Für die Demo ist das großzügige origin: '*' in Ordnung; eine echte Anwendung würde die erlaubten Origins einschränken. Das ist die gesamte Server-Seite: ein gescaffoldeter Agent, eine Route, ein Konfigurationseintrag.
Noch kürzer: die CopilotKit Runtime
Der Vollständigkeit halber: Auf der Server-Seite geht es sogar noch kürzer. CopilotKit bringt eine eigene Runtime mit, die das Veröffentlichen von Agenten übernimmt – damit muss man die AG-UI-Route gar nicht mehr selbst schreiben. Den Mastra-Agenten zu registrieren und auszuliefern reduziert sich auf wenige Zeilen:
const runtime = new CopilotRuntime({
agents: { weatherAgent: new MastraAgent({ agent: weatherAgent }) },
});
const app = createCopilotHonoHandler({ runtime, basePath: '/' });
serve({ fetch: app.fetch, port: 4555 }, (info) => {
console.log(`CopilotKit runtime listening on http://localhost:${info.port}`);
});
Diese Abkürzung habe ich für unsere Demo bewusst nicht genommen: Mir ist wichtig zu zeigen, dass unser Angular-Client mit jedem beliebigen Server zusammenarbeitet, der AG-UI spricht – ganz gleich, ob auf der Server-Seite CopilotKit im Spiel ist oder nicht. Die handgeschriebene Route macht genau das deutlich.
Neu: Agentic UI mit Angular
Wer nicht nur einen Agenten anbinden, sondern Agentic UI in eine skalierbare Architektur einbetten möchte:
In meinem Buch Agentic UI with Angular behandle ich die zugrunde liegenden Muster und Trade-offs im Detail.
Der Client
Zeit für den Missing Link: CopilotKit für Angular. Installiert wird es per npm:
npm i @copilotkit/angular
Die Funktion provideCopilotKit in app.config.ts verbindet die Anwendung mit unserem Agenten:
// src/app/app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideCopilotKit } from '@copilotkit/angular';
import { HttpAgent } from '@ag-ui/client';
const AG_UI_URL = 'http://localhost:4111/ag-ui/weatherAgent';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
[...]
provideCopilotKit({
agents: {
default: new HttpAgent({ url: AG_UI_URL }),
},
}),
],
};
CopilotKit erwartet schlicht einen AG-UI-Agenten. Der HttpAgent aus dem AG-UI-Client-SDK zeigt auf unseren Endpunkt /ag-ui/weatherAgent – keine CopilotKit-Runtime, keine zusätzliche Middleware dazwischen und keine einzige Zeile Mastra-spezifischer Code im Browser. Der Schlüssel default ist der clientseitige Name, über den Komponenten auf diesen Agenten verweisen.
Die Benutzeroberfläche ist ein Einzeiler. Die Komponente CopilotChat bringt Nachrichtenliste, Eingabefeld und Streaming-Anzeige mit:
// src/app/app.ts
import { CopilotChat } from '@copilotkit/angular';
@Component({
selector: 'app-root',
imports: [CopilotChat],
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main class="chat-host">
<copilot-chat agentId="default" />
</main>
Das war's – ein vollständiger, streamender Chat über einen echten Agenten, ohne eine einzige Zeile Event-Handling-Code in der Anwendung.
Ausprobieren
Für den Start der Demo braucht es zwei Terminals im Projektstamm. Die vom Mastra-Wizard erzeugte .env-Datei muss den LLM-Key enthalten – für OpenAI ist das OPENAI_API_KEY. Das erste Terminal startet den Mastra-Dev-Server, der auf Port 4111 lauscht:
npm run dev
Das zweite startet den Angular-Dev-Server auf Port 4200:
npm start
Nun fragen wir nach dem Wetter in Wien – die Antwort streamt Wort für Wort herein, und hinter den Kulissen hat der Agent die Stadt bereits geocodiert und sein Wetter-Tool aufgerufen:

Dank der serverseitigen Memory funktionieren auch Anschlussfragen wie Is it warmer than in Paris?: Beide Fragen gehören zum selben Konversations-Thread, der Agent weiß also, worauf sich das „it“ bezieht.
Headless-Modus
Die fertige Chat-Komponente ist der schnellste Weg zu einem lauffähigen Ergebnis. Doch echte Anwendungen wachsen schnell darüber hinaus: Der Chat muss zum Designsystem passen, eigene Widgets sollen in der Konversation erscheinen – und nicht alles ist ein Chat. Ein Agent kann genauso gut ein Formular, ein Dashboard oder einen ganzen Workflow steuern. Für diese Flexibilität gibt es CopilotKits Headless-Modus: Er liefert State und Verhalten und überlässt jedes Pixel des Renderings uns.
Das Demo-Projekt enthält eine selbst gebaute Chat-Komponente, die denselben Agenten über die Headless-API konsumiert:
// src/app/headless-chat/headless-chat.ts
import { Component, computed, inject } from '@angular/core';
import { CopilotKit, injectAgentStore } from '@copilotkit/angular';
import { randomUUID } from '@copilotkit/shared';
@Component({
selector: 'app-headless-chat',
[...]
})
export class HeadlessChat {
private readonly copilotKit = inject(CopilotKit);
private readonly store = injectAgentStore('default');
protected readonly isRunning = computed(() => this.store().isRunning());
protected readonly visibleMessages = computed(() =>
this.store()
.messages()
.filter((m) => m.role === 'user' || m.role === 'assistant')
.map((m) => [...])
);
protected async send(content: string) {
const agent = this.store().agent;
agent.addMessage({ id: randomUUID(), role: 'user', content });
await this.copilotKit.core.runAgent({ agent });
}
}
Die Funktion injectAgentStore liefert einen Signal-basierten Store für den registrierten Agenten: messages() hält die Konversation, isRunning() den Ausführungszustand – beides gewöhnliche Angular-Signals, die direkt in computed und das Template passen. Das Senden einer Nachricht ist ebenso explizit: die Benutzernachricht dem Agenten hinzufügen, dann per runAgent einen Run starten. Der Store aktualisiert sich, während die Events hereinströmen, sodass die UI kontinuierlich neu rendert.
Das Template gehört ganz uns – eine schlichte Liste und ein Formular, gestaltet nach Belieben:
<!-- src/app/headless-chat/headless-chat.html -->
<ol class="log" aria-live="polite">
@for (message of visibleMessages(); track message.id) {
<li [attr.data-role]="message.role">
<span>{{ message.role === 'user' ? 'Sie' : 'Agent' }}</span>
<p>{{ message.text }}</p>
</li>
}
</ol>
<form (submit)="[...]">
[...]
</form>
Nächste Schritte
Was wir hier gebaut haben, ist erst der Einstieg. Für ausgewachsene Agentic UIs braucht es mehr, zum Beispiel:
- Clientseitige Tools, damit der Agent Aufgaben innerhalb der Anwendung automatisieren kann und den aktuellen Benutzerkontext kennt – was ausgewählt ist, welcher Screen offen ist, woran der Anwender gerade arbeitet.
- Vom Agenten gewählte clientseitige Visualisierungen: Statt mit einer Textwüste zu antworten, soll der Agent dynamisch aus den Komponenten der Anwendung wählen.
- A2UI-Unterstützung, damit der Agent seine Antworten on the fly als deklarative UI komponieren kann.
- MCP-Apps-Unterstützung, damit sich Dritt-Tools in die eigene Anwendung integrieren und visualisieren lassen.
- Human-in-the-Loop-Muster jenseits einfacher Freigaben, damit Anwender die Kontrolle darüber behalten, was der Agent tut.
Ein Beispiel dafür, wohin die Reise geht: ein dynamisches Dashboard, dessen Inhalt der Agent steuert:

Auch dieses Terrain deckt CopilotKit ab – und meine Blog-Serie geht diese Schritte einen nach dem anderen durch. Dabei entsteht unter anderem genau dieses Dashboard:
Interesse an produktionsreifen Agentic-UI-Architekturen?
In meinem Workshop beschäftigen wir uns mit AG-UI, A2UI, MCP Apps, HITL-Patterns und modernen Angular-Architekturen für reale agentische Systeme.

