Skip to main content
Clever Ops

Sub-Agent Tree

Nested agent delegation as an expandable tree, hairline connectors, per-node status lamps and mono duration chips, for orchestrator runs that fan out past a single level.

Preview & code

Loading Sub-Agent Tree preview

Installation

pnpm dlx shadcn@latest add @cleverui/sub-agent-tree

Requires the @cleverui registry in your components.json, a one-off, two-line setup.

Usage

tsx
import { SubAgentTree } from '@/components/ui/sub-agent-tree'
Exampletsx
'use client'

import { useEffect, useMemo, useState } from 'react'
import { useReducedMotion } from 'motion/react'
import { SubAgentTree, type AgentNode } from '@/components/ui/sub-agent-tree'

type Frame = AgentNode[]

/** Meridian orchestrator fanning into invoice-fetch + reconcile with verify grandchildren */
const FRAMES: Frame[] = [
  [
    {
      id: 'orchestrator',
      name: 'meridian-orchestrator',
      task: 'Plan May reconciliation for Apex Holdings',
      status: 'running',
      children: [
        {
          id: 'invoice-fetch',
          name: 'invoice-fetch',
          task: 'Queued, pull AUTHORISED invoices',
          status: 'pending',
        },
        {
          id: 'reconcile',
          name: 'reconcile',
          task: 'Queued, match payments',
          status: 'pending',
          children: [
            {
              id: 'verify-stripe',
              name: 'verify-stripe',
              task: 'Awaiting parent',
              status: 'pending',
            },
            {
              id: 'verify-diff',
              name: 'verify-diff',
              task: 'Awaiting parent',
              status: 'pending',
            },
          ],
        },
      ],
    },
  ],
  [
    {
      id: 'orchestrator',
      name: 'meridian-orchestrator',
      task: 'Delegating fetch and reconcile branches',
      status: 'running',
      children: [
        {
          id: 'invoice-fetch',
          name: 'invoice-fetch',
          task: 'Pulling open invoices from Xero…',
          status: 'running',
        },
        {
          id: 'reconcile',
          name: 'reconcile',
          task: 'Waiting on invoice set',
          status: 'pending',
          children: [
            {
              id: 'verify-stripe',
              name: 'verify-stripe',
              task: 'Awaiting parent',
              status: 'pending',
            },
            {
              id: 'verify-diff',
              name: 'verify-diff',
              task: 'Awaiting parent',
              status: 'pending',
            },
          ],
        },
      ],
    },
  ],
  [
    {
      id: 'orchestrator',
      name: 'meridian-orchestrator',
      task: 'Fetch complete, starting reconcile',
      status: 'running',
      children: [
        {
          id: 'invoice-fetch',
          name: 'invoice-fetch',
          task: 'Loaded 128 AUTHORISED invoices since March 1',
          status: 'done',
          durationMs: 842,
        },
        {
          id: 'reconcile',
          name: 'reconcile',
          task: 'Matching payments against the trial balance…',
          status: 'running',
          children: [
            {
              id: 'verify-stripe',
              name: 'verify-stripe',
              task: 'Pulling March charges for Acme Logistics',
              status: 'running',
            },
            {
              id: 'verify-diff',
              name: 'verify-diff',
              task: 'Awaiting Stripe sample',
              status: 'pending',
            },
          ],
        },
      ],
    },
  ],
  [
    {
      id: 'orchestrator',
      name: 'meridian-orchestrator',
      task: 'Verification branch running',
      status: 'running',
      children: [
        {
          id: 'invoice-fetch',
          name: 'invoice-fetch',
          task: 'Loaded 128 AUTHORISED invoices since March 1',
          status: 'done',
          durationMs: 842,
        },
        {
          id: 'reconcile',
          name: 'reconcile',
          task: 'Trial balance matched, 2 open exceptions',
          status: 'running',
          durationMs: 2140,
          children: [
            {
              id: 'verify-stripe',
              name: 'verify-stripe',
              task: 'Found two $1,240 charges on 2026-03-14',
              status: 'done',
              durationMs: 1180,
            },
            {
              id: 'verify-diff',
              name: 'verify-diff',
              task: 'Flagging amount mismatches over $1…',
              status: 'running',
            },
          ],
        },
      ],
    },
  ],
  [
    {
      id: 'orchestrator',
      name: 'meridian-orchestrator',
      task: 'Reconciliation complete, refund candidate ready',
      status: 'done',
      durationMs: 4820,
      children: [
        {
          id: 'invoice-fetch',
          name: 'invoice-fetch',
          task: 'Loaded 128 AUTHORISED invoices since March 1',
          status: 'done',
          durationMs: 842,
        },
        {
          id: 'reconcile',
          name: 'reconcile',
          task: 'Trial balance matched, 2 open exceptions',
          status: 'done',
          durationMs: 2140,
          children: [
            {
              id: 'verify-stripe',
              name: 'verify-stripe',
              task: 'Found two $1,240 charges on 2026-03-14',
              status: 'done',
              durationMs: 1180,
            },
            {
              id: 'verify-diff',
              name: 'verify-diff',
              task: 'inv_2026_03_8841 double-charged; refund candidate',
              status: 'done',
              durationMs: 420,
            },
          ],
        },
      ],
    },
  ],
]

const HOLDS = [1100, 1300, 1500, 1600, 3200]

export default function SubAgentTreeDemo() {
  const reduced = useReducedMotion()
  const [index, setIndex] = useState(0)

  useEffect(() => {
    if (reduced) {
      setIndex(FRAMES.length - 1)
      return
    }
    const hold = HOLDS[index] ?? 2000
    const id = window.setTimeout(() => {
      setIndex((i) => (i + 1) % FRAMES.length)
    }, hold)
    return () => window.clearTimeout(id)
  }, [index, reduced])

  const nodes = useMemo(
    () => (reduced ? FRAMES[FRAMES.length - 1]! : FRAMES[index]!),
    [index, reduced]
  )

  return (
    <div className="mx-auto flex min-h-[580px] w-full max-w-2xl flex-col justify-center px-4 py-12 sm:px-6 sm:py-16">
      <div className="rounded-2xl border border-border/60 bg-card p-5 sm:p-6">
        <SubAgentTree
          label="Delegation tree"
          nodes={nodes}
          defaultExpandedIds={[
            'orchestrator',
            'reconcile',
          ]}
        />
      </div>
    </div>
  )
}

API reference

SubAgentTree accepts the following props.

PropTypeDefaultDescription
nodes*AgentNode[]delegation topology at arbitrary depth, all status is prop-driven
defaultExpandedIdsstring[]node ids whose branches start open; omitted → every branch expanded
onNodeToggle(id: string, open: boolean) => voidfires when a branch is expanded or collapsed
labelstring'Delegation tree'mono micro-label above the tree

…plus everything from Omit<React.HTMLAttributes<HTMLElement>, 'onToggle'>: className, event handlers, aria attributes and the rest pass straight through.

Referenced types

Typestsx
export interface AgentNode {
  id: string
  name: string
  task?: string
  status: AgentNodeStatus
  durationMs?: number
  error?: string
  children?: AgentNode[]
}

export type AgentNodeStatus = 'pending' | 'running' | 'done' | 'failed'

Frequently asked questions

How do I install the Sub-Agent Tree component?

Register the @cleverui registry in your components.json ("@cleverui": "https://cleverops.com.au/r/{name}.json"), then run `npx shadcn@latest add @cleverui/sub-agent-tree`. The shadcn CLI copies the source into your project, you own the code from that point. You can also copy the source directly from this page.

What dependencies does Sub-Agent Tree require?

Sub-Agent Tree uses motion and lucide-react on top of React and Tailwind CSS. You don't need to install them manually, the shadcn CLI resolves and installs npm dependencies automatically when you add the component.

Can I theme Sub-Agent Tree to match my brand?

Yes. The component is styled entirely with Tailwind utilities over standard design tokens (primary, background, muted, border), so it inherits your existing shadcn/ui theme automatically. Change your CSS variables and the component follows, no source edits needed for palette, radius, or fonts.

Want this built for you?

CleverUI is built by the Clever Ops web team. If you'd rather ship a hand-coded, fast, custom website than assemble one, we build them for Australian businesses every week.

Explore web development services