Elements

Agent plan

A checklist the agent works through, with progress you can glance.

Plan0 of 5
  • Read existing composer state
  • Design the draft store
  • Wire runtime persistence
  • Add regression tests
  • Update the docs
fig. 01 · plays once, replay from the corner

Installation

npx shadcn@latest add "@assistant-ui/elements-agent-plan"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

A checklist with a header count, a progress bar, and each step marked done, active, or still ahead. With a runtime the plan comes from a tool call the model drives; standalone you hold the step list and the active index yourself.

Getting started

A plan like this is a natural shape for a tool call: the model calls a tool with the full step list and the index of the step it is currently on.

Render the tool call

Register a toolkit entry whose render maps the call straight onto AgentPlan. Both fields come from args, so the card needs nothing beyond what the model already sent.

app/toolkit.tsx
"use client";

import { defineToolkit } from "@assistant-ui/react";
import { AgentPlan } from "@/components/assistant-ui/elements/agent-plan";

export const toolkit = defineToolkit({
  update_plan: {
    type: "backend",
    render: ({ args }) => (
      <AgentPlan steps={args.steps} activeIndex={args.activeIndex} />
    ),
  },
});

The model calls update_plan again each time it checks a step off, so a plan reads down the transcript as a short sequence of cards rather than one card mutating in place. The schema and the tool that produces these calls live on your server; only the renderer is shown here.

Register the toolkit

app/MyRuntimeProvider.tsx
import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
import { toolkit } from "./toolkit";

const config = AuiConfig({ tools: Tools({ toolkit }) });

export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      {children}
    </AssistantRuntimeProvider>
  );
}

Every update_plan call now renders as an AgentPlan card wherever it lands in the message.

Anatomy

<div data-slot="agent-plan">
  <div>
    <span>Plan</span>
    <span>{/* n of m */}</span>
  </div>
  <div>{/* progress bar */}</div>
  <ul>
    <li>
      <span>{/* check, spinner, or dot */}</span>
      <span>{/* step text */}</span>
    </li>
  </ul>
</div>

activeIndex is clamped into 0…steps.length before anything is drawn from it, so an out-of-range value never breaks the layout: a value at or past steps.length marks every step done, and NaN falls back to 0. A step is done when its index is before the clamped active index or every step is already done, active when it sits exactly at that index, and otherwise still ahead. With an empty steps array the header reads 0 of 0, the bar sits at 0%, and the list renders nothing.

Examples

Restyle the plan

Both lanes take className on the root. The counter and the icon column share mono and foreground opacity tokens from surfaces.tsx, so a single palette change carries to every element built on those tokens.

<AgentPlan className="max-w-md gap-4" steps={steps} activeIndex={activeIndex} />

Restarting a plan

A plan that needs to restart is just another update_plan call with activeIndex reset to 0. There is no separate reset action; the next call is the reset.

render: ({ args }) => (
  <AgentPlan steps={args.steps} activeIndex={args.activeIndex} />
),

API reference

Render props

PropTypeDescription
args.stepsstring[]The plan's steps, in order.
args.activeIndexnumberIndex of the step currently in progress.