书接上文:我用阿里 AgentScope 复刻了一个 WorkBuddy(二)
前面两篇我们聊了关于模型配置、工具管理、权限审批,后来又聊了MCP连接器和Skills机制。到第二篇结束的时候,我们使用agentScope开发的mini-WorkBuddy已经能操作本地文件、调用外部MCP服务、加载技能来工作了。
我们平时用的Agent,大概也就这样了,但是这个对普通用户其实不友好的,开发这个还是用的开发者思维,大多数的用户 根本不关心,你的什么系统提示词,工具,技能这些东西,而且这些东西对用户来说,门槛也挺高的,还需要学习不少的内容。大部分用户想要的是一个能干活的AI工具,给它一个任务,AI能把它做好就行。
那么最近很火的WorkBuddy是怎么来做的呢,我觉得是专家和专家团。用户不需要知道Prompt工程是什么东西,选一个专家,Agent就用那个身份和工作方法来干活。选一个专家团,就把多个专家拉到一起,组件一个团队一起干活。

这篇文章我们就来看看这两块是如何工作的,先搞清楚WorkBuddy怎么设计专家和专家团,再看AgentScope怎么把这两套机制落地。
把专业方法封装成一个专家

WorkBuddy 的专家是什么
我们之前写过一篇关于WorkBuddy专家和专家团的文章,有兴趣的可以去看看那
WorkBuddy专家团提示词全曝光:多Agent协作原来是这样产品化的
我们之前在开发Agent的时候,都会有一个新建Agent的操作,让用户手可以手动创建一个自己的Agent来完成特定的任务,增加灵活性,这种Agent我们一般都是让用户自己配置系统提示词,选择工具和技能,我开始认为的这就是垂直类的Agent,也就是我们常说的专家Agent。
每个专家都是一个经过角色化封装的垂直领域AI Agent。专家得知道自己擅长什么,也得知道哪些事做不了。收到任务后,它应该按一套相对稳定的办法做事,不能每次都靠模型临场发挥。交付物也需要提前规划好,可能是一篇文章、一份代码、一份报告,也可能是一张检查单,甚至它还可能带着自己的 Skill、脚本和参考资料。
我们可以把专家拆成五个核心要素:
人设:专家是谁,有什么专业背景,说话风格什么样 方法论:这个领域的标准工作流,比如写作专家会按“选题→大纲→初稿→修改→定稿”的路子走 工具链:绑定了哪些Skill、MCP连接器和内置工具 工作模板:常见任务的输出格式,比如数据分析师出报告的结构 沟通风格:输出是简洁的要点还是详细的分析,用不用表格
就拿这个 去 AI 味道写作专家 来说,用户不用自己写一大段 Prompt,只要把文章交给它。它就会按照预先固化的流程,从词汇、句式、逻辑、语气和结构五个维度检查,先评估AI 味浓度,再定向改写,最后给出对比和修改说明。
WorkBuddy调用专家非常简单,在专家页面选择一个专家召唤就行,也可以直接在聊天页面点击 '+' 号 选择一个专家来工作。我们选择专家并使用后,在workbuddy的默认目录下,会把这个专家相关的文档给下载下来,这个有点像skills,这里一个文件夹就是一个专家。

打开一个专家的,我们可以看下具体的目录是什么样的

如上图,专家的文件目录,大概成为以下几个部分,
plugin.json 这个是整个专家的一个说明,名称,描述,有哪些技能等 agents 这个目录下面有一份提示词说明,关于这个专家如何干活,工作流,输出格式,沟通说明等都在这个提示词文件中 avatars 专家头像 skills 这个专家拥有的技能
这个目录结构式Workbuddy专家特有的,我们在设计专家的时候,也是可以参考的,Workbuddy创建专家的时候,会默认使用一个内置技能 expert-manager

这个技能有详细的描述,要如何引导用户来创建一个专家,必要时会不停的和用户对话来确认流程和工作机制,最后生成的目录结构和我们上面描述的专家目录保持一致。
AgentScope 如何复刻专家
事实上,我在开发这个专家的时候,因为有了workbuddy这个专家的参考,首先想到的就是 使用文件夹来管理,一个文件夹对应一个专家。这个真的非常方便,比较容易维护和分享。
最后,我把每个专家做成了独立目录包,在项目的工作目录workspace下面新建了一个experts目录用来存放专家文件夹,例如 去 AI 化写作专家的目录如下。
workspace/experts/ai-detox-writer/
├── .workbuddy-plugin/
│ └── plugin.json
├── agents/
│ └── ai-detox-writer.md
├── avatars/
├── skills/ # 可选
│ └── rewrite-helper/
│ └── SKILL.md
└── README.md
这个分工就很明确了,plugin.json是专家入口,agents/放专家的角色定义,工作流的说明,skills/只属于这个专家的技能,avatars/存放专家头像,README.md给人看的说明。
那plugin.json里到底写了什么?简化后的配置长这样:
{
"name": "ai-detox-writer",
"expertType": "agent",
"agentName": "ai-detox-writer",
"displayName": {"zh": "去AI味道写作专家"},
"profession": {"zh": "去AI化写作专家"},
"displayDescription": {"zh": "识别并去除文章AI生成痕迹,让文本回归自然表达"},
"skills": ["./skills/rewrite-helper"],
"runtime": {"accent": "#ec4899"}
}
挨个说一下这些字段是干嘛的。
name是专家包的唯一标识,内部查找和缓存key都会用到它。expertType这里是agent,表示单专家。agentName指向agents/目录下的Markdown文件名,不带.md后缀。displayName、profession、displayDescription这三个纯粹是给用户看的,显示在专家卡片和详情页上。profession是职业名称,displayDescription是能力描述,让用户一眼就知道这专家能干嘛。skills声明这个专家自带的Skill目录。加载专家的时候,这些Skill会和用户选择的Skill一起注册进Toolkit。runtime是我们自己扩展的运行参数,比如accent控制页面上专家卡片的强调色。

不过这些都只是卡片展示用的信息,真正决定专家怎么工作的,是agents/ai-detox-writer.md这个文件:

到这里我们手上只有一堆文件,怎么把它们变成AgentScope能用的Agent?
我们做的事其实很简单,扫描目录->校验字段->拼出system_prompt->调AgentScope的API。
先看怎么发现和加载专家,项目启动时扫描workspace/experts/目录, 项目启动后,ExpertPackageRepository 会扫描 workspace/experts/ 的直接子目录。
for package_dir in experts_dir.iterdir():
manifest = load_json(package_dir / ".codebuddy-plugin/plugin.json")
agent_name = manifest["agentName"]
prompt, max_iters = load_agent_markdown(
package_dir / "agents" / f"{agent_name}.md"
)
skill_dirs = load_skill_dirs(package_dir, manifest)
experts[manifest["name"]] = ExpertSpec(
id=manifest["name"],
handle=agent_name,
prompt=prompt,
max_iters=max_iters,
skill_dirs=skill_dirs,
)
这里的代码是为了说明流程做的简化。真实实现还会解析多语言展示字段、快捷问法、分类、头像和版本指纹。 用户发起专家对话时,WorkBuddyService._expert_chat() 先选中 ExpertSpec,随后仍走通用 Agent 的创建逻辑。
agent = Agent(
name=expert.handle,
system_prompt=expert_system_prompt,
model=create_model_client(model_config),
toolkit=toolkit,
context_config=ContextConfig(...),
react_config=ReActConfig(max_iters=expert.max_iters),
)
我们没有为它新写一个 ExpertAgent 子类,底层还是普通的 AgentScope Agent,只是 system_prompt 换成了专家的工作方法,Toolkit 多加载了专家自带的 Skill,ReAct 轮数改用 maxTurns,缓存和状态也按专家配置隔离开。
这层复用省了很多事,前两篇接好的本地工具、权限审批、MCP、上下文压缩和长期记忆,专家可以直接使用。
这个专家的Skill在plugin.json里声明了路径,加载的时候需要解析出来,和用户选的Skill一起交给Toolkit:
skill_loaders = list(expert.skill_dirs)
if selected_skill_dir:
skill_loaders.append(selected_skill_dir)
toolkit = Toolkit(
tools=[Read(), Glob(), Grep(), Write(), Edit(), Bash(cwd=workdir)],
skills_or_loaders=skill_loaders,
mcps=mcp_clients,
)
这样组装完,专家既有 mini-WorkBuddy 内置的文件和命令工具,也有自己包里的 Skill。用户临时选中的 Skill和 MCP 连接器也会一起挂上去。模型看到的就是一个统一的 Toolkit,用的时候不需要管这项能力到底从哪里来。
专家通常会常态化调整,如果直接修改 agents/*.md,但内存里的 Agent 还拿着旧 Prompt,页面显示和真实行为就会对不上。
为了避开旧缓存,加载器会根据 Prompt、max_iters 和 Skill 目录生成一个 revision,并把它放进 Agent 缓存键。
revision = sha256({
"prompt": prompt,
"max_iters": max_iters,
"skill_dirs": skill_dirs,
})[:12]
agent_key = f"expert:{expert.id}:{revision}:{model_id}"
缓存键还会继续加上连接器和当前手动选中的 Skill。只要 Prompt、轮次、Skill、连接器或模型中的任何一项变了,旧缓存就不会被命中,系统会创建一个新 Agent。

让多个 Agent 围绕一个目标协作

WorkBuddy 的专家团是什么
单个专家解决了找谁做的问题,但有些事情本来就不是一个人能包圆的。开发一个系统,要做架构、写前后端、跑测试,做一份完整调研,也可能同时需要研究、数据分析和风险审查。
专家团就是为这类任务准备的,它是由多个专家组成的团队。workbuddy的设计思路是,团队里有一个主理人负责任务拆解和调度,下面是职责不同的成员,每个人各干各的,最后再把结果聚合输出。
比如一个研发专家团有五个角色。
这个里面的研发交付总监就是主理人,它需要先把需求问清楚,再让架构师出设计,设计定下来之后前端和后端分头开工,两边都做完以后,QA 再进场。主理人一直盯着任务进展,最后由它向用户交付。

专家团的核心--主理人
主理人就是这个团队的项目负责人,主理人只管调度,不管执行。主理人不代写任何成员的专业产出,成员之间不直接通信,所有信息流经过主理人中转。
它主要管协调,任务超出团队能力就给用户说清楚,这个事情干不了,任务信息不够就先进行询问,如果任务能做,就做拆分分给对应的Agent干活。哪些任务可以并行,哪些必须等前一步,也由它判断。中间如果有Agent失败、产物冲突或者漏了东西,主理人负责补任务和返工。任务产出的结果都整理完成后,它再给用户一份完整结果。
不过,主理人也不能有点小事就把全队叫起来,如果用户的问题很简单,主理人可以直接回答,不用启用团队。
专家、专家团、技能和连接器有什么区别
这四个概念放在一起很容易混淆。
专家管谁来做,专家团管这些人怎么合作,Skill 告诉 Agent 具体怎么做,连接器让它能去真实系统里干活。
专家团也是一个自包含目录包
WorkBuddy的专家团实现方式和专家 区别不大,也是采用的一个文件夹一个专家团的配置,在新建专家团的时候 使用的是同一个 技能 expert-manager 来辅助用户创建专家团。
它们主要是通过 plugin.json 中的 expertType 来区分
支持两种专家类型:
Agent 型( expertType: "agent"):单个 AI 专家Team 型( expertType: "team"):多角色协作团队

专家团在 agents目录下 会有多个提示词,一个提示词就是一个专家干活的指令,还有就是avatar目录下有多少个专家就有多少个头像
我们把专家团放在 workspace/teams/ 下。一个研发专家团结构如下。
workspace/teams/rd-expert-team/
├── .workbuddy-plugin/
│ └── plugin.json
├── agents/
│ ├── rd-expert-team-team-lead.md
│ ├── rd-expert-team-architect.md
│ ├── rd-expert-team-backend.md
│ ├── rd-expert-team-frontend.md
│ └── rd-expert-team-qa.md
├── avatars/
├── settings.json
└── README.md
专家团的 plugin.json 与单专家大体相同,关键区别有下面几个。
{
"name": "rd-expert-team",
"expertType": "team",
"agentName": "rd-expert-team-team-lead",
"teamInfo": {
"leadAgent": "rd-expert-team-team-lead",
"memberAgents": [
"rd-expert-team-architect",
"rd-expert-team-backend",
"rd-expert-team-frontend",
"rd-expert-team-qa"
]
},
"members": [
{"id": "rd-expert-team-team-lead", "role": "lead"},
{"id": "rd-expert-team-architect", "role": "member"}
],
"runtime": {
"workflows": ["需求分析与架构设计", "开发实施与质量保障", "汇总交付"]
}
}
expertType 变成了 team,teamInfo 显式声明主理人和成员列表,members 保存每个角色的名字、职业和头像,runtime.workflows 给出团队的高层流程。
settings.json.agent 还必须与 teamInfo.leadAgent 一致。加载时,主理人和每位成员的 Markdown 都会被校验,再组装成一个 TeamSpec。
这里有个设计我考虑了很久,专家团包内保存成员的完整 Agent Markdown,不依赖外部专家引用,只有这样,团队包才能独立安装和分发。换一台机器没有安装某个外部专家,团队仍然是完整的。
AgentScope Agent Service 如何实现专家团
做专家时,一个 Agent 就够了,到专家团这里,只是多创建几个 Agent 肯定不够,团队还得有任务、依赖、会话、消息和共享工作区,AgentScope Agent Service 正好把这几件事都包了。
Agent Service 已经提供了团队运行需要的核心对象:
Agent:主理人和成员 Session:每个 Agent 独立的对话和状态 Team:团队及成员关系 Task:任务、负责人、依赖和状态 Message Bus:主理人和成员之间传递事件 Workspace Manager:为团队分配工作目录
主理人还可以使用一组团队工具:
TeamCreate:创建团队TaskCreate:创建结构化任务TaskUpdate:设置负责人、依赖和任务状态AgentCreate:根据模板创建成员 AgentTeamSay:在团队中发送消息TeamDelete:任务结束后清理团队
完整流程大致如下:

注册成员
主理人调用 AgentCreate 时,需要告诉 AgentScope 要创建哪一类成员。因此,项目会把团队包中的所有成员转换成 SubAgentTemplate。
SubAgentTemplate(
type=member.id,
description=f"{member.name}:{member.summary}",
system_prompt_template=(
"你是 {member_name},隶属于 {team_name},"
"主理人是 {leader_name}。\n"
f"{member.prompt}\n\n"
"只完成职责范围内的专业工作;"
"禁止联系其他成员;"
"所有结论、风险和产物路径必须用 TeamSay 回传。"
),
react_config=ReActConfig(max_iters=member.max_iters),
)
type 是成员类型的唯一标识。主理人在 AgentCreate 里的 name 和 subagent_type 都必须使用这个 Agent ID,不能传中文名字,也不能临时编一个标识。
description 是给主理人看的能力说明。system_prompt_template 则把团队名称、主理人、本轮职责和成员自己的 Prompt 合并起来。不得跨职责,成员之间禁止直连。必须通过 TeamSay 回传也会被统一追加进成员 Prompt。
专家包在进程启动后仍然可能新建。因此每次团队对话开始前,refresh_subagent_templates() 都会重新生成并更新模板注册表,新安装的专家团不需要重启进程就能调度。
主理人的编排
NativeTeamRuntime 会为当前工作区、聊天 Session 和专家团生成稳定的主理人 Agent ID 与原生 Session ID,再把团队包中的主理人 Prompt、可调度成员、能力关键词和高层工作流程组合成最终的 system_prompt。
这个 Prompt 要求主理人遵守下面这套流程。

这里没有用 Python 写死成员和顺序,专家团只给出了SOP和业务边界,具体让谁干活、拆成几个任务,都由主理人根据本轮目标决定。
一项完整研发任务可以这样拆。
任务1 架构设计 owner=architect depends_on=[]
任务2 后端开发 owner=backend depends_on=[任务1]
任务3 前端开发 owner=frontend depends_on=[任务1]
任务4 质量验收 owner=qa depends_on=[任务2, 任务3]
任务1完成后,任务2和任务3之间没有直接依赖,主理人可以连续创建前端和后端成员,让他们分别在独立 Session 中执行。等两者都完成以后,QA 任务的前置条件才满足。
这里的前端和后端是两个不同的 Session 和执行流的 Worker,可以同时推进。它们并没有再同一个 Agent 里轮流切换角色。
成员之间如何通信
当前的成员模板禁止 Worker 直接联系其他成员。所有信息都必须走下面的路径。
成员 A ── TeamSay ──> 主理人 ── 完整结果中转 ──> 成员 B
这么做虽然会多一次中转,但是主理人却能始终知道当前发生了什么,它可以等并行任务都回传后做冲突检查,也可以在进入下一阶段前补齐上下文。两个成员私下改了方案,最终交付人却毫不知情,这类问题也就不存在了。
主理人是唯一信息枢纽,也是唯一对用户负责的 Agent。
如何显示子Agent的工作记录
多个 Agent 在后台工作,用户端却什么都看不见,不知道当前Agent正在做什么,这个问题很容易被漏掉。
主理人和每个 Worker 都有自己的工作 Session,如果前端只订阅主理人的事件流,当成员连续工作几十秒时,页面上看起来就像卡住了。
为了让页面始终有反馈,项目加了一个 WorkerEventProjector,它会检查 Worker Session 的 team_id,找到团队主理人的 Session,再把 Worker 的原生 AgentScope 事件包装成自定义事件,发到主理人的事件通道。
if session_record.team_id:
team = await storage.get_team(user_id, session_record.team_id)
if team and team.session_id != session_record.id:
await projection.publish(
team.session_id,
"workbuddy.team.worker_event",
{
"agent_id": agent_record.id,
"session_id": session_record.id,
"name": agent_record.data.name,
"event": event.model_dump(mode="json"),
},
)
NativeTeamRuntime 再把这些事件翻译成前端已经认识的消息格式。
team_member_event表示成员开始工作或产生了一个 Agent 事件。team_member_delta表示成员正在增量输出文本。team_member_done表示成员本轮完成。team_tasks带回当前任务看板和状态。
这样前端页面不用同时管理多条 AgentScope 连接,也能在同一个聊天页面里显示主理人建了哪些任务、调了哪些成员、谁正在执行、谁已经完成。
专家团怎样跑完一条异步任务
普通 Agent 收到 REPLY_END,这一轮对话通常就结束了,专家团不能这样处理,因为主理人的回复结束时,成员可能才刚开始工作。
主理人调用 AgentCreate 后,Worker 会进入各自的 Session 执行任务。主理人不必一直占着当前回复等待,可以先结束这一轮消息,这里的 REPLY_END 只说明主理人暂时说完了,不能表明整个团队已经完成任务。
成员完成工作后,会通过 TeamSay 把结果发给主理人,AgentScope 的 dispatcher 收到消息,再次唤醒主理人的 Session。主理人拿到结果后,可能继续派发下一阶段,也可能要求成员返工,或者整理现有产出交付给用户,一次完整任务往往会经历多轮这样的唤醒。
流式运行时收到主理人的 REPLY_END 后,不会马上关闭连接,它会先等待 AgentScope 保存最新状态,读取任务看板,再检查这个 Session 是否仍然绑定着 Team。
只要绑定关系还在,就说明团队仍有工作需要处理,运行时就会继续监听事件,等待 Worker 回传或 dispatcher 再次唤醒主理人,等主理人完成交付并调用 TeamDelete,Team 被清理,这一轮专家团对话才真正结束。
专家和专家团应该怎么选
做完之后,我们再来看看专家和专家团怎么选择
我们目前的判断方法比较简单,如果一位专家能够从头做到交付,就交给专家。任务里确实存在多种专业责任,拆开后又有清楚的输入、输出和依赖,才用专家团。
AgentScope 恰好提供了两层对应的能力,普通 Agent 跑单专家,Agent Service 撑起专家团。
然后对 AI 深度学习感兴趣的可以点击:
