Skip to main content

Shared State

CopilotKit provides a powerful shared state layer that enables bidirectional communication between your agents and UI components. Agents can update application state, and your UI automatically reflects these changes in real-time.

Overview

The shared state system allows agents to:
  • Replace the entire application state with a new snapshot
  • Apply incremental updates using JSON Patch operations
  • Coordinate state changes across multiple agents
  • Maintain state consistency throughout the agent execution lifecycle

State Update Tools

Agents have access to two built-in tools for state management:

AGUISendStateSnapshot

Replaces the entire application state with a new snapshot. Use this when you need to set or reset the complete state structure.
// Agent has access to this tool automatically
AGUISendStateSnapshot({
  snapshot: {
    counter: 5,
    items: ["x", "y", "z"],
    user: {
      name: "Alice",
      settings: { theme: "dark" }
    }
  }
})
When to use:
  • Initial state setup
  • Complete state replacement
  • Resetting application state
Tool schema:
{
  description: "Replace the entire application state with a new snapshot",
  inputSchema: {
    snapshot: z.any().describe("The complete new state object")
  }
}

AGUISendStateDelta

Applies incremental updates to application state using JSON Patch operations. This is more efficient for targeted updates and preserves existing state.
// Agent applies specific changes
AGUISendStateDelta({
  delta: [
    { op: "replace", path: "/counter", value: 10 },
    { op: "add", path: "/newField", value: "test" },
    { op: "remove", path: "/oldField" }
  ]
})
JSON Patch Operations:
OperationDescriptionExample
addAdd a new field or array element{ op: "add", path: "/items/-", value: "new" }
replaceUpdate an existing field{ op: "replace", path: "/counter", value: 42 }
removeDelete a field or array element{ op: "remove", path: "/items/0" }
Tool schema:
{
  description: "Apply incremental updates to application state using JSON Patch operations",
  inputSchema: {
    delta: z.array(z.object({
      op: z.enum(["add", "replace", "remove"]),
      path: z.string().describe("JSON Pointer path (e.g., '/foo/bar')"),
      value: z.any().optional()
    }))
  }
}

Using State in Your Application

Frontend (React)

Access and react to state changes in your React components using the useAgentState hook:
import { useAgentState } from "@copilotkit/react";

function MyComponent() {
  const [state, setState] = useAgentState("myAgent");

  return (
    <div>
      <p>Counter: {state.counter}</p>
      <p>Items: {state.items?.join(", ")}</p>
      
      <button onClick={() => setState({ counter: (state.counter || 0) + 1 })}>
        Increment
      </button>
    </div>
  );
}

Backend (Agent)

The agent receives the current state in the system message automatically:
import { BasicAgent } from "@copilotkit/agent";

const agent = new BasicAgent({
  model: "openai/gpt-4o",
  instructions: `
    You can read the current application state and update it as needed.
    Use AGUISendStateSnapshot to replace the entire state.
    Use AGUISendStateDelta for incremental updates.
  `
});

State Manager Implementation

CopilotKit internally uses a StateManager to track state changes per agent, thread, and run:
// State tracking structure
// agentId -> threadId -> runId -> state
private stateByRun: Map<string, Map<string, Map<string, State>>>

// Message tracking structure  
// agentId -> threadId -> messageId -> runId
private messageToRun: Map<string, Map<string, Map<string, string>>>

Key Methods

Get state for a specific run:
runtime.core.getStateByRun(agentId, threadId, runId)
Get run ID associated with a message:
runtime.core.getRunIdForMessage(agentId, threadId, messageId)
Get all run IDs for a thread:
runtime.core.getRunIdsForThread(agentId, threadId)

State Events

The system emits events when state changes occur:

STATE_SNAPSHOT Event

Emitted when AGUISendStateSnapshot is called:
{
  type: EventType.STATE_SNAPSHOT,
  snapshot: { /* new state */ },
  threadId: "thread1",
  runId: "run1"
}

STATE_DELTA Event

Emitted when AGUISendStateDelta is called:
{
  type: EventType.STATE_DELTA,
  delta: [
    { op: "replace", path: "/counter", value: 10 }
  ],
  threadId: "thread1",
  runId: "run1"
}

Multi-Agent State Coordination

When using multiple agents, state is tracked independently per agent:
// Agent 1 updates its state
agent1.run({
  threadId: "thread1",
  runId: "run1",
  state: { agentName: "agent1", count: 1 }
});

// Agent 2 updates its state independently
agent2.run({
  threadId: "thread2",
  runId: "run1",
  state: { agentName: "agent2", count: 2 }
});

// States are isolated
runtime.core.getStateByRun("agent1", "thread1", "run1"); // { agentName: "agent1", count: 1 }
runtime.core.getStateByRun("agent2", "thread2", "run1"); // { agentName: "agent2", count: 2 }

Multi-Agent Architecture

Learn more about coordinating multiple agents with shared state

Example: Task List Agent

Here’s a complete example of an agent that manages a task list using state updates:
import { BasicAgent } from "@copilotkit/agent";

const taskAgent = new BasicAgent({
  model: "openai/gpt-4o",
  instructions: `
    You manage a task list. The current state contains:
    - tasks: array of { id, title, completed }
    
    When the user asks to:
    - Add a task: Use AGUISendStateDelta with op: "add" to append to /tasks/-
    - Complete a task: Use AGUISendStateDelta with op: "replace" to set /tasks/[index]/completed to true
    - Remove a task: Use AGUISendStateDelta with op: "remove" to delete /tasks/[index]
    - Clear all: Use AGUISendStateSnapshot to reset tasks to []
  `
});

// Frontend component
function TaskList() {
  const [state] = useAgentState("taskAgent");
  
  return (
    <ul>
      {state.tasks?.map((task) => (
        <li key={task.id}>
          <input 
            type="checkbox" 
            checked={task.completed}
            readOnly
          />
          {task.title}
        </li>
      ))}
    </ul>
  );
}

Best Practices

Use Deltas for Updates

Prefer AGUISendStateDelta for incremental changes to avoid unnecessary re-renders and maintain better performance

Validate State Shape

Define a TypeScript interface for your state structure to ensure type safety across agents and UI

Keep State Flat

Avoid deeply nested state structures - they’re harder to update with JSON Patch and debug

Use Snapshots Sparingly

Reserve AGUISendStateSnapshot for initialization or complete resets, not frequent updates

JSON Pointer Path Syntax

JSON Patch operations use JSON Pointer (RFC 6901) for paths:
// Root level
{ op: "replace", path: "/counter", value: 5 }

// Nested object
{ op: "replace", path: "/user/name", value: "Alice" }

// Array by index
{ op: "remove", path: "/items/0" }

// Append to array
{ op: "add", path: "/items/-", value: "new item" }

// Escape special characters
{ op: "replace", path: "/user/settings~1theme", value: "dark" } // "settings/theme"

Multi-Agent

Coordinate state across multiple agents

Runtime Middleware

Intercept and modify state updates with middleware