Subagents

エージェントが他のエージェントに任せた作業を、実行ごとに1行で表示します。各実行は今していることを示し、終わると短い要約に折りたたまれます。並列の実行はライブの進行状況を持つ共通のヘッダーにまとまり、どの実行も停止、再試行でき、ツール呼び出しとレポートまで開けます。

Claude Code、Codex、Cursor のようなコーディングエージェントは、仕事の一部を別のエージェントに任せます。複数を同時に走らせることもよくあります。Subagents はそれぞれの実行に、人が仕事を任せるように読める 1 行を与えます。エージェント、平易な言葉のタスク、今していることのライブラベル(「Reading auth/session.ts」)、タイマー、使ったツールの数です。

実行は折りたたまれた状態で始まるため、作業の多い返信も短く保てます。各行はその実行がいま何をしているかを表示し続け、開くと最新のステップを追う小さなウィンドウにツール呼び出しがストリーミングされます。実行が終わると、行は「Mapped the token refresh flow · 14 tool uses · 32s」のような過去形の要約に変わり、その下にレポートの最初の一文、中にレポート全文が入ります。

並列実行は、進捗バー付きで「Running 3 agents」、その後「3 agents finished in 48s」と表示する共通のヘッダーを持ちます。実行の状態は、待機中、実行中、完了、理由と Retry 付きの失敗、停止、またはバックグラウンドで継続のいずれかです。getSubagentFromPart は出力がサブエージェント自身のメッセージをストリーミングする AI SDK ツールを読み取り、ネストされたステップは Tool Calls で、レポートは Markdown で表示されます。

  1. HextaUI Pro を入手する

    一度の支払いで、今後追加されるものも含めてすべてのブロックが使えます。料金を見る。

  2. Proレジストリをcomponents.jsonに追加する

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  3. トークンを追加する

    アカウントページでトークンを作成し、.env.local に HEXTAUI_PRO_TOKEN として設定してください。

  4. ブロックを追加する

    pnpm dlx shadcn@latest add @hextaui-pro/subagents

AI SDK と使う

サブエージェントを、execute がサブエージェントの UI メッセージを yield する非同期ジェネレーターのツールにすると、各メッセージが暫定出力として届きます。getSubagentFromPart はそのパートを実行に、ネストされたツールパートを Tool Calls のステップに、最後のツール呼び出し以降のテキストをレポートに変換します。ツールはサーバーに、描画はクライアントコンポーネントに置いてください。

import type * as React from "react"

import { execFile } from "node:child_process"
import { readFile } from "node:fs/promises"
import { promisify } from "node:util"
import { useChat } from "@ai-sdk/react"
import {
  isToolUIPart,
  readUIMessageStream,
  stepCountIs,
  tool,
  ToolLoopAgent,
  type UIMessage,
} from "ai"
import { z } from "zod"

import { CodeBlock } from "../code-block/code-block"
import type { SubagentProps } from "@/components/blocks/subagents/subagent"
import { getSubagentFromPart } from "@/components/blocks/subagents/subagent-part"
import { Subagents } from "@/components/blocks/subagents/subagents"

const run = promisify(execFile)

const explorer = new ToolLoopAgent({
  model: "anthropic/claude-haiku-4.5",
  instructions:
    "You explore a codebase to answer one question. Read only what you need, then finish with a short report that names the files.",
  stopWhen: stepCountIs(40),
  tools: {
    grep: tool({
      inputSchema: z.object({ pattern: z.string() }),
      execute: async ({ pattern }) =>
        (await run("rg", ["-n", "--max-count", "20", pattern])).stdout,
    }),
    readFile: tool({
      inputSchema: z.object({ path: z.string() }),
      execute: async ({ path }) => ({ content: await readFile(path, "utf8") }),
    }),
  },
})

export const explore = tool({
  description:
    "Hand a self-contained question about the codebase to an explorer agent. Call it several times in one step to explore in parallel.",
  inputSchema: z.object({
    description: z.string().describe("The task in 3 to 6 words"),
    prompt: z.string().describe("Everything the agent needs to know"),
  }),
  execute: async function* ({ prompt }, { abortSignal }) {
    const result = await explorer.stream({ prompt, abortSignal })
    yield* readUIMessageStream({
      stream: result.toUIMessageStream({
        messageMetadata: () => ({ model: "Haiku 4.5" }),
      }),
    })
  },
  toModelOutput: ({ output }) => {
    const report = output?.parts.findLast((part) => part.type === "text")
    return { type: "text", value: report?.text ?? "The agent found nothing." }
  },
})

function AssistantMessage({
  message,
  streaming,
  onStopAll,
}: {
  message: UIMessage
  streaming: boolean
  onStopAll: () => void
}) {
  const blocks: (React.ReactNode | SubagentProps[])[] = []

  message.parts.forEach((part, index) => {
    if (isToolUIPart(part) && part.type === "tool-explore") {
      const run: SubagentProps = getSubagentFromPart(part, {
        stopped: !streaming,
        describeTool: (child) =>
          child.state === "output-available" && child.type === "tool-readFile"
            ? {
                content: (
                  <CodeBlock
                    code={String((child.output as { content: string }).content)}
                    filename={String((child.input as { path: string }).path)}
                    lineNumbers
                    collapseAfter={10}
                  />
                ),
              }
            : undefined,
      })
      const last = blocks.at(-1)
      if (Array.isArray(last)) last.push(run)
      else blocks.push([run])
    } else if (part.type === "text") {
      blocks.push(<p key={index}>{part.text}</p>)
    }
  })

  return (
    <div className="flex flex-col gap-4">
      {blocks.map((block, index) =>
        Array.isArray(block) ? (
          <Subagents key={block[0].id} runs={block} onStopAll={onStopAll} />
        ) : (
          <div key={index}>{block}</div>
        )
      )}
    </div>
  )
}

export function Chat() {
  const { messages, status, stop } = useChat()
  const busy = status === "submitted" || status === "streaming"

  return messages.map((message, index) =>
    message.role === "assistant" ? (
      <AssistantMessage
        key={message.id}
        message={message}
        streaming={busy && index === messages.length - 1}
        onStopAll={stop}
      />
    ) : null
  )
}

並列実行

Subagents に複数の実行を渡すと、ライブ進捗と Stop all を備えた 1 つのヘッダーになります。onStop と onRetry をエージェントを動かす仕組みにつなぐと、行のボタンは実行の状態に合わせて切り替わります。

"use client"

import * as React from "react"

import type { SubagentProps } from "@/components/blocks/subagents/subagent"
import { Subagents } from "@/components/blocks/subagents/subagents"

const initialRuns: SubagentProps[] = [
  {
    id: "sessions",
    agent: "Explore",
    task: "Trace how sessions are created",
    status: "done",
    duration: 14,
    toolUses: 5,
    preview: "Sessions live in the sid cookie for 7 days.",
  },
  {
    id: "refresh",
    agent: "Explore",
    task: "Map the token refresh flow",
    status: "running",
    calls: [
      { id: "a", kind: "read", status: "done", subject: "middleware.ts" },
      { id: "b", kind: "read", status: "running", subject: "lib/auth/refresh.ts" },
    ],
  },
  {
    id: "changes",
    agent: "General",
    task: "Check recent auth changes",
    status: "failed",
    error: "The model was overloaded and stopped responding.",
  },
]

export function Delegation() {
  const [runs, setRuns] = React.useState(initialRuns)
  const update = (id: string, status: SubagentProps["status"]) =>
    setRuns((current) =>
      current.map((run) =>
        run.id === id ? { ...run, status, error: undefined } : run
      )
    )

  return (
    <Subagents
      runs={runs.map((run) => ({
        ...run,
        onStop: () => update(run.id, "stopped"),
        onRetry: () => update(run.id, "running"),
      }))}
    />
  )
}

保存済みデータから

履歴から完了した実行をシンプルなプロップで表示します。保存した所要時間がライブタイマーの代わりになり、レポートはワンクリックで開けます。

import { Subagent } from "@/components/blocks/subagents/subagent"

const report = `Sessions are created by \`createSession()\` in \`lib/auth/session.ts\`.

- The \`sid\` cookie lasts **7 days**.
- The token inside it expires after **15 minutes**, so refresh keeps people signed in.`

export function SessionsRun() {
  return (
    <Subagent
      id="sessions"
      agent="Explore"
      task="Trace how sessions are created"
      model="Haiku 4.5"
      status="done"
      duration={14}
      calls={[
        { id: "1", kind: "search", status: "done", subject: "createSession", meta: "4 results" },
        { id: "2", kind: "read", status: "done", subject: "lib/auth/session.ts" },
        { id: "3", kind: "read", status: "done", subject: "lib/auth/cookies.ts" },
      ]}
      result={report}
    />
  )
}

構造

外側から内側へ組み合わせるパーツ。

パーツ説明
Subagents概要ヘッダー、進捗、Stop all、読み上げを備えた実行のグループ。
Subagent1 つの実行:状態マーク、エージェント、タスク、ライブラベル、ツール数、タイマー、Stop または Retry ボタン。
SubagentMark状態マーク単体:実行中はオーブ、その後はチェック、警告、停止アイコン。
getSubagentFromPartストリーミングされたサブエージェントのメッセージを含む AI SDK のツールパートを実行に変換します。

Subagents

children 以外のすべての section プロップも受け取ります。実行が 1 つだけのときはヘッダーなしで表示されます。

プロパティ型デフォルト
runs開始した順の実行。終わっても位置は変わりません。
SubagentProps[]–
labelリージョンと進捗バーの名前になります。
string"Subagents"
duration履歴用に保存した合計秒数。既定では実行の作業中に計測した時間。
number–
defaultOpenヘッダーの下に実行を並べた状態で始めます。
booleantrue
onStopAllStop all から呼ばれます。指定しない場合、Stop all は実行中の各 run の onStop を呼びます。
() => void–

Subagent

children 以外のすべての div プロップも受け取ります。

プロパティ型デフォルト
id安定した ID。通常は toolCallId。
string–
taskエージェントに頼んだことを短い言葉で:「Map the token refresh flow」。
string–
statusqueued、running、done、failed、stopped のいずれか。
SubagentStatus–
agentエージェントの種類または名前(Explore や Plan など)。
string–
title実行が作業している間、行のタスクを置き換えます。
string–
doneTitle完了後の行。最初の単語が既知の動詞なら、既定ではタスクの過去形。
string–
prompt開いたときに上部に表示される完全な指示。
string–
callsTool Calls で表示される実行のステップ。
ToolCallProps[]–
activityライブラベルを置き換えます。指定しない場合は最新の実行中ステップから作られます。
string–
resultMarkdown の最終レポート。
string–
resultStreamingレポートはまだ届いている途中です。
boolean–
preview折りたたんだ実行の 1 行。既定ではレポートの最初の一文。
string–
error実行が失敗した理由。行に表示され、開くと全文が表示されます。
string–
toolUses既定では呼び出し数になるカウントを上書きします。
number–
model広い画面ではバッジとして表示されます。
string–
duration履歴用に保存した秒数。
number–
startedAtすでに作業中の実行が始まった時刻(ミリ秒)。マウント時にタイマーがリセットされないようにします。
number–
backgroundチャットが進む間も実行は作業を続けます。Background バッジを表示し、折りたたまれたままです。
boolean–
defaultOpen実行をツール呼び出しとレポートが見える開いた状態で始めます。
booleanfalse
onStop実行が待機中または実行中の間、Stop を表示します。
() => void–
onRetry実行が失敗または停止したときに Retry を表示します。
() => void–

getSubagentFromPart(part, options)

ツールパートから実行を返します。結果をそのまま Subagent に、または Subagents の runs に渡します。

プロパティ型デフォルト
partサブエージェントのツールパート。出力は、推論、ツール呼び出し、テキストのパートを持つ UI メッセージです。
ToolUIPart | DynamicToolUIPart–
options.stoppedチャットが停止したため、未完了の実行は Stopped と表示されます。
booleanfalse
options.describeToolネストされたツールごとに種類、対象、ビューを追加します。readFile、grep、bash のような一般的な名前は自動で推測されます。
(part) => Partial<ToolCallProps>–
options.agentエージェント名。既定では入力の subagent_type、次にツール名。
string–
options.modelモデルのバッジ。既定ではメッセージのメタデータの model。
string–
キーアクション
EnterSpace実行、またはグループヘッダーの下のリストを開閉します。
Tab実行からその Stop または Retry ボタンへ、ヘッダーから Stop all へ移動します。
  • 各実行のヘッダーは aria-expanded を持つボタンで、その名前でエージェント、タスク、ライブラベル、ツール数、時間を読み上げます。
  • グループは、エージェントの作業中に aria-busy を持つ名前付きリージョンです。丁寧なライブリージョンが、実行の再開、完了、失敗、停止と、その後のグループの要約を読み上げます。ツールのステップやタイマーの刻みは読み上げません。
  • Stop と Retry は実行名の付いた本物のボタンです(例:「Stop Explore: Map the token refresh flow」)。ボタンが消えると、フォーカスは実行のヘッダーに戻ります。
  • 状態を色だけで伝えることはありません。各状態に固有のマークと文言があります。視差効果を減らす設定では、オーブは止まり、行はスライドせずに切り替わります。

使用技術

Subagents を構成する無料のHextaUIコンポーネントです。それぞれ単独でインストールできます。

コード

6 個のファイルを components/blocks/subagents に追加しました。