CopilotKit

Interactive components

Create approval flows where the agent pauses and waits for human input.


using System.Text.Json;using Microsoft.Agents.AI;using Microsoft.Extensions.AI;using OpenAI;using System.ClientModel;// =================// Interrupt Agent Factory// =================//// Adaptation note: the Microsoft Agent Framework (.NET) does not have a// LangGraph-equivalent `interrupt()` primitive that can pause execution// mid-tool and resume with a caller-supplied value. The scheduling demos// use a frontend-provided `schedule_meeting` tool; AG-UI forwards that tool// definition to the model, then the client renders the picker and resolves// the tool call with the user's selected slot.//// This factory reuses the existing SharedStateAgent pattern for// consistency with the rest of the showcase, even though state-sync isn't// the primary concern for interrupt demos. The agent's system prompt// instructs it to always call `schedule_meeting` whenever the user asks// to book a call or schedule a meeting.public sealed class InterruptAgentFactory{    private const string DefaultOpenAiEndpoint = "https://models.inference.ai.azure.com";    private readonly IConfiguration _configuration;    private readonly OpenAIClient _openAiClient;    private readonly ILogger _logger;    private readonly ILoggerFactory _loggerFactory;    private readonly JsonSerializerOptions _jsonSerializerOptions;    public InterruptAgentFactory(IConfiguration configuration, ILoggerFactory loggerFactory, JsonSerializerOptions jsonSerializerOptions)    {        _configuration = configuration;        _loggerFactory = loggerFactory;        _logger = loggerFactory.CreateLogger<InterruptAgentFactory>();        _jsonSerializerOptions = jsonSerializerOptions;        var githubToken = _configuration["GitHubToken"]            ?? throw new InvalidOperationException(                "GitHubToken not found in configuration. " +                "Please set it using: dotnet user-secrets set GitHubToken \"<your-token>\" " +                "or get it using: gh auth token");        var endpointEnv = Environment.GetEnvironmentVariable("OPENAI_BASE_URL");        var endpoint = endpointEnv ?? DefaultOpenAiEndpoint;        _logger.LogInformation(            "InterruptAgentFactory using OpenAI endpoint: {Endpoint} (from OPENAI_BASE_URL: {HasEnv})",            endpoint,            !string.IsNullOrEmpty(endpointEnv));        _openAiClient = new(            new ApiKeyCredential(githubToken),            AimockHeaderPolicy.CreateOpenAIClientOptions(endpoint));    }    public AIAgent CreateInterruptAgent()    {        var chatClient = _openAiClient.GetChatClient("gpt-4o-mini").AsIChatClient();        // No backend fallback tool is registered. If the frontend tool is        // missing, the demo should fail visibly instead of bypassing the        // picker with a server-side response.        var chatClientAgent = new ChatClientAgent(            chatClient,            name: "InterruptAgent",            description: @"You are a scheduling assistant. Whenever the user asks you to book a callor schedule a meeting, you MUST call the `schedule_meeting` tool. Pass a short `topic`describing the purpose and `attendee` describing who the meeting is with. After the toolreturns, confirm briefly whether the meeting was scheduled and at what time, or that theuser cancelled.",            tools: []);        return new SharedStateAgent(chatClientAgent, _jsonSerializerOptions, _loggerFactory.CreateLogger<SharedStateAgent>());    }}

What is this?#

Interactive generative UI creates flows where the agent pauses execution and waits for user input before continuing. This enables approval workflows, confirmation dialogs, and any scenario where human judgment is needed mid-execution.

When should I use this?#

Use interactive generative UI when you need:

  • Approval/rejection flows (e.g. "Run this command?")
  • User decisions that the agent should know about
  • Confirmation dialogs with structured responses
  • Any flow where the agent pauses for human judgment

How it works in code#

On the frontend, register an interrupt renderer with useInterrupt. When the agent pauses, your component mounts inline in the chat, captures the user's choice, and resumes the run with that input.

page.tsx
import {  CopilotKit,  CopilotChat,  useHumanInTheLoop,} from "@copilotkit/react-core/v2";import { z } from "zod";import type { TimeSlot } from "./_components/time-picker-card";import { TimePickerCard } from "./_components/time-picker-card";import { generateFallbackSlots } from "../_shared/interrupt-fallback-slots";import { useGenUiInterruptSuggestions } from "./suggestions";export default function GenUiInterruptDemo() {  return (    <CopilotKit runtimeUrl="/api/copilotkit" agent="gen-ui-interrupt">      <div className="flex justify-center items-center h-screen w-full">        <div className="h-full w-full max-w-4xl">          <Chat />        </div>      </div>    </CopilotKit>  );}function Chat() {  useGenUiInterruptSuggestions();  // MS Agent Framework has no `interrupt()` primitive, so the LangGraph  // showcase's `useInterrupt({ renderInChat: true })` hook is silently dead  // here — it listens for AG-UI `interrupt` events that the MAF backend  // never emits, leaving the chat stuck on the "[Scheduling...]" tool-call  // placeholder.  //  // `interrupt_agent.py` instead exposes `schedule_meeting` as a tool the  // model is instructed to call; the frontend registers a matching  // `useHumanInTheLoop` here, renders the picker inline, and resolves the  // call via `respond(...)`. UX matches LGP's interrupt-rendered card; the  // mechanism differs.  useHumanInTheLoop({    agentId: "gen-ui-interrupt",    name: "schedule_meeting",    description:      "Ask the user to pick a meeting time. The picker renders inline in " +      "the chat; the chosen slot is returned to the agent so it can confirm.",    parameters: z.object({      topic: z        .string()        .describe("What the meeting is about (e.g. 'Intro with sales')"),      attendee: z        .string()        .optional()        .describe("Who the meeting is with (e.g. 'Alice')"),    }),    render: ({ args, respond }: any) => {      // `TimePickerCard` here is the gen-ui-interrupt-specific variant      // (under `_components/`) that gates buttons on its own internal      // `picked`/`cancelled` state — it doesn't take a `status` prop like      // the hitl-in-chat version. That's fine: the buttons stay clickable      // until the user makes a choice and `respond(...)` resolves the      // tool call.      const topic = (args?.topic as string | undefined) ?? "a call";      const attendee = args?.attendee as string | undefined;      const slots = generateFallbackSlots();      return (        <TimePickerCard          topic={topic}          attendee={attendee}          slots={slots}          onSubmit={(result) => respond?.(result)}        />      );    },  });

On the backend, the agent calls into the interrupt primitive and waits for the resumed response before continuing the graph.

InterruptAgent.cs
using System.Text.Json;using Microsoft.Agents.AI;using Microsoft.Extensions.AI;using OpenAI;using System.ClientModel;// =================// Interrupt Agent Factory// =================//// Adaptation note: the Microsoft Agent Framework (.NET) does not have a// LangGraph-equivalent `interrupt()` primitive that can pause execution// mid-tool and resume with a caller-supplied value. The scheduling demos// use a frontend-provided `schedule_meeting` tool; AG-UI forwards that tool// definition to the model, then the client renders the picker and resolves// the tool call with the user's selected slot.//// This factory reuses the existing SharedStateAgent pattern for// consistency with the rest of the showcase, even though state-sync isn't// the primary concern for interrupt demos. The agent's system prompt// instructs it to always call `schedule_meeting` whenever the user asks// to book a call or schedule a meeting.public sealed class InterruptAgentFactory{    private const string DefaultOpenAiEndpoint = "https://models.inference.ai.azure.com";    private readonly IConfiguration _configuration;    private readonly OpenAIClient _openAiClient;    private readonly ILogger _logger;    private readonly ILoggerFactory _loggerFactory;    private readonly JsonSerializerOptions _jsonSerializerOptions;    public InterruptAgentFactory(IConfiguration configuration, ILoggerFactory loggerFactory, JsonSerializerOptions jsonSerializerOptions)    {        _configuration = configuration;        _loggerFactory = loggerFactory;        _logger = loggerFactory.CreateLogger<InterruptAgentFactory>();        _jsonSerializerOptions = jsonSerializerOptions;        var githubToken = _configuration["GitHubToken"]            ?? throw new InvalidOperationException(                "GitHubToken not found in configuration. " +                "Please set it using: dotnet user-secrets set GitHubToken \"<your-token>\" " +                "or get it using: gh auth token");        var endpointEnv = Environment.GetEnvironmentVariable("OPENAI_BASE_URL");        var endpoint = endpointEnv ?? DefaultOpenAiEndpoint;        _logger.LogInformation(            "InterruptAgentFactory using OpenAI endpoint: {Endpoint} (from OPENAI_BASE_URL: {HasEnv})",            endpoint,            !string.IsNullOrEmpty(endpointEnv));        _openAiClient = new(            new ApiKeyCredential(githubToken),            AimockHeaderPolicy.CreateOpenAIClientOptions(endpoint));    }    public AIAgent CreateInterruptAgent()    {        var chatClient = _openAiClient.GetChatClient("gpt-4o-mini").AsIChatClient();        // No backend fallback tool is registered. If the frontend tool is        // missing, the demo should fail visibly instead of bypassing the        // picker with a server-side response.        var chatClientAgent = new ChatClientAgent(            chatClient,            name: "InterruptAgent",            description: @"You are a scheduling assistant. Whenever the user asks you to book a callor schedule a meeting, you MUST call the `schedule_meeting` tool. Pass a short `topic`describing the purpose and `attendee` describing who the meeting is with. After the toolreturns, confirm briefly whether the meeting was scheduled and at what time, or that theuser cancelled.",            tools: []);        return new SharedStateAgent(chatClientAgent, _jsonSerializerOptions, _loggerFactory.CreateLogger<SharedStateAgent>());    }}