Headless Interrupts
Resolve agent interrupts from any UI, without a useInterrupt render slot.
"""Agno 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. Agno does NOT have that primitive, so we adapt using thesame "Strategy B" pattern as the MS Agent Framework port: the backend agent'ssystem prompt tells the LLM to call `schedule_meeting`, but no localimplementation is registered -- the tool is provided entirely by the frontendvia `useFrontendTool` with an async handler that returns a Promise resolvingonly once the user picks a time slot (or cancels).See `src/agents/main.py` for the shared Agno agent used by most other demos."""from __future__ import annotationsfrom agno.agent.agent import Agentfrom agno.models.openai import OpenAIChatfrom dotenv import load_dotenvload_dotenv()SYSTEM_PROMPT = ( "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.\n\n" "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.\n\n" "Keep responses short and friendly. After you finish executing tools, " "always send a brief final assistant message summarizing what happened so " "the message persists.")agent = Agent( model=OpenAIChat(id="gpt-4o-mini", timeout=120), # 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=[], tool_call_limit=5, description="Scheduling assistant for the interrupt-adapted demos.", instructions=SYSTEM_PROMPT,)What is this?#
useInterrupt's render callback is the 80% path: it keeps the UI
glued to a <CopilotChat> transcript and handles "when to show the
picker" logic for you. This page covers the escape hatch: a
render-less interrupt resolver you assemble from the same
primitives useInterrupt uses internally — a pattern that lives
anywhere in your React tree, takes any shape you like (button grid,
form, modal, keyboard shortcut), and resolves the interrupt without
mounting a chat at all.
Not available on this framework. Headless interrupts are built on top of
useInterrupt/useFrontendToolpatterns that require the runtime to expose either a nativeinterrupt(...)primitive (LangGraph) or a Promise-resolving frontend-tool path (Microsoft Agent Framework). For all other integrations, useuseHumanInTheLoopinstead — it's the standard hook for tool-call-based pause/resume flows and works on every framework that supports tool calls.
When should I use this?#
- Testing / Playwright fixtures — a deterministic, chat-less button grid is easier to drive than a chat surface where the picker only appears after an LLM call.
- Non-chat UIs — dashboards, side panels, inspector surfaces, or any place where you want the agent's interrupt without the chat transcript.
- Custom flow control — when you need to know exactly when the interrupt arrived (e.g. to gate other UI) and when it was resolved.
- Research / debugging — when you want to observe the raw AG-UI custom events without the abstraction layer.
If you just want "a picker in chat", just use
useInterrupt.
Going further#
- Tool-based HITL with
useHumanInTheLoop— for LLM-initiated pauses where the model decides on the fly to ask the user, rather than the runtime forcing the pause itself. useInterrupt— the render-prop version of this page, withenabledgating andhandlerpreprocessing.
