useAgent
React hook for accessing AG-UI agent instances
Overview#
useAgent is a React hook that returns an AG-UI AbstractAgent instance. The hook subscribes to agent state changes and triggers re-renders when the agent's state, messages, or execution status changes.
Throws error if no agent is configured with the specified agentId.
Signature#
import { useAgent } from "@copilotkit/react-core/v2";
function useAgent(options?: UseAgentProps): { agent: AbstractAgent }Parameters#
Configuration object for the hook.
ID of the agent to retrieve. Must match an agent configured in CopilotKit.
Optional thread ID to scope the agent instance per conversation thread. When provided, useAgent returns a per-thread clone of the registered agent so that messages, state, and threadId are isolated to that thread. Falls back to the enclosing CopilotChatConfigurationProvider's threadId if not provided.
Controls which agent changes trigger component re-renders. Options:
UseAgentUpdate.OnMessagesChanged- Re-render when messages changeUseAgentUpdate.OnStateChanged- Re-render when state changesUseAgentUpdate.OnRunStatusChanged- Re-render when execution status changes
Pass an empty array [] to prevent automatic re-renders.
Throttle interval (in milliseconds) for React re-renders triggered by OnMessagesChanged notifications. Useful to reduce re-render frequency during streaming, where new tokens can fire many updates per second.
The default is resolved as a cascade: throttleMs ?? provider.defaultThrottleMs ?? 0. When you omit throttleMs, the hook inherits defaultThrottleMs from the enclosing CopilotKit; when neither is set, the floor is 0 (no throttling). Passing throttleMs={0} explicitly disables throttling even when the provider specifies a non-zero defaultThrottleMs.
Uses a leading+trailing pattern:
- First update fires immediately (no delay for the initial token)
- Subsequent updates within the window are coalesced
- A trailing timer ensures the final state is always rendered
Only affects OnMessagesChanged — OnStateChanged and OnRunStatusChanged always fire immediately. Has no effect if updates does not include OnMessagesChanged.
Must be a non-negative finite number. Invalid values fall back to 0 with a console.error.
Return Value#
Object containing the agent instance.
The AG-UI agent instance. See AbstractAgent documentation for full interface details.
Core Properties#
Unique identifier for the agent instance.
Human-readable description of the agent's purpose.
Unique identifier for the current conversation thread.
Array of conversation messages. Each message contains:
id: string- Unique message identifierrole: "user" | "assistant" | "system"- Message rolecontent: string- Message content
Shared state object synchronized between application and agent. Both can read and modify this state.
Indicates whether the agent is currently executing.
The agent's declared capabilities (tools, streaming, multi-agent, etc.), populated from the runtime /info response. See useCapabilities for a convenience hook that reads this directly.
Methods#
Manually triggers agent execution.
Parameters:
options.forwardedProps?: any- Data to pass to the agent execution context
Example:
await agent.runAgent({
forwardedProps: {
command: { resume: "user response" }
}
});Updates the shared state. Changes are immediately available to both application and agent.
Example:
agent.setState({
...agent.state,
theme: "dark"
});Subscribes to agent events. Returns cleanup function.
Subscriber Events:
onCustomEvent?: ({ event: { name: string, value: any } }) => void- Custom eventsonRunStartedEvent?: () => void- Agent execution startsonRunFinalized?: () => void- Agent execution completesonStateChanged?: (state: any) => void- State changesonMessagesChanged?: (messages: Message[]) => void- Messages added/modified
Adds a single message to the conversation and notifies subscribers.
Adds multiple messages to the conversation and notifies subscribers once.
Replaces the entire message history with a new array of messages.
Aborts the currently running agent execution.
Creates a deep copy of the agent with cloned messages, state, and configuration.
Usage#
Basic Usage#
import { useAgent } from "@copilotkit/react-core/v2";
function AgentStatus() {
const { agent } = useAgent();
return (
<div>
<div>Agent: {agent.agentId}</div>
<div>Messages: {agent.messages.length}</div>
<div>Running: {agent.isRunning ? "Yes" : "No"}</div>
</div>
);
}Accessing and Updating State#
import { useAgent } from "@copilotkit/react-core/v2";
function StateController() {
const { agent } = useAgent();
return (
<div>
<pre>{JSON.stringify(agent.state, null, 2)}</pre>
<button onClick={() => agent.setState({ ...agent.state, count: 1 })}>
Update State
</button>
</div>
);
}Event Subscription#
import { useEffect } from "react";
import { useAgent } from "@copilotkit/react-core/v2";
function EventListener() {
const { agent } = useAgent();
useEffect(() => {
const { unsubscribe } = agent.subscribe({
onRunStartedEvent: () => console.log("Started"),
onRunFinalized: () => console.log("Finished"),
});
return unsubscribe;
}, []);
return null;
}Multiple Agents#
import { useAgent } from "@copilotkit/react-core/v2";
function MultiAgentView() {
const { agent: primary } = useAgent({ agentId: "primary" });
const { agent: support } = useAgent({ agentId: "support" });
return (
<div>
<div>Primary: {primary.messages.length} messages</div>
<div>Support: {support.messages.length} messages</div>
</div>
);
}Optimizing Re-renders#
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core/v2";
// Only re-render when messages change
function MessageCount() {
const { agent } = useAgent({
updates: [UseAgentUpdate.OnMessagesChanged]
});
return <div>Messages: {agent.messages.length}</div>;
}Throttling Streaming Re-renders#
import { useAgent } from "@copilotkit/react-core/v2";
// During streaming, coalesce rapid token updates into ~10 renders/sec
function StreamingChat() {
const { agent } = useAgent({ throttleMs: 100 });
return (
<div>
{agent.messages.map((msg) => (
<div key={msg.id}>{msg.content}</div>
))}
</div>
);
}Performance#
Two levels of throttle control#
CopilotKit provides throttle controls at two independent layers. They are additive — you can use one or both depending on your architecture:
| Layer | Option | Where | What it throttles |
|---|---|---|---|
| React hook | useAgent({ throttleMs }) | Per-component | React re-renders triggered by OnMessagesChanged |
| AG-UI middleware | EventThrottleMiddleware | Per-agent (all subscribers) | AG-UI stream events at the source — batches and coalesces rapid chunk events before they reach subscribers |
When to use which:
throttleMsonuseAgent— best for headless UIs where you control the React component but not the agent creation. Simple, one-line fix.EventThrottleMiddleware— best when you control agent instantiation and want to throttle for ALL consumers (not just one React component). Supports both time-based (intervalMs) and character-count-based (minChunkSize) thresholds. Coalesces consecutive chunk events (text, tool call, reasoning) with the same ID into a single event with merged deltas.- Both together — the middleware reduces event frequency at the source, and the hook-level throttle further smooths the React rendering. They compose naturally.
Agent-level event throttle middleware#
When creating an agent directly (e.g., HttpAgent), you can add EventThrottleMiddleware to throttle stream events:
import { HttpAgent } from "@ag-ui/client";
import { EventThrottleMiddleware } from "@ag-ui/event-throttle-middleware";
const agent = new HttpAgent({
url: "https://my-agent.example.com",
});
agent.use(
new EventThrottleMiddleware({
intervalMs: 16, // ~60fps cap on notifications
minChunkSize: 20, // also fire when 20+ new chars accumulate
}),
);When both intervalMs and minChunkSize are set, a notification fires when either threshold is hit first — whichever comes sooner. This gives you both a minimum visual update granularity (chunk size) and a maximum latency cap (time).
Bufferable events (text chunks, tool call chunks, state snapshots) are batched and coalesced, while lifecycle events (RUN_STARTED, TOOL_CALL_START, etc.) flush the buffer and pass through immediately.
Memoizing message components#
Throttling reduces how often your component re-renders, but each re-render still runs .map() over all messages. For best performance, wrap your message component in React.memo so only the actively-streaming message re-renders:
const MessageBubble = React.memo(function MessageBubble({ message }) {
return <div>{message.content}</div>;
}, (prev, next) => {
return prev.message.id === next.message.id
&& prev.message.content === next.message.content;
});This is what CopilotKit's built-in CopilotChat component does internally — it wraps every message type in React.memo with deep content comparators.
Behavior#
- Automatic Re-renders: Component re-renders when agent state, messages, or execution status changes (configurable via
updatesparameter) - Render Throttling: When
throttleMs > 0, message-change re-renders use a leading+trailing throttle — the first token renders instantly, rapid mid-stream updates are coalesced, and the final state is always rendered after the window expires. State and run-status changes are never throttled. - Error Handling: Throws error if no agent exists with specified
agentId - State Synchronization: State updates via
setState()are immediately available to both app and agent - Event Subscriptions: Subscribe/unsubscribe pattern for lifecycle and custom events
Related#
useCapabilities-- convenience hook for reading agent capabilities- AG-UI AbstractAgent -- full agent interface documentation
