State Streaming
Stream partial agent state updates to the UI while a tool call is still running.
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?#
By default, agent state only updates between backend checkpoints, so a long-running tool call, such as writing a full document or drafting an email, appears to the UI as one big burst at the end. For agent-native apps, that feels broken: users expect to watch the output materialize.
State streaming forwards the value of a specific tool argument straight into an agent state key as the argument is being generated. The UI, subscribed through useAgent, re-renders every token.
When should I use this?#
Use state streaming whenever a tool's output is long-form text or a growing structured value and you want the user to see it assemble in real time. Common shapes:
- A collaborative writing agent that emits a document
- A research agent that accumulates a list of findings
- A planning agent that builds up a step-by-step plan
Without streaming, the user stares at a spinner. With streaming, they see the answer grow token-by-token.
The backend: one streaming state mapping#
The backend pattern is always the same: map one streaming tool argument to one shared-state key. Middleware-backed frameworks usually expose this as a declarative mapping. Direct SDK adapters do the same work in their streaming loop by parsing partial tool arguments and emitting STATE_SNAPSHOT whenever the mapped value changes. When the LLM streams that argument, CopilotKit writes every partial value into shared state before the tool finishes executing.
A few things to note:
- The state key must exist in your agent state (
documentin this example). - The tool and argument names must match the exact LLM-facing tool call you want to forward.
- When the tool call completes, its final return value is written to the same key, so the streamed partial eventually becomes the authoritative final value.
The frontend: useAgent + OnStateChanged#
The UI side is identical to any other shared-state subscription: useAgent with OnStateChanged gives you a reactive agent.value.state. Add OnRunStatusChanged if you want a live/done indicator.
<script setup lang="ts">
import { computed } from "vue";
import { useAgent, UseAgentUpdate } from "@copilotkit/vue/v2";
type DocumentState = { document?: string };
const { agent } = useAgent({
agentId: "writer",
updates: [
UseAgentUpdate.OnStateChanged,
UseAgentUpdate.OnRunStatusChanged,
],
});
const state = computed(
() => (agent.value?.state ?? {}) as DocumentState,
);
</script>
<template>
<article aria-live="polite">
<span v-if="agent?.isRunning">LIVE</span>
<p>{{ state.document ?? "Waiting for a draft…" }}</p>
</article>
</template>From there, state.document is a string that grows on every token, and agent.isRunning tells you whether to show a streaming indicator.
<script setup lang="ts">
import { CopilotChat, CopilotKitProvider } from "@copilotkit/vue/v2";
import StreamingDocument from "./components/StreamingDocument.vue";
</script>
<template>
<CopilotKitProvider runtime-url="/api/copilotkit">
<StreamingDocument />
<CopilotChat agent-id="writer" />
</CopilotKitProvider>
</template>If the runtime does not emit partial state snapshots, the UI sees only the final state. The Vue frontend cannot invent updates the backend never sends.
Related#
- Shared State (overview) — the bidirectional read and write pattern this extends.
- Agent read-only context — for the inverse UI-to-agent one-way channel.