Subagents

Bir ajanın diğer ajanlara devrettiği işi, çalıştırma başına bir satırla gösterin. Her çalıştırma şu anda ne yaptığını söyler ve bitince kısa bir özete katlanır. Paralel çalıştırmalar canlı ilerleme gösteren ortak bir başlığı paylaşır; her biri durdurulabilir, yeniden denenebilir ve araç çağrılarına ve raporuna kadar açılabilir.

Claude Code, Codex ve Cursor gibi kodlama ajanları bir işin parçalarını, çoğu zaman aynı anda birkaçına olmak üzere, başka ajanlara devreder. Subagents bu çalıştırmaların her birine iş devreden birinin cümlesi gibi okunan bir satır verir: ajan, sade sözcüklerle görevi, o anda ne yaptığını gösteren canlı bir etiket (“Reading auth/session.ts”), bir zamanlayıcı ve kaç araç kullandığı.

Çalıştırmalar katlanmış başlar, böylece yoğun bir yanıt kısa kalır. Her satır çalıştırmasının şu an ne yaptığını söylemeye devam eder; açıldığında araç çağrıları en yeni adımı takip eden kısa bir pencereye akar. Bir çalıştırma bittiğinde satırı “Mapped the token refresh flow · 14 tool uses · 32s” gibi geçmiş zamanlı bir özete dönüşür; altında raporunun ilk cümlesi, içinde de raporun tamamı bulunur.

Paralel çalıştırmalar, ilerleme çubuğuyla “Running 3 agents”, ardından “3 agents finished in 48s” yazan ortak bir başlığı paylaşır. Çalıştırmalar sırada, çalışıyor, bitti, nedeni ve Retry ile başarısız, durduruldu ya da arka planda çalışmaya devam ediyor olabilir. getSubagentFromPart, çıktısı alt ajanın kendi mesajlarını akıtan bir AI SDK aracını okur; iç içe adımlar Tool Calls ile, rapor Markdown ile gösterilir.

  1. HextaUI Pro'yu alın

    Tek bir ödeme, gelecekte eklenecekler dahil tüm blokların kilidini açar. Fiyatları görün.

  2. Pro registry'yi components.json dosyasına ekleyin

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  3. Token'ınızı ekleyin

    Hesap sayfanızda bir token oluşturun ve .env.local dosyasına HEXTAUI_PRO_TOKEN olarak ekleyin.

  4. Bloğu ekleyin

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

AI SDK ile

Alt ajanı, execute’u alt ajanın arayüz mesajlarını üreten asenkron bir generator olan bir araca dönüştürün; böylece her biri ön çıktı olarak gelir. getSubagentFromPart bu parçayı bir çalıştırmaya, iç içe araç parçalarını Tool Calls adımlarına ve son araç çağrısından sonraki metni rapora dönüştürür. Aracı sunucuda, görüntülemeyi bir istemci bileşeninde tutun.

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
  )
}

Paralel çalıştırmalar

Canlı ilerleme ve Stop all içeren tek bir başlık için Subagents’a birden çok çalıştırma verin. onStop ve onRetry’ı ajanları çalıştıran şeye bağlayın; satırdaki düğme çalıştırmanın durumu değiştikçe ikisi arasında geçiş yapar.

"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"),
      }))}
    />
  )
}

Kayıtlı verilerden

Geçmişten bitmiş bir çalıştırmayı sade prop’larla gösterin. Kayıtlı bir süre canlı zamanlayıcının yerini alır ve rapor bir tık uzakta kalır.

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}
    />
  )
}

Yapı

Dıştan içe doğru birleştirdiğiniz parçalar.

ParçaAçıklama
SubagentsÖzet başlığı, ilerleme, Stop all ve duyurular içeren bir çalıştırma grubu.
SubagentBir çalıştırma: durum işareti, ajan, görev, canlı etiket, araç sayısı, zamanlayıcı ve Stop veya Retry düğmesi.
SubagentMarkTek başına durum işareti: çalışırken bir küre, ardından onay, uyarı veya durdurma simgesi.
getSubagentFromPartAkışla gelen alt ajan mesajlarını içeren bir AI SDK araç parçasını bir çalıştırmaya dönüştürür.

Subagents

children dışındaki tüm section prop’larını da kabul eder. Tek bir çalıştırma başlıksız gösterilir.

PropTürVarsayılan
runsBaşlatıldıkları sırayla çalıştırmalar. Bittikçe yerlerini korurlar.
SubagentProps[]–
labelBölgeyi ve ilerleme çubuğunu adlandırır.
string"Subagents"
durationKayıtlı toplam saniye, geçmiş için. Varsayılan olarak çalıştırmalar çalışırken ölçülen süre.
number–
defaultOpenÇalıştırmalar başlığın altında listelenmiş olarak başlar.
booleantrue
onStopAllStop all tarafından çağrılır. Verilmezse Stop all, çalışan her çalıştırmanın onStop’unu çağırır.
() => void–

Subagent

children dışındaki tüm div prop’larını da kabul eder.

PropTürVarsayılan
idKararlı kimlik, genellikle toolCallId.
string–
taskAjandan istenen iş, birkaç sözcükle: “Map the token refresh flow”.
string–
statusqueued, running, done, failed veya stopped.
SubagentStatus–
agentAjanın türü veya adı, örneğin Explore ya da Plan.
string–
titleÇalıştırma çalışırken satırdaki görevin yerini alır.
string–
doneTitleBitince gösterilen satır. İlk sözcüğü bilinen bir fiilse varsayılan olarak görevin geçmiş zaman hali.
string–
promptAçıldığında en üstte gösterilen tam talimatlar.
string–
callsÇalıştırmanın Tool Calls ile gösterilen adımları.
ToolCallProps[]–
activityCanlı etiketin yerini alır; etiket aksi halde en yeni etkin adımdan gelir.
string–
resultMarkdown olarak son rapor.
string–
resultStreamingRapor hâlâ geliyor.
boolean–
previewKatlanmış çalıştırma için bir satır. Varsayılan olarak raporun ilk cümlesi.
string–
errorÇalıştırmanın neden başarısız olduğu; satırda ve açıldığında tamamıyla gösterilir.
string–
toolUsesVarsayılan olarak çağrı sayısı olan sayıyı geçersiz kılar.
number–
modelDaha geniş ekranlarda rozet olarak gösterilir.
string–
durationKayıtlı saniye, geçmiş için.
number–
startedAtZaten çalışan bir çalıştırmanın milisaniye cinsinden başlangıç zamanı; böylece zamanlayıcı bileşen yüklenince sıfırlanmaz.
number–
backgroundSohbet devam ederken çalıştırma çalışmayı sürdürür. Background rozeti gösterir ve katlı kalır.
boolean–
defaultOpenÇalıştırmayı araç çağrıları ve raporu görünür şekilde açık başlatır.
booleanfalse
onStopÇalıştırma sıradayken veya çalışırken Stop gösterir.
() => void–
onRetryÇalıştırma başarısız olduğunda veya durdurulduğunda Retry gösterir.
() => void–

getSubagentFromPart(part, options)

Bir araç parçasından bir çalıştırma döndürür. Sonucu doğrudan Subagent’a ya da Subagents’ın runs’ına verin.

PropTürVarsayılan
partAlt ajanın araç parçası. Çıktısı, parçaları akıl yürütme, araç çağrıları ve metin içeren bir arayüz mesajıdır.
ToolUIPart | DynamicToolUIPart–
options.stoppedSohbet durdu, bu yüzden bitmemiş bir çalıştırma Stopped gösterir.
booleanfalse
options.describeToolHer iç içe araç için bir tür, hedef veya görünüm ekler. readFile, grep ve bash gibi yaygın adlar sizin için tahmin edilir.
(part) => Partial<ToolCallProps>–
options.agentAjan adı. Varsayılan olarak girdideki subagent_type, sonra araç adı.
string–
options.modelModel rozeti. Varsayılan olarak mesaj meta verisindeki model.
string–
TuşAction
EnterSpaceBir çalıştırmayı ya da grup başlığının altındaki listeyi açar veya kapatır.
TabBir çalıştırmadan onun Stop veya Retry düğmesine, başlıktan Stop all’a geçer.
  • Her çalıştırmanın başlığı, adı ajanı, görevi, canlı etiketi, araç sayısını ve süreyi okuyan aria-expanded’li bir düğmedir.
  • Grup, ajanlar çalışırken aria-busy taşıyan adlandırılmış bir bölgedir. Kibar bir canlı bölge, bir çalıştırma yeniden başladığında, bittiğinde, başarısız olduğunda veya durduğunda ve ardından grup özetini duyurur. Araç adımları ve zamanlayıcı tıkırtıları sessiz kalır.
  • Stop ve Retry, çalıştırmalarının adını taşıyan gerçek düğmelerdir; örneğin “Stop Explore: Map the token refresh flow”. Bir düğme kaybolduğunda odak çalıştırmanın başlığına döner.
  • Durum asla yalnızca renkle anlatılmaz: her durumun kendi işareti ve ifadesi vardır. Azaltılmış harekette küre sabit durur ve satırlar kaymadan değişir.

Şunlarla geliştirildi

Subagents bloğunun yapıldığı ücretsiz HextaUI bileşenleri. Her biri tek başına yüklenir.

Kod

6 dosya, components/blocks/subagents konumuna eklendi.