Marble UI
← Components

Agent Plan / Task List

Preview: Live

An agent's live plan: ordered steps with status and timing, steering notes, and an optional composer.

Import

import { AgentPlan } from '@marbleui/ui'

Props

<AgentPlan>

PropTypeDefaultDescription
status *required'running' | 'done' | 'failed' | 'waiting'—
steps *requiredPlanStep[]—
title *requiredReactNode—
classNamestring—
elapsedstring—
footerReactNode—Composer for steering notes (e.g. a PromptInput).
metaReactNode—e.g. "Claude · 5 steps"

Usage notes

Live checklist of one agent’s work: ordered steps with status, timing, substeps and steering notes. It only displays what you pass, so drive every status from your run’s events. For several agents use AgentOrchestration.

<AgentPlan
  title="Migrate users table"
  status="running"
  steps={[
    { id: 'read', title: 'Read schema and 14 migrations', status: 'done', durationMs: 1400 },
    { id: 'write', title: 'Write migration 0042', status: 'running' },
    { id: 'pr', title: 'Open a pull request', status: 'pending' },
  ]}
  footer={<PromptInput variant="pill" allowAttachments={false} placeholder="Steer the agent…" onSubmit={queueNote} />}
/>
  • Step status is pending, running, done, failed, waiting or skipped; each glyph has its own shape and a text label, and the step list is a polite live region.
  • The header shows done/total (top-level steps) while running; elapsed appears only once status="done".
  • Steering is yours to wire: on submit, add { id, text, state: 'queued' } to a step’s notes, then set it to picked-up or interrupted.
Example source · show
import { useEffect, useState } from 'react'
import { PromptInput } from '../prompt-input'
import { AgentPlan, type PlanStep } from './agent-plan'

const base: PlanStep[] = [
  { id: '1', title: 'Read schema and 14 migrations', status: 'pending', durationMs: 1400 },
  { id: '2', title: 'Plan the split into accounts and profiles', status: 'pending', durationMs: 4600 },
  { id: '3', title: 'Write migration 0042 for Postgres 16', status: 'pending', durationMs: 5200 },
  { id: '4', title: 'Dry run on staging', status: 'pending', durationMs: 2600 },
  { id: '5', title: 'Open a pull request for review', status: 'pending', durationMs: 1800 },
]

function Live() {
  const [i, setI] = useState(0)
  const [notes, setNotes] = useState<NonNullable<PlanStep['notes']>>([])
  useEffect(() => {
    if (i > base.length) return
    const t = setTimeout(() => setI(i + 1), 1400)
    return () => clearTimeout(t)
  }, [i])
  const steps = base.map((s, k) => ({
    ...s,
    status: (k < i ? 'done' : k === i ? 'running' : 'pending') as PlanStep['status'],
    durationMs: k < i ? s.durationMs : undefined,
    notes: k === 2 ? notes.map((n) => ({ ...n, state: i > 2 ? ('picked-up' as const) : n.state })) : undefined,
  }))
  return (
    <AgentPlan
      className="w-[32rem]"
      title="Migrate users table"
      meta="Claude · 5 steps"
      status={i >= base.length ? 'done' : 'running'}
      elapsed="17.6s"
      steps={steps}
      footer={i < base.length ? <PromptInput variant="pill" allowAttachments={false} placeholder="Steer the agent…" onSubmit={(t) => setNotes((n) => [...n, { id: String(Date.now()), text: t, state: 'queued' }])} /> : undefined}
    />
  )
}

export const styles = {
  Live,
  Done: () => (
    <AgentPlan
      className="w-[32rem]"
      title="Weekly metrics digest"
      meta="Claude · 3 steps"
      status="done"
      elapsed="4m 12s"
      steps={[
        { id: 'a', title: 'Pull numbers from three dashboards', status: 'done', durationMs: 42000 },
        { id: 'b', title: 'Summarize changes', status: 'done', durationMs: 18000, substeps: [{ id: 'b1', title: 'Revenue', status: 'done' }, { id: 'b2', title: 'Activation', status: 'done' }] },
        { id: 'c', title: 'Post to #metrics', status: 'done', durationMs: 900 },
      ]}
    />
  ),
}

Source: packages/ui/src/agent-plan/agent-plan.tsx

Catalog

Category
ai-agents
Priority
Pro
Primitive
composed
React Aria
GridList