# Agent Switcher Recipes Three copy-paste patterns for multi-agent UIs. All subscribe to `copilotkit.subscribe({ onAgentsChanged })` for live agent discovery — there is no `useAgents()` hook. ## Recipe 1 — Dropdown switcher ```tsx "use client"; import { CopilotChat, useCopilotKit } from "@copilotkit/react-core/v2"; import { useEffect, useState } from "react"; export function DropdownAgentSwitcher() { const { copilotkit } = useCopilotKit(); const [agentIds, setAgentIds] = useState(() => Object.keys(copilotkit.agents ?? {}), ); const [activeAgent, setActiveAgent] = useState( () => Object.keys(copilotkit.agents ?? {})[0] ?? "default", ); useEffect(() => { const sub = copilotkit.subscribe({ onAgentsChanged: ({ agents }) => { setAgentIds(Object.keys(agents ?? {})); }, }); return () => sub.unsubscribe(); }, [copilotkit]); return (
); } ``` ## Recipe 2 — Tabs switcher ```tsx "use client"; import { CopilotChat, useCopilotKit } from "@copilotkit/react-core/v2"; import { useEffect, useRef, useState } from "react"; export function TabsAgentSwitcher() { const { copilotkit } = useCopilotKit(); const [agentIds, setAgentIds] = useState(() => Object.keys(copilotkit.agents ?? {}), ); const [activeAgent, setActiveAgent] = useState( () => agentIds[0] ?? "default", ); // Hold activeAgent in a ref so the subscribe effect only re-binds when // `copilotkit` changes. Depending on `activeAgent` would tear down and // re-establish the subscription on every tab click. const activeAgentRef = useRef(activeAgent); useEffect(() => { activeAgentRef.current = activeAgent; }, [activeAgent]); useEffect(() => { const sub = copilotkit.subscribe({ onAgentsChanged: ({ agents }) => { const ids = Object.keys(agents ?? {}); setAgentIds(ids); if (!ids.includes(activeAgentRef.current) && ids.length > 0) { setActiveAgent(ids[0]); } }, }); return () => sub.unsubscribe(); }, [copilotkit]); return (
{agentIds.map((id) => ( ))}
); } ``` ## Recipe 3 — Keyboard shortcut switcher Cycles through agents with `Cmd/Ctrl + Shift + A`. ```tsx "use client"; import { CopilotChat, useCopilotKit } from "@copilotkit/react-core/v2"; import { useEffect, useState } from "react"; export function KeyboardAgentSwitcher() { const { copilotkit } = useCopilotKit(); const [agentIds, setAgentIds] = useState(() => Object.keys(copilotkit.agents ?? {}), ); const [activeAgent, setActiveAgent] = useState( () => agentIds[0] ?? "default", ); useEffect(() => { const sub = copilotkit.subscribe({ onAgentsChanged: ({ agents }) => setAgentIds(Object.keys(agents ?? {})), }); return () => sub.unsubscribe(); }, [copilotkit]); useEffect(() => { function onKey(e: KeyboardEvent) { const isCombo = (e.metaKey || e.ctrlKey) && e.shiftKey && e.key === "A"; if (!isCombo || agentIds.length === 0) return; e.preventDefault(); const idx = agentIds.indexOf(activeAgent); const next = agentIds[(idx + 1) % agentIds.length]; setActiveAgent(next); } window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [agentIds, activeAgent]); return (
Active: {activeAgent} — press ⌘/Ctrl+Shift+A to cycle
); } ``` ## Key rules across all three recipes - Use `copilotkit.subscribe({ onAgentsChanged })` — there is no `useAgents()` hook. - Always `key={activeAgent}` on `` so thread state doesn't leak when swapping agents in the same slot. - Clean up the subscription with `sub.unsubscribe()` in the effect cleanup.