最近项目中,我需要把一个 Android 项目的全部页面迁移到鸿蒙。目标不是重新设计,而是尽可能完整地复刻原项目的页面、UI、交互、状态和跳转。

实际效果比我预想得好:我只设置了一次总 Goal,中间没有继续指挥,Agent 持续执行了四天多。阶段结果里,鸿蒙界面的主体完成度已经接近九成。

一次 Goal 的执行记录

这里最重要的不是“四天”这个数字,而是:Agent 为什么能够在没有人持续补充指令的情况下,知道下一步该做什么,并且没有把大型迁移变成一堆不可追踪的改动?

先把两个概念说清楚:Harness Engineering 与 Loop Engineering

这两个词经常一起出现,但关注的层级不同。

  • Harness Engineering(执行环境工程):围绕一次 Agent 执行,为它准备工具、上下文、规则、权限、验证方式,以及“什么才算完成”。它解决的是:这一轮怎样做对?
  • Loop Engineering(循环工程):位于 Harness 的外层,负责反复启动任务、读取上轮状态、选择下一步、调用验证、记录结果,并决定继续还是停止。它解决的是:做完这一轮以后,下一轮怎样自动开始?

可以把它们理解成:Harness 是给 Agent 铺好的跑道和护栏,Loop 是让它一圈圈继续跑下去的调度系统。我的实践把两者结合在了一起:仓库里的规则、契约和验证组成 Harness;外部 Loop Runner 则重复驱动“读取状态 → 选择任务 → 实现 → 验证 → 记录 → 下一轮”。

先说结论:Loop 不等于重复调用模型

如果只是把同一句提示放进 while 循环,Agent 很快就会遇到几个问题:

  • 不知道整个项目还有多少页面;
  • 不知道当前页面做到什么程度;
  • 上一轮说“完成”,下一轮无法验证;
  • 上下文压缩或进程重启后,执行状态丢失;
  • 为了继续推进,可能开始猜测、简化,甚至把平台不支持的能力标成完成;
  • 大量改动堆在一起,一旦失败很难回滚。

我理解的 Harness,不是某一条神奇 Prompt,而是围绕模型搭建的一层工程环境:规则负责约束,契约负责定义完成,文件负责保存状态,工具负责验证,Git 负责提供恢复点。

Loop Harness 总体结构

模型仍然负责理解代码、做判断和实现迁移,但它不再只依赖对话记忆。每一轮都能从仓库中的文件重新得到三个答案:

  1. 总目标是什么;
  2. 当前做到哪里;
  3. 下一步应该选哪一项。

一、先用 AGENTS.md 固定总边界

这套工作流的入口是项目根目录下的 AGENTS.md。它不承担每个页面的全部细节,而是声明长期不变的边界:

  • Android 原项目是事实基准;
  • 默认只修改鸿蒙目标项目,不修改 Android 源项目;
  • 迁移前先判断任务属于 UI、功能、资源、数据还是权限;
  • 没有页面或功能契约时,不直接开始大规模编码;
  • 每个迁移任务完成后都要做二次审查;
  • 平台差异必须进入差异日志,不能只留在聊天记录里;
  • 禁止主观重设计,禁止只迁移 happy path;
  • 未验证或平台不支持的能力,不能标成已完成。

它更像仓库内的“执行宪法”。Goal 可以变化,页面可以增加,但这些边界在每一轮都成立。

二、把长规则拆成按任务读取的路由

我没有把所有要求都塞进一份超长提示,而是按迁移类型拆成多份规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
AGENTS.md
└── docs/migration/
    ├── README.md
    ├── ui-migration-rules.md
    ├── feature-migration-rules.md
    ├── resource-migration-rules.md
    ├── data-migration-rules.md
    ├── permission-migration-rules.md
    ├── review-rules.md
    ├── migration-status.md
    ├── difference-log.md
    └── templates/
        ├── page-contract-template.md
        └── feature-contract-template.md

Agent 先判断当前任务类型,再读取对应规则。迁移一个页面时,通常需要 UI、资源和功能规则;涉及本地存储或系统能力时,再加载数据和权限规则。

每份 Markdown 的职责尽量保持单一:

文件 主要职责
AGENTS.md 项目级总入口,声明事实基准、修改边界、执行顺序和禁止事项
README.md 迁移文档索引,告诉 Agent 不同任务应该继续读哪些规则
ui-migration-rules.md 约束布局、状态、交互、截图对照和视觉一致性
feature-migration-rules.md 约束功能入口、调用链、输入输出与成功/失败分支
resource-migration-rules.md 约束图片、图标、颜色、字符串、字体和资源复用
data-migration-rules.md 约束字段语义、默认值、缓存、排序、读写和异常数据
permission-migration-rules.md 约束权限申请时机、拒绝处理、系统能力和降级路径
review-rules.md 规定二次审查必须反向核对 Android 基准,并输出明确结论
migration-status.md 保存全局进度,供下一轮选择优先级最高的未完成任务
difference-log.md 持久记录无法完全等价的差异、原因、影响和处理决策
page-contract-template.md 为每个页面生成可检查的 UI 与交互标准
feature-contract-template.md 为每个功能生成输入、输出、分支和异常标准

迁移规则的文档路由

这样做有两个直接收益:

  • 减少无关上下文:当前任务只加载当前需要的规则;
  • 让约束可维护:发现遗漏时修改对应规则,而不是重新发明一条越来越长的总 Prompt。

三、先盘点,再按依赖关系决定迁移顺序

总 Goal 的第一阶段不是马上写 ArkTS,而是同时盘点两边的工程。

Android 侧需要找到的不只是 Activity 和 Fragment,还包括:

  • Layout;
  • Adapter / ViewHolder;
  • 自定义 View;
  • 弹窗和菜单;
  • 列表项;
  • 图片、图标、颜色、字符串和状态资源;
  • 页面入口、跳转关系以及相关功能调用链。

鸿蒙侧则要盘点已有页面、路由、组件、资源和目录结构。两份盘点结果合并成迁移总清单,写入 migration-status.md

迁移顺序也不是简单按文件名遍历,而是优先:

  1. 主入口和首页;
  2. 一级页面;
  3. 公共组件;
  4. 高复用资源;
  5. 被多个页面依赖的功能;
  6. 最后处理低频页面和平台差异较大的能力。

这样可以尽早打通主链路,也能减少同一组件被重复实现。

四、先给大模型一个“标准”,再让它开始实现

大模型其实并不知道我们口中的“完成”“还原”或“1:1”具体指什么。如果只说“把这个页面复刻出来”,它只能根据通用经验自行猜测:能显示主界面,也许就会被当成完成;至于空状态、按压态、错误分支、弹窗和返回行为,则很容易被遗漏。

所以关键不是要求模型自己领会标准,而是先把标准写出来。我采用的落地方式,是在页面实现前,根据统一模板建立一份页面迁移契约:

1
docs/migration/pages/<page-name>.md

契约至少记录:

  • Android 页面入口、源码、Layout、Adapter 和资源;
  • 默认、加载、空数据、错误、禁用、选中、按压、弹窗和菜单状态;
  • 鸿蒙页面文件、路由、组件拆分和状态管理;
  • 布局、间距、字号、颜色、图标、文案和动画要求;
  • 点击、长按、返回、刷新、重试、跳转和权限触发;
  • 已知平台差异;
  • 截图对照和验收清单。

页面迁移契约的组成

契约的价值是把“复刻原页面”拆成一组 Agent 可以逐项核对的事实。它既是实现说明,也是后续二次审查的输入。

功能迁移同样有独立契约,重点记录输入、输出、默认值、排序、过滤、缓存,以及成功、失败、取消、权限拒绝、设备不支持、重复执行和生命周期分支。

五、一个小例子:迁移“清理缓存”功能

假设要迁移一个很普通的“清理缓存”功能。用户点击按钮后,页面需要二次确认,执行时显示处理中状态,成功后刷新缓存大小;如果没有权限、清理失败或用户取消,还要有对应反馈。

没有这套工程时,常见的指令可能只有一句:“把 Android 的清理缓存功能迁到鸿蒙。”模型很可能先做出一个能点击的按钮,调用清理方法后弹出“完成”。主路径看起来通了,但二次确认、执行中禁用、取消、失败、重复点击和数据刷新都没有统一标准。下一轮模型也不知道上轮漏了什么,最后只能靠人逐项试出来再返工。

有了这套工作流后,执行方式会变成:

  1. 从 Android 代码反向列出入口、确认弹窗、调用链和全部结果分支;
  2. 在功能契约中写明输入、默认值、成功、失败、取消、无权限和重复点击行为;
  3. 在状态台账中把该功能从“契约中”推进到“迁移中”;
  4. 实现后按契约验证,二次审查再从 Android 基准反向核对;
  5. 发现平台 API 不同,就写入差异日志,而不是静默改掉行为;
  6. 验证通过后更新台账并提交 Git,Loop 再选择下一项。

没有工程约束与使用 Loop Harness 的功能迁移对比

同一个模型、同一个功能,区别不只是“Prompt 写得更长”,而是从模型自己猜什么叫完成,变成了仓库里已经存在可读取、可验证、可恢复的完成标准

六、用状态台账驱动下一轮,而不是依赖聊天记忆

migration-status.md 是整个循环的调度中心。页面和功能会在这些状态之间移动:

1
2
未开始 → 契约中 → 迁移中 → 待审查 → 通过 / 有条件通过
                                      └→ 不通过 → 继续修复

迁移状态循环

Agent 每轮都可以从台账中选择优先级最高、依赖已满足、尚未完成的对象。执行过程中同步更新状态,因此即使上下文被压缩,下一轮仍能从文件恢复现场。

这也是“人不在,AI 干活不能停”的基础:不是要求模型永远记住一切,而是让它随时能够重新读取事实并继续

七、差异日志不是失败记录,而是防止“假完成”的护栏

Android 和鸿蒙的控件、权限模型、生命周期和系统能力并不完全相同。要求百分之百使用相同技术方案并不现实,但业务意图和用户体验应尽可能等价。

我的处理方式是:

  1. 先寻找鸿蒙上的等价实现;
  2. 能迁移的部分继续推进,不因局部不确定而停住;
  3. 无法等价时写入 difference-log.md
  4. 记录 Android 基准、鸿蒙实现、原因、影响范围、决策和后续动作;
  5. 审查时给出“通过、条件通过或不通过”,不允许含糊地写“基本完成”。

差异进入持久台账后,Agent 就不需要为了维持“任务在前进”的表象而掩盖问题。

八、二次审查必须反向阅读 Android 原实现

第一次实现很容易受到当前鸿蒙代码的锚定。如果 Review 只看迁移后的代码,往往只能检查代码风格,很难发现“原项目里其实还有一个状态没迁”。

所以 review-rules.md 要求二次审查重新从 Android 侧开始:

  1. 重新阅读 Android 原实现,列出真实存在的元素、分支和状态;
  2. 对照鸿蒙实现逐项确认;
  3. 核对图片、图标、颜色和文案是否被近似资源替代;
  4. 核对点击、长按、返回、取消、确认、失败和重试;
  5. 核对差异是否有原因、影响和替代方案;
  6. 明确给出审查结论。

它不是普通的 Code Review,而是一次迁移一致性 Review

九、Git 阶段提交给长任务提供恢复点

四天级别的任务不能等到最后再提交。Agent 连续工作时间越长,未提交的改动就越多;一旦出现进程异常、误操作或工作区损坏,前面完成的代码就可能一起丢失。为此,我在 Goal 中明确要求:每完成一个阶段或一组页面,就进行一次 Git 提交。

阶段提交有四个作用:

  • 及时把已经验证的代码保存到版本历史,避免意外丢失;
  • 将大任务切成可以回滚的小批次;
  • 让后续审查能看到每组页面的修改边界;
  • Agent 中断或走错方向时,可以回到最近一个可信节点,而不是推翻四天的全部工作。

通常每组页面会经历:

1
2
3
4
5
6
7
迁移实现
  → 构建 / 检查 / 测试
  → 按 review-rules.md 二次审查
  → 修复审查问题
  → 更新状态与差异日志
  → Git 提交
  → 选择下一组页面

如果构建环境暂时不可用,也不能静默跳过:需要记录原因,并继续完成可做的静态审查。

十、我实际下发的 Goal 怎么写

这次 Goal 的核心不是一句“把 Android 项目迁到鸿蒙”,而是同时写清楚范围、执行规则、停止策略和完成标准。压缩后可以概括为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
目标:完整迁移 Android 原项目的全部页面到当前鸿蒙项目。

执行:
1. 先读 AGENTS.md 和迁移规则;
2. 自动盘点 Android 页面入口与鸿蒙现状;
3. 生成总清单,为每个页面建立迁移契约;
4. 按入口优先级和依赖顺序迁移;
5. UI、交互、状态和资源尽可能与 Android 一致;
6. 平台差异进入 difference-log.md,不阻塞其他可迁移内容;
7. 每组页面后构建、二次审查、修复并提交 Git;
8. 只有遇到真实外部阻塞才询问用户。

完成:
- 页面盘点完整;
- 每页有状态和契约;
- 可迁移页面全部实现;
- 差异全部记录;
- 二次审查完成;
- 输出最终迁移报告。

这里有一个很重要的措辞:不要因为缺截图、命名不确定或实现路径不确定而停止。 Agent 可以做合理假设,但必须记录依据;只有遇到无法通过代码、工具或其他任务绕开的真实外部阻塞,才安全停下来。

十一、为什么这套方式能工作

回头看,这套流程真正解决了长任务的五个问题:

1. 范围可枚举

先盘点全部页面和入口,任务不再是模糊的“迁完一个 App”,而是有限集合上的状态推进。

2. 完成可验证

契约里同时包含正常路径、异常路径、视觉状态和交互行为,避免把“能打开”当成“已迁移”。

3. 状态可恢复

迁移状态、审查结论和平台差异都写在仓库中,不依赖某一轮对话上下文。

4. 失败半径可控制

阶段构建、二次审查和 Git 提交让每组页面成为独立恢复点。

5. Agent 有自主推进权,但没有随意改目标的权力

它可以决定下一页如何实现、遇到差异先做哪一部分,却不能主观重设计、隐藏差异或把未验证能力标成完成。

这正是我对 Harness Engineering 的理解:不是让模型变得更自由,而是让它在清晰边界、持久状态和自动反馈中持续行动。

最后

这次实践让我感受最明显的一点是:大模型能不能长时间工作,关键不只在模型本身,更在模型外面有没有一套足够清晰的 Harness。

当目标、约束、状态、验证和恢复点都落到仓库里以后,Agent 才真正从“一次性回答问题”变成了“持续推进工程任务”。

一次 Goal,四天执行,界面主体接近九成。真正起作用的不是让 AI 不停生成代码,而是先把它工作的轨道铺好。

做到这里,我也在某种程度上重新理解了 AI:它不再只是靠一轮轮 Prompt 对话来干活,也可以发展成一套能够自循环的系统。这应该是一项长期工程。AI 是个人能力的放大器,有 Token,也有想法,个人真的可以做很多事情。

文中使用的工作流模板已经开源:android-harmonyos-loop-harness

有不了解 Harness 和 Loop 循环工程的,在这里推荐一篇文章,花叔写的 《Loop Engineering 橙皮书》,写得非常好。