跳到主要内容

子 Agent 与委派 (Sub-agents)

一个 Agent 可以把手上的活交给专精的下属去做——查代码的交给「代码高手」,写文案的交给「写作能人」——这个动作叫委派 (Delegation)。 SDK 会把「这一轮交给了谁」原样交到你手里,你可以选择在对话里标出来,也可以完全不显示。

前提:Agent 的所有者需先在控制台里为它配置子 Agent。配置好之后,主 Agent 会自行判断什么时候该委派、交给谁——你不需要在代码里指定。

委派是怎么发生的

  1. 你像平常一样用 SDK 发消息(见 对话 (Chat))。
  2. 主 Agent 判断该委派时,调用委派工具并指明交给哪个子 Agent。
  3. 子 Agent 独立执行。这段时间父对话是安静的——子 Agent 的中间过程不会流回来。
  4. 子 Agent 交回结果,主 Agent 把它转述给用户。

⚠️ 委派期间没有输出,是正常的。 实测一次写整页 HTML 的委派安静了约 140 秒。如果你自己控制了超时时间,注意放宽——按普通回复的节奏设置会误判成卡死。React 集成的 idleTimeoutMs 也可以调大。

两个不同的问题:谁说的,交给了谁

SDK 在每条 Agent 消息上给你两样东西,它们回答的是不同的问题:

字段回答的问题何时可用
message.agent这条消息是谁说的始终
message.delegations这一轮交给了谁该轮发生委派时

两者刻意分开。发生委派的那一轮,说话的仍然是主 Agent——它调用子 Agent、等待、再把结果转述出来。所以 agent 记录的是主 Agent,而 delegations 记录的是实际干活的子 Agent。

把两者合并会得出错误的结论:一条主 Agent 说的话,被标成由一个根本没开口的子 Agent 说出。

message.agent 的结构:

字段类型说明
source'main' | 'sub'主 Agent 还是子 Agent
idstring?Agent 的唯一标识
namestring?展示名称
avatarstring?头像地址

message.delegations 里每一条:

字段类型说明
namestring子 Agent 名称
descriptionstring?这次交办任务的一句话摘要
promptstring?交给子 Agent 的完整指令
toolCallIdstring?用于与返回结果关联

结果不对时,先看 prompt——那是主 Agent 实际交代下去的原话,多数偏差都出在这里而不是子 Agent 本身。

在原生 JS / TS 里处理

用事件工具从流里取出委派:

import { OpenhexClient, extractDelegations } from '@openhex-ai/agent-sdk';

const client = new OpenhexClient({ apiKey, agentId });

for await (const record of client.runTurn('让代码高手写个快排')) {
for (const d of extractDelegations(record)) {
console.log('交给了', d.name, '——', d.description);
}
}

委派工具改过名字(早期叫 Task,现在叫 Agent),extractDelegations 两个都认,翻旧对话也能取到。你不需要关心工具叫什么。

想把委派从普通工具调用里摘出去单独展示,用 isDelegationTool(name) 判断。

在 React 里处理

标签默认不显示。两个 prop 任选其一即可开启:

import { ChatBox } from '@openhex-ai/agent-sdk/react';

export function AssistantPanel({ token }: { token: string }) {
return <ChatBox agentId="ce9382ad-…" token={token} showSubAgent />;
}
Prop效果
showSubAgent使用内置标签
subAgentRenderer自己渲染标签,隐含开启 showSubAgent
都不传不显示标签(默认)

内置标签按以下优先级呈现:

子 Agent · 代码高手 平台已将该消息归属给子 Agent
大顺 → 代码高手 该轮发生了委派
大顺 普通回复,只显示名称
(不显示) 既无名称、也无委派、也无子 Agent 归属

主 Agent 本身不带徽标,这是刻意的:在一个会委派的会话里主 Agent 是基线,每条都打徽标反而让委派本身不显眼了。

自己渲染标签

subAgentRenderer 收到 { agent, isSubAgent, delegations, message },返回任意节点;返回 null 表示这条不显示:

<ChatBox
agentId="ce9382ad-…"
token={token}
subAgentRenderer={({ agent, delegations }) =>
delegations?.length ? (
<span className="handoff">
{agent.name} 委派给 {delegations.map(d => d.name).join('、')}
</span>
) : null
}
/>

两个 prop 可以自由组合,呈现方式完全由你决定——SDK 没有预置几种固定样式让你挑。

自己画转录

useOpenhexChat 时,两个字段同样可用,实时回复与加载的历史都有:

const { messages } = useOpenhexChat({ agentId: 'ce9382ad-…', token });

messages.map(m => (
<Row
key={m.id}
speaker={m.agent?.name}
isSubAgent={m.agent?.source === 'sub'}
handedTo={m.delegations?.map(d => d.name)}
message={m}
/>
));

想让自绘的标签和内置样式一致,复用这三个类名即可(内置样式表已包含它们,记得调 useInjectStyles(true)):

<span className={`ohx-agent-tag${isSubAgent ? ' sub' : ''}`}>
{isSubAgent && <span className="ohx-agent-badge">子 Agent</span>}
{agent.name && <span className="ohx-agent-name">{agent.name}</span>}
{handedTo && <span className="ohx-agent-handoff">{handedTo}</span>}
</span>

当前行为说明

有三点会影响这些字段的实际取值。都不需要你做适配,后续能力增强时也不必改代码。

委派产出目前不单独归属。 由于委派轮次由主 Agent 转述,实际记录里 agent.source 始终是 'main''sub' 保留给后续直接归属委派产出的场景。当前请读 message.delegations 来呈现委派关系。 待归属能力上线后,内置标签会自动切成「子 Agent」徽标,你这边不用改。

委派信息随历史下发,不在实时流里。 实时流承载的是消息文本;记录委派的那部分随会话历史获取。所以 message.delegations(以及 message.toolCalls)在加载历史时填充——挂载时或下次恢复会话时——而不是在回复流式输出的过程中。发生委派的一轮会立即显示文本,委派标签则在下次读取历史后出现。

agent.name 不保证存在。 它来自记录上的发送者名称,并非每个 Agent 的回复都携带。内置标签在既无名称、也无委派、也无子 Agent 归属时不显示任何内容,而不是留一个空标签。如果开了 showSubAgent 却看不到东西,打印 message.agent 看看记录里到底有什么,再用 subAgentRenderer 给个兜底。

下一步