# Plan

An agent's plan. Proposed, it is the second turn of the two-turn rule: the person can drop steps they don't want, then accept or ask for changes. Accepted, it becomes the live run: each step shows its status, agent and the metadata it touches. A step waiting on another names what it waits for. Sent back, it shows the steps as they were proposed, marked as not accepted.

Drift: once the plan is running, a step can say how the run left the approved plan (`drift` on the step: added, skipped or changed), from your app's records. Each such step says so in words, and a line above the steps counts them ("The run left the approved plan: 1 step added, 1 step skipped").

Autonomy: with `autonomy`, accepting says what it allows. The person picks one option ("Ask before each edit", "Run without asking") before Accept is available; none is picked for them. `onAccept` gets the choice.

`density="compact"` fits it inside a thread: a smaller header, tighter rows, and a step's summary only while it's running or waiting (the "now" line); a failed step still shows why. `actions` go underneath the steps (Pause, Stop), in any mode.

- Registry name: `plan` (`@candor/plan`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/plan

## Install

```bash
npx candor-ui add plan
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/plan`.
It brings in: agent-avatar, button, entity-chip, locale, locked, status, step-list, work, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import type { AgentIdentity } from "@/lib/chat"
import type { PlanStep } from "@/lib/work"
import { Plan } from "@/components/plan"
import { Button } from "@/components/ui/button"

const dev: AgentIdentity = { id: "a-dev", name: "Dev agent", role: "dev" }
const qa: AgentIdentity = { id: "a-qa", name: "QA agent", role: "qa" }

const proposed: PlanStep[] = [
  { id: "s1", title: "Add the region column", summary: "us, eu, apac, other; nullable; indexed.", status: "idle", agent: dev, touches: [{ kind: "migration", name: "0142_add_customer_region" }, { kind: "column", name: "customers.region", label: "region" }] },
  { id: "s2", title: "Show it on the customer page", status: "idle", agent: dev, dependsOn: ["s1"], touches: [{ kind: "component", name: "web/src/customers/CustomerHeader.tsx", label: "CustomerHeader" }] },
  { id: "s3", title: "Backfill from billing country", status: "idle", agent: dev, dependsOn: ["s1"] },
  { id: "s4", title: "Test on Staging", status: "idle", agent: qa, dependsOn: ["s2", "s3"] },
]

// In a thread: compact, the running step's summary as its "now" line, and actions underneath.
const running: PlanStep[] = [
  { id: "r1", title: "Add the region column", status: "succeeded", agent: dev },
  { id: "r2", title: "Backfill from billing country", summary: "4,120 of 9,800 customers so far.", status: "running", agent: dev },
  { id: "r3", title: "Test on Staging", summary: "Runs the checkout suite.", status: "idle", agent: qa, dependsOn: ["r2"] },
]

// Where the run left the approved plan, from the app's own records: Candor shows drift, it never works it out.
const drifted: PlanStep[] = [
  { id: "d1", title: "Add the region column", status: "succeeded", agent: dev },
  {
    id: "d2",
    title: "Backfill from shipping country",
    status: "succeeded",
    agent: dev,
    drift: { kind: "changed", from: "Backfill from billing country", note: "212 customers had no billing country." },
  },
  { id: "d3", title: "Flag customers with no country", status: "running", agent: dev, drift: { kind: "added", note: "So someone can fill them in by hand." } },
  { id: "d4", title: "Show it on the customer page", status: "skipped", agent: dev, drift: { kind: "skipped", note: "Waiting on design." } },
  { id: "d5", title: "Test on Staging", status: "idle", agent: qa },
]

export default function Example() {
  const [mode, setMode] = React.useState<"proposed" | "accepted" | "sent-back">("proposed")
  const [steps, setSteps] = React.useState(proposed)
  const [paused, setPaused] = React.useState(false)
  const [granted, setGranted] = React.useState<string>()
  return (
    <div className="flex max-w-xl flex-col gap-6">
      <Plan
        title="Add region to customers"
        steps={steps}
        mode={mode}
        autonomy={[{ id: "askBeforeEdits" }, { id: "runWithoutAsking" }]}
        onAccept={(skipped, autonomy) => {
          setGranted(autonomy)
          setSteps((s) => s.map((x, i) => (skipped.includes(x.id) ? { ...x, status: "skipped" } : i === 0 ? { ...x, status: "running" } : x)))
          setMode("accepted")
        }}
        onRevise={() => setMode("sent-back")}
      />
      {granted && <p className="-mt-4 text-xs text-muted-foreground">onAccept got autonomy: {granted}</p>}
      <Plan title="Add region to customers (running)" steps={drifted} mode="accepted" />
      <Plan
        density="compact"
        title="Backfill customer regions"
        steps={running}
        mode="accepted"
        actions={
          <>
            <Button variant="ghost" size="sm" onClick={() => setPaused((p) => !p)}>
              {paused ? "Resume" : "Pause"}
            </Button>
            <Button variant="outline" size="sm">
              Stop and keep what’s done
            </Button>
          </>
        }
      />
    </div>
  )
}
```

## <Plan>

Props:

- `title: string`: What the plan does, in a few words.
- `steps: PlanStep[]`: The steps, in order.
- `mode: PlanMode`: Proposed (waiting for a decision), accepted (the live run), or sent back for changes.
- `onAccept?: ((skipped: string[], autonomy: string | undefined) => void) | undefined`: Accept the proposed plan, with the ids of any steps the person dropped and, with `autonomy`, the id of the option they picked (undefined without `autonomy`).
- `autonomy?: PlanAutonomyOption[] | undefined`: What accepting can allow, in order. The person picks one before Accept is available; nothing is picked for them. Leave it out and accepting says nothing about autonomy, as before.
- `onRevise?: (() => void) | undefined`: Send the proposed plan back for changes.
- `acceptLocked?: string | undefined`: Why the viewer can't accept, if they can't.
- `activeStepId?: string | undefined`: The step shown as selected.
- `onSelectStep?: ((id: string) => void) | undefined`: Makes each step a button that selects it.
- `density?: "default" | "compact" | undefined` (default `"default"`): "compact" for inside a thread: tighter, with a step's summary shown only while it's live. Defaults to "default".
- `actions?: React.ReactNode | undefined`: Buttons under the steps, such as Pause and Stop; beside Accept when proposed.
- `className?: string | undefined`

## Strings (`plan`)

The fixed words, English by default. Override any of them under `plan` in `<LocaleProvider strings={{ plan: { … } }}>`; the table is exported as `planStrings`.

- `label`: Plan: {title}
- `proposed`: Proposed plan
- `sentBack`: Plan sent back for changes
- `plan`: Plan
- `progress`: {done} of {total} done
- `steps`: { one: "{count} step", other: "{count} steps" }
- `agents`: { one: "{count} agent", other: "{count} agents" }
- `sentBackSummary`: {steps} · not accepted
- `proposedSummary`: {steps} · {agents}
- `after`: After {steps}
- `stepRef`: step {number}
- `keep`: Keep
- `skip`: Skip
- `skipped`: {count} skipped
- `askForChanges`: Ask for changes
- `accept`: Accept plan
- `driftSummary`: The run left the approved plan: {changes}
- `driftAddedCount`: { one: "{count} step added", other: "{count} steps added" }
- `driftSkippedCount`: { one: "{count} step skipped", other: "{count} steps skipped" }
- `driftChangedCount`: { one: "{count} step changed", other: "{count} steps changed" }
- `driftAdded`: Added during the run, not in the approved plan
- `driftSkipped`: Skipped by the run, though it was approved
- `driftChanged`: Changed from the approved plan
- `driftFrom`: Approved as: {from}
- `autonomyLabel`: What accepting allows
- `autonomyChoose`: Choose what accepting allows to accept the plan.
- `askBeforeEdits`: Ask before each edit
- `askBeforeEditsDetail`: The agent runs the plan and stops to ask before each change it makes.
- `runWithoutAsking`: Run without asking
- `runWithoutAskingDetail`: The agent runs every step you kept without stopping to ask.

## Types

### PlanMode

```ts
"proposed" | "accepted" | "sent-back"
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
