CopilotKit

Programmatic Control

Drive agent runs directly from code — no chat UI required.


Not available for MS Agent Harness (.NET) yet

This feature (headless-complete) hasn't been tagged in any MS Agent Harness (.NET) cell yet. Try CopilotKit's Built-in Agent, LangGraph (Python), LangGraph (TypeScript).

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, 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 with copilotkit.runAgent({ agent }) when you want the appended message to kick off a turn.
  • copilotkit.runAgent({ agent }) — the same entry point <CopilotChat /> calls under the hood. Orchestrates frontend tools, follow-up runs, and the subscriber lifecycle.
  • agent.subscribe(subscriber) — low-level AG-UI event subscription (onCustomEvent, onRunStartedEvent, onRunFinalized, onRunFailed, …). Pairs with copilotkit.runAgent({ agent, forwardedProps: { command: { resume, interruptEvent } } }) to drive interrupt resolution from arbitrary UI.

Every example on this page is pulled from two live cells: headless-complete (full chat surface, shown here for the message-send path) and interrupt-headless (button-driven interrupt resolver, shown here for the subscribe + resume path).

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 message-send path in headless-complete is the canonical pattern: append a user message with agent.addMessage, then call copilotkit.runAgent({ agent }). The same handleStop calls copilotkit.stopAgent({ agent }) to cancel mid-run. Note the connectAgent effect at the top, which opens the backend session on mount so the very first runAgent doesn't race the handshake.

copilotkit.runAgent() vs agent.runAgent()#

Both methods trigger the agent, but they operate at different levels:

  • copilotkit.runAgent({ agent }) — the recommended default. Orchestrates the full lifecycle: executes frontend tools, handles follow-up runs, and routes errors through the subscriber system.
  • agent.runAgent(options) — low-level method on the agent instance. Sends the request to the runtime but does not execute frontend tools or chain follow-ups. Reach for this only when you need direct control. (For the interrupt-resume case, use copilotkit.runAgent({ agent, forwardedProps: { command: { resume, interruptEvent } } }) — see the snippet below — so the subscriber lifecycle still wraps the resumed run.)

Subscribing to agent events#

agent.subscribe(subscriber) returns { unsubscribe }. The subscriber object accepts every AG-UI lifecycle callback: onCustomEvent, onRunStartedEvent, onRunFinalized, onRunFailed, and the streaming deltas. Use it to drive custom progress UI, forward events to analytics, or catch LangGraph interrupt(...) events and resume with a payload (the pattern below).

Resolving a pause from a button#

Interrupt-style pause/resume isn't available on this framework. The headless interrupt pattern shown above requires the underlying runtime to expose either a native interrupt(...) primitive (LangGraph) or a Promise-resolving frontend-tool path (Microsoft Agent Framework). For all other integrations, drive pauses through useHumanInTheLoop instead — it's the standard hook for tool-call-based pause/resume flows and works on every framework that supports tool calls. The agent.addMessage, copilotkit.runAgent, and agent.subscribe primitives above still apply — only the interrupt-resolution path is framework-specific.

See also#

  • Headless UI — the full useRenderedMessages composition that mirrors <CopilotChatMessageView> line-for-line.
  • Human-in-the-Loop — the useHumanInTheLoop and useInterrupt hooks with their render-prop contracts, for the "paused mid-chat" pattern this page's headless variant replaces.