Shopify 重回原生背后:当 Agent 成为开发主力,App 架构如何演进?

9 月 10 日,Shopify 发了一篇文章,宣布移动端开发将从 React Native 回到 Swift 和 Kotlin。很多讨论都集中在“跨端还是原生”上,但我更在意文章后半段关于 Agent 的一段复盘。
Shopify 说,模型改代码可能只需要几秒,控制模拟器、读取 Accessibility Tree、截图、确认结果却要几分钟。代码生成变快以后,团队发现最慢的环节已经变了:
“It doesn’t matter how good the model is if it can’t test its work quickly.”
模型再强,如果不能快速验证自己的工作,也很难独立完成复杂开发。
这和我最近做 Agenaxy 桌面端的感受很接近。过去我们谈高内聚、低耦合,主要是为了维护性、多人协作和跨平台复用。现在又多了一个很现实的理由:你需要让 Agent 在修改代码以后,尽快知道自己有没有把产品改坏。
代码生成变快,反馈循环开始决定效率
一次开发迭代的成本,从来不只有写代码:
理解问题 → 修改代码 → 启动环境 → 准备状态 → 执行操作 → 观察结果 → 判断对错
模型首先压缩的是“修改代码”这一步。剩下的步骤没有自动变快,反而更显眼了。
端应用尤其明显。Web 服务可以直接调用接口、读取日志、比较返回值;移动端和桌面端通常还要启动应用,进入某个页面,恢复特定状态,再通过模拟器或真实窗口操作。Agent 如果只能看截图、猜控件位置,或者反复读取庞大的无障碍树,它的反馈速度和可靠性都会迅速下降。
这意味着,评价一套面向 Agent 的工程体系,不能只看它一分钟能生成多少代码。更有意义的指标是:从代码发生变化,到系统给出可信的验证结果,需要多久;其中有多少步骤需要人接手;一次失败能否留下足够的信息,让 Agent 自己继续定位。
Shopify 这次重新评估技术栈,也是在新的成本模型下做选择。2020 年选择 React Native 时,共享实现能显著减少双端开发和功能对齐的成本。现在 Agent 已经能够参考 iOS 实现完成 Android 版本,或反过来工作;共享实现带来的收益相对下降,原生平台能力、第一方工具和更少的中间层重新占据更大权重。
React Native 并没有因此变差。发生变化的是“同一个功能写两遍”在总成本里的占比。
App 要同时允许人和 Agent 操作
Shopify 给出的解法很直接:把业务逻辑和 UI 完全解耦,让业务核心可以在桌面环境无界面运行,再通过 CLI 向 Agent 暴露状态、操作和导航。需要验证模拟器时,同一套命令还可以连接真实 UI,减少 Agent 对截图和 Accessibility Tree 的依赖。
这里的 CLI 超出了输入命令的便利性。它代表一种架构要求:App 的核心能力必须拥有独立于界面的调用方式,系统状态必须能够被机器准确读取,业务操作也必须有稳定的语义。
这并非全新的架构思想。六边形架构很早就提出,应用应当能够被图形界面、自动化测试、批处理程序或其他应用通过不同适配器驱动。Agent 让这条原则从“良好的可测试性”变成了高频使用的生产能力:原来偶尔由自动化测试调用的接口,现在也要承接 Agent 反复观察和操作系统的需求。
因此,“先做 CLI / SDK,再包 UI”更适合被理解为依赖方向,而不是固定的开发排期:
CLI、自动化测试、桌面 UI、移动 UI
↓ 依赖
稳定的 command / query 契约
↓ 访问
业务状态与行为
UI 仍然决定产品是否好用。只是它不再独占业务能力,也不应该成为业务事实唯一能够被观察的地方。
UI 是事实的投影,也是人的控制面
在 Agenaxy 的架构里,Engine 负责执行、权限、状态流转和事件记录,Workbench UI 负责展示这些事实,并把人的操作转成命令交给 Engine。界面可以保存输入草稿、滚动位置、面板开合等交互状态,任务是否正在运行、一次审批是否生效、某项操作是否完成,则必须由 Engine 给出答案。
我很看重这个边界,因为它同时解决了两个问题。
第一,核心逻辑可以脱离 UI 测试。复杂状态流转被拆成可判定的状态和信号,外部调用集中在执行层。Agent 可以快速枚举状态组合、执行动作、读取结果,不必为了验证一条业务规则启动整个桌面应用。
第二,UI 不会悄悄长出另一套业务状态。桌面端常见的问题是,前端为了体验先做了一份本地推断,后端又维护一份真实状态。正常路径看不出问题,遇到恢复、取消、后台任务或异常中断时,两边就可能分叉。让 UI 只消费事实投影,实际上也在减少 Agent 需要理解和修改的状态源数量。
Shopify 和 Agenaxy 在这里采用了相同的分层思路:核心逻辑可以脱离 UI 做无头测试,完整 App 也有自动化 E2E。核心测试为业务规则提供快速反馈,E2E 覆盖真实交互和端到端链路。两层配合,让 Agent 既能快速检查逻辑,也能回到实际运行的 App 验证结果。
核心逻辑可测以后,仍然要回到真实 UI
业务测试通过,只能证明核心规则大概率没有错。用户实际使用的 App 还包含窗口、构建后的前端、进程通信、原生系统能力和输入输出链路。按钮可能连错命令,状态可能没有刷新,文件选择和剪贴板可能在真实环境失效,布局也可能遮住一个本来可以点击的控件。
所以我后来又为 Agenaxy 做了真实桌面壳的 E2E。Agent 可以启动完整 App,读取压缩后的界面语义,执行点击、输入、选择、滚动和等待,也可以在需要时取得截图。测试经过真实 Tauri Shell、Workbench、进程通信和 Engine 生命周期,覆盖用户真正走过的路径。
这套 E2E 不把所有判断都压在截图上,而是把证据分成三层:
- 界面语义回答“用户现在看到了什么、可以操作什么”;
- Engine 的归一化事实回答“系统实际上执行了什么”;
- 截图用于检查布局、遮挡、样式和其他视觉问题,也为失败定位保留现场。
前两层适合稳定的自动比对。截图会受到字体、窗口、动画和系统控件影响,在 Agenaxy 里主要用于失败取证和视觉审阅,没有被包装成一套脆弱的像素真值。
这也是我对端应用测试分层的理解:能在核心层验证的规则,尽量在核心层快速验证;真正属于交互和视觉的问题,必须回到完整 App。两层都需要,运行频率和承担的判断不同。
MockServer 解决的是外部依赖成本
即使核心和 UI 都能自动测试,外部依赖仍然可能拖慢反馈循环。
Agenaxy 会连接不同的模型服务。每次回归都调用真实模型,会产生费用和等待时间,返回内容也有随机性。网络状态、账户额度和服务波动还会把测试失败变成一道归因题:究竟是代码错了,还是上游今天不稳定?
我的做法是,用 MockServer 录制一次经过真实 Provider 官方接口的 HTTP / SSE 交互,脱敏后保存成可审查的 fixture。后续测试离线回放这些响应,仍然经过真实 SDK、解析器、业务逻辑和完整桌面链路。
我把测试边界定为:模型响应回放,工具真实执行。
模型给出的下一步可以固定下来,但读文件、搜索、修改内容等本地行为继续真实发生。这样既能重复一次完整的 Agent 过程,也不会把业务执行一起替换成一套只存在于测试里的假实现。
MockServer 直接省下了重复调用的 API 费用,也把一个昂贵、缓慢、会变化的外部条件,变成可以重复使用的测试输入。Agent 修改上下文组装、工具协议或界面以后,可以重放同一组模型响应,比较变化前后的系统行为。失败也更容易定位,因为上游输出没有跟着变化。
这套思路可以自然延伸到 IoT
模型服务之外,硬件设备也是一种外部依赖,而且往往更难获得、更难并行,也更难稳定制造异常。
做 IoT 端应用时,App 处理的是一组有时间关系的业务状态:连接建立,命令下发,设备确认,状态上报,连接中断,重连后重新同步。真实设备当然要测,但如果每一次代码修改都必须等设备就位,Agent 的自主反馈循环很难成立。
因此可以在设备通信边界外包一层可控制的场景环境。固定几个返回值还不够,模拟环境需要覆盖设备状态随操作变化、延迟上报、超时、断连和乱序。Agent 在没有连接硬件时,也能启动一个业务场景,操作 App,读取状态并检查界面。
具体实现取决于设备接口。HTTP 链路可以使用 MockServer 或录制回放,MQTT 可以接测试 Broker;蓝牙、串口等能力更适合在协议适配层接入模拟设备。真实硬件联调仍然负责校准模拟行为,并验证射频、功耗、系统权限、固件差异等无法由软件完整复刻的部分。
以固件升级为例,可以把“升级到 60% 时断连,重新连接后从断点继续”固化成一个场景。HTTP MockServer 提供版本信息、固件清单和异常响应;设备模拟器通过蓝牙、串口等协议适配层上报电量、版本、ACK 和升级进度。App 的重连、幂等和断点恢复逻辑因此可以在没有硬件时反复回归。
模型和 IoT 设备看起来相距很远,对 App 架构提出的问题却很接近:外部世界能否被替换成一个可控环境,同时保持应用核心与真实运行路径不变。
共享层可能从代码上移到意图
Shopify 文章里还有一个值得继续推演的信号。他们发现,Agent 可以依据一个平台的实现完成另一个平台的版本,并通过共享的规格、测试和评审节点维持功能一致性。
沿着这个变化继续推演,跨平台开发长期维护的“共享层”可能会向上移动:
过去:Shared implementation → iOS + Android
未来:Shared specification / behavior / tests → Agent → Swift + Kotlin
我把它理解为从 shared code 走向 shared intent。这里的 intent 不是一句模糊需求,而是能够约束实现的规格、行为和测试:什么状态下允许执行什么动作,成功与失败如何呈现,取消以后哪些副作用必须停止,同一个业务结果在不同平台上需要保持哪些一致。
具体代码可以充分使用各自平台的原生能力。共享资产负责描述“必须一致的是什么”,Agent 负责实现和翻译,人通过评审节点决定结果是否可以接受。
这不表示共享代码会消失。业务模型、协议定义和适合跨端复用的模块仍然值得共享。变化在于,平台一致性不必只靠同一份实现来获得。当生成和迁移代码的成本下降,规格、行为测试和评审标准会承担更高的价值。
它也提出了一个新的治理问题:如果规格、实现和测试都由同一个 Agent 同时改写,错误很可能被一起固化。测试成为 source of truth 的前提,是它表达了经过确认的业务意图,并且修改测试本身需要独立审查。否则 Agent 只是在把错误的答案更新成新的基线。
给 Agent 修一条更短的路
面向 Agent 的端架构,可以先问几个问题:
- 核心业务能否脱离 UI、云服务和真实设备运行?
- Agent 能否准确读取状态、执行动作,并理解失败原因?
- 外部依赖能否在真实与可控环境之间切换?
- 核心测试通过后,Agent 能否继续进入真实 App 验证交互和视觉?
- 跨平台的一致性,是否有实现代码之外的规格和行为测试承接?
过去我们衡量端架构,常看性能、包体、开发效率和代码复用率。Agent 进入开发流程以后,还应该加上反馈时间、可自主验证范围和人工介入频率。
代码生成会继续变快。架构要让观察、判断和验证的速度跟上它。
这些思路也在 Agenaxy 的开发中持续接受检验。它是我正在开发的本地优先 AI 智能体工作台,欢迎到 Agenaxy 官网(agenaxy.ai) 了解产品或下载 Mac 版体验,也欢迎交流你在 Agent 开发和端架构上的实践。