Angular apps can now run any agent, with the streaming, tool calls, and shared state already handled.
Today we're releasing agent into your Angular app. It's built with Angular's own patterns, standalone components, dependency injection and signals.
You get the building blocks for agent-native apps in Angular: pre-built chat components or a fully headless setup, generative UI, shared state, human-in-the-loop, multimodal attachments, threads and more.
Use the CLI to scaffold a full starter Angular app with a Google ADK agent.
npx copilotkit@latest init --framework adk-angular
Let's see how to set everything up, then go through each of the pieces and give your agent the context. Quickstart docs are on
How everything fits together
Everything runs on
Your Angular app is the browser half. The runtime is a small Node server that keeps your model credentials off the client. Everything below the AG-UI line is swappable, including running the model locally.
Set up CopilotKit in your Angular app
The CLI scaffolds all three parts for you, including a runtime server with a Google ADK agent, already wired with generative UI and threads.
npx copilotkit@latest init --framework adk-angular --name my-angular-adk-app
endpoint, which is where your agent runs.
// src/app/app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection } from "@angular/core";
import { provideCopilotKit, provideCopilotChatConfiguration } from "@copilotkit/angular";
export const AGENT_ID = "default";
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
provideCopilotKit({
runtimeUrl: "http://localhost:8200/api/copilotkit",
}),
// owns the active thread that the threads drawer drives
provideCopilotChatConfiguration({ agentId: AGENT_ID }),
],
};
Now import the stylesheet. It comes with everything the chat needs, so you don't have to write any CSS.
/* src/styles.css */
@import "@copilotkit/angular/styles.css";
You can drop in the ready-made chat, compose it from smaller pieces, or go fully headless and build your own UI.
// src/app/app.ts
import { Component } from "@angular/core";
import {
CopilotChat,
CopilotThreadsDrawer,
CopilotChatMessageView,
CopilotChatInput,
} from "@copilotkit/angular";
import { AGENT_ID } from "./app.config";
@Component({
selector: "app-root",
imports: [CopilotChat, CopilotThreadsDrawer, CopilotChatMessageView, CopilotChatInput],
template: `
<!-- pre-built chat component -->
<copilot-chat [agentId]="AGENT_ID" />
<!-- or compose the pieces into your own panel -->
<aside class="copilot-panel">
<copilot-threads-drawer />
<copilot-chat-message-view />
<copilot-chat-input />
</aside>
`,
})
export class App {
protected readonly AGENT_ID = AGENT_ID;
}
For a fully custom UI, injectAgentStore() gives you the agent's messages and run state as signals, and you render everything yourself. I've covered this in-depth in the shared state section.
The last piece is the runtime.
// server.ts
import "dotenv/config";
import { createServer } from "node:http";
import { BuiltInAgent, CopilotRuntime } from "@copilotkit/runtime/v2";
import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node";
const runtime = new CopilotRuntime({
agents: {
default: new BuiltInAgent({
model: "openai:gpt-5.5",
prompt: "You are a helpful assistant inside an Angular app.",
}),
},
});
createServer(
createCopilotNodeListener({ runtime, basePath: "/api/copilotkit", cors: true }),
).listen(8200, () => {
console.log("Copilot Runtime on http://localhost:8200/api/copilotkit");
});
The agent is named default on purpose, since that's the AGENT_ID the chat is pointed at.
That's the whole setup. It streams responses, renders tool calls, and keeps conversation state. Read more on the
Generative UI, native in Angular
Generative UI lets the agent build interfaces in real time. That ranges from the agent picking components you already wrote, to assembling them from a catalog you define, to generating a whole surface on its own.
If you are interested, read more on the
You can also use , which is worth a read if you want to see it end to end.
Human-in-the-loop: pause for approval
Some critical actions shouldn't run on their own like deleting a record or sending an email, so it's important to configure human-in-the-loop. It pauses the agent mid-run and waits for your approval before it continues.
The component reads the paused tool call, shows the controls, and calls respond() to unblock the agent. Whatever you pass to respond() becomes the tool result, so the approve and cancel paths send different messages.
// src/app/delete-confirm.ts
import { Component, inject, input } from "@angular/core";
import type { HumanInTheLoopToolCall } from "@copilotkit/angular";
import { TaskStore } from "./task-store";
@Component({
selector: "app-delete-confirm",
standalone: true,
template: `
@if (toolCall().status === "complete") {
<div class="resolved">{{ toolCall().result }}</div>
} @else {
<div class="confirm">
<p>Delete "{{ toolCall().args.title }}"?</p>
<button class="danger" (click)="approve()">Delete</button>
<button (click)="cancel()">Keep it</button>
</div>
}
`,
})
export class DeleteConfirm {
private store = inject(TaskStore);
readonly toolCall =
input.required<HumanInTheLoopToolCall<{ id: string; title: string }>>();
approve(): void {
const { id, title } = this.toolCall().args as { id: string; title: string };
this.store.remove(id);
this.toolCall().respond?.(`Deleted "${title}".`);
}
cancel(): void {
this.toolCall().respond?.("User kept the task.");
}
}
Register it as a human-in-the-loop tool. There's no handler here, unlike a frontend tool, because the component is the handler. The outcome depends on what the person clicks.
// inside a component's constructor (injection context)
import { registerHumanInTheLoop } from "@copilotkit/angular";
import { z } from "zod";
import { DeleteConfirm } from "./delete-confirm";
registerHumanInTheLoop({
name: "delete_task",
description: "Delete a task. Requires user confirmation.",
parameters: z.object({ id: z.string(), title: z.string() }),
component: DeleteConfirm,
});
Now, if you ask the agent to delete something, the agent will stop the run and ask for your confirmation before proceeding.
The snippets use
That's the whole loop. The agent sees your state, changes it through tools, renders UI for the result, and pauses for a person when it matters.
Available today
@copilotkit/angular is open source in the now taking on its ongoing maintenance in collaboration with CopilotKit.
- , . thanks for reading!
Follow CopilotKit on and
SOCIAL SHARE CARD GENERATOR