CopilotKit

Agent Config

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


"use client";// Agent Config Object (Mastra port).//// The LangGraph reference uses a dedicated route + agent that reads runtime// config props and rebuilds its system prompt per turn. Mastra's Memory// primitive doesn't forward these the same way, so this port sends the// config as `useAgentContext` entries — the agent still sees the values,// but they arrive as frontend context, not as LangGraph config. The UI is// otherwise identical.import { useMemo } from "react";import { CopilotKit } from "@copilotkit/react-core";import { CopilotChat, useAgentContext } from "@copilotkit/react-core/v2";import { ConfigCard } from "./config-card";import { useAgentConfig } from "./use-agent-config";export default function AgentConfigDemoPage() {  return (    <CopilotKit runtimeUrl="/api/copilotkit" agent="agent-config">      <Inner />    </CopilotKit>  );}function Inner() {  const { config, setTone, setExpertise, setResponseLength } = useAgentConfig();  const stableConfig = useMemo(    () => ({      tone: config.tone,      expertise: config.expertise,      responseLength: config.responseLength,    }),    [config.tone, config.expertise, config.responseLength],  );  useAgentContext({    description:      "Agent configuration: tone, expertise, and response length preferences. " +      "The agent should adapt its answers to match these.",    value: stableConfig,  });  return (    <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-[var(--text-muted)]">          Forwarded config lets the frontend tell the agent how to behave. This          demo passes <code>tone</code>, <code>expertise</code>, and          <code> responseLength</code> via <code>useAgentContext</code>; the          Mastra agent receives them as frontend context and adapts its          responses.        </p>      </header>      <ConfigCard        config={config}        onToneChange={setTone}        onExpertiseChange={setExpertise}        onResponseLengthChange={setResponseLength}      />      <div className="flex-1 overflow-hidden rounded-md border border-[var(--border)]">        <CopilotChat agentId="agent-config" className="h-full rounded-md" />      </div>    </div>  );}

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 keeps in sync with the agent. There are two pieces: the UI side, which owns the React state and pushes every change into agent state, and the backend node, which reads those fields out of state and turns them into a system prompt.

The UI side stays simple. Hold the typed config in React state, then mirror every change into the agent through agent.setState({...}):

frontend/src/app/page.tsx — UI owns the typed config
function ConfigStateSync({ config }: { config: AgentConfig }) {
  const { agent } = useAgent({ agentId: "agent-config" });
  useEffect(() => {
    agent.setState({ ...config });
  }, [agent, config]);
  return null;
}

The backend half is also a single node. Read the config out of state 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
async def my_agent_node(state: AgentState, config: RunnableConfig):
    cfg = state.get("config", {})
    tone = cfg.get("tone", "casual")
    expertise = cfg.get("expertise", "intermediate")
    response_length = cfg.get("response_length", "medium")
    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.