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.
≈ 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.
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
toolsit uses (only tools it actually has). - Mark where it will stop.
requiresApprovalshows "Pauses for approval";risk: "medium"or"high"shows a warning for bulk, shared or irreversible writes. - Fill
dependsOnfor 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
totalEstimateinstead. - List assumptions that would change the plan if wrong, five at most. "These are wrong" opens the feedback box for them.
- Run nothing until
approvearrives, then run exactly the returned steps, in order, skippingenabled: false. For steps withaddedByUser, 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 noexpiresAt; setfetchedAtandstaleAfterMsonly 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.
| Change | How |
|---|---|
| Reorder | Drag 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 |
| Edit | Inline title and details, with Save and Cancel. Enter saves, Escape cancels |
| Remove | The step leaves a "Removed" line in its place with Undo, which puts it back where it was |
| Skip | Optional 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
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,
}),
}<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
| Prop | Type | Notes |
|---|---|---|
title | string | e.g. "Plan the Q4 launch" |
goal | string? | One sentence on the outcome. Screen readers only |
steps | PlanStep[] | 1–20, in run order |
assumptions | string[]? | Up to 5, shown under "Assuming" |
totalEstimate | string? | Shown when the step estimates can't be summed |
editable | boolean | Reorder, edit, remove and skip steps. Default true |
allowAdd | boolean | Let the user add steps. Default true |
PlanStep
| Field | Type | Notes |
|---|---|---|
id | string | Returned in the result; used by dependsOn |
title | string | An action: "Export contacts" |
description | string? | One line of detail |
tools | string[]? | Data-bound. Connected tools the step uses. Read to screen readers. Up to 6 |
estimate | string? | "~2 min", "1 h", "1 h 30 min" are summed into the header total |
risk | "low" | "medium" | "high" | Medium and high show a warning |
requiresApproval | boolean? | Shows "Pauses for approval" |
optional | boolean? | Adds an on/off switch |
dependsOn | string[]? | Ids of steps this one needs |
Host props: status (streaming, ready, submitting, approved, revised, dismissed), onResult, result and className.