---
title: Programmatic Control
icon: "lucide/SendHorizontal"
description: Chat with an agent using CopilotKit's UI components.
---

## 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.

## Navigation

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 title="page.tsx"
export function NodeStatus() {
  const { agent } = useAgent();

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

  return (
    <div>
      {/* [!code highlight:6] */}
      {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 title="page.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 title="page.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 title="page.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 title="page.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](/reference/v2/hooks/useInterrupt).

## See Also

- [Shared State](/langgraph/shared-state) - Deep dive into state management
- [Human-in-the-Loop](/langgraph/human-in-the-loop) - Approval workflows
- [Agent App Context](/langgraph/agent-app-context) - Pass context to your agent
- [useAgent API Reference](/reference/v2/hooks/useAgent) - Complete API documentation
