Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new

Subagent

A subagent run with nested tool calls, task details, and a final report.

Interactive preview

The shared preview uses React Base. The source below uses Radix UI. Each example runs with local data.

Installation

Complete project setup with react/radix, then install these components:

pnpm exec frontfriend download trace code-block badge icon button message bubble -f react --primitive radix

Keep preliminary output in the running state until the subagent returns its final result. Nested steps use stable IDs and controlled disclosures. The task and model badges describe the run, and the duration appears after its title.

Usage

Complete example

import { Message, MessageContent } from "@/components/ui/message";
import { Bubble, BubbleContent } from "@/components/ui/bubble";
import {
  Trace,
  TraceLine,
  TraceIcon,
  TraceTitle,
  TraceDescription,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceCollapsibleContent,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
import { Badge } from "@/components/ui/badge";
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block";
import { Button } from "@/components/ui/button";
import * as React from "react";
type Phase = "complete" | "running" | "error" | "pending";
function useRun(initial: Phase) {
  const [phase, setPhase] = React.useState(initial);
  const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(
    undefined,
  );
  React.useEffect(() => {
    clearTimeout(timer.current);
    setPhase(initial);
  }, [initial]);
  React.useEffect(() => () => clearTimeout(timer.current), []);
  function replay() {
    clearTimeout(timer.current);
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      setPhase("complete");
      return;
    }
    setPhase("running");
    timer.current = setTimeout(() => setPhase("complete"), 3600);
  }
  return { phase, replay };
}
type AgentStep = {
  id: string;
  title: string;
  status?: "running" | "error" | "pending";
  output?: string;
  children?: AgentStep[];
  report?: string;
  task?: string;
  duration?: string;
  model?: string;
  tool?: string;
};
const frame: React.CSSProperties = {
  width: "100%",
  maxWidth: 384,
  minWidth: 0,
  minHeight: 288,
  marginInline: "auto",
  display: "flex",
  flexDirection: "column",
  gap: 16,
  paddingBlock: 24,
  fontSize: "var(--text-sm)",
  lineHeight: 1.5,
};
function TracePanel({
  trigger,
  children,
  open: controlledOpen,
  defaultOpen = false,
  onOpenChange,
  ...props
}: React.ComponentProps<typeof TraceCollapsible> & {
  trigger: React.ReactNode;
}) {
  const contentRef = React.useRef<HTMLDivElement>(null);
  const [height, setHeight] = React.useState<number>();
  const [localOpen, setLocalOpen] = React.useState(defaultOpen);
  const expanded = controlledOpen ?? localOpen;
  const [hasOpened, setHasOpened] = React.useState(expanded);
  React.useEffect(() => {
    if (expanded) setHasOpened(true);
  }, [expanded]);
  React.useLayoutEffect(() => {
    const content = contentRef.current;
    const inner = content?.firstElementChild;
    if (!expanded || !content || !inner) return;
    const measure = () => setHeight(content.scrollHeight);
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(inner);
    return () => observer.disconnect();
  }, [expanded]);
  return (
    <TraceCollapsible
      {...props}
      open={expanded}
      onOpenChange={(value) => {
        if (!value && contentRef.current) {
          setHeight(contentRef.current.getBoundingClientRect().height);
        }
        setLocalOpen(value);
        onOpenChange?.(value);
      }}
    >
      <TraceCollapsibleTrigger>{trigger}</TraceCollapsibleTrigger>
      <TraceCollapsibleContent
        ref={contentRef}
        style={
          height === undefined
            ? undefined
            : ({
                "--radix-collapsible-content-height": `${height}px`,
              } as React.CSSProperties)
        }
        forceMount={hasOpened ? true : undefined}
        inert={!expanded}
      >
        {children}
      </TraceCollapsibleContent>
    </TraceCollapsible>
  );
}
function AgentRow({
  step,
  open,
  onOpen,
}: {
  step: AgentStep;
  open: Record<string, boolean>;
  onOpen: (id: string, value: boolean) => void;
}) {
  const line = (
    <TraceLine status={step.status}>
      <TraceIcon>
        <Icon
          name={
            step.status === "running"
              ? "LoaderCircle"
              : step.status === "error"
                ? "CircleAlert"
                : step.status === "pending"
                  ? "CircleDashed"
                  : "CircleCheck"
          }
        />
      </TraceIcon>
      <TraceTitle>{step.title}</TraceTitle>
      {step.duration && <TraceDescription>{step.duration}</TraceDescription>}
    </TraceLine>
  );
  if (!step.children && !step.output) return line;
  return (
    <TracePanel
      open={open[step.id] ?? Boolean(step.children)}
      onOpenChange={(value) => onOpen(step.id, value)}
      trigger={line}
    >
      <div style={{ display: "grid", gap: 12 }}>
        {step.task && (
          <>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
              <Badge variant="secondary">{step.model}</Badge>
              <Badge variant="outline">{step.tool}</Badge>
            </div>
            <p>{step.task}</p>
          </>
        )}
        {step.children && (
          <Trace>
            {step.children.map((child) => (
              <AgentRow
                key={child.id}
                step={child}
                open={open}
                onOpen={onOpen}
              />
            ))}
          </Trace>
        )}
        {step.output && (
          <CodeBlock
            size="sm"
            variant={step.status === "error" ? "destructive" : "default"}
          >
            <CodeBlockContent wrap style={{ maxHeight: 256 }}>
              {step.output}
            </CodeBlockContent>
          </CodeBlock>
        )}
        {step.report && <p>{step.report}</p>}
      </div>
    </TracePanel>
  );
}
export default function AiSubagentExample() {
  const { phase, replay: run } = useRun("complete");
  const [open, setOpen] = React.useState<Record<string, boolean>>({});
  function onOpen(id: string, value: boolean) {
    setOpen((current) => ({ ...current, [id]: value }));
  }
  function replay() {
    setOpen({});
    run();
  }
  const step: AgentStep = {
    id: "research",
    title:
      phase === "complete"
        ? "Ran the research subagent"
        : phase === "running"
          ? "Running the research subagent"
          : phase === "pending"
            ? "Research subagent queued"
            : "Research subagent failed",
    status: phase === "complete" ? undefined : phase,
    task: "Find out why image optimization reruns on every deploy.",
    duration: phase === "complete" ? "48s" : undefined,
    model: "Research model",
    tool: "runCommand",
    children: [
      {
        id: "find",
        title:
          phase === "pending"
            ? "Run rg cacheKey scripts"
            : "Ran rg cacheKey scripts",
        status: phase === "pending" ? "pending" : undefined,
        output:
          phase === "pending"
            ? undefined
            : "scripts/optimize-images.ts:14: const cacheKey = process.env.COMMIT_SHA",
      },
      {
        id: "read",
        title:
          phase === "complete"
            ? "Read scripts/optimize-images.ts"
            : phase === "error"
              ? "Failed to read scripts/optimize-images.ts"
              : phase === "pending"
                ? "Read scripts/optimize-images.ts"
                : "Reading scripts/optimize-images.ts",
        status: phase === "complete" ? undefined : phase,
        output:
          phase === "complete"
            ? 'const cacheDir = ".next/cache/images"\nconst cacheKey = process.env.COMMIT_SHA\nconst cached = await restoreCache(cacheDir, cacheKey)'
            : phase === "error"
              ? "The file could not be read."
              : undefined,
      },
      {
        id: "count",
        title:
          phase === "complete"
            ? "Counted public/images"
            : "Count public/images",
        status: phase === "complete" ? undefined : "pending",
        output: phase === "complete" ? "1240" : undefined,
      },
    ],
    report:
      phase === "complete"
        ? "The cache is keyed on the commit SHA, so each deployment optimizes all 1,240 images again."
        : undefined,
  };
  return (
    <div style={frame}>
      <Message align="end">
        <MessageContent>
          <Bubble align="end" variant="muted">
            <BubbleContent>
              Why does image optimization rerun on every deploy?
            </BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent style={{ gap: 16, minWidth: 0 }}>
          <Trace>
            <AgentRow step={step} open={open} onOpen={onOpen} />
          </Trace>
          {phase === "complete" && (
            <p>
              Key the cache on a hash of public/images, so unchanged images will
              be skipped.
            </p>
          )}
        </MessageContent>
      </Message>
      <div>
        <Button
          size="sm"
          variant="outline"
          onClick={replay}
          disabled={phase === "running"}
        >
          Replay
        </Button>
      </div>
    </div>
  );
}

AI SDK

The research tool returns a nested UIMessage. Its output.parts render recursively, including nested tool calls and text. An output-available part with preliminary: true still runs while the chat streams. It becomes a failure if streaming stops before final output. Final output replaces the preliminary message.

The basic example runs without a provider. For the AI SDK adapter example only, install the SDK types:

pnpm add ai

Pass a message from your chat client to the adapter. The supplied message does not call a provider.

import type { UIMessage } from "ai";
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceCollapsibleContent,
  TraceLine,
  TraceIcon,
  TraceTitle,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block";
import { Badge } from "@/components/ui/badge";
import * as React from "react";
const frame: React.CSSProperties = {
  width: "100%",
  maxWidth: 384,
  minWidth: 0,
  minHeight: 288,
  marginInline: "auto",
  display: "flex",
  flexDirection: "column",
  gap: 16,
  paddingBlock: 24,
  fontSize: "var(--text-sm)",
  lineHeight: 1.5,
};
type SdkAgentMessage = UIMessage<
  unknown,
  {},
  {
    runCommand: {
      input: {
        command: string;
      };
      output: {
        stdout: string;
        exitCode: number;
      };
    };
    research: {
      input: {
        task: string;
        model?: string;
      };
      output: SdkAgentMessage;
    };
  }
>;
type SdkCommandMessage = UIMessage<
  unknown,
  {},
  {
    runCommand: {
      input: {
        command: string;
      };
      output: {
        stdout: string;
        exitCode: number;
      };
    };
  }
>;
type SdkCommandPart = Extract<
  SdkCommandMessage["parts"][number],
  {
    type: "tool-runCommand";
  }
>;
function sdkCommandState(part: SdkCommandPart, streaming: boolean) {
  const command = part.input?.command ?? "command";
  const denied =
    part.state === "output-denied" ||
    (part.state === "approval-responded" && !part.approval.approved);
  const finished = part.state === "output-available" && !part.preliminary;
  const waiting = part.state === "approval-requested";
  const approved =
    part.state === "approval-responded" && part.approval.approved;
  const failed =
    part.state === "output-error" ||
    (finished && part.output.exitCode !== 0) ||
    (!denied && !finished && !waiting && !approved && !streaming);
  const status = denied
    ? undefined
    : waiting
      ? "pending"
      : failed
        ? "error"
        : finished
          ? undefined
          : "running";
  const title = `${denied ? "Denied" : waiting ? "Waiting to run" : failed ? "Failed to run" : finished ? "Ran" : "Running"} ${command}`;
  const output =
    part.state === "output-error"
      ? part.errorText
      : part.state === "output-available"
        ? part.output.stdout
        : undefined;
  return { status, title, output } as const;
}
function TracePanel({
  trigger,
  children,
  open: controlledOpen,
  defaultOpen = false,
  onOpenChange,
  ...props
}: React.ComponentProps<typeof TraceCollapsible> & {
  trigger: React.ReactNode;
}) {
  const contentRef = React.useRef<HTMLDivElement>(null);
  const [height, setHeight] = React.useState<number>();
  const [localOpen, setLocalOpen] = React.useState(defaultOpen);
  const expanded = controlledOpen ?? localOpen;
  const [hasOpened, setHasOpened] = React.useState(expanded);
  React.useEffect(() => {
    if (expanded) setHasOpened(true);
  }, [expanded]);
  React.useLayoutEffect(() => {
    const content = contentRef.current;
    const inner = content?.firstElementChild;
    if (!expanded || !content || !inner) return;
    const measure = () => setHeight(content.scrollHeight);
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(inner);
    return () => observer.disconnect();
  }, [expanded]);
  return (
    <TraceCollapsible
      {...props}
      open={expanded}
      onOpenChange={(value) => {
        if (!value && contentRef.current) {
          setHeight(contentRef.current.getBoundingClientRect().height);
        }
        setLocalOpen(value);
        onOpenChange?.(value);
      }}
    >
      <TraceCollapsibleTrigger>{trigger}</TraceCollapsibleTrigger>
      <TraceCollapsibleContent
        ref={contentRef}
        style={
          height === undefined
            ? undefined
            : ({
                "--radix-collapsible-content-height": `${height}px`,
              } as React.CSSProperties)
        }
        forceMount={hasOpened ? true : undefined}
        inert={!expanded}
      >
        {children}
      </TraceCollapsibleContent>
    </TraceCollapsible>
  );
}
function SdkPanel({
  title,
  status,
  children,
  initialOpen = false,
}: {
  title: string;
  status?: "running" | "pending" | "error";
  children: React.ReactNode;
  initialOpen?: boolean;
}) {
  const [open, setOpen] = React.useState<boolean>();
  const expanded = open ?? initialOpen;
  return (
    <TracePanel
      open={expanded}
      onOpenChange={setOpen}
      trigger={
        <TraceLine status={status}>
          <TraceIcon>
            <Icon name={status === "error" ? "CircleAlert" : "Terminal"} />
          </TraceIcon>
          <TraceTitle>{title}</TraceTitle>
        </TraceLine>
      }
    >
      {children}
    </TracePanel>
  );
}
function SdkCommandRow({
  part,
  streaming,
}: {
  part: SdkCommandPart;
  streaming: boolean;
}) {
  const view = sdkCommandState(part, streaming);
  const output = view.output;
  if (output !== undefined)
    return (
      <SdkPanel
        title={view.title}
        status={view.status}
        initialOpen={view.status === "error" || view.status === "running"}
      >
        <CodeBlock
          size="sm"
          variant={view.status === "error" ? "destructive" : "default"}
        >
          <CodeBlockContent wrap style={{ maxHeight: 256 }}>
            {output}
          </CodeBlockContent>
        </CodeBlock>
      </SdkPanel>
    );
  return (
    <TraceLine status={view.status}>
      <TraceIcon>
        <Icon name="Terminal" />
      </TraceIcon>
      <TraceTitle>{view.title}</TraceTitle>
    </TraceLine>
  );
}
function SubagentSdkAdapter({
  message,
  streaming,
}: {
  message: SdkAgentMessage;
  streaming: boolean;
}) {
  return (
    <Trace>
      {message.parts.map((part, index) => {
        if (part.type === "text") return <p key={index}>{part.text}</p>;
        if (part.type === "tool-runCommand")
          return (
            <SdkCommandRow
              key={part.toolCallId}
              part={part}
              streaming={streaming}
            />
          );
        if (part.type !== "tool-research") return null;
        const finished = part.state === "output-available" && !part.preliminary;
        const pending = part.state === "approval-requested";
        const denied =
          part.state === "output-denied" ||
          (part.state === "approval-responded" && !part.approval.approved);
        const failed =
          part.state === "output-error" ||
          (!finished && !pending && !denied && !streaming);
        const title = `${denied ? "Denied" : pending ? "Waiting for" : failed ? "Failed to run" : finished ? "Ran" : "Running"} research subagent`;
        return (
          <SdkPanel
            key={part.toolCallId}
            title={title}
            status={
              pending
                ? "pending"
                : failed
                  ? "error"
                  : !finished && !denied
                    ? "running"
                    : undefined
            }
            initialOpen
          >
            <div style={{ display: "grid", gap: 12 }}>
              {part.input?.model && (
                <div>
                  <Badge variant="secondary">{part.input.model}</Badge>
                </div>
              )}
              <p>{part.input?.task}</p>
              {part.state === "output-error" && (
                <CodeBlock size="sm" variant="destructive">
                  <CodeBlockContent wrap>{part.errorText}</CodeBlockContent>
                </CodeBlock>
              )}
              {part.state === "output-available" && (
                <SubagentSdkAdapter
                  message={part.output}
                  streaming={streaming && part.preliminary === true}
                />
              )}
            </div>
          </SdkPanel>
        );
      })}
    </Trace>
  );
}
const sdkMessage: SdkAgentMessage = {
  id: "research",
  role: "assistant",
  parts: [
    {
      type: "tool-research",
      toolCallId: "research-agent",
      state: "output-available",
      preliminary: true,
      input: {
        task: "Find why image optimization runs on every deploy.",
        model: "Research model",
      },
      output: {
        id: "child",
        role: "assistant",
        parts: [
          {
            type: "tool-runCommand",
            toolCallId: "find",
            state: "output-available",
            input: { command: "rg cacheKey scripts" },
            output: {
              stdout: "scripts/optimize-images.ts:14: process.env.COMMIT_SHA",
              exitCode: 0,
            },
          },
          {
            type: "tool-runCommand",
            toolCallId: "read",
            state: "input-available",
            input: { command: "cat scripts/optimize-images.ts" },
          },
        ],
      },
    },
  ],
};
export default function AiSubagentExample() {
  return (
    <div style={frame}>
      <SubagentSdkAdapter message={sdkMessage} streaming />
    </div>
  );
}

Running

import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceCollapsibleContent,
  TraceLine,
  TraceIcon,
  TraceTitle,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
import * as React from "react";
const frame: React.CSSProperties = {
  width: "100%",
  maxWidth: 384,
  minWidth: 0,
  minHeight: 288,
  marginInline: "auto",
  display: "flex",
  flexDirection: "column",
  gap: 16,
  paddingBlock: 24,
  fontSize: "var(--text-sm)",
  lineHeight: 1.5,
};
function TracePanel({
  trigger,
  children,
  open: controlledOpen,
  defaultOpen = false,
  onOpenChange,
  ...props
}: React.ComponentProps<typeof TraceCollapsible> & {
  trigger: React.ReactNode;
}) {
  const contentRef = React.useRef<HTMLDivElement>(null);
  const [height, setHeight] = React.useState<number>();
  const [localOpen, setLocalOpen] = React.useState(defaultOpen);
  const expanded = controlledOpen ?? localOpen;
  const [hasOpened, setHasOpened] = React.useState(expanded);
  React.useEffect(() => {
    if (expanded) setHasOpened(true);
  }, [expanded]);
  React.useLayoutEffect(() => {
    const content = contentRef.current;
    const inner = content?.firstElementChild;
    if (!expanded || !content || !inner) return;
    const measure = () => setHeight(content.scrollHeight);
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(inner);
    return () => observer.disconnect();
  }, [expanded]);
  return (
    <TraceCollapsible
      {...props}
      open={expanded}
      onOpenChange={(value) => {
        if (!value && contentRef.current) {
          setHeight(contentRef.current.getBoundingClientRect().height);
        }
        setLocalOpen(value);
        onOpenChange?.(value);
      }}
    >
      <TraceCollapsibleTrigger>{trigger}</TraceCollapsibleTrigger>
      <TraceCollapsibleContent
        ref={contentRef}
        style={
          height === undefined
            ? undefined
            : ({
                "--radix-collapsible-content-height": `${height}px`,
              } as React.CSSProperties)
        }
        forceMount={hasOpened ? true : undefined}
        inert={!expanded}
      >
        {children}
      </TraceCollapsibleContent>
    </TraceCollapsible>
  );
}
function SubagentRunningExample() {
  return (
    <Trace>
      <TracePanel
        defaultOpen
        trigger={
          <TraceLine status="running">
            <TraceIcon>
              <Icon name="LoaderCircle" />
            </TraceIcon>
            <TraceTitle>Running the research agent</TraceTitle>
          </TraceLine>
        }
      >
        <Trace>
          <TraceLine>
            <TraceIcon>
              <Icon name="CircleCheck" />
            </TraceIcon>
            <TraceTitle>Ran grep -rn cacheKey scripts</TraceTitle>
          </TraceLine>
          <TraceLine status="running">
            <TraceIcon>
              <Icon name="LoaderCircle" />
            </TraceIcon>
            <TraceTitle>
              Running sed -n 13,15p scripts/optimize-images.ts
            </TraceTitle>
          </TraceLine>
        </Trace>
      </TracePanel>
    </Trace>
  );
}
export default function AiSubagentExample() {
  return (
    <div style={frame}>
      <SubagentRunningExample />
    </div>
  );
}

Details

import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceCollapsibleContent,
  TraceLine,
  TraceIcon,
  TraceTitle,
  TraceDescription,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
import { Badge } from "@/components/ui/badge";
import * as React from "react";
const frame: React.CSSProperties = {
  width: "100%",
  maxWidth: 384,
  minWidth: 0,
  minHeight: 288,
  marginInline: "auto",
  display: "flex",
  flexDirection: "column",
  gap: 16,
  paddingBlock: 24,
  fontSize: "var(--text-sm)",
  lineHeight: 1.5,
};
function TracePanel({
  trigger,
  children,
  open: controlledOpen,
  defaultOpen = false,
  onOpenChange,
  ...props
}: React.ComponentProps<typeof TraceCollapsible> & {
  trigger: React.ReactNode;
}) {
  const contentRef = React.useRef<HTMLDivElement>(null);
  const [height, setHeight] = React.useState<number>();
  const [localOpen, setLocalOpen] = React.useState(defaultOpen);
  const expanded = controlledOpen ?? localOpen;
  const [hasOpened, setHasOpened] = React.useState(expanded);
  React.useEffect(() => {
    if (expanded) setHasOpened(true);
  }, [expanded]);
  React.useLayoutEffect(() => {
    const content = contentRef.current;
    const inner = content?.firstElementChild;
    if (!expanded || !content || !inner) return;
    const measure = () => setHeight(content.scrollHeight);
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(inner);
    return () => observer.disconnect();
  }, [expanded]);
  return (
    <TraceCollapsible
      {...props}
      open={expanded}
      onOpenChange={(value) => {
        if (!value && contentRef.current) {
          setHeight(contentRef.current.getBoundingClientRect().height);
        }
        setLocalOpen(value);
        onOpenChange?.(value);
      }}
    >
      <TraceCollapsibleTrigger>{trigger}</TraceCollapsibleTrigger>
      <TraceCollapsibleContent
        ref={contentRef}
        style={
          height === undefined
            ? undefined
            : ({
                "--radix-collapsible-content-height": `${height}px`,
              } as React.CSSProperties)
        }
        forceMount={hasOpened ? true : undefined}
        inert={!expanded}
      >
        {children}
      </TraceCollapsibleContent>
    </TraceCollapsible>
  );
}
function SubagentDetailsExample() {
  return (
    <Trace>
      <TracePanel
        defaultOpen
        trigger={
          <TraceLine>
            <TraceIcon>
              <Icon name="CircleCheck" />
            </TraceIcon>
            <TraceTitle>Ran the research agent</TraceTitle>
            <TraceDescription>48s</TraceDescription>
          </TraceLine>
        }
      >
        <div
          style={{
            display: "flex",
            flexWrap: "wrap",
            gap: 6,
          }}
        >
          <Badge variant="secondary">anthropic/claude-sonnet-4.5</Badge>
          <Badge variant="outline">runCommand</Badge>
        </div>
        <p>Find out why image optimization reruns on every deploy.</p>
        <Trace>
          <TraceLine>
            <TraceIcon>
              <Icon name="CircleCheck" />
            </TraceIcon>
            <TraceTitle>Ran grep -rn cacheKey scripts</TraceTitle>
          </TraceLine>
          <TraceLine>
            <TraceIcon>
              <Icon name="CircleCheck" />
            </TraceIcon>
            <TraceTitle>
              Ran sed -n 13,15p scripts/optimize-images.ts
            </TraceTitle>
          </TraceLine>
        </Trace>
        <p>The cache is keyed on the commit SHA.</p>
      </TracePanel>
    </Trace>
  );
}
export default function AiSubagentExample() {
  return (
    <div style={frame}>
      <SubagentDetailsExample />
    </div>
  );
}

Components and API

This composition has no separate component API. Use the APIs for its installed components.

ComponentTarget reference
trace Radix UI API
code-block Radix UI API
badge Radix UI API
icon Radix UI API
button Radix UI API
message Radix UI API
bubble Radix UI API