Programmatic Control
Drive agent runs directly from code — no chat UI required.
Open your coding agent in your project's folder, or in an empty folder for a new app.This runs in a coding agent on your computer.
What is this?#
Programmatic control is what you reach for when you want to drive an agent run from code rather than from a chat composer: a button, a form, a cron job, a keyboard shortcut, or a graph callback. CopilotKit exposes three primitives that cover every triggering pattern:
agent.addMessage(...)— append a message to the conversation without running the agent. Pair it withcopilotkit.runAgent({ agent })when the message should kick off a turn.copilotkit.runAgent({ agent })— the same entry point<CopilotChat />calls under the hood. It orchestrates frontend tools, follow-up runs, and the subscriber lifecycle.agent.subscribe(subscriber)— low-level AG-UI event subscription (onCustomEvent,onRunStartedEvent,onRunFinalized,onRunFailed, and streaming deltas).
When should I use this?#
Use programmatic control when you want to:
- Trigger agent runs from buttons, forms, or other UI elements
- Execute specific tools directly from UI interactions without an LLM turn
- Build agent features without a chat window
- Access agent state and results programmatically
- Create fully custom agent-driven workflows
Sending a message from code#
The canonical pattern is to append a user message with agent.addMessage, then call copilotkit.runAgent({ agent }). Use copilotkit.stopAgent({ agent }) to cancel an in-flight run.
<script setup lang="ts">
import { toRaw } from "vue";
import { useAgent, useCopilotKit } from "@copilotkit/vue/v2";
const { agent } = useAgent({ agentId: "sales" });
const { copilotkit } = useCopilotKit();
async function run() {
const current = agent.value;
if (!current || current.isRunning) return;
current.addMessage({
id: crypto.randomUUID(),
role: "user",
content: "Summarize the latest sales data",
});
try {
await copilotkit.value.runAgent({ agent: toRaw(current) });
} catch (error) {
console.error("CopilotKit runAgent failed:", error);
}
}
function stop() {
const current = agent.value;
if (!current) return;
copilotkit.value.stopAgent({ agent: toRaw(current) });
}
</script>
<template>
<button :disabled="agent?.isRunning" @click="run">Run agent</button>
<button :disabled="!agent?.isRunning" @click="stop">Stop</button>
</template><script setup lang="ts">
import { CopilotKitProvider } from "@copilotkit/vue/v2";
import AgentTrigger from "./components/AgentTrigger.vue";
</script>
<template>
<CopilotKitProvider runtime-url="/api/copilotkit">
<AgentTrigger />
</CopilotKitProvider>
</template>copilotkit.runAgent() vs agent.runAgent()#
Both methods trigger the agent, but they operate at different levels:
copilotkit.runAgent({ agent })— the recommended default. It orchestrates the full lifecycle, executes frontend tools, handles follow-up runs, and routes errors through the subscriber system.agent.runAgent(options)— a low-level method on the agent instance. It sends the request to the runtime but does not execute frontend tools or chain follow-ups. Reach for it only when you need direct control.
Subscribing to agent events#
agent.subscribe(subscriber) returns { unsubscribe }. The subscriber accepts every AG-UI lifecycle callback: onCustomEvent, onRunStartedEvent, onRunFinalized, onRunFailed, and the streaming deltas. Use it to drive progress UI, forward events to analytics, or inspect framework pause/resume events.
import { watch } from "vue";
watch(
agent,
(current, _previous, onCleanup) => {
if (!current) return;
const subscription = current.subscribe({
onRunStartedEvent: () => console.log("started"),
onRunFinalized: () => console.log("done"),
onRunFailed: ({ error }) => console.error(error),
});
onCleanup(() => subscription.unsubscribe());
},
{ immediate: true },
);See also#
- Headless UI — build your own chat surface from lower-level components.
- Human-in-the-Loop — compare
useHumanInTheLoopanduseInterruptfor paused runs.