showcase/shell-docs/src/content/docs/integrations/pydantic-ai/agent-app-context.mdx
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 on 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 Pydantic AI agent.
The [`useAgentContext` hook](/reference/v2/hooks/useAgentContext) is used to add data as context to the Copilot.
```tsx title="YourComponent.tsx"
"use client" // only necessary if you are using Next.js with the App Router. // [!code highlight]
import { useAgentContext } from "@copilotkit/react-core/v2"; // [!code highlight]
import { useState } from 'react';
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" }
]);
// Share context with the agent
// [!code highlight:4]
useAgentContext({
description: "The current user's colleagues",
value: colleagues,
});
return (
// Your custom UI component
<>...</>
);
}
```
The entries arrive on `RunAgentInput.context`. Build the adapter in two steps so you can
reach `run_input`, pass the entries into `deps`, and expose them to the model through a
tool.
```python title="agent.py"
import json
from dataclasses import dataclass
from ag_ui.core import Context
from pydantic_ai import Agent, RunContext
from pydantic_ai.ui.ag_ui import AGUIAdapter
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route
@dataclass
class AppContextDeps:
"""The entries the frontend shared on this run."""
context: list[Context]
agent = Agent(
"openai:gpt-5.4-mini",
instructions="You are a helpful assistant that can help emailing colleagues.",
deps_type=AppContextDeps, # [!code highlight]
)
# [!code highlight:10]
@agent.tool
def colleagues(ctx: RunContext[AppContextDeps]) -> list[dict]:
"""The current user's colleagues, as the app shared them."""
for entry in ctx.deps.context:
if entry.description == "The current user's colleagues":
# `useAgentContext` JSON-stringifies `value` before it leaves the
# browser, and AG-UI types `Context.value` as a string, so parse it.
return json.loads(entry.value)
return []
async def run_agent(request: Request) -> Response:
# `dispatch_request` parses the request internally, so build the adapter in
# two steps instead: `run_input` is what carries the frontend's entries.
# [!code highlight:4]
adapter = await AGUIAdapter.from_request(request, agent=agent)
deps = AppContextDeps(context=adapter.run_input.context)
return adapter.streaming_response(adapter.run_stream(deps=deps))
app = Starlette(routes=[Route("/", run_agent, methods=["POST"])])
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
```
Two details are easy to miss:
- **`value` is a string, not your object.** `useAgentContext` calls `JSON.stringify` on
the value before the run leaves the browser, and AG-UI types `Context.value` as a
string on both ends. So `json.loads` is required, and a shape check like
`isinstance(entry.value, list)` can never pass.
- **Reach for `from_request`, not `dispatch_request`.** The one-line
`AGUIAdapter.dispatch_request(request, agent=agent)` used elsewhere in these docs
parses the request for you, which leaves you no `run_input` to build `deps` from.
Every entry is a claim your frontend made, so it describes a request — it never establishes
who is making it. Deliver entries to the model as data, the way the tool above does.
Do not build instructions out of
them: instructions carry operator authority, so composing them from client-submitted text
lets a prompt injection inherit that authority.
Facts your server established — the authenticated user, the workspace, the tenant — are what belong in instructions. To let a client-supplied fact change how the agent behaves, authenticate it first, look up the policy your server holds for it, and write the instruction from that. The entry itself stays data.