子 Agent 与委派 (Sub-agents)
一个 Agent 可以把手上的活交给专精的下属去做——查代码的交给「代码高手」,写文案的交给「写作能人」——这个动作叫委派 (Delegation)。 SDK 会把「这一轮交给了谁」原样交到你手里,你可以选择在对话里标出来,也可以完全不显示。
前提:Agent 的所有者需先在控制台里为它配置子 Agent。配置好之后,主 Agent 会自行判断什么时候该委派、交给谁——你不需要在代码里指定。
委派是怎么发生的
- 你像平常一样用 SDK 发消息(见 对话 (Chat))。
- 主 Agent 判断该委派时,调用委派工具并指明交给哪个子 Agent。
- 子 Agent 独立执行。这段时间父对话是安静的——子 Agent 的中间过程不会流回来。
- 子 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 |
id | string? | Agent 的唯一标识 |
name | string? | 展示名称 |
avatar | string? | 头像地址 |
message.delegations 里每一条:
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 子 Agent 名称 |
description | string? | 这次交办任务的一句话摘要 |
prompt | string? | 交给子 Agent 的完整指令 |
toolCallId | string? | 用于与返回结果关联 |
结果不对时,先看
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 给个兜底。
下一步
- 对话 (Chat) — 发消息、流式接收与多轮对话
- 信息收集卡片 (Info Collection) — 对话内收集联系人信息
- API 参考 — 客户端配置、类型与错误处理