前阵子跑 OpenGUI,在真机上试了一个长程任务:打开 X,搜索 mobile AI agents 相关的近期讨论,收集主要观点,再总结大家关心的问题。
用自然语言描述只有一句话,执行起来却拆成了几十个判断和动作。App 打开了吗?是不是首页?搜索框点中了吗?结果加载了吗?中间有没有登录弹窗?有没有推荐关注?页面跳走了是回退还是重试?
在传统手机自动化的思路里,这种任务很难稳定跑完。点击本身不难,麻烦的是真实手机不按脚本走。
为了验证这个判断,我用三种方案各跑了三次同一个任务。
纯脚本(Appium):三次全失败。一次卡在更新弹窗,两次搜索后页面结构变化导致 xpath 失效。平均存活 4 步。
VLM 截图循环(v2 Agent):三次里一次成功,耗时 18 分钟,中间在推荐关注弹窗上重试了 7 次。另外两次分别在第 12 步和第 23 步陷入循环:截图显示没变化,模型继续点同一个位置,再截图还是没变化。
OpenGUI:三次全部成功,平均耗时 11 分钟。最长一次遇到登录弹窗 + 网络加载慢 + 推荐关注,supervisor 做了两次重新规划,没有人工干预。
这组对比说明的不是 OpenGUI"更聪明",而是它把任务状态显式地维护在系统里,而不是依赖模型隐式地记住上下文。
传统手机自动化:假设世界会配合你
目前市面上主流的方案大致分三类:
录制回放,你在手机上操作一遍,工具把点击坐标、滑动轨迹、输入内容录下来,之后按原样回放。
UI 自动化框架,如 Appium、UIAutomator,通过 accessibility tree 或 xpath 定位元素,然后执行操作。
RPA 平台,可视化编排,把上述能力封装成流程图,加上条件判断和异常捕获。
这些方案在简单场景下都很好用。每天自动打卡、定时抢券、批量处理固定流程,只要页面不变,它们能跑得很稳。页面一变,或者流程稍长,问题就冒出来了。
v1:脚本的天敌是弹窗
录制回放是最直观的方案。你操作一遍,它记下来,下次照做。
拿那个 X 搜索的任务来说,录制下来的流程大概是:点击 X 图标,等待 3 秒,点击搜索框,输入 "mobile AI agents",点击搜索按钮,再等 3 秒,滑动浏览,截图保存。
这个脚本在理想情况下可以跑通,但现实世界不合作。打开 X 时弹了一个更新提醒,点击坐标就错位了。网络慢,3 秒不够,页面还在加载 skeleton。搜索前让你登录,脚本不知道这是哪一步。结果页中间插了一个推荐关注,滑动被拦截了。某个博主的内容需要点击"显示更多",但脚本没有这条分支。
脚本没有状态。每一步都预设了上一页的结果和当前页的状态,现实一偏离就报错退出,没有恢复能力。
v2:视觉理解让脚本变聪明一点,但没解决状态问题
坐标和 xpath 不可靠,能不能让机器看懂屏幕?
这是近一两年手机 Agent demo 的主流思路:截图,传给多模态模型(VLM),模型返回下一步动作,执行,再截图。
这个循环比脚本灵活。模型能看到当前屏幕,能识别搜索框在哪,甚至能处理一些弹窗。不需要预先定义坐标,告诉它"打开 X 搜索 mobile AI agents"就行。
问题是,VLM 每次只看当前这一张截图。短任务里没问题,三步之内打开 App、点一个按钮、输一段文字,模型通常能搞定。任务一长,短板就暴露了:
模型不知道前面做了什么,第十步失败时不知道要回退到第几步。它也不知道整体目标,只看当前截图,容易被局部最优带偏。看到一个不顺眼的 UI,可能会顺手"优化"一下,偏离主线。任务完成了,它可能还在继续点点点。
v2 解决了看懂屏幕的问题,但没有解决维护长程状态的问题。
更底层的问题是上下文窗口的硬限制。VLM 处理截图 + prompt 的 token 消耗很大,一张 1080p 截图编码后可能占 1000-3000 token。5-10 轮循环后,前面做了什么、最初的目标是什么,物理上就被挤出上下文了。这不是"忘了",是装不下了。
很多手机 Agent demo 停在 v2。三分钟的 demo 很惊艳,三十分钟的实际任务,大概率会在某个弹窗或加载状态上卡住,然后陷入截图、识别、点击、没变化、再截图的死循环。
v3:把目标变成状态流
OpenGUI 的做法是把任务放进一个有状态的后端 graph 里,而不是让模型在本地做一个无状态的截图循环。
看过源码,架构很清晰。核心链路大概是这样:
User/IM 命令 → Plan Supervisor → Executor Graph → Android Client → 真实设备
↑ ↓
└──── 执行结果 + 设备状态 ────┘
主 graph 在 mobile-agent.graph.ts 里构建,用的是 LangGraph 的 StateGraph:
const graph = new StateGraph(AgentStateSchema)
.addNode("supervisor", supervisorNode)
.addNode("extract_todo", extractTodoNode)
.addNode("fallback_extract", fallbackExtractNode)
.addNode("gui_executor", executorSubgraph)
.addNode("summarizer", summarizerNode)
.addEdge(START, "supervisor")
.addConditionalEdges("supervisor", routeAfterSupervisor)
.addConditionalEdges("extract_todo", routeAfterExtractTodo)
.addConditionalEdges("gui_executor", routeAfterExecutor)
.addEdge("summarizer", END);
Plan Supervisor 维护任务状态。复杂任务进来,supervisor 先拆成可执行的子任务,形成计划文档,然后逐个派发给 Executor。它本身也是一个 LLM agent,带有 write_todos 和 read_todos 两个 tool,可以动态调整任务列表。第一次调用时生成计划,后续调用时评估 Executor 回传的结果,决定标记完成、重试还是重新规划。
Supervisor 的路由逻辑很简单,但足够说明问题(routing.ts):
export function routeAfterExtractTodo(state: AgentState) {
if (state.isCancelled) return "summarizer";
if (state.planTodoComplete) return "summarizer";
if (state.todoFound) return "gui_executor";
return "fallback_extract"; // 用 Haiku 做兜底提取
}
export function routeAfterExecutor(state: AgentState) {
if (state.isCancelled) return "summarizer";
if (isExecutionConnectionLostMessage(state.executorOutput?.fail_reason)) {
return "summarizer"; // 设备断连,停止重试
}
return "supervisor"; // 把执行结果送回 supervisor 评估
}
Executor Graph 负责把子任务落到设备上,本身也是一个 subgraph。执行循环在 executor.graph.ts 里定义:
const subgraph = new StateGraph(AgentStateSchema)
.addNode("entry", entryNode)
.addNode("sense", senseNode, { retryPolicy: { maxAttempts: 3 } })
.addNode("vision_model", visionModelNode)
.addNode("parse_action", parseActionNode)
.addNode("execute_action", executeActionNode, { retryPolicy: { maxAttempts: 3 } })
.addNode("post_execute", postExecuteNode)
.addEdge(START, "entry")
.addEdge("entry", "sense")
.addEdge("sense", "vision_model")
.addConditionalEdges("vision_model", routeAfterVisionModel)
.addConditionalEdges("parse_action", routeByAction)
.addConditionalEdges("execute_action", routeAfterExecuteAction)
.addConditionalEdges("post_execute", routeAfterPostExecute);
Entry 节点初始化执行状态;Sense 节点从设备获取截图和当前 App 信息;Vision Model 节点把截图和上下文发给 VLM,获取下一步动作;Parse Action 节点把 VLM 的输出解析成结构化动作;Execute Action 节点通过 WebSocket 把动作发到 Android 设备执行;Post Execute 节点做异常检测(后面细说),然后决定回退到 Sense 继续循环,还是退出 subgraph。
Summarizer 在收尾时介入,把执行过程的关键信息整理成结构化结果返回给用户。
这几个组件的协作,让目标从一个 prompt 里的文字,变成了一整套可以被引用、暂停、恢复和清理的状态。
状态具体存在哪里?看 state.types.ts 里的 AgentStateSchema:
-
planDocument:supervisor 生成的计划文档(Markdown) -
executorInput/executorOutput:当前子任务的输入和输出 -
executor字段:Executor subgraph 的内部状态,包括截图 URI、当前预测、循环计数、异常标记、消息历史、token 用量等 -
todoFound/planTodoComplete:supervisor 决策用的布尔标志 -
isCancelled/isPaused:用户中断状态
这个状态不是存在模型上下文里,而是存在后端 graph 的 state 对象中,每步执行完都由 LangGraph 的 reducer 合并更新。
Android 端通过 WebSocket 和后端保持连接。StandbySocketManager.kt 负责设备待命,GestureService.kt 负责把动作执行到真实设备上。设备不是被脚本驱动的傀儡,而是一个持续反馈状态的 worker。
异常检测:executor 里的保险丝
v2 方案最容易出现的是"循环":截图没变化,模型继续点同一个位置,再截图还是没变化。OpenGUI 在 Post Execute 节点里做了显式的异常检测。
看 post-execute.node.ts 里的检测逻辑:
动作重复检测:检查最近 10 个动作里是否有连续 5 个相似动作(同类型 + 坐标距离小于 50 像素)。如果是,标记为可能循环。
动作周期检测:检查是否存在 A-B-A-B 的交替模式。比如点击返回再点进去,再点返回再点进去,模型在两个页面间来回跳。
截图异常检测:用 perceptual hash(pHash)比较最近几张截图。如果连续 3 张截图完全相同且动作不是 wait/scroll,说明页面没响应。如果截图呈现 A-B-A-B 的交替模式,说明页面在两个状态间切换。
连续滚动检测:连续 scroll 超过 8 次,判定当前搜索策略没有进展,强制退出 executor 让 supervisor 重新规划。
检测到异常后,Post Execute 节点会设置 needRemind = true,并在下一轮 Vision Model 调用时注入提醒:
const remindMessage = new HumanMessage(
`The current task may be stuck in a loop or drifting from the goal.
Execution anomaly: ${exec.remindReason}
Original task: ${instruction}
Check whether the execution is drifting from the original goal or stuck in a loop.`
);
这个设计的关键是:异常不是报错终止的理由,而是被消费掉的输入。检测到循环 → 注入提醒 → VLM 下一轮输出修正动作 → 继续执行。整个链路在系统内部闭环,不需要人工干预。
关键差异:状态管理
传统脚本没有状态,只有"下一步该做什么"。v2 的 Agent 也没有状态,只有"当前屏幕该怎么处理"。OpenGUI 的状态分布在计划文档、当前子任务、执行结果、失败分类这些数据结构里,supervisor 每一步都能基于完整状态做决策。
走迷宫的比喻很贴切:传统脚本手里只有一张路线图,走错一步就迷路。v2 Agent 每到一个路口抬头看四周,但记不住来时的路。OpenGUI 手里有一张实时更新的地图,知道自己在哪、目标在哪、哪条路试过不通。
另一个关键差异是模型角色的分离。v2 通常用一个模型做所有决策,OpenGUI 把规划、监督、VLM 执行拆到不同模型。
从 README 里的数据,全 Claude Opus 配置跑一个中等长度任务(约 60 次截图分析),VLM + Planner + Supervisor 全部用 Opus,预估费用在 $8-15 区间。换成 Qwen 3.6 Plus 做 Planner 和 Supervisor,Doubao Pro 做 VLM,同样任务降到 $0.6-1.2,成本差 10-15 倍。
这个成本差异来自两个因素:一是 Qwen/Doubao 的单价远低于 Opus,二是 OpenGUI 的架构允许不同角色用不同模型。Planner 和 Supervisor 处理的是文本计划,不需要多模态能力,可以用便宜的文本模型。只有 Executor 里的 VLM 需要看图,这部分费用被隔离在 subgraph 里。
一个具体的例子
X 搜索的任务在 OpenGUI 里会经历这些状态:
Plan Supervisor 先生成计划:打开 X,搜索关键词,浏览结果,收集观点,总结。然后派发子任务"打开 X"给 Executor。Executor 截图,VLM 判断当前是桌面还是其他 App,执行点击。结果回传:X 已打开,但弹出了登录框。Supervisor 判断这是异常,需要处理登录,无法处理就标记失败并尝试跳过。登录处理完,继续派发"搜索关键词"。Executor 执行搜索,网络慢页面没加载完,内部重试,等待再截图再判断。搜索完成,进入"浏览结果"子任务。中间遇到推荐关注弹窗,Executor 识别为干扰,尝试关闭或跳过。所有子任务完成,Supervisor 调用 Summarizer 生成结构化总结。
没有一个环节假设页面会按顺序走。每一步都基于当前真实状态判断,失败被当作正常输入消费,不是异常终止的理由。
执行完成后,Summarizer 返回的结果大致长这样:
## Task Summary
**Goal**: Search X for recent discussions on "mobile AI agents" and summarize key concerns.
**Execution**:
- Opened X app successfully
- Searched "mobile AI agents"
- Scrolled through top 20 results
- Collected 8 relevant posts/threads
**Key Findings**:
1. Privacy concerns dominate: users worried about screen recording and data access
2. Reliability: agents failing on non-standard UI patterns (custom keyboards, overlays)
3. Cost: VLM per-screenshot pricing makes long tasks expensive
4. Latency: 5-15s per action too slow for real-time interaction
**Blocked Items**:
- Login prompt appeared; task continued after handling
- One result required app switch to Safari; skipped per constraints
**Conclusion**: Mobile AI agents are technically feasible but face UX, cost, and trust hurdles before mainstream adoption.
代价是什么
这套设计更重,代价在三方面。
模型成本。VLM 每次分析截图都要调 API,一张 1080p 截图编码后可能占 1000-3000 token。一个 10 分钟的任务如果有 60 次截图分析,总 token 消耗可能在 15-30 万之间。全 Opus 配置下这是不可忽视的开销,混合模型配置能把费用压到可接受范围,但代价是模型能力的差异:Qwen 的规划质量不如 Opus,Doubao 的视觉理解在某些场景下会漏掉细节。
延迟。截图 → 后端 → VLM 推理 → 动作解码 → 网络传输 → 设备执行 → 等待 UI 稳定 → 再截图,这个链路单轮通常在 5-15 秒。一个 60 步的任务,纯等待时间就 5-15 分钟。v2 方案如果把 VLM 放在本地或近端可以更快,但 OpenGUI 的后端 graph 架构天然引入了一层网络跳转。对延迟敏感的任务(比如实时游戏辅助),这套架构不适用。
系统复杂度。你需要跑后端(NestJS + LangGraph)、数据库(PostgreSQL)、缓存(Redis)、WebSocket gateway,还要维护 Android 客户端的待命连接。部署一套 OpenGUI 比跑一个 Python 脚本重得多。设备会休眠,后台会被系统杀掉,WebSocket 会断线。standby 机制不是"连上就行",要处理心跳(35 秒间隔)、重连、状态同步。
但手机端很难绕开这些复杂度。真实 App 会弹窗,会卡加载,会误触,会把你带到一个完全不同的页面。只靠循环 prompt,稍微长一点的任务就会退化成截图版的 while true。
OpenGUI 把复杂度显式地放进系统里。一次没点对,变成 supervisor 要消费的执行结果。设备掉线,被 standby gateway 检测到并停止重试。任务跑了一半被暂停,可以在恢复时从当前子任务继续。这个设计更重,但它给了长程任务一个可以调试、可以恢复、可以复盘的位置。
Checkpoint:任务中断后能恢复
LangGraph 提供了 checkpointing 机制,OpenGUI 把它接进了 PostgreSQL(PostgresCheckpointerService)。这意味着:
- 任务执行到第 20 步,后端重启了,重启后可以从第 20 步的 checkpoint 继续,而不是从头来
- 用户手动暂停了任务,恢复时 supervisor 重新评估当前状态,决定继续执行还是重新规划
- 多个子任务共享同一个 thread ID,状态在 graph 节点之间持久化
这个功能对长程任务很关键。一个跑了两小时的任务,如果因为后端滚动更新而丢失全部进度,是不可接受的。checkpoint 把"状态在内存里"变成"状态在数据库里",牺牲了性能,换来了可靠性。
贤者时刻
自动化系统的能力边界,不是由"能执行什么动作"决定的,而是由"能维护多少状态"决定的。
脚本只能维护一步的状态(下一步做什么)。v2 Agent 能维护一轮的状态(当前屏幕怎么理解)。OpenGUI 维护的是整个任务生命周期的状态:计划、进度、异常、恢复。
Codex 的 /goal 在 coding agent 里做了类似的事:把目标从一轮对话里的文字,变成会话里可恢复的状态。OpenGUI 在手机端走了更远,它不仅保存目标,还把设备反馈、执行结果和失败处理接成了一条完整的状态流。场景不同,问题很接近:长程 agent 不能只会执行下一步,还要持续维护"我在哪、要去哪、边界是什么"这些信息。
如果你只是每天自动签到一次,脚本就够了。要让 AI 在真实手机上跑一个持续几十分钟、涉及多 App 切换和复杂判断的任务,就需要把目标从 prompt 里抽出来,变成一个可以被持续维护的状态。这个选择更重,但它是从 demo 走向 production 的必经之路。
参考
- OpenGUI 官网:https://opengui.ai
- OpenGUI 源码:https://github.com/Core-Mate/open-gui
- OpenAI Codex 0.128.0 release: https://github.com/openai/codex/releases/tag/rust-v0.128.0
- Simon Willison on Codex goals: https://simonwillison.net/2026/Apr/30/codex-goals/
United States
NORTH AMERICA
Related News
How Braze’s CTO is rethinking engineering for the agentic area
11h ago
Amazon Employees Are 'Tokenmaxxing' Due To Pressure To Use AI Tools
22h ago
KDE Receives $1.4 Million Investment From Sovereign Tech Fund
2h ago
Instagram’s new ‘Instants’ feature combines elements from Snapchat and BeReal
2h ago
Six Claude Code Skills That Close the AI Agent Feedback Loop
2h ago