Files
meida/docs/五轮多Agent评审迭代-复盘与追溯.md
xsl c9d13697a6 docs: 五轮多 Agent 评审迭代复盘 + 真机闭环自动化测试实践指南
- 复盘:编排设计、逐轮决策链、35 项修复全量清单、关键技术点、踩坑与复用流程
- 真机指南:闭环模型(注入/截图/dumpLayout/hilog 三通道)、命令手册、坐标库、
  安全访问选择器等 7 条踩坑、成本红线、标准测试脚本骨架
2026-08-30 23:22:02 +08:00

23 KiB
Raw Permalink Blame History

五轮多 Agent 评审迭代 · 复盘与追溯

日期:2026-08-30 | 执行:ZCode 主控 + PM/开发/测试三类子 agent | 产出:35 项修复(App 24 文件 +2134/-409,后端 3 文件 +65) 关联提交:主仓 0b5d151、子仓 meida_backend@00ef56b(均已本地提交,未 push) 过程产物:.scratch/multiagent/(CONTEXT.md、R1R5 五轮 PM/测试报告、checks/checks.mjs、shots-r1r5 截图档案、FINAL-REPORT.md)

本文目标:完整还原这次多 agent 迭代的机制、每一轮的决策链、所有修复的技术细节与踩坑,供后续追溯与复用。真机自动化测试单独成篇:见《真机闭环自动化测试-实践指南.md》。


1. 任务与总体设计

1.1 原始需求

用户原话要点:了解项目(端侧模型/后端/PDF 报告/试衣间/发型设计);开三个 agent——PM 视角 review、开发 agent 按反馈开发并提建议、测试 agent 尽量自动化测试;多 agent 迭代 5 轮;最后用户验收。

1.2 多 agent 编排模型

主控(ZCode 会话)
 ├─ 基线验证(构建门禁 / 后端单测 / 设备探测)—— 不交给子 agent,自己先跑
 ├─ 写 .scratch/multiagent/CONTEXT.md(共享上下文,所有子 agent 必读)
 ├─ Round N:PM agent(只读)→ 开发 agent(改码+门禁)→ 测试 agent(门禁+静态+真机)
 │            ↑ 每轮把上一轮测试发现(Nx' 问题)+ 遗留池喂给下一轮 PM
 └─ 终局:测试发现的 P0 回归由主控亲手热修(不再开一轮,控制成本)

关键设计决策:

  1. 共享上下文文件而不是长 prompt。CONTEXT.md 固化项目结构、验证命令(全部主控亲测过可用)、硬约束(禁 commit/push、不跑花钱的 smoke、不开集成测试、最小 diff、代码约定)。每轮 agent prompt 只写"本轮主题 + 读哪些文件 + 输出到哪",避免 prompt 膨胀也避免 agent 复述项目背景浪费上下文。
  2. 角色硬隔离。PM agent 只许读源码、唯一可写的是自己的报告文件;开发 agent 不许碰后端(除非该轮显式授权一处,如 R3 的 hint 改口、R4 的模板 meta);测试 agent 可修 .scratch 下的检查脚本(测试基建)但不能改产品代码。
  3. 每轮有主题,避免转圈。R1 全局走查 → R2 AI 功能域 → R3 PDF 链路+一致性 → R4 结果可靠性+遗留池裁决 → R5 终局修复+内测验收清单。R3 起 PM 的职责从"找新问题"过渡到"裁决遗留池 + 定修复方案"。
  4. 报告落盘编号体系。问题编号 #1~#28 全局连续;测试新发现用 N1、N2'… 前缀;文件名固定 R{n}-{pm|test}.md。这让第 5 轮 PM 能产出完整的 40 项遗留池终审表。
  5. 门禁前移。开发 agent 的任务书里就写死"BUILD SUCCESSFUL 是硬门禁,失败必须修到过",测试 agent 复跑做独立复核——两道验证互相独立。

1.3 基线(迭代前主控亲测)

  • App 构建门禁:cd app && cmd //c .build-gate.bat → BUILD SUCCESSFUL(~1 分钟,增量更快)
  • 后端单测(必须 WSL,Windows 侧 uv 管不了 WSL 建的 venv;必须 --extra dev 才有 pytest): wsl -e bash -lc "cd /mnt/d/meida/meida_backend && uv run --extra dev pytest tests/unit --no-cov -q" → 574 passed 基线
  • 真机:hdc list targets → 88Z9K26525009283(华为真机,有 PIN 锁)

2. 逐轮还原(决策链与证据)

Round 1:全局走查(16:10–16:44)

PM 输入:核心旅程走查(登录→首页→报告→PDF;试衣间/发型/我的/对话),6 维度(流程完整性/三态/文案/配额/隐私合规/一致性)。 PM 输出(R1-pm.md):

  • P0 ×3:#1 报告唯一入口在 AppStorage(重启即丢,后端 GET /api/v1/reports 现成未接);#2 doRegenerate 丢弃响应新 report_id(消耗 30 天 1 次的配额却永远看旧报告);#3 人脸照片无单独知情同意(违反合规方案 CR-01/FR-ACC-08)
  • P1 ×3:#4 失败重试复用同 request_key(幂等重放原样失败→死循环);#5 消耗配额前无提示(违反"禁止静默扣减");#6 技术黑话 8 处
  • P2 ×6:#7 会话过期无出路 ~ #12 对话无快捷问题

开发(10 文件,新增 ReportListTab.ets / FacePhotoConsent.ets):

  • #1:报告 Tab 从"建设中"占位换成真列表(组件级复用,不算新路由页,绕开"无底部 Tab 锁死用户"的约定冲突)
  • #2+#4:采纳新 report_id;三处(create/regen/export)FAILED 后清幂等键换新键
  • #3+#5:同意弹窗(会话级)+ 四个消耗点事前确认(复用现有 quota 接口,额度 0 置灰+恢复时间)
  • 计划外重大发现:后端任务状态是小写(succeeded/failed),App 全按大写比较——轮询完成检测恒假、"导出 PDF"按钮从未真正出现过。真机注释里"轮询完成不自动跳转"就是它的症状。统一 toUpperCase() 归一修复。

测试(R1-test.md):门禁绿;建 checks.mjs 静态检查 17(跨页 import 禁令/路由注册/大小写/幂等键清理/同意门控/黑话/前后端路由比对);真机被 PIN 锁阻塞(只完成装包+启动);#1#5 验收 ✅;新发现 N1~N4。放行。

Round 2:AI 功能域(16:46–17:18)

PM 输出(R2-pm.md,#13~#20,无 P0):#13 对话单轮失忆(M4-b 范围,只登记);#14 缺「AI 生成」标识(CR-05 法定,2025-09-01 已施行);#15 发送失败丢输入无重试未带幂等键;#16 试衣生成期锁屏即丢结果;#17 结果只活在签名 URL;#18 发型页列表态/保存误报;#19 调试页处置;#20 同意弹窗承诺无工程背书。 开发(4 文件):

  • ChatPage 四合一:AI 标识常驻(列表与输入区之间固定层)、4 个快捷问题 chips、额度 0 置灰+reset_at 恢复时间、失败回填+失败气泡重试+request_key(失败保留/重试复用/成功换新)、500 字上限
  • TryonPage:setWindowKeepScreenOn(true)(结束/取消/销毁恢复)、onBackPress 挽留确认、取消出口(Phase A 直连无服务端任务,文案如实说"不涉及额度扣减")、genSeq 防迟到响应串扰;TryonApi 加 cancelChangeCloth
  • TaskPollingPanel:连续 3 次网络错误=「放弃轮询」不再上抛 onFailed(宿主幂等键不清,额度未退),面板内给"稍后在报告列表查看"引导;仅后端终态 FAILED 走失败流程 测试:12 项静态检查 11 PASS(#6 黑话范围外单列);真机仍 PIN 锁;6 项验收全 ✅;新发现 N5(UTC 时区差 8h,波及 5 处)/N6(压缩期取消不彻底)/N7(额度耗尽不同步)。放行。

Round 3:PDF 链路收尾(17:20–19:40,测试一度被中断后重跑)

PM 输出(R3-pm.md,#21~#26 + 遗留池首轮裁决):

  • P1 ×3:#21 PDF 下载前不删旧文件(OHOS download 不能覆盖已存在——同报告第二次导出/重试必失败,且发生在额度已扣之后);#22 loadReport 只清 regen 不清 export 幂等键(导出→重生成→再导出=IDEMPOTENCY_CONFLICT 死路,且后端 hint 说"任务中心"——App 不存在的页面);#23 PDF 失败态无出路+loadDocument 挂载时序风险+FR-RPT-12 分享长图(V1 P0)反而不做却先做了 V2 的 PDF(范围倒挂登记)
  • 裁决:本轮修 #6 清尾/#7/N5/N2/#10 最小集;R4 修 #17/#18/N7/N6/N4/#24/#25;不修项给理由 开发(17 文件 + 新增 Theme.ets;后端唯一授权改动 errors.py 两处 hint):
  • #21 下载前 accessSync→unlinkSync;#22 loadReport 清两个幂等键;#23 PdfView 常驻挂载+遮罩式加载/失败态+重新加载按钮+分享另存文案拆分
  • #7:ApiTypes 加 isAuthError()(40100~40105),四处错误态加「重新登录」出路
  • N5:formatIsoTime 改 new Date(s) 本地时区解析(原手工截串);连带 ProfileContent 7 处时间归正
  • #6 十处逐条替换(含 PDF 文件名内嵌报告 ID 改「形象报告 vN」);N2 报告 Tab onChange+onPageShow 双触发刷新;#10 主色收敛 core/Theme.ets(15 文件替换两套主色)+返回控件统一+Tab3 文案 测试(R3-test.md,重跑后完成):真机解锁,PDF 全链首次完整走通——登录→报告 Tab→导出确认→轮询(无黑话)→服务端渲染 350KB→下载→PdfView ready→另存→分享取消静默;二次导出同报告(旧文件在场)成功——#21 unlink 修复实证(旧代码必 2300037);19/19 静态检查;#21/#22/#23/…验收 ✅;新发现 N1'~N7'(PDF 元数据缺 AI 声明等)。放行。

Round 4:结果可靠性(19:42–21:35)

PM 输出(R4-pm.md):10 项修复清单(#17 试衣转存为首位,含"下载前 unlink"同款坑提醒;#18 与 #17 统一方案;N2'-① PDF meta;N1' 提示自清;#24 修正 R3 的"6 卡"为 4 卡真实消耗集——tryon/hairstyle 直连不经后端配额,上卡会恒显满额假象;#25 登录页内测口径;N7(正确码 40301,R3 写的 42910 是短信频控);#19 debug 守卫——发现门禁产物恒 debug,包裹≠隐藏,但 T18-REPORT 未产出前不能彻底删;#10 尾差)+ 40 项遗留池终审表(已修 22/本轮 10/R5 修 3/登记不修 5)。新登记 #27(结果图无水印=Phase B 放量红线)、#28(T18-REPORT 未产出)。 开发(8 文件 + 后端模板/单测):

  • #17:生成成功即下载 filesDir/results/tryon_last.jpg(unlink 重入);展示/保存走本地副本(fileUri.getUriFromPath);aboutToAppear 回看「上次结果」卡;保存三态(photoAccessHelper 返回空数组=取消/异常=失败,d.ts:3570 判别);转存失败降级 URL+提示;N6 genSeq 复查加在压缩后 changeCloth 前
  • #18:发型列表三态(listLoaded 复位支持重试)、保存三态、结果落 hair_last.jpg;ImageCodec 抽 writeBase64File 助手,新增 base64ToPersistFile(filesDir,自动建目录),cacheDir 版保留作兜底
  • N2'-①:report.html.j2 加 author/description/generator meta(用现成 report_id/version/date_text 零新数据;不伪造 27 位编号——后端 compliance/service.py 明示 fail-close"half-way is worse");+2 单测(WeasyPrint 把 Info 字典放压缩对象流,测试用 zlib 解压后断言 /Author 等标记,不新增 pypdf 依赖)
  • #24/#25/N7/#19/#10尾差 按清单落地;BuildProfile 踩坑:虚拟路径 import 不解析,改直引 build/default/generated/.../BuildProfile(clean 构建亦可用,任务顺序有保证,但 IDE 升级有脆性——已记录) 测试(R4-test.md,两段完成):前任 agent 真机实操 58 张截图(含相机拍照选图、飞行模式、杀 App、登出重登、PDF 另存)后上下文超限挂掉;接任 agent 不重复操作、只分析证据+跑门禁:三门禁绿、#18 列表三态/#24/#25/#19/N2'-① 真机或单测实证;但发现 R4N1 [P0]:真机上 photoUris 已送达 App 而 humanUri 永不更新(await 链静默死亡)、cameraPicker resultUri 恒空——试衣/发型照片链真机全断(存量缺陷,此前轮次照片链从未在真机走通过所以没暴露)。有条件放行。

Round 5:终局(21:40–23:10)

PM 输出(R5-pm.md):

  • R4N1 根因排序(排除法):三个会进 catch 的假设被"从未出现失败 statusText"排除;H1(最可能)= describe() 内 await getImageInfo() 挂起不回调(ImageSource 懒加载+真机媒体路径,官方 FAQ 给过 getImageInfoSync 替代方案);H2 = picker Promise 提前 resolve 空/续体丢失(拍照路径"resolve 但 resultUri 空"实证此形态)。事实修正:报告页照片是"即将开放"预告没有接选图代码,修复面收窄为两页
  • 最小改动方案:收口 ImageCodec 单文件——A 裁 describe(width/height 全仓零消费,纯风险零收益);B select/pick 套 Promise.race 超时;C createImageSource(uri) URI 直建;拍照降级预案预授权
  • R5 清单 4 项:R4N1 / N4 同意持久化+撤回 / N5' f2 换用 / statusText 1 行;内测发布验收清单(A12+B16+C8+D5 条) 开发(两任 agent:第一任被用户熄屏打断留下半成品,第二任核对发现四项其实已全做,补 checks 断言 25~28 + 跑门禁):
  • ImageCodec:A/B/C 全落地 + MEIDA_IMG 打点 11 处;FaceConsentStore 镜像 AuthStore 的 preferences 先例({granted, version:'v1', grantedAt},init hydrate 校验版本,revoke 清空);ProfileContent 授权管理行+撤回确认框;ReportResultPage/ReportListTab 换 f2 LoadingView/ErrorView(保留 #7 重新登录出路);TryonPage 两处 statusText 清空 测试(R5-test.md):三门禁绿、契约零越界;A1~A12 真机:选图+拍照送达修通(gallery: select end, 1 uri(s) 带真实 URI、camera: pick end, resultUri length=63)、N4 全链(杀 App 不弹/撤回后重现)、N5' 断网闭环、0 崩溃;换装/发型真实生成各 1 次止步压缩失败未触网(服务零消耗);发现 R5 回归:R5N2 [P0] image.createImageSource('file://media/…') 真机返回 undefined→toJpegBuffer 抛 TypeError→生成链全断(修复 C 引入);R5N1 [P1] TryonPage @Builder PhotoSlot 按值传参不响应 @State→预览永不显示(发型页直引写法正常)。结论:未达内测门槛,差两个小修复。

终局热修(主控亲手,23:10–23:40)

  • R5N2:ImageCodec 加 openSource()——先 image.createImageSource(uri),真机返回空则 fs.openSync(uri, READ_ONLY) → createImageSource(file.fd)(华为官方文档模式);closeSource() 在 finally 里 release+关 fd(成功/失败都回收)
  • R5N1:@Builder PhotoSlot($$: PhotoSlotArgs)(显式 interface,ArkTS 严格模式不许裸对象字面量作类型),调用点改对象字面量传参——按引用、@State 变化即刷新
  • 踩坑复现:第一次构建失败(arkts-no-obj-literals-as-types),补 interface 后绿;install -r 不杀进程——验证时发现跑的还是旧代码(ps 看启动时间),aa force-stop + 重启后才是新包
  • 真机实证:60s 超时兜底可感知(两次触发「读取照片失败」+hilog)、cameraPicker resolve 正常(退出=空 URI 取消语义)、N4 跨 force-stop 不重弹、0 崩溃;合成点击进不了"安全访问"选择器(设备限制,详见真机指南文档)——预览刷新与压缩链留一分钟人工验收
  • 静态检查项 26 更新为 openSource 双路口径后 28/28 PASS;提交 0b5d151(主仓)+ 00ef56b(子仓)

3. 修复全量清单(35 项速查)

# 问题 轮次 修复要点 验证
1 报告重启即丢 R1 ReportListTab 接 GET /reports 替换占位 Tab 真机 ✅
2 重新生成显示旧报告 R1 采纳响应新 report_id 静态 ✅
3 人脸照片无知情同意 R1→R5 弹窗→preferences 持久化+版本+撤回 真机 ✅
4 失败重试幂等死循环 R1 FAILED 清 requestKey 换新键(三处) 静态 ✅
5 静默扣配额 R1 四消耗点确认框(代价+剩余+置灰+恢复时间) 真机 ✅
6 技术黑话 10 处 R3 逐处替换文案表 真机抽证 ✅
7 会话过期无出路 R3 isAuthError+四处「重新登录」 静态 ✅
10 主色两套/返回三套 R3 Theme.ets 唯一来源,15 文件替换 静态 ✅
12 对话无快捷问题 R2 4 chips 对应需求文档四类意图 真机 ✅
14 缺 AI 生成标识 R2 列表与输入区之间固定层常驻 真机 ✅
15 发送失败丢输入 R2 回填+重试+request_key 静态+契约 ✅
16 生成期锁屏丢结果 R2 keepScreenOn+挽留+取消+genSeq 静态 ✅
17 试衣结果只活在 URL R4 filesDir/results 转存+回看卡+离线保存 静态 ✅/真机待人工
18 发型页三态缺失 R4 列表三态+保存三态+结果转存 真机(列表/错误态)✅
21 PDF 二次下载必败 R3 下载前 unlink 真机实证 ✅
22 导出幂等冲突死路 R3 loadReport 清双幂等键+后端 hint 改口 静态 ✅
23 PDF 失败态无出路 R3 常驻挂载+重试+文案拆分 真机 ✅
24 配额卡信息架构 R4 4 张真实消耗卡+名称对齐 真机 ✅
25 登录页临时性呈现 R4 内测徽章+账号标识话术+断网指引 真机 ✅
N1 轮询网络误判失败 R2 放弃≠终态,不清幂等键 静态 ✅
N2 报告列表不刷新 R3 Tab 切换+页面重现双触发 真机 ✅
N3/N7 额度 0 体验 R2/R4 置灰+恢复时间;40301 同步 真机 ✅
N4 同意不持久化 R5 preferences+撤回入口 真机 ✅
N5 UTC 时区差 8h R3 new Date 本地解析,7 处归正 真机实证 ✅
N6 压缩期取消不彻底 R4 genSeq 复查移到 changeCloth 前 静态 ✅
N2'-① PDF 缺 AI 元数据 R4 模板 meta+2 单测(zlib 解压断言) 单测 ✅
N1' 保存提示常驻 R4 3s 自清(白名单防误清) 真机 ✅
N5' f2 组件零采用 R5 两页换用,保留重新登录出路 真机(断网)✅
#19 调试页暴露 R4 BUILD_MODE_NAME 守卫+「内部调试」文案 真机 ✅
R4N1 真机照片链断裂 R5 裁 describe+超时兜底+URI 直建 真机(送达)✅
R5N1 @Builder 不刷新 热修 $$ 对象接口按引用传参 构建+机制 ✅/待人工点选
R5N2 createImageSource 空 热修 openSource 双路(URI→fd 回退) 构建+机制 ✅/待人工点选
状态大小写 导出按钮从未可达 R1 toUpperCase 归一(多处) 真机(PDF 链)✅

(#8/#9/#11/#13/#20/#27/#28 等=登记不修或后置,理由见 R4-pm.md 遗留池终审表与 FINAL-REPORT.md 第五节。)


4. 关键技术知识点(学习重点)

  1. 幂等键生命周期(request_key):发起前生成→失败保留→重试复用→成功作废;页面加载新实体(loadReport)时要清掉旧实体的键,否则跨版本操作撞 IDEMPOTENCY_CONFLICT。前后端契约:ChatRequest.request_key 描述即"相同键不会重复扣配额"。
  2. ArkTS @Builder 传参语义:原始类型按值传参不响应 @State;要响应式必须 $$ + 显式 interface 对象字面量(且 ArkTS 严格模式禁止裸对象字面量作类型声明——会报 arkts-no-obj-literals-as-types)。
  3. image kit 双口径:createImageSource(uri) 对 file://media/… 在部分真机 ROM 返回空(不是抛错!);官方稳妥路径是 fs.openSync(uri, READ_ONLY) + createImageSource(fd),fd 需调用方在 release 后关闭。getImageInfo() 在媒体 URI 上有整类"挂起不回调"风险(官方 FAQ 承认),能不用就不用、必须用就加超时。
  4. OHOS request.download 不能覆盖已存在文件(错误码 2300037)——下载前必须 accessSync→unlinkSync。
  5. install -r 不杀进程:验证新包前必须 aa force-stop + 重启,否则测的还是旧代码(用 ps -ef 的启动时间戳甄别)。
  6. 系统 picker 安全访问模式:合成点击(uitest)可能完全无效;picker 空结果收场后可能残留不可见蒙层吞输入,aa force-stop com.huawei.hmos.photos 恢复。详见真机指南。
  7. 后端纪律:Prompt 固定前缀不许含易变内容(缓存单价差 30 倍);DeepSeek 峰谷双价成本落库不重算;compliance fail-close(宁可不做标识也不伪造编号)。
  8. WeasyPrint PDF 元数据在压缩对象流里,测试断言要先 zlib 解压再 grep,裸字节 grep 不到。

5. 过程踩坑与复盘(下次怎么做得更好)

踩坑

坑 后果 对策
测试 agent 真机实操 58 张截图+几十个 dump 全进上下文 agent 上下文超限挂掉("Invalid string length") 接任 agent 只分析精选 16 张关键帧;dump json 只 grep 不整读;视觉结论必须与 dump 文本/源码交叉验证(视觉模型对中文文案有幻觉)
R3 测试执行中被外部打断 报告没写、checks.mjs 半成品 重派时明确"接续半成品:核对→补完→产出报告",半成品价值不浪费
真机 PIN 锁连续三轮挡住交互 只能装包+启动+hilog 第四轮用户解锁后一次清偿三轮欠账;环境依赖要早发现早向用户求助
主控热修后直接装机验证 测到旧进程行为,一度误判修复无效 流程固化:install → force-stop → start → 验证(已写进真机指南)
agent 汇报"全做了"与实际半成品 — 第二任开发 agent 先 git diff 核对半成品清单再动手,避免重复或漏做
Git Bash 路径转义吃掉 /data/local/tmp hdc file recv 找不到文件 export MSYS_NO_PATHCONV=1(详见真机指南)

做得好的

  • 门禁双保险(开发自跑+测试复跑)+ 静态检查资产滚动累积(7→12→19→24→28 项,每轮修复都固化成断言,防回归)
  • PM 裁决制:R3 起不再堆新问题,而是对遗留池逐条给"修/不修/排哪轮"+理由,5 轮收敛而不是发散
  • 开发 agent 被授权质疑 PM:如 R4 把 R3 的"配额 6 卡"修正为 4 卡(假象比缺卡更糟)、R5 PM 修正"报告页照片受影响"的事实错误——角色对抗出真知
  • 红线写在任务书里:换装/发型真实生成各 ≤1 次、对话 ≤2 条、不跑花钱 smoke——全程服务成本近零
  • 编号体系全局连续,第 5 轮能产出完整 40 项终审表,最终报告有据可查

复用这套流程

  1. CONTEXT.md 模板:项目结构/已验证命令/硬约束/角色权限——换项目只需改这一份
  2. 每轮 prompt 三段式:必读上下文(文件路径)→ 本轮主题与任务(含验收标准出处)→ 输出格式与落盘文件名
  3. 测试 agent 任务书必带:门禁复跑命令原文、静态检查脚本路径、"新问题喂下一轮 PM"的交接语义
  4. agent 挂掉的恢复策略:不重做,派"接任者"核对半成品→补完→出报告

6. 遗留事项(放量前)

  • 一分钟人工验收(热修两项):试衣间选图看预览即时显示;点「开始试穿」看生成进入
  • 评审会落 owner:#11 认同/修正+预约留资、#13 对话多轮上下文(M4-b)、FR-RPT-12 分享长图、FR-ACC-04 手机号登录、#20 即删背书、#27 结果图水印、#28 T18-REPORT
  • 环境债:BuildProfile 直引生成物路径偏脆(IDE 升级风险);picker 空结果蒙层(ROM 级,已记规避法)
  • WSL 后端/worker/PG/Redis 仍在运行(供真机联调,停法见 FINAL-REPORT.md 第七节)