CopilotKit

Agent Config

Forward typed configuration from your UI into the agent's reasoning loop.


"""AG2 agent backing the Agent Config Object demo.Reads three forwarded properties — tone, expertise, responseLength — fromshared state (ContextVariables on each run) and adapts its responsesaccordingly.Wire format-----------The frontend uses `agent.setState({ tone, expertise, responseLength })` fromthe demo page. AG2's AGUIStream maps that initial state into ContextVariableson every run. The agent has a `get_current_config` tool that returns thecurrent rulebook for the assistant to consult before answering.The system prompt instructs the agent to call `get_current_config` once atthe start of every conversation turn so the response style adapts to thelatest UI selection.References:- src/agents/shared_state_read_write.py — same ContextVariables pattern."""import loggingfrom autogen import ConversableAgent, LLMConfigfrom autogen.ag_ui import AGUIStreamfrom autogen.agentchat import ContextVariablesfrom autogen.tools import toolfrom fastapi import FastAPIlogger = logging.getLogger(__name__)VALID_TONES = {"professional", "casual", "enthusiastic"}VALID_EXPERTISE = {"beginner", "intermediate", "expert"}VALID_RESPONSE_LENGTHS = {"concise", "detailed"}DEFAULT_TONE = "professional"DEFAULT_EXPERTISE = "intermediate"DEFAULT_RESPONSE_LENGTH = "concise"TONE_RULES = {    "professional": "Use neutral, precise language. No emoji. Short sentences.",    "casual": (        "Use friendly, conversational language. Contractions OK. Light humor welcome."    ),    "enthusiastic": (        "Use upbeat, energetic language. Exclamation points OK. Emoji OK."    ),}EXPERTISE_RULES = {    "beginner": "Assume no prior knowledge. Define jargon. Use analogies.",    "intermediate": ("Assume common terms are understood; explain specialized terms."),    "expert": ("Assume technical fluency. Use precise terminology. Skip basics."),}LENGTH_RULES = {    "concise": "Respond in 1-3 sentences.",    "detailed": ("Respond in multiple paragraphs with examples where relevant."),}SYSTEM_PROMPT = (    "You are a helpful assistant whose response style is governed by a UI-"    "supplied configuration object. Before answering ANY user question, "    "call the `get_current_config` tool exactly once to read the latest "    "tone / expertise / response-length rulebook. Then answer the user's "    "question, strictly following those rules. Never mention the tool call "    "or the configuration in your reply — just adapt your style.")@tool()def get_current_config(context_variables: ContextVariables) -> str:    """Return the current rulebook (tone / expertise / length) for the assistant.    Reads the forwarded ``tone``, ``expertise``, and ``responseLength``    properties from shared state, falling back to defaults for any missing    or unrecognized value.    """    data = context_variables.data or {}    tone = data.get("tone", DEFAULT_TONE)    expertise = data.get("expertise", DEFAULT_EXPERTISE)    response_length = data.get("responseLength", DEFAULT_RESPONSE_LENGTH)    if tone not in VALID_TONES:        tone = DEFAULT_TONE    if expertise not in VALID_EXPERTISE:        expertise = DEFAULT_EXPERTISE    if response_length not in VALID_RESPONSE_LENGTHS:        response_length = DEFAULT_RESPONSE_LENGTH    return (        f"Tone ({tone}): {TONE_RULES[tone]}\n"        f"Expertise ({expertise}): {EXPERTISE_RULES[expertise]}\n"        f"Response length ({response_length}): {LENGTH_RULES[response_length]}"    )agent_config_agent = ConversableAgent(    name="agent_config_assistant",    system_message=SYSTEM_PROMPT,    llm_config=LLMConfig({"model": "gpt-4o-mini", "stream": True}),    human_input_mode="NEVER",    max_consecutive_auto_reply=5,    functions=[get_current_config],)agent_config_stream = AGUIStream(agent_config_agent)agent_config_app = FastAPI()agent_config_app.mount("/", agent_config_stream.build_asgi())

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:

frontend/src/app/page.tsx — UI publishes the typed config
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:

backend/agent.py — agent reads config and rebuilds the system prompt
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.