Readables

Share app specific context with your agent.


One of the most common use cases for CopilotKit is to register app state and context using useAgentContext. This way, you can notify CopilotKit of what is going in your app in real time. Some examples might be: the current user, the current page, etc.

This context can then be shared with your AG-UI server and agent logic.

Implementation#

Check out the Frontend Data documentation to understand what this is and how to use it.

You'll need to run your agent and connect it to CopilotKit before proceeding. If you haven't done so already, you can follow the instructions in the Getting Started guide.

If you don't already have an agent, you can use the coagent starter as a starting point as this guide uses it as a starting point.

Add the data to the Copilot#

The useAgentContext hook is used to add data as context to the Copilot.

YourComponent.tsx
"use client" // only necessary if you are using Next.js with the App Router.

export function YourComponent() {
    // Create colleagues state with some sample data
    const [colleagues, setColleagues] = useState([
        { id: 1, name: "John Doe", role: "Developer" },
        { id: 2, name: "Jane Smith", role: "Designer" },
        { id: 3, name: "Bob Wilson", role: "Product Manager" }
    ]);

    // Define Copilot readable state
    useAgentContext({
        description: "The current user's colleagues",
        value: colleagues,
    });
    return (
        // Your custom UI component
        <>...</>
    );
}

Consume the data in your AG-UI server#

The context you register on the frontend is forwarded in the AG-UI RunAgentInput. Use middleware to read it and inject it into the agent's conversation.

Program.cs
using System.Runtime.CompilerServices;
using System.Text;
using AGUI.Abstractions;
using AGUI.Server;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Chat;
using AIChatMessage = Microsoft.Extensions.AI.ChatMessage;

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");

// Create the base agent
AIAgent baseAgent = new OpenAIClient(openAiApiKey)
    .GetChatClient("gpt-5.4-mini")
    .AsAIAgent(
        name: "AGUIAssistant",
        instructions: "You are a helpful assistant. Use the provided context about colleagues to answer questions.");

// Wrap the agent with middleware to inject context
AIAgent agent = baseAgent
    .AsBuilder()
    .Use(runFunc: null, runStreamingFunc: InjectContextMiddleware)
    .Build();

// Map the AG-UI endpoint
app.MapAGUIServer("/", agent);
await app.RunAsync();

// Middleware to inject useAgentContext context as a system message
async IAsyncEnumerable<AgentResponseUpdate> InjectContextMiddleware(
    IEnumerable<AIChatMessage> messages,
    AgentSession? session,
    AgentRunOptions? options,
    AIAgent innerAgent,
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    // Recover the AG-UI request and inject its context if present
    if (options is ChatClientAgentRunOptions { ChatOptions: { } chatOptions } &&
        chatOptions.TryGetRunAgentInput(out RunAgentInput? input) &&
        input?.Context is { Count: > 0 } context)
    {
        var contextBuilder = new StringBuilder();
        contextBuilder.AppendLine("The following context from the user's application is available:");
        foreach (AGUIContext item in context)
        {
            contextBuilder.AppendLine($"- {item.Description}: {item.Value}");
        }

        var contextMessage = new AIChatMessage(
            ChatRole.System,
            [new TextContent(contextBuilder.ToString())]);

        messages = messages.Append(contextMessage);
    }

    await foreach (var update in innerAgent.RunStreamingAsync(messages, session, options, cancellationToken))
    {
        yield return update;
    }
}
main.py (excerpt)

import json
from collections.abc import AsyncGenerator
from typing import Any
from uuid import uuid4

from ag_ui.core import BaseEvent
from agent_framework import Agent, BaseChatClient
from agent_framework_ag_ui import AgentFrameworkAgent


def build_context_system_message(context: Any) -> str | None:
    if not isinstance(context, list) or not context:
        return None

    lines = ["## Context from the application"]
    for entry in context:
        if not isinstance(entry, dict):
            continue

        description = entry.get("description")
        value = entry.get("value")
        if not isinstance(description, str) or not description or value is None:
            continue

        if not isinstance(value, str):
            try:
                value = json.dumps(value, ensure_ascii=False, indent=2)
            except (TypeError, ValueError):
                value = str(value)
        lines.extend(["", description, value])

    return "\n".join(lines) if len(lines) > 1 else None


class ContextAwareAgent(AgentFrameworkAgent):
    """Add app context to this request without mutating the shared agent."""

    async def run(
        self,
        input_data: dict[str, Any],
    ) -> AsyncGenerator[BaseEvent, None]:
        context_prompt = build_context_system_message(input_data.get("context"))
        messages = input_data.get("messages")

        # The adapter skips the model when messages are empty. Context
        # alone must not create an unsolicited model call.
        if context_prompt and isinstance(messages, list) and messages:
            run_id = input_data.get("runId") or str(uuid4())
            request_input = dict(input_data)
            request_input["runId"] = run_id
            request_input["messages"] = [
                {
                    "id": f"{run_id}-app-context",
                    "role": "system",
                    "content": context_prompt,
                },
                *[
                    message
                    for message in messages
                    if not (
                        isinstance(message, dict)
                        and isinstance(message.get("id"), str)
                        and message["id"].endswith("-app-context")
                    )
                ],
            ]
            input_data = request_input

        async for event in super().run(input_data):
            yield event


def create_agent(chat_client: BaseChatClient) -> AgentFrameworkAgent:
    base_agent = Agent(
        name="sample_agent",
        instructions="You are a helpful assistant.",
        client=chat_client,
    )

    return ContextAwareAgent(
        agent=base_agent,
        name="CopilotKitMicrosoftAgentFrameworkAgent",
        description="Assistant using request-local app context.",
        require_confirmation=False,
    )

Context registered with useAgentContext is forwarded in RunAgentInput.Context as AGUIContext entries. TryGetRunAgentInput recovers the request without depending on hosting-layer keys.

Configuration & Error Handling: This example uses OPENAI_API_KEY for configuration. Set it with user secrets or an environment variable. For production deployments, add appropriate error handling and consider using the Quickstart or Authentication guides for complete setup patterns.

Give it a try!#

Ask your agent a question about the context (e.g., "Who are my colleagues?"). The agent will use the forwarded context to answer!

On this page