Back to Copilotkit

Programmatic Control

showcase/shell-docs/src/content/docs/integrations/langgraph/programmatic-control.mdx

1.57.07.1 KB
Original Source

Overview

The useAgent hook provides direct access to your LangGraph agent from any React component. It gives you real-time access to the agent's state, messages, execution status, and allows you to subscribe to custom events like interrupts.

This enables you to build custom agent dashboards, monitoring tools, and interactive features that respond to your agent's behavior.

This page covers everything you need to know about using useAgent with LangGraph. Select where you'd like to get started below.

<Cards className="gap-6"> <Card icon={<MessageCircle className="text-primary" />} className="p-6 rounded-xl text-base md:col-span-2" title="Getting started" description="Learn the basics of accessing your agent and displaying its properties." href="#getting-started" /> <Card icon={<Play className="text-primary" />} className="p-6 rounded-xl text-base" title="Running the Agent" description="Trigger your agent programmatically with copilotkit.runAgent()." href="#running-the-agent-programmatically" /> <Card icon={<Database className="text-primary" />} className="p-6 rounded-xl text-base" title="Working with State" description="Access and update shared state between your app and agent." href="#working-with-state" /> <Card icon={<Zap className="text-primary" />} className="p-6 rounded-xl text-base" title="Agent Events" description="Subscribe to agent lifecycle events and custom events." href="#subscribing-to-agent-events" /> <Card icon={<AlertCircle className="text-primary" />} className="p-6 rounded-xl text-base" title="LangGraph Interrupts" description="Handle interrupt() calls from your LangGraph agent." href="#handling-langgraph-interrupts" /> <Card icon={<Settings className="text-primary" />} className="p-6 rounded-xl text-base" title="Fully Headless UI" description="Build a completely custom chat interface from the ground up using CopilotKit's headless hooks." href="custom-look-and-feel/headless-ui" /> <Card icon={<BookOpen className="text-primary" />} className="p-6 rounded-xl text-base" title="Reference" description="Complete API reference documentation for useAgent." href="/reference/v2/hooks/useAgent" /> </Cards>

Getting started

Let's start by building a simple component that displays agent information.

<UseAgentSnippet />

Node-Specific State

If your LangGraph agent tracks which node it's in, you can show contextual UI:

tsx
export function NodeStatus() {
  const { agent } = useAgent();

  // [!code highlight:1]
  const currentNode = agent.state.currentNode;

  return (
    <div>
      {currentNode === "research_node" && (
        <div className="alert">Agent is researching your query...</div>
      )}
      {currentNode === "summarize_node" && (
        <div className="alert">Agent is summarizing findings...</div>
      )}
    </div>
  );
}

Running the Agent Programmatically

Use copilotkit.runAgent() to trigger your agent from any component — no chat UI required. This is the same method CopilotKit's built-in <CopilotChat /> uses internally.

tsx
export function AgentTrigger() {
  const { agent } = useAgent();
  // [!code highlight:1]
  const { copilotkit } = useCopilotKit();

  const handleRun = async () => {
    // Add a user message to the agent's conversation
    agent.addMessage({
      id: randomUUID(),
      role: "user",
      content: "Summarize the latest sales data",
    });

    // [!code highlight:2]
    // Run the agent — handles tool execution, follow-ups, and streaming
    await copilotkit.runAgent({ agent });
  };

  return <button onClick={handleRun}>Run Agent</button>;
}

copilotkit.runAgent() vs agent.runAgent()

Both methods trigger the agent, but they operate at different levels:

  • copilotkit.runAgent({ agent }) — The recommended approach. Orchestrates the full agent lifecycle: executes frontend tools, handles follow-up runs when tools request them, and manages errors through the subscriber system.
  • agent.runAgent() — Low-level method on the agent instance. Sends the request to the runtime but does not execute frontend tools or handle follow-ups. Use this only when you need direct control over the agent execution (e.g., resuming from an interrupt with forwardedProps).

Stopping a Run

You can stop a running agent using copilotkit.stopAgent():

tsx
const handleStop = () => {
  copilotkit.stopAgent({ agent });
};

Handling LangGraph Interrupts

LangGraph's interrupt() function emits custom events that you can capture and respond to.

Simple Interrupt Handler

tsx
export function InterruptHandler() {
  const { agent } = useAgent();

  useEffect(() => {
    const subscriber: AgentSubscriber = {
      // [!code highlight:12]
      onCustomEvent: ({ event }) => {
        if (event.name === "on_interrupt") {
          // LangGraph interrupt() was called
          const response = prompt(event.value);

          if (response) {
            // Resume the agent with the user's response
            agent.runAgent({
              forwardedProps: {
                command: { resume: response },
              },
            });
          }
        }
      },
    };

    const { unsubscribe } = agent.subscribe(subscriber);
    return () => unsubscribe();
  }, []);

  return null;
}

Custom Interrupt UI

For a more sophisticated UI, you can render a custom component:

tsx
export function CustomInterruptHandler() {
  const { agent } = useAgent();
  const [interrupt, setInterrupt] = useState<{ message: string } | null>(null);

  useEffect(() => {
    const subscriber: AgentSubscriber = {
      onCustomEvent: ({ event }) => {
        // [!code highlight:3]
        if (event.name === "on_interrupt") {
          setInterrupt({ message: event.value });
        }
      },
    };

    const { unsubscribe } = agent.subscribe(subscriber);
    return () => unsubscribe();
  }, []);

  const handleResponse = (response: string) => {
    // [!code highlight:5]
    agent.runAgent({
      forwardedProps: {
        command: { resume: response },
      },
    });
    setInterrupt(null);
  };

  if (!interrupt) return null;

  return (
    <div className="interrupt-modal">
      <h3>Agent Needs Your Input</h3>
      <p>{interrupt.message}</p>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          const formData = new FormData(e.currentTarget);
          handleResponse(formData.get("response") as string);
        }}
      >
        <input type="text" name="response" placeholder="Your response" />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

For a more declarative approach, see useInterrupt.

See Also