showcase/shell-docs/src/content/docs/integrations/deepagents/generative-ui/state-rendering.mdx
<IframeSwitcher id="agent-state-example" exampleUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_generative_ui?sidebar=false&chatDefaultOpen=false" codeUrl="https://feature-viewer.copilotkit.ai/langgraph/feature/agentic_generative_ui?view=code&sidebar=false&codeLayout=tabs" exampleLabel="Demo" codeLabel="Code" height="700px" />
State rendering lets you build UI that reflects your agent's state in real-time. As your agent progresses through nodes and emits state updates, your frontend renders those changes — showing progress, drafts, or intermediate results.
Use state rendering when you want to:
Define the `searches` state, then add a tool that returns each completed update.
<Tabs groupId="agent_language" items={['Python', 'TypeScript']} persist>
<Tab value="Python">
```python title="agent.py"
from typing import Any, TypedDict
from copilotkit import (
CopilotKitMiddleware,
CopilotKitState,
StateItem,
StateStreamingMiddleware,
)
from deepagents import create_deep_agent
from langchain.agents.middleware import AgentMiddleware
from langchain.messages import ToolMessage
from langchain.tools import ToolRuntime, tool
from langgraph.types import Command
class Search(TypedDict):
query: str
done: bool
class AgentState(CopilotKitState):
searches: list[Search]
class SearchesStateMiddleware(AgentMiddleware[AgentState, Any, Any]):
state_schema = AgentState
@tool
def report_research_progress(
searches: list[Search],
runtime: ToolRuntime[None, AgentState],
) -> Command:
"""Report the current research tasks and completion status."""
return Command(
update={
"searches": searches,
"messages": [
ToolMessage(
content="Research progress saved.",
tool_call_id=runtime.tool_call_id,
)
],
}
)
agent = create_deep_agent(
model="openai:gpt-5.4",
tools=[report_research_progress],
middleware=[
SearchesStateMiddleware(),
CopilotKitMiddleware(),
StateStreamingMiddleware(
StateItem(
state_key="searches",
tool="report_research_progress",
tool_argument="searches",
)
),
],
system_prompt=(
"You are a research assistant. Use report_research_progress "
"to show each task and mark it done when complete."
),
)
```
</Tab>
<Tab value="TypeScript">
```ts title="agent.ts"
import { ToolMessage } from "@langchain/core/messages";
import { tool, type ToolRuntime } from "@langchain/core/tools";
import { Command } from "@langchain/langgraph";
import {
copilotkitMiddleware,
zodState,
} from "@copilotkit/sdk-js/langgraph";
import {
stateItem,
stateStreamingMiddleware,
} from "@copilotkit/sdk-js/langgraph-middlewares";
import { createDeepAgent } from "deepagents";
import { createMiddleware } from "langchain";
import { z } from "zod";
const SearchSchema = z.object({
query: z.string(),
done: z.boolean(),
});
type Search = z.infer<typeof SearchSchema>;
const SearchesStateSchema = z.object({
searches: z.array(SearchSchema),
});
const searchesStateMiddleware = createMiddleware({
name: "SearchesState",
stateSchema: z.object({
searches: zodState(z.array(SearchSchema).default(() => [])),
}),
});
const reportResearchProgress = tool(
(
input: { searches: Search[] },
runtime: ToolRuntime<typeof SearchesStateSchema>,
) =>
new Command({
update: {
searches: input.searches,
messages: [
new ToolMessage({
content: "Research progress saved.",
tool_call_id: runtime.toolCallId,
}),
],
},
}),
{
name: "report_research_progress",
description:
"Report the current research tasks and completion status.",
schema: z.object({ searches: z.array(SearchSchema) }),
},
);
export const agent = createDeepAgent({
model: "openai:gpt-5.4",
tools: [reportResearchProgress],
middleware: [
searchesStateMiddleware,
copilotkitMiddleware,
stateStreamingMiddleware(
stateItem({
stateKey: "searches",
tool: "report_research_progress",
toolArgument: "searches",
}),
),
],
systemPrompt:
"You are a research assistant. Use report_research_progress " +
"to show each task and mark it done when complete.",
});
```
</Tab>
</Tabs>
The state-streaming middleware sends partial `searches` arguments while the model creates them. The frontend can show each partial value immediately.
The tool then returns a `Command` that saves the completed list. Its `ToolMessage` closes the active tool call.
Keep the state key, tool name, and tool argument identical. A mismatch sends updates to the wrong state field.
Use the `useAgent` hook to access agent state anywhere in your app. You can render it in the chat, in dashboards, sidebars, or custom layouts.
```tsx title="app/page.tsx"
import { useAgent } from "@copilotkit/react-core/v2"; // [!code highlight]
function YourMainContent() {
// [!code highlight:3]
const { agent } = useAgent({
agentId: "sample_agent",
});
const state = (agent.state ?? {}) as {
searches?: { query: string; done: boolean }[];
};
const searches = state.searches ?? [];
return (
<div>
{searches.map((search, index) => (
<div key={index}>
{search.done ? "✅" : "⏳"} {search.query}
</div>
))}
</div>
);
}
```
Ask the agent to research a topic. The search items appear and update while the agent works.