Pausing the Agent for Input
Pause an agent run mid-tool, hand control to a custom React component, and resume with the user's answer.
"""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?#
useInterrupt lets your agent pause mid-run, hand control to the user
through a custom React component, and resume with whatever the user
returns. How that pause is implemented depends on the framework's
runtime.
The Microsoft Agent Framework runtime can't pause a run mid-tool the
way LangGraph's interrupt() does, so this demo uses useFrontendTool
with a Promise-based handler instead. The agent calls schedule_meeting
like any other tool; the client-side handler renders the picker, holds
the request open, and only resolves the Promise once the user picks a
slot or cancels. Same UX from the reader's perspective — agent pauses,
user answers, agent resumes — different mechanism underneath.
When should I use this?#
Reach for useInterrupt when the pause is a graph-enforced
checkpoint where the code path must stop and wait for a human,
not an LLM-initiated tool call. Typical cases:
- A sensitive action (payments, irreversible writes) must be approved
- A required piece of state isn't known and can only be collected from the user
- The agent explicitly reaches an approval node in a longer workflow
- You want the server-side contract to be
interrupt(...)and resume with a payload
For LLM-initiated pauses where the model decides on the fly to ask
the user, prefer useHumanInTheLoop.
The frontend: useFrontendTool with a Promise-resolving handler#
The handler stores its resolve callback in a ref, returns a Promise
that the user's pick eventually resolves, and renders the picker
inline in the chat. This is the MS Agent equivalent of
useInterrupt's event / resolve pair:
frontend-promise-handler not found in ms-agent-python::gen-ui-interrupt. Tag the relevant source lines with // @region[frontend-promise-handler] / // @endregion[frontend-promise-handler].The backend: agent instructed to call the frontend tool#
The agent has no local schedule_meeting implementation — the tool is
registered entirely on the frontend. The backend's only job is to
instruct the model to call schedule_meeting whenever the user wants
to book a meeting. AG-UI routes the tool call to the client, where
the Promise-returning handler takes over:
SYSTEM_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, )Going further#
- Tool-based HITL with
useHumanInTheLoop— for LLM-initiated pauses. - Headless interrupts — compose the lower-level primitives
(
useAgent,agent.subscribe,copilotkit.runAgent) to resolve interrupts outside a chat surface.
