Plan Review

The agent's step-by-step plan, shown before it runs anything. The user reorders, edits, removes, adds or skips steps, then approves.

Pattern

≈ 1,292 tokens per use (tool 700 · guidance 312 · typical payload 280)How token costs are estimated

Example: Plan Review

Plan the Q4 launch announcement for Sync 2.0

Plan the Q4 launch announcement

Announce Acme Sync 2.0 on the blog, social and email.

5 steps. About a day

Press Space or Enter to pick up this step. Then use the up and down arrow keys to move it, Space or Enter to drop it, or Escape to cancel.

  1. 1.
    Draft the launch post600 words, from the 2.0 release notes.Uses Docs. Takes ~10 min.
  2. 2.
    Design the visualsHero image and three social cards.Uses Canva. Takes ~15 min.
  3. 3.
    Review with legalSend to Priya Nair for sign-off.Uses Gmail. Takes ~1 day.
    Pauses for approval
  4. 4.
    Schedule social postsLinkedIn and X, Nov 18 at 9:00 AM PT.Uses Buffer. Takes ~5 min.
  5. 5.
    Email customers4,200 people on the product updates list.Uses Mailchimp. Takes ~10 min.
    Medium riskOptional

Assuming

  • Launch date is Nov 18

When to use

  • Before multi-step work the agent will carry out itself: a launch, a data clean-up, a migration.
  • The user should shape the steps (order, scope, what to skip) before anything runs.
  • Some steps write to shared systems or are hard to undo, and the user should see them first.

When not to use

  • The steps are for the user to do. Use a Checklist.
  • It's one or two quick, reversible actions. Just do them, or confirm in text.
  • The agent is proposing specific field values. Use a Change Preview.

How the agent should use it

  • Write each step as an action the agent will take, in run order, with the tools it uses (only tools it actually has).
  • Mark where it will stop. requiresApproval shows "Pauses for approval"; risk: "medium" or "high" shows a warning for bulk, shared or irreversible writes.
  • Fill dependsOn for real dependencies only. The user is warned when a reorder, removal or skip breaks one, and can't approve until it's fixed.
  • Give estimates as minutes or hours ("~2 min", "1 h") so the header can sum them. If any step can't be summed ("~1 day"), the header shows totalEstimate instead.
  • List assumptions that would change the plan if wrong, five at most. "These are wrong" opens the feedback box for them.
  • Run nothing until approve arrives, then run exactly the returned steps, in order, skipping enabled: false. For steps with addedByUser, ask how to do them if it isn't clear.
  • Lifecycle: supersede, don't stack. When the agent proposes a revised plan after revise, mark the old card superseded (markSuperseded(calls, { names: ["propose_plan"] })). A plan needs no expiresAt; set fetchedAt and staleAfterMs only when it was built on live data that may change before the user approves.

Editing the plan

Nothing runs while the user edits. Every change is marked on its step ("Moved", "Edited", "Added by you"), and the approve button becomes Approve edited plan. The footer keeps Request changes and Approve as buttons; Dismiss (✕) and Reset (↺) are icon buttons with labels. The header shows only the total time; the step count, goal, each step's tools and estimate are read to screen readers but not shown, to keep the card short.

ChangeHow
ReorderDrag a step by its handle. With the keyboard, focus the handle, press Space or Enter to pick it up, ↑/↓ to move (Home/End for the ends), Space or Enter to drop, Escape to cancel. Each step is announced to screen readers
EditInline title and details, with Save and Cancel. Enter saves, Escape cancels
RemoveThe step leaves a "Removed" line in its place with Undo, which puts it back where it was
SkipOptional steps have a switch. Off keeps the step in the list, struck through and labelled "Skipped"
Add"Add a step" at the end (with allowAdd), title plus optional details. The step is marked "Added by you"

Dependency guard. If a step would run before a step it needs, or that step is removed or skipped, the step shows a warning ("Step 1 needs “Find duplicates”, which now runs later.") and approve is disabled. Move it back, undo the removal, or remove the dependent step.

Installation

npx shadcn@latest add https://your-site.com/r/plan-review.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { planReviewTool } from "@/components/genui/plan-review.schema"

const tools = {
  // No execute(): the call is completed by the user's decision in the UI.
  [planReviewTool.name]: tool({
    description: planReviewTool.description,
    inputSchema: planReviewTool.inputSchema,
  }),
}
components/message.tsx
<PlanReview
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : undefined}
  result={part.state === "output-available" ? part.output : undefined}
  onResult={(result) =>
    addToolResult({ tool: "propose_plan", toolCallId: part.toolCallId, output: result })
  }
/>

The agent receives one of:

{
  action: "approve",
  steps: [
    { id: "export", title: "Export contacts", enabled: true },
    { id: "find", title: "Find duplicates", description: "Match on email first", enabled: true },
    { id: "merge", title: "Merge duplicates", enabled: true },
    { id: "notify", title: "Notify record owners", enabled: false },
    { id: "added-1", title: "Tell the sales team", enabled: true, addedByUser: true },
  ],
  edited: true,
  changes: { moved: [], removed: ["report"], edited: [], added: ["added-1"], disabled: ["notify"] },
}
{ action: "revise", feedback: "About the assumptions: leave Enterprise records out" }
{ action: "dismiss" }

steps is the final order; removed steps are left out. moved is the smallest set of steps whose relocation explains the new order, so moving one step counts once and moving it back counts zero. On revise or dismiss, run nothing.

With LangGraph

Push it from a graph node with ui.push({ name: "propose_plan", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

Pass the stored result back as result. An approved plan restores read-only in its final order, with skipped steps struck through and "Approved · running" in the footer; a revise shows the request. It never calls onResult again.

<PlanReview {...part.input} result={part.state === "output-available" ? part.output : undefined} />

Schema

PropTypeNotes
titlestringe.g. "Plan the Q4 launch"
goalstring?One sentence on the outcome. Screen readers only
stepsPlanStep[]1–20, in run order
assumptionsstring[]?Up to 5, shown under "Assuming"
totalEstimatestring?Shown when the step estimates can't be summed
editablebooleanReorder, edit, remove and skip steps. Default true
allowAddbooleanLet the user add steps. Default true

PlanStep

FieldTypeNotes
idstringReturned in the result; used by dependsOn
titlestringAn action: "Export contacts"
descriptionstring?One line of detail
toolsstring[]?Data-bound. Connected tools the step uses. Read to screen readers. Up to 6
estimatestring?"~2 min", "1 h", "1 h 30 min" are summed into the header total
risk"low" | "medium" | "high"Medium and high show a warning
requiresApprovalboolean?Shows "Pauses for approval"
optionalboolean?Adds an on/off switch
dependsOnstring[]?Ids of steps this one needs

Host props: status (streaming, ready, submitting, approved, revised, dismissed), onResult, result and className.

Source

On this page