Interactive components

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


Not supported on LangGraph (TypeScript)
LangGraph (TypeScript) doesn't support Human in the Loop: Interrupts. See the framework grid for which integrations support this feature.

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#

Install the CopilotKit LangGraph SDK

npm install @copilotkit/sdk-js

Wire CopilotKit state + tools into your graph

Tool-based HITL (useHumanInTheLoop) registers the tool on the frontend and forwards it via state.copilotkit.actions — the same wiring as frontend tools. The graph-paused pattern (useInterrupt) uses LangGraph's native interrupt(...) primitive inside a node.

frontend-tools.ts
import { RunnableConfig } from "@langchain/core/runnables";
import { SystemMessage } from "@langchain/core/messages";
import { MemorySaver, START, StateGraph } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { makeChatOpenAI } from "./openai-headers";

import {
  convertActionsToDynamicStructuredTools,
  CopilotKitStateAnnotation,
} from "@copilotkit/sdk-js/langgraph";

// CopilotKit forwards frontend tools to the agent via
// `state.copilotkit.actions`. `CopilotKitStateAnnotation` adds that
// channel to your graph's state; `convertActionsToDynamicStructuredTools`
// turns the forwarded action schemas into LangChain tools you can bind
// at model-invocation time.
const AgentStateAnnotation = CopilotKitStateAnnotation;
export type AgentState = typeof AgentStateAnnotation.State;

const SYSTEM_PROMPT = "You are a helpful, concise assistant.";

async function chatNode(state: AgentState, config: RunnableConfig) {
  const model = makeChatOpenAI(config, {
    temperature: 0,
    model: "gpt-4o-mini",
  });

  const modelWithTools = model.bindTools!([
    ...convertActionsToDynamicStructuredTools(state.copilotkit?.actions ?? []),
  ]);

  const response = await modelWithTools.invoke(
    [new SystemMessage({ content: SYSTEM_PROMPT }), ...state.messages],
    config,
  );

  return { messages: response };
}

const workflow = new StateGraph(AgentStateAnnotation)
  .addNode("chat_node", chatNode)
  .addEdge(START, "chat_node")
  .addEdge("chat_node", "__end__");

const memory = new MemorySaver();

export const graph = workflow.compile({
  checkpointer: memory,
});

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.

Not supported on LangGraph (TypeScript)
LangGraph (TypeScript) doesn't support Human in the Loop: Interrupts. See the framework grid for which integrations support this feature.

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

Not supported on LangGraph (TypeScript)
LangGraph (TypeScript) doesn't support Human in the Loop: Interrupts. See the framework grid for which integrations support this feature.