examples/v2/docs/reference/use-agent.mdx
useAgent is a React hook that provides access to AG-UI agents and subscribes to their state
changes. It enables components to interact with agents, access their messages, and respond to updates in real-time.
The hook always returns an agent instance. While the runtime is syncing, it returns a provisional runtime agent; once
the runtime has synced, if the agent id does not exist the hook throws an error.
The useAgent hook:
import { useAgent } from "@copilotkit/react-core";
function ChatComponent() {
const { agent } = useAgent({ agentId: "assistant" });
return (
<div>
<h2>Agent: {agent.id}</h2>
<div>Messages: {agent.messages.length}</div>
<div>Running: {agent.isRunning ? "Yes" : "No"}</div>
</div>
);
}
string (optional)
The ID of the agent to retrieve. If not provided, defaults to "default".
const { agent } = useAgent({ agentId: "customer-support" });
UseAgentUpdate[] (optional)
An array of update types to subscribe to. This allows you to optimize re-renders by only subscribing to specific changes.
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core";
const { agent } = useAgent({
agentId: "assistant",
updates: [UseAgentUpdate.OnMessagesChanged],
});
Available update types:
UseAgentUpdate.OnMessagesChanged - Updates when messages are added or modifiedUseAgentUpdate.OnStateChanged - Updates when agent state changesUseAgentUpdate.OnRunStatusChanged - Updates when agent starts or stops runningIf updates is not provided, the hook subscribes to all update types by default.
The hook returns an object with a single property:
AbstractAgent
The agent instance. During runtime synchronization, the hook returns a provisional runtime agent so you can bind UI immediately. After the runtime has synced (Connected or Error), if the agent id does not exist the hook throws an error.
The agent's message history is available through the messages property. You can read messages and add new ones to
create interactive conversations.
function SimpleChat() {
const { agent } = useAgent();
return (
<div>
{agent.messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong> {message.content}
</div>
))}
</div>
);
}
To send a message, add it to the agent and then run the agent:
import { useAgent } from "@copilotkit/react-core";
import { useCopilotKit } from "@copilotkit/react-core";
function MessageSender() {
const { agent } = useAgent({ agentId: "assistant" });
const { copilotkit } = useCopilotKit();
const sendMessage = async (content: string) => {
// Add message to agent
agent.addMessage({
id: crypto.randomUUID(),
role: "user",
content,
});
// Run the agent to get a response
await copilotkit.runAgent({
agent,
agentId: "assistant",
});
};
return (
<div>
<button onClick={() => sendMessage("Hello!")} disabled={agent.isRunning}>
Send Hello
</button>
</div>
);
}
Shared state enables real-time collaboration between users and agents. Both can read and modify the state, creating a synchronized workspace for interactive features.
The agent's state is a shared data structure that:
State updates cause re-renders when:
agent.setState() from your applicationfunction StateDisplay() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnStateChanged], // Subscribe to state changes
});
return (
<div>
<h3>Current State</h3>
<pre>{JSON.stringify(agent.state, null, 2)}</pre>
</div>
);
}
function StateController() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnStateChanged],
});
const updateState = (key: string, value: any) => {
// Update the shared state
agent.setState({
...agent.state,
[key]: value,
});
// This will trigger re-renders for all components
// subscribed to OnStateChanged
};
return (
<div>
<button
onClick={() => updateState("counter", (agent.state.counter || 0) + 1)}
>
Increment Counter: {agent.state.counter || 0}
</button>
</div>
);
}
Shared state enables collaborative features where users and agents work together:
function CollaborativeTodo() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnStateChanged],
});
const { copilotkit } = useCopilotKit();
const todos = agent.state.todos || [];
const addTodo = (text: string) => {
agent.setState({
...agent.state,
todos: [...todos, { id: crypto.randomUUID(), text, done: false }],
});
};
const toggleTodo = (id: string) => {
agent.setState({
...agent.state,
todos: todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo,
),
});
};
const askAgentToOrganize = async () => {
// Add a message asking the agent to organize todos
agent.addMessage({
id: crypto.randomUUID(),
role: "user",
content: "Please organize my todos by priority",
});
// The agent can read and modify the todos in agent.state
await copilotkit.runAgent({ agent });
// After the agent runs, the state will be updated
// and the component will re-render automatically
};
return (
<div>
<h3>Shared Todo List</h3>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span className={todo.done ? "done" : ""}>{todo.text}</span>
</li>
))}
</ul>
<button onClick={() => addTodo(prompt("New todo:") || "")}>
Add Todo
</button>
<button onClick={askAgentToOrganize}>Ask Agent to Organize</button>
</div>
);
}
Subscribe only to message changes to avoid unnecessary re-renders:
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core";
function MessageList() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnMessagesChanged],
});
return (
<ul>
{agent.messages.map((msg) => (
<li key={msg.id}>{msg.content}</li>
))}
</ul>
);
}
Show a loading indicator when the agent is processing:
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core";
function RunStatus() {
const { agent } = useAgent({
agentId: "assistant",
updates: [UseAgentUpdate.OnRunStatusChanged],
});
return (
<div className={`status ${agent.isRunning ? "running" : "idle"}`}>
{agent.isRunning ? "🔄 Processing..." : "✅ Ready"}
</div>
);
}
Access different agents in different components:
function DualAgentView() {
const { agent: primaryAgent } = useAgent({
agentId: "primary-assistant",
});
const { agent: supportAgent } = useAgent({
agentId: "support-assistant",
});
return (
<div className="dual-view">
<div className="primary">
<h3>Primary Assistant</h3>
<div>Messages: {primaryAgent.messages.length}</div>
</div>
<div className="support">
<h3>Support Assistant</h3>
<div>Messages: {supportAgent.messages.length}</div>
</div>
</div>
);
}
For static access without subscribing to updates:
const { agent } = useAgent({
agentId: "assistant",
updates: [], // No subscriptions
});
// Component won't re-render on agent changes
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core";
import { Message } from "@ag-ui/core";
function MessageRenderer() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnMessagesChanged], // Only re-render on message changes
});
if (agent.messages.length === 0) {
return <div>No messages yet</div>;
}
const renderMessage = (message: Message) => {
switch (message.role) {
case "user":
return (
<div className="user-message">
<span className="avatar">👤</span>
<span className="content">{message.content}</span>
</div>
);
case "assistant":
return (
<div className="assistant-message">
<span className="avatar">🤖</span>
<span className="content">{message.content}</span>
</div>
);
default:
return null;
}
};
return (
<div className="message-list">
{agent.messages.map((msg) => (
<div key={msg.id}>{renderMessage(msg)}</div>
))}
</div>
);
}
By default, useAgent subscribes to all update types, which can cause frequent re-renders. Use the updates parameter
to subscribe only to the changes you need:
// ❌ Subscribes to all updates (may cause unnecessary re-renders)
const { agent } = useAgent({ agentId: "assistant" });
// ✅ Only subscribes to message changes
const { agent } = useAgent({
agentId: "assistant",
updates: [UseAgentUpdate.OnMessagesChanged],
});
// ✅ Only subscribes to run status changes
const { agent } = useAgent({
agentId: "assistant",
updates: [UseAgentUpdate.OnRunStatusChanged],
});
Split components by update type to optimize rendering:
// Message display component - only updates on message changes
function Messages() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnMessagesChanged],
});
// Render messages...
}
// Status indicator - only updates on run status changes
function StatusIndicator() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnRunStatusChanged],
});
// Render status...
}
// Parent component doesn't need to subscribe
function ChatView() {
return (
<>
<StatusIndicator />
<Messages />
</>
);
}
After the runtime has synced (Connected or Error), useAgent throws if the requested agent id does not exist. During
runtime syncing, a provisional agent is returned. To avoid runtime errors:
agents__unsafe_dev_only or exposed by your runtime).useAgent in an error boundary to render a fallback UI if the agent is
missing due to misconfiguration in development.Example configuration with a local agent for development:
<CopilotKitProvider agents__unsafe_dev_only={{ myAgent: new MyAgent() }}>
<App />
</CopilotKitProvider>