Predictive state updates
Stream in-progress agent state updates to the frontend.
This example demonstrates predictive state updates in the CopilotKit Feature Viewer.
What is this?#
Microsoft Agent Framework agents can stream state updates through AG-UI as tool arguments are generated by the LLM. CopilotKit surfaces these updates in the UI, enabling optimistic, real-time rendering. We call these predictive state updates.
When should I use this?#
Use predictive state updates when you want to:
- Keep users engaged during long-running operations
- Show step-by-step progress
- Build trust by exposing what the agent is doing now, not only at the end
- Enable agent steering (users can intervene if needed)
Source of truth
When the tool completes, the agent emits a final state snapshot. Any predictive updates should be reflected in that final state or they will be overwritten.
Implementation#
Define the state#
We will define an observed_steps array that is updated while the agent performs long-running tasks.
using System.Text.Json.Serialization;
public class AgentStateSnapshot
{
[JsonPropertyName("observed_steps")]
public List<string> ObservedSteps { get; set; } = new();
}STATE_SCHEMA: dict[str, object] = {
"observed_steps": {
"type": "array",
"items": {"type": "string"},
"description": "Array of completed steps"
}
}Map tool calls to state#
Configure AG-UI state management to map the step_progress tool arguments to observed_steps. The .NET adapter emits a state snapshot when it receives the completed tool call. The Python adapter can also stream partial tool arguments as state deltas.
using System.ComponentModel;
using System.Text.Json;
using AGUI.Abstractions;
using AGUI.Server;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Chat;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAGUIServer();
var app = builder.Build();
string openAiApiKey = builder.Configuration["OPENAI_API_KEY"]
?? throw new InvalidOperationException("Set OPENAI_API_KEY");
// Define a tool the LLM may call to report its progress
[Description("Report current step progress.")]
static string StepProgress([Description("Steps completed so far")] string[] steps)
=> "Progress received.";
AITool stepProgress = AIFunctionFactory.Create(StepProgress, name: "step_progress");
var agent = new OpenAIClient(openAiApiKey)
.GetChatClient("gpt-5.4-mini")
.AsAIAgent(
name: "AGUIAssistant",
instructions: "You are a helpful assistant that may call the 'step_progress' tool to report intermediate steps.",
tools: [stepProgress]);
// Map the completed tool arguments to a shared-state snapshot
AGUIStreamOptions streamOptions = new AGUIStreamOptions()
.MapCall("step_progress", call =>
{
if (call.Arguments?.TryGetValue("steps", out object? steps) is not true)
{
return [];
}
JsonElement snapshot = JsonSerializer.SerializeToElement(
new { observed_steps = steps });
return [new StateSnapshotEvent { Snapshot = snapshot }];
});
app.MapAGUIServer("/", agent).WithMetadata(streamOptions);
await app.RunAsync();from __future__ import annotations
from typing import Annotated
from agent_framework import Agent, SupportsChatGetResponse, tool
from agent_framework.ag_ui import AgentFrameworkAgent
from pydantic import Field
# 1) Define state schema for AG-UI
STATE_SCHEMA: dict[str, object] = {
"observed_steps": {
"type": "array",
"items": {"type": "string"},
"description": "Array of completed steps"
}
}
# 2) Predictive state mapping: observed_steps <- step_progress.steps
PREDICT_STATE_CONFIG: dict[str, dict[str, str]] = {
"observed_steps": {
"tool": "step_progress",
"tool_argument": "steps",
}
}
# 3) Tool that the LLM will call with step updates
@tool
def step_progress(
steps: Annotated[list[str], Field(description="Steps completed so far")]
) -> str:
return "Progress received."
def create_agent(chat_client: SupportsChatGetResponse) -> AgentFrameworkAgent:
base = Agent(
name="sample_agent",
instructions="You are a task performer. Report progress using step_progress.",
client=chat_client,
tools=[step_progress],
)
return AgentFrameworkAgent(
agent=base,
name="CopilotKitMicrosoftAgentFrameworkAgent",
description="Agent with predictive state updates for observed steps.",
state_schema=STATE_SCHEMA,
predict_state_config=PREDICT_STATE_CONFIG,
require_confirmation=False,
)On .NET, MapCall maps the completed FunctionCallContent to a state
snapshot. Progressive .NET tool-argument updates require a provider-specific
extractor registered with MapStreamingToolCallArguments.
Observe state on the client#
Add a state renderer to observe the observed_steps updates as they arrive.
"use client";
import { useAgent } from "@copilotkit/react-core/v2";
type AgentState = {
observed_steps: string[];
};
export default function Page() {
// Access the latest shared state
const { agent } = useAgent({ agentId: "sample_agent" });
// Read the agent's shared state
const progress =
agent.state?.observed_steps?.length ? (
<div>
<h3>Current Progress:</h3>
<ul>
{agent.state.observed_steps.map((step, i) => (
<li key={i}>{step}</li>
))}
</ul>
</div>
) : null;
return <div>{progress}</div>;
}Give it a try!#
Ask the agent to perform a multi-step task (e.g., “write a short outline and report progress each step”). You’ll see observed_steps update in real time as the tool arguments stream in.