Interactive components
Create approval flows where the agent pauses and waits for human input.
"""MS Agent Framework scheduling agent — interrupt-adapted.This agent powers two demos (gen-ui-interrupt, interrupt-headless) that in theLangGraph showcase rely on the native `interrupt()` primitive withcheckpoint/resume. The MS Agent Framework does NOT have that primitive, so weadapt by delegating the time-picker interaction to a **frontend tool** that theagent calls by name (`schedule_meeting`). The frontend registers the tool via`useFrontendTool` with an async handler; that handler renders the interactivepicker, waits for the user to choose a slot (or cancel), and resolves the toolcall with the result. The backend only defines the system prompt and advertisesno local `schedule_meeting` implementation — the agent's tool call is satisfiedentirely by the frontend.See `src/agents/agent.py` for the related `approval_mode="always_require"`pattern used elsewhere in this package."""from __future__ import annotationsfrom textwrap import dedentfrom agent_framework import Agent, BaseChatClientfrom agent_framework_ag_ui import AgentFrameworkAgentSYSTEM_PROMPT = dedent( """ You are a scheduling assistant. Whenever the user asks you to book a call or schedule a meeting, you MUST call the `schedule_meeting` tool. Pass a short `topic` describing the purpose of the meeting and, if known, an `attendee` describing who the meeting is with. The `schedule_meeting` tool is implemented on the client: it surfaces a time-picker UI to the user and returns the user's selection. After the tool returns, briefly confirm whether the meeting was scheduled and at what time, or note that the user cancelled. Do NOT ask for approval yourself — always call the tool and let the picker handle the decision. Keep responses short and friendly. After you finish executing tools, always send a brief final assistant message summarizing what happened so the message persists. """.strip())def create_interrupt_agent(chat_client: BaseChatClient) -> AgentFrameworkAgent: """Instantiate the scheduling-only agent used by the interrupt-adapted demos.""" base_agent = Agent( client=chat_client, name="scheduling_agent", instructions=SYSTEM_PROMPT, # No backend tools. `schedule_meeting` is registered on the frontend # via `useFrontendTool` and dispatched through the CopilotKit runtime. # When the agent calls `schedule_meeting`, the request is routed to # the frontend handler, which returns a Promise that only resolves # once the user picks a slot — equivalent to `interrupt()` in the # LangGraph reference. tools=[], ) return AgentFrameworkAgent( agent=base_agent, name="CopilotKitMicrosoftAgentFrameworkInterruptAgent", description=( "Scheduling assistant for the interrupt-adapted demos. Delegates " "the time-picker interaction to a frontend tool." ), require_confirmation=False, )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.
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.
from __future__ import annotationsfrom textwrap import dedentfrom agent_framework import Agent, BaseChatClientfrom agent_framework_ag_ui import AgentFrameworkAgentSYSTEM_PROMPT = dedent( """ You are a scheduling assistant. Whenever the user asks you to book a call or schedule a meeting, you MUST call the `schedule_meeting` tool. Pass a short `topic` describing the purpose of the meeting and, if known, an `attendee` describing who the meeting is with. The `schedule_meeting` tool is implemented on the client: it surfaces a time-picker UI to the user and returns the user's selection. After the tool returns, briefly confirm whether the meeting was scheduled and at what time, or note that the user cancelled. Do NOT ask for approval yourself — always call the tool and let the picker handle the decision. Keep responses short and friendly. After you finish executing tools, always send a brief final assistant message summarizing what happened so the message persists. """.strip())def create_interrupt_agent(chat_client: BaseChatClient) -> AgentFrameworkAgent: """Instantiate the scheduling-only agent used by the interrupt-adapted demos.""" base_agent = Agent( client=chat_client, name="scheduling_agent", instructions=SYSTEM_PROMPT, # No backend tools. `schedule_meeting` is registered on the frontend # via `useFrontendTool` and dispatched through the CopilotKit runtime. # When the agent calls `schedule_meeting`, the request is routed to # the frontend handler, which returns a Promise that only resolves # once the user picks a slot — equivalent to `interrupt()` in the # LangGraph reference. tools=[], ) return AgentFrameworkAgent( agent=base_agent, name="CopilotKitMicrosoftAgentFrameworkInterruptAgent", description=( "Scheduling assistant for the interrupt-adapted demos. Delegates " "the time-picker interaction to a frontend tool." ), require_confirmation=False, )