AgentScope第16式·归元框架
AgentScope第16式·Harness
把手工装配的 Agent 交给 Harness,交出去的不只是代码,还有装配顺序的决定权。
[读者] 已用 AgentScope Java 跑通 ReAct + Tool + MCP + Memory 的 Java 后端
[痛点] runtime 装配段越堆越长,每加一个能力都要改同一处
[现在读] 系列走到第 16 式,装配权该由框架收回了
[读完] 能用 HarnessAgent 替换手工装配,并说清它自动给了什么
[旧方案] 手工 new ReActAgent + 逐个注册工具与能力
|
v
[新需求] 统一装配 Workspace / Memory / Compaction / Skill / Plan / Sandbox / Subagent / MCP
|
v
[冲突] 装配逻辑散落在业务 runtime,顺序与开关没人负责
|
v
[后果] 每次加能力都在改同一段,回归边界不可控
我是老李,带一个小团队做企业数字人。从第 3 式把最小 Demo 立起来之后,这个项目就成了整个系列的宿主:注册登录、项目 CRUD、运行页 /run/{projectId}、LiveKit 房间、Realtime Bridge,场景一直没换过,教程代码在 https://github.com/lifuchun522/agentscopepractice ,版本基线固定为 AgentScope Java v2.0.3。
过去十几式,我们一直在这个宿主上做加法:工具、MCP、权限、状态、Memory、Skill、Plan、Sandbox、Subagent 陆续接进来,第 15 式还走完了一次上游 Issue/PR 的贡献闭环。加法都落在同一段装配代码里——构造 Agent 的那几十行。
这一周,我们在这个宿主上做一件不太一样的事:不是再加能力,而是换掉装配方式。
装配权到底该留在业务代码手里,还是交给框架?
是继续在 AvatarRuntime 里加 if,还是把 ReActAgent 换成 HarnessAgent?
如果换错了,代价是功能全在、却没人说得清哪些能力到底是谁开的?
01、故事
周五下午的评审,白板上贴着前十五式的能力清单。运行页 /run/{projectId} 打开,LiveKit 房间建立,Realtime Bridge 把会话事件转给 AgentScope,再把文本回推给前端。链路是通的。
问题出在链路中间那一层。AvatarRuntime 这个类,从最初几十行的构造方法,长成了很长的装配段:先建 Toolkit,逐个 register;再建 Memory;再决定压缩挂在哪一步后面;再判断这个项目开不开 Plan、装不装 Skill;最后 new 一个 ReActAgent。每加一项能力,就多一段分支。
任务是:把数字人运行时从手工拼装的 ReActAgent 迁移到 HarnessAgent,保留已经注册好的工具与权限,并让迁移前后核心业务用例全部回归通过。
会上有两种意见。一种主张直接重写装配层,理由是能力都测过了;另一种主张先读源码,搞清楚 HarnessAgent 到底包了什么、自动开了什么,再决定删什么。我们选了后者——前面吃过一次亏:某个能力的挂载顺序被顺手改掉,直到回归时才发现。
真正让我们下决心的不是懒,而是一个判断:装配顺序本身就是设计,它不该只活在某个人的记忆里。
场景未更换
宿主只一个
装配要移交
回归先立好
02、问题
旧方案失效不是因为它跑不通,而是因为它不可控。下面是迁移前装配段的结构示意(字段名与调用形态以 v2.0.3 源码为准):
public final class AvatarRuntimeV1 {
private final AvatarProjectService projectService;
private final ModelRouter modelRouter;
private final McpClientRegistry mcpClients;
public ReActAgent build(AvatarProject project) {
Toolkit toolkit = new Toolkit();
toolkit.register(new ProjectCrudTools(projectService));
toolkit.register(new McpToolkit(mcpClients));
Memory memory = new InMemoryMemory();
PermissionGuard guard = new PermissionGuard(project.getPolicy());
return ReActAgent.builder()
.name(project.getAgentName())
.model(modelRouter.forName(project.getModel()))
.sysPrompt(project.getSystemPrompt())
.toolkit(toolkit)
.memory(memory)
.middleware(guard)
.build();
}
}
三种失效表现:
第一是顺序耦合。Memory 装在哪、压缩挂在谁后面、权限包在哪一层,全靠读代码推断,改动一次就要重新推一遍。
第二是开关散落。压缩、Plan、Skill 的开关分散在 runtime 的不同分支里,同一个能力在装配、运行、日志三处各有一套判断。
第三是回归边界模糊。改一行装配,说不清影响哪些用例,只能全量重跑。
业务影响也很直接:加一个能力的时间,大部分花在确认「有没有把上一个能力碰坏」,而不是花在新能力本身。
本篇的完成标准有三条:一是运行时装配收敛到一个 HarnessAgent 构建入口;二是已注册的工具与权限在 Harness 管线下仍生效;三是迁移前后核心业务用例全部回归,且能列出一张「Harness 自动提供了什么」的清单。
代码会说话
顺序即契约
开关不收拢
回归无边界
03、原理
先划名词边界。ReActAgent 是推理循环本体:想一步、调一次工具、再想;Middleware 是挂在 Agent 生命周期切点上的可插拔单元,在推理或工具调用前后做事;Toolkit 是工具的统一注册与分发容器。三者关系里,HarnessAgent 是外层装配者。
反直觉判断一:HarnessAgent 的价值不是让 Agent 更聪明,而是让装配权归一。
官方架构文档 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/docs/v2/zh/docs/harness/architecture.md 与核心类 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java 描述的是同一件事:推理循环仍然由 ReActAgent 承担,Harness 层负责把 Workspace、Memory、Compaction、Skill、Plan Mode、Filesystem、Sandbox、Subagent、MCP 这些能力,按统一顺序装进管线。
换句话说,你换掉的不是推理引擎,而是「谁来负责装配」——以及这份装配顺序有没有被写进框架,而不是写在你的 runtime 里。
反直觉判断二:能力越多,越不该由业务代码装配。
能力少的时候,手工装配成本低、可见性高;能力一多,装配顺序、开关组合、状态归属会呈组合式增长,业务代码里就出现大量只为「把它们按某种顺序放好」而存在的行。这些行不产生业务价值,却必须被 review。
这也解释了两件事。第一,为什么 Harness 的入口是 Middleware + Toolkit 而不是更多 API:中间件负责顺序,工具容器负责能力。第二,为什么后面的架构、排查和优化都围绕「顺序」和「重复」展开,而不是围绕「功能有没有」。
一个必须说清的边界:Harness 具体在哪些切点暴露钩子、每个能力的默认开关如何,需按当前官方文档与 v2.0.3 源码核验,本文不把未核验的细节写成结论。
包装非升级
循环仍如旧
装配归一后
能力可插拔
04、架构
[输入] LiveKit 会话事件 / 用户文本
|
v
[模块] HarnessAgent(包装 ReActAgent + Middleware 链 + Toolkit)
|
v
[数据/状态] Workspace / Memory / Compaction 状态
|
v
[处理] ReAct 循环 + 中间件前后钩子(权限、工具、Skill、Plan)
|
v
[输出] 文本结果经 Bridge 回推 LiveKit 房间
边界:LiveKit 只管 WebRTC 房间与实时音视频传输;Realtime Bridge 只做媒体与消息适配,保持最薄;Agent 的推理、工具、状态、压缩、技能、计划、子代理全部由 AgentScope Java 侧承担。HarnessAgent 位于 AgentScope 内部,不跨到传输层。
收益:装配顺序从业务代码移到框架管线,加能力的改动面从「runtime 多段」收敛到「构建入口一处」;能力默认组合有统一来源,review 时可以直接对照官方文档。
代价:调试路径变深。以前断点打在 new ReActAgent 那一行,现在要先理解中间件链的插入顺序,再定位到具体切点。另外,框架默认策略会覆盖一部分自定义,需要显式确认哪些默认可以接受。
适用条件:能力数量超过三四个、需要长期演进、团队不止一人维护同一段装配代码。
不适用条件:只有一两个工具、无状态、一次性脚本或 PoC——这时候 Harness 的收益抵不上理解成本,手工装配反而更透明。
涉及框架行为的结论,落脚点如下:架构与装配顺序见 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/docs/v2/zh/docs/harness/architecture.md ,HarnessAgent 的字段、构建入口与包装方式见 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java 。本文实验按 v2.0.3 复现;写作当天最新正式版请以 https://github.com/agentscope-ai/agentscope-java/releases 为准,本文不对最新版号做断言。
输入是会话
中间是管线
输出回房间
边界须写死
05、实战一次
以下代码与命令未在当前环境实测,给的是预期形态;builder 方法名、包名与依赖坐标请以 v2.0.3 源码为准。
第一步,从上一章分支继续创建本章分支:
git checkout chapter/15-issue-pr
git checkout -b chapter/16-harness
第二步,确认依赖坐标。harness 模块坐标请直接查教程仓库与 v2.0.3 源码中的 pom,不要凭记忆写:
grep -rn "agentscope-harness" ./pom.xml ./**/pom.xml
预期在运行时模块中引入(版本与 groupId 以上一步结果为准):
<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-harness</artifactId>
<version>2.0.3</version>
</dependency>
第三步,迁移装配层。工具注册逻辑原样保留,只换宿主:
public final class AvatarRuntimeV2 {
private final AvatarProjectService projectService;
private final ModelRouter modelRouter;
private final McpClientRegistry mcpClients;
private final Path workspaceRoot;
public AvatarRuntimeV2(AvatarProjectService projectService,
ModelRouter modelRouter,
McpClientRegistry mcpClients,
Path workspaceRoot) {
this.projectService = projectService;
this.modelRouter = modelRouter;
this.mcpClients = mcpClients;
this.workspaceRoot = workspaceRoot;
}
public HarnessAgent build(AvatarProject project) {
return HarnessAgent.builder()
.name(project.getAgentName())
.model(modelRouter.forName(project.getModel()))
.systemPrompt(project.getSystemPrompt())
.toolkit(buildToolkit(project))
.workspace(workspaceRoot.resolve(project.getId().toString()))
.build();
}
private Toolkit buildToolkit(AvatarProject project) {
Toolkit toolkit = new Toolkit();
toolkit.register(new ProjectCrudTools(projectService));
toolkit.register(new McpToolkit(mcpClients));
return toolkit;
}
}
注意这里删掉了三样东西:手工 new 的 Memory、手工挂在构造处的权限中间件、以及散落的能力开关。删得对不对,不靠感觉,靠第 06 章的排查证据。
第四步,启动与请求。Bridge 侧调用路径保持不变,只是注入的 Agent 变成 Harness 产物:
HarnessAgent agent = runtime.build(project);
String reply = bridge.ask(agent, sessionId, userText);
第五步,回归验收。用例清单不新增,全部复用前十几式已经写下的核心用例,逐条在 chapter/16-harness 分支上重跑:项目信息查询、数字人开场白与结束语、权限拦截、MCP 工具调用。四条全部通过才允许提交。
第六步,提交:
git add -A
git commit -m "refactor(ch16): migrate runtime to HarnessAgent"
验收的第二件事,是列出一张清单:Harness 自动提供了什么。按官方架构文档与 HarnessAgent 源码梳理,至少包括这些方向(具体开关与默认值需按 v2.0.3 源码核验):
| 能力 | 迁移前 | 迁移后 |
|---|---|---|
| ReAct 推理循环 | 手工装配 | Harness 包装 ReActAgent |
| 工具注册与分发 | 业务 runtime | Toolkit + Harness 装配 |
| 中间件管线与顺序 | 业务 runtime 决定 | Harness 统一编排 |
| Workspace / Filesystem | 无或自建 | Harness 提供 |
| Memory | 手工注入 | Harness 装配 |
| Compaction 上下文压缩 | 手工挂载 | Harness 装配 |
| Skill 加载 | 分支判断 | Harness 装配 |
| Plan Mode | 分支判断 | Harness 装配 |
| Sandbox | 手工接入 | Harness 装配 |
| Subagent | 手工创建 | Harness 装配 |
| MCP 接入 | 手工注册 | 经 Toolkit 接入 |
这张表本身就是本章的交付物:迁移之后,业务代码只回答「用哪些工具、给什么提示词、工作目录在哪」,其余问题由框架回答。
先建新分支
再换装配层
工具仍原地
权限不放手
06、排查
第一条证据链:迁移后工具像「不存在」。
现象:迁移完成后,数字人回答「我无法查询项目信息」,而运行日志里没有任何工具调用记录。
怀疑:第一反应是模型变弱了,或者 systemPrompt 被覆盖。
检查:按顺序看三件事——Toolkit 里实际注册了几个工具、HarnessAgent 构建时 Toolkit 是否被真正接收、请求到达模型时工具描述是否出现在上下文中。
证据:对照 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java 的字段与构建入口,确认工具必须注册进 Harness 实际持有的那个 Toolkit 实例。迁移时如果一边建 Toolkit、一边又走了旧的传递路径,就会出现「工具建了但没被接住」。
错误尝试: 第一轮修复选择了在 systemPrompt 里加一句「你必须调用工具查询项目信息」。这是错的。工具能不能被模型看见,由 Toolkit 到推理上下文的传递决定,不由提示词的真实性决定;提示词只会让模型在不存在的工具上编造一次调用,从而把「工具没接住」伪装成「模型不听话」,浪费一轮排查。
根因:装配入口换层时,工具注册的对象与 Harness 消费的对象不是同一个。
修复:统一在 buildToolkit 里构造 Toolkit,并只通过 HarnessAgent 构建入口传入,删掉其余注册路径。
第二条证据链:对话历史重复,且长会话内容莫名丢失。
现象:迁移后同一轮问答在历史里出现两份;稍长的会话里,早期内容会消失。
怀疑:前端重复发送消息,或 Bridge 重试导致重复写入。
检查:先看 Bridge 的重试日志(无重复发送),再检查运行时是否同时存在两套记忆来源。
证据:Harness 侧自带 Memory 与上下文 Compaction 装配;迁移时如果保留手工 new 的 Memory 再注入,同时 Harness 又装配了默认记忆与压缩,就会出现两份历史,以及压缩策略各算一次。
根因:迁移只做了「加」,没做「删」,形成能力重复。
修复:移除手工 Memory 与手工压缩挂载,只保留 Harness 装配;如果确实需要自定义策略,改为在 Harness 管线上替换对应单元,而不是并列第二套。
上游贡献判断: 目前观察到的是文档与 API 理解成本问题(构建入口、默认开关、装配顺序的说明密度不足),不是可在最小 Demo 复现的功能缺陷;v2.0.3 上复现的是理解偏差而非错误行为,main 分支是否已有变化需按当前源码与 Release 核验;是否已有相关 Issue/PR 需现场搜索 https://github.com/agentscope-ai/agentscope-java/issues 与 https://github.com/agentscope-ai/agentscope-java/discussions 后才能判断,当前只能记为候选,不构成已提交的贡献。
现象先复现
证据再落位
猜测最耗时
根因只一条
07、优化
按第 06 章的两条根因,V2 的修改只有三处,其余代码不动。
第一处,装配入口唯一化:删除 AvatarRuntime 里所有独立的 Toolkit、Memory、压缩挂载路径,只保留一个 build 方法。原因是双入口必然导致「谁生效」不可判定。新行为是:运行时只有一个 Agent 构建点,Harness 管线内的顺序由框架决定。验证方法是搜索装配相关关键字,确认全仓库只剩一处构建调用。
第二处,能力开关外置:把「这个项目是否启用 Plan、是否挂载 Skill」从运行时 if 改成项目配置字段,构建时一次性读入。原因是开关散落在运行分支里,回归时无法枚举组合。新行为是:组合空间可枚举,用例可以按组合覆盖。验证方法是列出配置组合与对应用例的对照表。
第三处,权限保留在 Harness 管线上:权限中间件不删,改为按 Harness 的中间件接入方式挂载,保持与迁移前相同的拦截语义。原因是安全语义不能因为迁移而放松。验证方法是用一条必然被拒的请求,确认拦截原因与迁移前一致。
不虚构验证数据:以上三步的验证方式都是可执行的检查动作,实际通过与否要在自己的分支上跑完核心用例后填写,本文不代替你的运行结果。
删掉重复的
留下必须的
收拢注册口
再跑一遍测
08、演进
[同一输入] 运行页 /run/{projectId} 的一次问答
|
+--[V1] 手工装配 ReActAgent
| 代价:装配顺序隐式、开关散落、回归边界模糊
|
+--[V2] HarnessAgent 统一装配
| 代价:调试路径变深、依赖官方默认策略
|
[Trade-off]
得到的是装配顺序有唯一来源、加能力改一处;
失去的是对每一层挂载位置的直接控制;
适用边界是能力多项、长期演进、多人维护的运行时。
比较五个维度。正确性:V2 的关键风险不是算错,而是能力重复导致语义漂移,必须靠回归用例守住。稳定性:V1 的稳定性依赖作者记忆,V2 的稳定性依赖框架版本,因此版本锁定更重要。复杂度:V1 是代码复杂度高、认知复杂度低;V2 相反。成本:V2 前期要读文档与源码,后期每次加能力更便宜。适用范围:V2 适合能力成体系的数字人运行时;单工具场景仍是 V1 更划算。
遗留问题有两个:一是可观测性,中间件链的执行顺序在日志里目前不可见,需要补结构化日志;二是版本耦合,框架升级会直接改变默认装配,需要把升级纳入回归计划。
旧路可控但重
新路统一但深
取舍写成表
边界才算清
09、洞见
9.1 反直觉判断:Harness 装得越多,你越需要先定义边界
能力被自动装配后,最容易出错的不是「没装上」,而是「装上了但你不知道」。第 06 章第二条证据链就是典型:默认记忆与手工记忆同时存在,功能看起来正常,语义已经变了。能力越多,越要先写清楚哪些交给框架、哪些必须自己管。
9.2 反直觉判断:迁移的最大风险是能力重复,不是功能缺失
功能缺失会立刻报错,能力重复不会。重复的 Memory、重复的压缩、重复的中间件,表现为「偶尔不对」,排查成本远高于一次启动失败。所以迁移的动作清单里必须有一列叫「删什么」,而不是只有「加什么」。
9.3 装配权交出去,调试权要自己拿回来
换装配层不能顺手把可观测性也交出去。迁移的同时至少要保证三样可见:本轮用了哪些工具、中间件按什么顺序执行、压缩是否触发。否则下一次线上问题会卡在没有断点可打的位置。
9.4 什么时候不应该用本章能力
只有一两个工具、没有状态、没有长会话、没有多轮技能切换的场景——例如一个只做文本润色的内部小工具——用 HarnessAgent 是过度装配:你会为了统一顺序付出读文档和源码的成本,而收益接近于零。还有一类不适用:能力边界尚未稳定、正在快速试错的探索期。装配顺序的价值来自「长期不变」,在能力还在天天换的时候,先把能力试清楚再收拢装配。
能力多一分
边界多一寸
迁移看重复
不看功能表
10、系统落地
原来有什么:一个能跑通 LiveKit 实时会话、具备工具调用与权限拦截的数字人 Demo,运行时的装配逻辑散在 AvatarRuntime 内部,能力清单只存在于代码与记忆里。
本篇新增什么:一个统一的 Agent 构建入口,Harness 管线承担 Workspace、Memory、Compaction、Skill、Plan Mode、Filesystem、Sandbox、Subagent、MCP 的装配;一张「Harness 自动提供什么」的对照表;一份迁移回归清单;一个长期保留的教程分支 chapter/16-harness。
现在能做什么:给数字人项目加一个新能力时,先问它在 Harness 里有没有对应单元,而不是先改 runtime;回归时按用例清单跑,不靠通读装配代码推断影响面。
还缺什么:中间件执行顺序的观测能力;Harness 版本升级的回归机制;以及跨项目(多租户)场景下 Workspace 隔离的验证——后者属于后续章节范围,本篇不展开。
下一步如何演进:把本章的迁移结果作为后续所有能力章节的宿主基线,任何新能力都优先走 Harness 装配路径;如果确实需要自定义,也是在管线上替换单元,而不是在旁边再挂一套。
开源参与点:本章未发现明确上游问题。第 06 章记录的两处现象均可在最小 Demo 中复现为理解偏差,而非框架缺陷;文档密度不足属于体验类问题,可作为讨论或文档 PR 的候选,但在搜索官方 Issues、Discussions 并确认没有重复条目之前,不做任何提交动作。
宿主已换代
工具仍原样
缺的是观测
下一步补它
11、小结
Q1 → 手工装配顶不住的地方不是功能,而是顺序与开关没有唯一负责人
Q2 → 该保留的是工具注册与权限语义,该交出去的是能力挂载与顺序编排
Q3 → 直接把 ReActAgent 换成 HarnessAgent 不够,必须同步清理重复能力与统一入口
状态 → chapter/16-harness 分支上,运行时装配收敛为单一 HarnessAgent 构建入口,工具与权限保留,核心业务用例可回归
本章真正可交付的不是一段新代码,而是一条判断:装配顺序是设计资产,它应该有唯一来源。交接完成后,后续章节的增长点从「往 runtime 加代码」变成「在 Harness 上换单元」。
问题有答案
状态可交付
分支已就位
下一式继续
12、作业
12.1 理解题:HarnessAgent 与 ReActAgent 的关系是什么,为什么说换掉的是装配权而不是推理引擎?
参考答案:HarnessAgent 在外层,包装并持有 ReActAgent,推理循环仍由 ReActAgent 承担;Harness 负责把 Workspace、Memory、Compaction、Skill、Plan、Sandbox、Subagent、MCP 等能力按统一顺序装进 Middleware 管线,并通过 Toolkit 接入工具。因此迁移改变的是「谁负责装配与排序」,不是「谁负责思考」。答这道题不能只凭印象,建议打开 https://github.com/agentscope-ai/agentscope-java/blob/v2.0.3/agentscope-harness/src/main/java/io/agentscope/harness/agent/HarnessAgent.java ,看它如何引用与构造 ReActAgent。
12.2 实战题:在 chapter/16-harness 分支上完成迁移,并产出两张清单。
参考答案:一张是回归清单(项目信息查询、开场白与结束语、权限拦截、MCP 调用,逐条跑通);一张是 Harness 自动能力清单(按第 05 章表格逐项核对你自己的项目实际命中了哪些,未命中的注明原因)。提交信息使用 refactor(ch16): migrate runtime to HarnessAgent。注意:不要在本分支上引用任何第三方式的自定义扩展绕开 Harness 装配路径,否则清单不可信。
12.3 排障题:迁移后工具调用消失,你会先看哪个环节,为什么不能先改提示词?
参考答案:先看 Toolkit 的实际注册对象与 HarnessAgent 构建入口是否指向同一实例,再看工具描述是否进入模型上下文。改提示词之所以错误,是因为工具可见性由装配决定,提示词只能诱发模型对不存在的工具做无效调用,把「工具没接住」伪装成「模型不听话」。如果排查后怀疑是框架侧行为,请先搜索官方 Issues 与 Discussions:https://github.com/agentscope-ai/agentscope-java/issues 。
12.4 架构判断题:什么情况下应该拒绝使用 HarnessAgent,坚持手工装配?
参考答案:能力只有一两个、无状态、无长会话、无技能与沙箱需求、且短期内不会扩展时,Harness 的统一编排带来的收益小于理解与版本耦合成本。另一种是探索期:能力边界尚未稳定,此时收拢装配会把不确定性固化进框架默认值。判断标准不是「框架有没有这个能力」,而是「装配顺序是否长期稳定、是否需要多人共同维护」。
做题要动手
源码是答案
回归不放过
判断题最难
13、思考
回到本篇的核心冲突:装配权该留在业务代码,还是交给框架?结论不是简单的「交给框架更好」,而是「装配顺序必须有唯一来源」。当能力只有两三个,唯一来源可以是你自己写的那二十行;当能力成体系、又要在多人之间交接,唯一来源就应该是一层被官方维护、可被文档描述的装配管线。
这条判断可以复用到任何框架迁移上:迁移前先列「删什么」,再列「加什么」;先跑通回归,再谈收益;接受默认策略之前,先确认自己知道默认值是什么。
「会用框架」是照着文档把 API 调通;「能参与框架演进」是在读懂装配顺序之后,判断哪些默认该被质疑、哪些缺口值得提一个 Issue 或一段文档 PR。这两者之间,隔着的正是本章读的那一份 architecture.md 和那一个 HarnessAgent.java。
会用框架者
知其装配序
能参演进者
识其边界线

浙公网安备 33010602011771号