Agent Config
Forward typed configuration from your UI into the agent's reasoning loop.
"use client";import { CopilotChat, CopilotKit } from "@copilotkit/react-core/v2";import { useMemo } from "react";import { ConfigCard } from "./config-card";import { useAgentConfig } from "./use-agent-config";export default function AgentConfigDemoPage() { const { config, setTone, setExpertise, setResponseLength } = useAgentConfig(); const providerProperties = useMemo<Record<string, unknown>>( () => ({ tone: config.tone, expertise: config.expertise, responseLength: config.responseLength, }), [config.tone, config.expertise, config.responseLength], ); return ( <CopilotKit runtimeUrl="/api/copilotkit-agent-config" agent="agent-config-demo" properties={providerProperties} > <div className="flex h-screen flex-col gap-3 p-6"> <header> <h1 className="text-lg font-semibold">Agent Config Object</h1> <p className="text-sm text-gray-500"> Forwarded provider properties (<code>tone</code>,{" "} <code>expertise</code>, <code>responseLength</code>) are threaded into the CrewAI agent server via a FastAPI middleware that splices them into <code>state.inputs</code>, which the crew chat flow appends to the system prompt on every turn. </p> </header> <ConfigCard config={config} onToneChange={setTone} onExpertiseChange={setExpertise} onResponseLengthChange={setResponseLength} /> <div className="flex-1 overflow-hidden rounded-md border border-gray-200"> <CopilotChat agentId="agent-config-demo" className="h-full rounded-md" /> </div> </div> </CopilotKit> );}You have a working agent and want the user to be able to tune how it behaves: tone, expertise level, response length, language, persona. By the end of this guide, your UI will own a typed config object that the agent reads on every run and rebuilds its system prompt from.
When to use this#
Reach for agent config whenever the agent's behaviour depends on user-controllable settings that don't fit naturally as chat input:
- Tone, voice, persona: "playful", "formal", "casual"
- Expertise level: "beginner", "intermediate", "expert"
- Response shape: short / medium / long, structured / prose, language
- Domain switches: which knowledge base to consult, which tool subset to enable
If the values are a channel the user occasionally tunes (a settings panel, a toolbar of selects), agent config is the right shape. If the values are content the agent should write back to (notes, a document, a plan), use Shared State instead.
How agent config flows from the UI into the agent's reasoning loop depends on your runtime architecture. Agents living behind a runtime read it from agent state on every run, while in-process agents receive the same object as forwarded properties on the provider — same UX, slightly different wiring on each side.
How it works#
Agent config is a typed object the frontend owns and publishes to the agent as runtime context. There are two pieces: the UI side, which owns the React state and publishes every change with useAgentContext, and the backend node, which reads that context entry and turns it into a system prompt.
The UI side stays simple. Hold the typed config in React state, then mirror every change into the agent through useAgentContext:
function ConfigContextRelay({ config }: { config: AgentConfig }) {
useAgentContext({
description: "Agent response preferences",
value: {
tone: config.tone,
expertise: config.expertise,
responseLength: config.responseLength,
},
});
return null;
}The backend half is also a single node. Read the latest config context at the top of every run and use it to build the system prompt for that turn:
import json
CONFIG_KEYS = ("tone", "expertise", "responseLength")
def read_config_value(entry):
value = entry.get("value")
if isinstance(value, str):
try:
value = json.loads(value)
except json.JSONDecodeError:
return None
if not isinstance(value, dict):
return None
if any(key in value for key in CONFIG_KEYS):
return value
return None
async def my_agent_node(state: AgentState, config: RunnableConfig):
context_entries = state.get("copilotkit", {}).get("context", [])
cfg = next(
(
value
for entry in reversed(context_entries)
if (value := read_config_value(entry)) is not None
),
{},
)
tone = cfg.get("tone", "professional")
expertise = cfg.get("expertise", "intermediate")
response_length = cfg.get("responseLength", "concise")
system_prompt = build_system_prompt(tone, expertise, response_length)
# ...The agent reads the latest typed config at the start of every turn, rebuilds the system prompt, runs the turn. This is the same shape as the shared-state write-side pattern; agent config is just a specific use of that pattern with a UI-owned typed object on top.
