State Rendering
Render the state of your agent with custom UI components.
What is this?#
AG2 agents can maintain state across a session through ContextVariables. CopilotKit can render this state in your application with custom UI components, which we call Agentic Generative UI.
CopilotKit consumes AG-UI protocol events streamed by AG2 over /chat. See the AG2 AG-UI integration docs.
When should I use this?#
Rendering the state of your agent in the UI is useful when you want to provide the user with feedback about the overall state of a session. A great example of this is a situation where a user and an agent are working together to solve a problem. The agent can store a draft in its state which is then rendered in the UI.
Implementation#
Run and connect your agent#
Start your AG2 backend with AG-UI streaming enabled on /chat.
Set up your agent with state#
Create your AG2 agent with ContextVariables and emit StateSnapshotEvent updates:
from typing import Annotated
from ag_ui.core import EventType, StateSnapshotEvent
from fastapi import FastAPI, Header
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from autogen import ConversableAgent, LLMConfig
from autogen.agentchat import ContextVariables
from autogen.ag_ui import AGUIStream, RunAgentInput
class Search(BaseModel):
query: str
done: bool
class AgentState(BaseModel):
searches: list[Search] = Field(default_factory=list)
def read_state(context: ContextVariables) -> AgentState:
raw_state = context.get("agent_state", {"searches": []})
return AgentState.model_validate(raw_state)
def write_state(context: ContextVariables, state: AgentState) -> StateSnapshotEvent:
snapshot = state.model_dump()
context["agent_state"] = snapshot
return StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=snapshot)
agent = ConversableAgent(
name="assistant",
system_message=(
"You are a helpful assistant for storing searches. "
"Use `add_search` once per query, then call `run_searches`."
),
llm_config=LLMConfig({"model": "gpt-5.4-mini"}),
human_input_mode="NEVER",
)
@agent.register_for_llm(description="Add a search to the state.")
def add_search(
context: ContextVariables,
new_query: Annotated[str, "The query to add to state"],
) -> StateSnapshotEvent:
state = read_state(context)
state.searches.append(Search(query=new_query, done=False))
return write_state(context, state)
@agent.register_for_llm(description="Run the queued searches and mark them done.")
def run_searches(context: ContextVariables) -> StateSnapshotEvent:
state = read_state(context)
for search in state.searches:
search.done = True
return write_state(context, state)
agent.register_for_execution(name="add_search")(add_search)
agent.register_for_execution(name="run_searches")(run_searches)
stream = AGUIStream(agent)
app = FastAPI()
@app.post("/chat")
async def run_agent(
message: RunAgentInput,
accept: str | None = Header(None),
):
return StreamingResponse(
stream.dispatch(message, accept=accept),
media_type=accept or "text/event-stream",
)Render state of the agent in the chat#
Now we can utilize useAgent with a render function to render the state of our agent in the chat.
// ...
import { useAgent } from "@copilotkit/react-core/v2";
// ...
// Define the state of the agent, should match the state streamed by your AG2 backend.
type AgentState = {
searches: {
query: string;
done: boolean;
}[];
};
function YourMainContent() {
// ...
// styles omitted for brevity
useAgent({
agentId: "my_agent",
render: ({ state }) => (
<div>
{state.searches?.map((search, index) => (
<div key={index}>
{search.done ? "✅" : "❌"} {search.query}{search.done ? "" : "..."}
</div>
))}
</div>
),
});
// ...
return <div>...</div>;
}Important
The name parameter must exactly match the agent name you defined in your CopilotRuntime configuration (e.g., my_agent from the quickstart).
Render state outside of the chat#
You can also render the state of your agent outside of the chat. This is useful when you want to render the state of your agent anywhere other than the chat.
import { useAgent } from "@copilotkit/react-core/v2";
// ...
// Define the state of the agent, should match the state streamed by your AG2 backend.
type AgentState = {
searches: {
query: string;
done: boolean;
}[];
};
function YourMainContent() {
// ...
const { agent } = useAgent({
agentId: "my_agent", // MUST match the agent name in CopilotRuntime
})
// ...
return (
<div>
{/* ... */}
<div className="flex flex-col gap-2 mt-4">
{agent.state.searches?.map((search, index) => (
<div key={index} className="flex flex-row">
{search.done ? "✅" : "❌"} {search.query}
</div>
))}
</div>
</div>
)
}Important
The agentId parameter must exactly match the agent name you defined in your CopilotRuntime configuration (e.g., my_agent from the quickstart).
Give it a try#
You've now created a component that will render the agent's state in the chat.
