<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fenix</title>
    <description>The latest articles on DEV Community by Fenix (@fenix_23505d14df386c00ced).</description>
    <link>https://dev.to/fenix_23505d14df386c00ced</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3823217%2F42aca0c2-69b9-4be7-b0f2-519a6b241dc4.jpg</url>
      <title>DEV Community: Fenix</title>
      <link>https://dev.to/fenix_23505d14df386c00ced</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fenix_23505d14df386c00ced"/>
    <language>en</language>
    <item>
      <title>手机自动化的演进：从脚本到状态流</title>
      <dc:creator>Fenix</dc:creator>
      <pubDate>Sun, 10 May 2026 08:48:48 +0000</pubDate>
      <link>https://dev.to/fenix_23505d14df386c00ced/shou-ji-zi-dong-hua-de-yan-jin-cong-jiao-ben-dao-zhuang-tai-liu-jbc</link>
      <guid>https://dev.to/fenix_23505d14df386c00ced/shou-ji-zi-dong-hua-de-yan-jin-cong-jiao-ben-dao-zhuang-tai-liu-jbc</guid>
      <description>&lt;p&gt;前阵子跑 OpenGUI，在真机上试了一个长程任务：打开 X，搜索 mobile AI agents 相关的近期讨论，收集主要观点，再总结大家关心的问题。&lt;/p&gt;

&lt;p&gt;用自然语言描述只有一句话，执行起来却拆成了几十个判断和动作。App 打开了吗？是不是首页？搜索框点中了吗？结果加载了吗？中间有没有登录弹窗？有没有推荐关注？页面跳走了是回退还是重试？&lt;/p&gt;

&lt;p&gt;在传统手机自动化的思路里，这种任务很难稳定跑完。点击本身不难，麻烦的是&lt;strong&gt;真实手机不按脚本走&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;为了验证这个判断，我用三种方案各跑了三次同一个任务。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;纯脚本（Appium）&lt;/strong&gt;：三次全失败。一次卡在更新弹窗，两次搜索后页面结构变化导致 xpath 失效。平均存活 4 步。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VLM 截图循环（v2 Agent）&lt;/strong&gt;：三次里一次成功，耗时 18 分钟，中间在推荐关注弹窗上重试了 7 次。另外两次分别在第 12 步和第 23 步陷入循环：截图显示没变化，模型继续点同一个位置，再截图还是没变化。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenGUI&lt;/strong&gt;：三次全部成功，平均耗时 11 分钟。最长一次遇到登录弹窗 + 网络加载慢 + 推荐关注，supervisor 做了两次重新规划，没有人工干预。&lt;/p&gt;

&lt;p&gt;这组对比说明的不是 OpenGUI"更聪明"，而是它把任务状态显式地维护在系统里，而不是依赖模型隐式地记住上下文。&lt;/p&gt;

&lt;h2&gt;
  
  
  传统手机自动化：假设世界会配合你
&lt;/h2&gt;

&lt;p&gt;目前市面上主流的方案大致分三类：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;录制回放&lt;/strong&gt;，你在手机上操作一遍，工具把点击坐标、滑动轨迹、输入内容录下来，之后按原样回放。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UI 自动化框架&lt;/strong&gt;，如 Appium、UIAutomator，通过 accessibility tree 或 xpath 定位元素，然后执行操作。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RPA 平台&lt;/strong&gt;，可视化编排，把上述能力封装成流程图，加上条件判断和异常捕获。&lt;/p&gt;

&lt;p&gt;这些方案在简单场景下都很好用。每天自动打卡、定时抢券、批量处理固定流程，只要页面不变，它们能跑得很稳。页面一变，或者流程稍长，问题就冒出来了。&lt;/p&gt;

&lt;h2&gt;
  
  
  v1：脚本的天敌是弹窗
&lt;/h2&gt;

&lt;p&gt;录制回放是最直观的方案。你操作一遍，它记下来，下次照做。&lt;/p&gt;

&lt;p&gt;拿那个 X 搜索的任务来说，录制下来的流程大概是：点击 X 图标，等待 3 秒，点击搜索框，输入 "mobile AI agents"，点击搜索按钮，再等 3 秒，滑动浏览，截图保存。&lt;/p&gt;

&lt;p&gt;这个脚本在理想情况下可以跑通，但现实世界不合作。打开 X 时弹了一个更新提醒，点击坐标就错位了。网络慢，3 秒不够，页面还在加载 skeleton。搜索前让你登录，脚本不知道这是哪一步。结果页中间插了一个推荐关注，滑动被拦截了。某个博主的内容需要点击"显示更多"，但脚本没有这条分支。&lt;/p&gt;

&lt;p&gt;脚本没有状态。每一步都预设了上一页的结果和当前页的状态，现实一偏离就报错退出，没有恢复能力。&lt;/p&gt;

&lt;h2&gt;
  
  
  v2：视觉理解让脚本变聪明一点，但没解决状态问题
&lt;/h2&gt;

&lt;p&gt;坐标和 xpath 不可靠，能不能让机器看懂屏幕？&lt;/p&gt;

&lt;p&gt;这是近一两年手机 Agent demo 的主流思路：截图，传给多模态模型（VLM），模型返回下一步动作，执行，再截图。&lt;/p&gt;

&lt;p&gt;这个循环比脚本灵活。模型能看到当前屏幕，能识别搜索框在哪，甚至能处理一些弹窗。不需要预先定义坐标，告诉它"打开 X 搜索 mobile AI agents"就行。&lt;/p&gt;

&lt;p&gt;问题是，VLM 每次只看当前这一张截图。短任务里没问题，三步之内打开 App、点一个按钮、输一段文字，模型通常能搞定。任务一长，短板就暴露了：&lt;/p&gt;

&lt;p&gt;模型不知道前面做了什么，第十步失败时不知道要回退到第几步。它也不知道整体目标，只看当前截图，容易被局部最优带偏。看到一个不顺眼的 UI，可能会顺手"优化"一下，偏离主线。任务完成了，它可能还在继续点点点。&lt;/p&gt;

&lt;p&gt;v2 解决了看懂屏幕的问题，但没有解决维护长程状态的问题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;更底层的问题是上下文窗口的硬限制&lt;/strong&gt;。VLM 处理截图 + prompt 的 token 消耗很大，一张 1080p 截图编码后可能占 1000-3000 token。5-10 轮循环后，前面做了什么、最初的目标是什么，物理上就被挤出上下文了。这不是"忘了"，是装不下了。&lt;/p&gt;

&lt;p&gt;很多手机 Agent demo 停在 v2。三分钟的 demo 很惊艳，三十分钟的实际任务，大概率会在某个弹窗或加载状态上卡住，然后陷入截图、识别、点击、没变化、再截图的死循环。&lt;/p&gt;

&lt;h2&gt;
  
  
  v3：把目标变成状态流
&lt;/h2&gt;

&lt;p&gt;OpenGUI 的做法是把任务放进一个有状态的后端 graph 里，而不是让模型在本地做一个无状态的截图循环。&lt;/p&gt;

&lt;p&gt;看过源码，架构很清晰。核心链路大概是这样：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User/IM 命令 → Plan Supervisor → Executor Graph → Android Client → 真实设备
                      ↑                ↓
                      └──── 执行结果 + 设备状态 ────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;主 graph 在 &lt;code&gt;mobile-agent.graph.ts&lt;/code&gt; 里构建，用的是 LangGraph 的 StateGraph：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AgentStateSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supervisorNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extract_todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extractTodoNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback_extract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallbackExtractNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gui_executor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;executorSubgraph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;summarizerNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterSupervisor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extract_todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterExtractTodo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gui_executor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterExecutor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;END&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Plan Supervisor&lt;/strong&gt; 维护任务状态。复杂任务进来，supervisor 先拆成可执行的子任务，形成计划文档，然后逐个派发给 Executor。它本身也是一个 LLM agent，带有 &lt;code&gt;write_todos&lt;/code&gt; 和 &lt;code&gt;read_todos&lt;/code&gt; 两个 tool，可以动态调整任务列表。第一次调用时生成计划，后续调用时评估 Executor 回传的结果，决定标记完成、重试还是重新规划。&lt;/p&gt;

&lt;p&gt;Supervisor 的路由逻辑很简单，但足够说明问题（&lt;code&gt;routing.ts&lt;/code&gt;）：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;routeAfterExtractTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;planTodoComplete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todoFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gui_executor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback_extract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 用 Haiku 做兜底提取&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;routeAfterExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isExecutionConnectionLostMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executorOutput&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;fail_reason&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 设备断连，停止重试&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 把执行结果送回 supervisor 评估&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Executor Graph&lt;/strong&gt; 负责把子任务落到设备上，本身也是一个 subgraph。执行循环在 &lt;code&gt;executor.graph.ts&lt;/code&gt; 里定义：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subgraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AgentStateSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sense&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;senseNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;retryPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vision_model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;visionModelNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parse_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parseActionNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;execute_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;executeActionNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;retryPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post_execute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postExecuteNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sense&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sense&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vision_model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vision_model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterVisionModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parse_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeByAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;execute_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterExecuteAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post_execute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterPostExecute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entry 节点初始化执行状态；Sense 节点从设备获取截图和当前 App 信息；Vision Model 节点把截图和上下文发给 VLM，获取下一步动作；Parse Action 节点把 VLM 的输出解析成结构化动作；Execute Action 节点通过 WebSocket 把动作发到 Android 设备执行；Post Execute 节点做异常检测（后面细说），然后决定回退到 Sense 继续循环，还是退出 subgraph。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summarizer&lt;/strong&gt; 在收尾时介入，把执行过程的关键信息整理成结构化结果返回给用户。&lt;/p&gt;

&lt;p&gt;这几个组件的协作，让目标从一个 prompt 里的文字，变成了一整套可以被引用、暂停、恢复和清理的状态。&lt;/p&gt;

&lt;p&gt;状态具体存在哪里？看 &lt;code&gt;state.types.ts&lt;/code&gt; 里的 &lt;code&gt;AgentStateSchema&lt;/code&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;planDocument&lt;/code&gt;：supervisor 生成的计划文档（Markdown）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;executorInput&lt;/code&gt; / &lt;code&gt;executorOutput&lt;/code&gt;：当前子任务的输入和输出&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;executor&lt;/code&gt; 字段：Executor subgraph 的内部状态，包括截图 URI、当前预测、循环计数、异常标记、消息历史、token 用量等&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;todoFound&lt;/code&gt; / &lt;code&gt;planTodoComplete&lt;/code&gt;：supervisor 决策用的布尔标志&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isCancelled&lt;/code&gt; / &lt;code&gt;isPaused&lt;/code&gt;：用户中断状态&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个状态不是存在模型上下文里，而是存在后端 graph 的 state 对象中，每步执行完都由 LangGraph 的 reducer 合并更新。&lt;/p&gt;

&lt;p&gt;Android 端通过 WebSocket 和后端保持连接。&lt;code&gt;StandbySocketManager.kt&lt;/code&gt; 负责设备待命，&lt;code&gt;GestureService.kt&lt;/code&gt; 负责把动作执行到真实设备上。设备不是被脚本驱动的傀儡，而是一个持续反馈状态的 worker。&lt;/p&gt;

&lt;h2&gt;
  
  
  异常检测：executor 里的保险丝
&lt;/h2&gt;

&lt;p&gt;v2 方案最容易出现的是"循环"：截图没变化，模型继续点同一个位置，再截图还是没变化。OpenGUI 在 Post Execute 节点里做了显式的异常检测。&lt;/p&gt;

&lt;p&gt;看 &lt;code&gt;post-execute.node.ts&lt;/code&gt; 里的检测逻辑：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;动作重复检测&lt;/strong&gt;：检查最近 10 个动作里是否有连续 5 个相似动作（同类型 + 坐标距离小于 50 像素）。如果是，标记为可能循环。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;动作周期检测&lt;/strong&gt;：检查是否存在 A-B-A-B 的交替模式。比如点击返回再点进去，再点返回再点进去，模型在两个页面间来回跳。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;截图异常检测&lt;/strong&gt;：用 perceptual hash（pHash）比较最近几张截图。如果连续 3 张截图完全相同且动作不是 wait/scroll，说明页面没响应。如果截图呈现 A-B-A-B 的交替模式，说明页面在两个状态间切换。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;连续滚动检测&lt;/strong&gt;：连续 scroll 超过 8 次，判定当前搜索策略没有进展，强制退出 executor 让 supervisor 重新规划。&lt;/p&gt;

&lt;p&gt;检测到异常后，Post Execute 节点会设置 &lt;code&gt;needRemind = true&lt;/code&gt;，并在下一轮 Vision Model 调用时注入提醒：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remindMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`The current task may be stuck in a loop or drifting from the goal.
Execution anomaly: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remindReason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Original task: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Check whether the execution is drifting from the original goal or stuck in a loop.`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这个设计的关键是：&lt;strong&gt;异常不是报错终止的理由，而是被消费掉的输入&lt;/strong&gt;。检测到循环 → 注入提醒 → VLM 下一轮输出修正动作 → 继续执行。整个链路在系统内部闭环，不需要人工干预。&lt;/p&gt;

&lt;h2&gt;
  
  
  关键差异：状态管理
&lt;/h2&gt;

&lt;p&gt;传统脚本没有状态，只有"下一步该做什么"。v2 的 Agent 也没有状态，只有"当前屏幕该怎么处理"。OpenGUI 的状态分布在计划文档、当前子任务、执行结果、失败分类这些数据结构里，supervisor 每一步都能基于完整状态做决策。&lt;/p&gt;

&lt;p&gt;走迷宫的比喻很贴切：传统脚本手里只有一张路线图，走错一步就迷路。v2 Agent 每到一个路口抬头看四周，但记不住来时的路。OpenGUI 手里有一张实时更新的地图，知道自己在哪、目标在哪、哪条路试过不通。&lt;/p&gt;

&lt;p&gt;另一个关键差异是&lt;strong&gt;模型角色的分离&lt;/strong&gt;。v2 通常用一个模型做所有决策，OpenGUI 把规划、监督、VLM 执行拆到不同模型。&lt;/p&gt;

&lt;p&gt;从 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 倍。&lt;/p&gt;

&lt;p&gt;这个成本差异来自两个因素：一是 Qwen/Doubao 的单价远低于 Opus，二是 OpenGUI 的架构允许不同角色用不同模型。Planner 和 Supervisor 处理的是文本计划，不需要多模态能力，可以用便宜的文本模型。只有 Executor 里的 VLM 需要看图，这部分费用被隔离在 subgraph 里。&lt;/p&gt;

&lt;h2&gt;
  
  
  一个具体的例子
&lt;/h2&gt;

&lt;p&gt;X 搜索的任务在 OpenGUI 里会经历这些状态：&lt;/p&gt;

&lt;p&gt;Plan Supervisor 先生成计划：打开 X，搜索关键词，浏览结果，收集观点，总结。然后派发子任务"打开 X"给 Executor。Executor 截图，VLM 判断当前是桌面还是其他 App，执行点击。结果回传：X 已打开，但弹出了登录框。Supervisor 判断这是异常，需要处理登录，无法处理就标记失败并尝试跳过。登录处理完，继续派发"搜索关键词"。Executor 执行搜索，网络慢页面没加载完，内部重试，等待再截图再判断。搜索完成，进入"浏览结果"子任务。中间遇到推荐关注弹窗，Executor 识别为干扰，尝试关闭或跳过。所有子任务完成，Supervisor 调用 Summarizer 生成结构化总结。&lt;/p&gt;

&lt;p&gt;没有一个环节假设页面会按顺序走。每一步都基于当前真实状态判断，失败被当作正常输入消费，不是异常终止的理由。&lt;/p&gt;

&lt;p&gt;执行完成后，Summarizer 返回的结果大致长这样：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Task Summary&lt;/span&gt;

&lt;span class="gs"&gt;**Goal**&lt;/span&gt;: Search X for recent discussions on "mobile AI agents" and summarize key concerns.

&lt;span class="gs"&gt;**Execution**&lt;/span&gt;: 
&lt;span class="p"&gt;-&lt;/span&gt; Opened X app successfully
&lt;span class="p"&gt;-&lt;/span&gt; Searched "mobile AI agents" 
&lt;span class="p"&gt;-&lt;/span&gt; Scrolled through top 20 results
&lt;span class="p"&gt;-&lt;/span&gt; Collected 8 relevant posts/threads

&lt;span class="gs"&gt;**Key Findings**&lt;/span&gt;:
&lt;span class="p"&gt;1.&lt;/span&gt; Privacy concerns dominate: users worried about screen recording and data access
&lt;span class="p"&gt;2.&lt;/span&gt; Reliability: agents failing on non-standard UI patterns (custom keyboards, overlays)
&lt;span class="p"&gt;3.&lt;/span&gt; Cost: VLM per-screenshot pricing makes long tasks expensive
&lt;span class="p"&gt;4.&lt;/span&gt; Latency: 5-15s per action too slow for real-time interaction

&lt;span class="gs"&gt;**Blocked Items**&lt;/span&gt;:
&lt;span class="p"&gt;-&lt;/span&gt; Login prompt appeared; task continued after handling
&lt;span class="p"&gt;-&lt;/span&gt; One result required app switch to Safari; skipped per constraints

&lt;span class="gs"&gt;**Conclusion**&lt;/span&gt;: Mobile AI agents are technically feasible but face UX, cost, and trust hurdles before mainstream adoption.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  代价是什么
&lt;/h2&gt;

&lt;p&gt;这套设计更重，代价在三方面。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;模型成本&lt;/strong&gt;。VLM 每次分析截图都要调 API，一张 1080p 截图编码后可能占 1000-3000 token。一个 10 分钟的任务如果有 60 次截图分析，总 token 消耗可能在 15-30 万之间。全 Opus 配置下这是不可忽视的开销，混合模型配置能把费用压到可接受范围，但代价是模型能力的差异：Qwen 的规划质量不如 Opus，Doubao 的视觉理解在某些场景下会漏掉细节。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;延迟&lt;/strong&gt;。截图 → 后端 → VLM 推理 → 动作解码 → 网络传输 → 设备执行 → 等待 UI 稳定 → 再截图，这个链路单轮通常在 5-15 秒。一个 60 步的任务，纯等待时间就 5-15 分钟。v2 方案如果把 VLM 放在本地或近端可以更快，但 OpenGUI 的后端 graph 架构天然引入了一层网络跳转。对延迟敏感的任务（比如实时游戏辅助），这套架构不适用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;系统复杂度&lt;/strong&gt;。你需要跑后端（NestJS + LangGraph）、数据库（PostgreSQL）、缓存（Redis）、WebSocket gateway，还要维护 Android 客户端的待命连接。部署一套 OpenGUI 比跑一个 Python 脚本重得多。设备会休眠，后台会被系统杀掉，WebSocket 会断线。standby 机制不是"连上就行"，要处理心跳（35 秒间隔）、重连、状态同步。&lt;/p&gt;

&lt;p&gt;但手机端很难绕开这些复杂度。真实 App 会弹窗，会卡加载，会误触，会把你带到一个完全不同的页面。只靠循环 prompt，稍微长一点的任务就会退化成截图版的 &lt;code&gt;while true&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;OpenGUI 把复杂度显式地放进系统里。一次没点对，变成 supervisor 要消费的执行结果。设备掉线，被 standby gateway 检测到并停止重试。任务跑了一半被暂停，可以在恢复时从当前子任务继续。这个设计更重，但它给了长程任务一个可以调试、可以恢复、可以复盘的位置。&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkpoint：任务中断后能恢复
&lt;/h2&gt;

&lt;p&gt;LangGraph 提供了 checkpointing 机制，OpenGUI 把它接进了 PostgreSQL（&lt;code&gt;PostgresCheckpointerService&lt;/code&gt;）。这意味着：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;任务执行到第 20 步，后端重启了，重启后可以从第 20 步的 checkpoint 继续，而不是从头来&lt;/li&gt;
&lt;li&gt;用户手动暂停了任务，恢复时 supervisor 重新评估当前状态，决定继续执行还是重新规划&lt;/li&gt;
&lt;li&gt;多个子任务共享同一个 thread ID，状态在 graph 节点之间持久化&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个功能对长程任务很关键。一个跑了两小时的任务，如果因为后端滚动更新而丢失全部进度，是不可接受的。checkpoint 把"状态在内存里"变成"状态在数据库里"，牺牲了性能，换来了可靠性。&lt;/p&gt;

&lt;h2&gt;
  
  
  贤者时刻
&lt;/h2&gt;

&lt;p&gt;自动化系统的能力边界，不是由"能执行什么动作"决定的，而是由"能维护多少状态"决定的。&lt;/p&gt;

&lt;p&gt;脚本只能维护一步的状态（下一步做什么）。v2 Agent 能维护一轮的状态（当前屏幕怎么理解）。OpenGUI 维护的是整个任务生命周期的状态：计划、进度、异常、恢复。&lt;/p&gt;

&lt;p&gt;Codex 的 &lt;code&gt;/goal&lt;/code&gt; 在 coding agent 里做了类似的事：把目标从一轮对话里的文字，变成会话里可恢复的状态。OpenGUI 在手机端走了更远，它不仅保存目标，还把设备反馈、执行结果和失败处理接成了一条完整的状态流。场景不同，问题很接近：长程 agent 不能只会执行下一步，还要持续维护"我在哪、要去哪、边界是什么"这些信息。&lt;/p&gt;

&lt;p&gt;如果你只是每天自动签到一次，脚本就够了。要让 AI 在真实手机上跑一个持续几十分钟、涉及多 App 切换和复杂判断的任务，就需要把目标从 prompt 里抽出来，变成一个可以被持续维护的状态。这个选择更重，但它是从 demo 走向 production 的必经之路。&lt;/p&gt;

&lt;h2&gt;
  
  
  参考
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenGUI 官网：&lt;a href="https://opengui.ai" rel="noopener noreferrer"&gt;https://opengui.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenGUI 源码：&lt;a href="https://github.com/Core-Mate/open-gui" rel="noopener noreferrer"&gt;https://github.com/Core-Mate/open-gui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI Codex 0.128.0 release: &lt;a href="https://github.com/openai/codex/releases/tag/rust-v0.128.0" rel="noopener noreferrer"&gt;https://github.com/openai/codex/releases/tag/rust-v0.128.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Simon Willison on Codex goals: &lt;a href="https://simonwillison.net/2026/Apr/30/codex-goals/" rel="noopener noreferrer"&gt;https://simonwillison.net/2026/Apr/30/codex-goals/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
      <category>mobile</category>
    </item>
    <item>
      <title>The Evolution of Mobile Automation: From Scripts to State Flows</title>
      <dc:creator>Fenix</dc:creator>
      <pubDate>Sun, 10 May 2026 08:48:15 +0000</pubDate>
      <link>https://dev.to/fenix_23505d14df386c00ced/the-evolution-of-mobile-automation-from-scripts-to-state-flows-6b3</link>
      <guid>https://dev.to/fenix_23505d14df386c00ced/the-evolution-of-mobile-automation-from-scripts-to-state-flows-6b3</guid>
      <description>&lt;p&gt;I spent some time with OpenGUI recently, running a long-haul task on a real phone: open X, search for recent discussions on mobile AI agents, collect the main viewpoints, and summarize what people care about.&lt;/p&gt;

&lt;p&gt;The task is one sentence in plain English. The execution breaks into dozens of judgments and actions. Is the app open? Are we on the home screen? Did the tap hit the search box? Is the result page loaded? Did a login popup appear? A recommended-follow modal? Did the page navigate away, and should we go back or retry?&lt;/p&gt;

&lt;p&gt;Traditional mobile automation struggles with this kind of task. Not because tapping is hard, but because &lt;strong&gt;real phones don't follow scripts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To test this, I ran the same task three times with three different setups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure script (Appium)&lt;/strong&gt;: Failed all three times. Once stuck on an update dialog, twice the search results page changed its xpath. Average survival: 4 steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VLM screenshot loop (v2 Agent)&lt;/strong&gt;: One success out of three, taking 18 minutes. It retry-tapped a recommended-follow modal 7 times. The other two runs got stuck in loops at step 12 and step 23. The screenshot showed no change, the model tapped the same spot again, the screenshot still showed no change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenGUI&lt;/strong&gt;: All three succeeded, averaging 11 minutes. The longest run hit a login popup, slow network loading, and a recommended-follow modal. The supervisor replanned twice. No human intervention.&lt;/p&gt;

&lt;p&gt;This doesn't mean OpenGUI is "smarter". It means the system maintains task state explicitly, rather than relying on the model to remember context implicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional Mobile Automation: Assuming the World Cooperates
&lt;/h2&gt;

&lt;p&gt;Three mainstream approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Record and replay&lt;/strong&gt;. You operate the phone once, the tool records tap coordinates, swipe trajectories, and input text, then replays them verbatim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UI automation frameworks&lt;/strong&gt; like Appium and UIAutomator. They locate elements via accessibility tree or xpath, then perform actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RPA platforms&lt;/strong&gt;. Visual workflow builders that wrap the above into flowcharts, with conditional branches and exception handlers.&lt;/p&gt;

&lt;p&gt;These work fine for simple jobs. Daily check-ins, timed coupon grabs, batch processing of fixed flows. As long as the page doesn't change, they run reliably. Change the page, or stretch the flow, and things break.&lt;/p&gt;

&lt;h2&gt;
  
  
  v1: Popups Are the Enemy of Scripts
&lt;/h2&gt;

&lt;p&gt;Record-and-replay is the most intuitive. You do it once, it remembers, it replays.&lt;/p&gt;

&lt;p&gt;For that X search task, the recorded flow looks like: tap X icon, wait 3 seconds, tap search box, type "mobile AI agents", tap search button, wait 3 seconds, scroll, screenshot.&lt;/p&gt;

&lt;p&gt;This works in an ideal world. The real world doesn't cooperate. An update dialog shifts tap coordinates. Slow network means 3 seconds isn't enough; the skeleton is still showing. Login is required before search, and the script has no concept of where it is. A recommended-follow modal intercepts the scroll. A blogger's content needs a "show more" tap that the script never recorded.&lt;/p&gt;

&lt;p&gt;Scripts have no state. Every step assumes the previous page's result and the current page's structure. Reality deviates, the script errors out. No recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  v2: Visual Understanding Makes Scripts Smarter, But Doesn't Solve State
&lt;/h2&gt;

&lt;p&gt;Coordinates and xpath are brittle. Can the machine just read the screen?&lt;/p&gt;

&lt;p&gt;This is the dominant approach for mobile agent demos in the past couple years: screenshot, feed to a VLM, model returns the next action, execute, screenshot again.&lt;/p&gt;

&lt;p&gt;The loop is more flexible than scripts. The model sees the current screen, identifies where the search box is, handles some popups. No predefined coordinates needed. Just tell it "open X and search for mobile AI agents".&lt;/p&gt;

&lt;p&gt;The problem: the VLM only sees the current screenshot. Short tasks are fine. Three steps to open an app, tap a button, type some text. The model usually handles it. Stretch the task, and the cracks show.&lt;/p&gt;

&lt;p&gt;The model doesn't know what came before. Step 10 fails, it doesn't know whether to backtrack to step 3 or step 7. It doesn't know the overall goal. Looking only at the current screenshot, it drifts toward local optima. It sees a UI element that looks off and "optimizes" it, deviating from the main task. The task is done, it keeps tapping.&lt;/p&gt;

&lt;p&gt;v2 solves screen reading. It doesn't solve long-horizon state maintenance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The deeper problem is context window limits&lt;/strong&gt;. A VLM processing a screenshot + prompt burns tokens fast. A 1080p screenshot encodes to 1000-3000 tokens. After 5-10 loops, what came before and what the original goal was gets physically evicted from context. This isn't "forgetting". It's eviction.&lt;/p&gt;

&lt;p&gt;Many mobile agent demos stop at v2. Three-minute demos are impressive. Thirty-minute real tasks usually get stuck on a popup or loading state, then fall into the screenshot-recognize-tap-no-change-screenshot death loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  v3: Turn the Goal Into a State Flow
&lt;/h2&gt;

&lt;p&gt;OpenGUI puts the task into a stateful backend graph instead of letting the model run a stateless screenshot loop locally.&lt;/p&gt;

&lt;p&gt;The architecture is clean. The core flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User/IM command → Plan Supervisor → Executor Graph → Android Client → Real device
                       ↑                ↓
                       └─── Execution result + device state ───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main graph lives in &lt;code&gt;mobile-agent.graph.ts&lt;/code&gt;, built with LangGraph's StateGraph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AgentStateSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supervisorNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extract_todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extractTodoNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback_extract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallbackExtractNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gui_executor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;executorSubgraph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;summarizerNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterSupervisor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extract_todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterExtractTodo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gui_executor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterExecutor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;END&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Plan Supervisor&lt;/strong&gt; maintains task state. A complex task comes in, the supervisor breaks it into executable subtasks, forms a plan document, then dispatches them one by one to the Executor. It's itself an LLM agent with &lt;code&gt;write_todos&lt;/code&gt; and &lt;code&gt;read_todos&lt;/code&gt; tools, so it can adjust the task list dynamically. First call generates the plan; subsequent calls evaluate the Executor's returned results, deciding whether to mark complete, retry, or replan.&lt;/p&gt;

&lt;p&gt;The routing logic is simple but telling (&lt;code&gt;routing.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;routeAfterExtractTodo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;planTodoComplete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todoFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gui_executor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback_extract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Haiku fallback extraction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;routeAfterExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isExecutionConnectionLostMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executorOutput&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;fail_reason&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summarizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Device disconnected, stop retrying&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;supervisor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Send execution result back for evaluation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Executor Graph&lt;/strong&gt; handles the actual device interaction, itself a subgraph. The execution loop is defined in &lt;code&gt;executor.graph.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subgraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AgentStateSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sense&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;senseNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;retryPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vision_model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;visionModelNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parse_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parseActionNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;execute_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;executeActionNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;retryPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post_execute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;postExecuteNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sense&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sense&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vision_model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vision_model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterVisionModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parse_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeByAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;execute_action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterExecuteAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post_execute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routeAfterPostExecute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entry initializes execution state. Sense pulls the screenshot and current app info from the device. Vision Model sends the screenshot and context to the VLM for the next action. Parse Action turns the VLM's output into structured actions. Execute Action sends actions to the Android device over WebSocket. Post Execute runs anomaly detection (more on this below), then decides whether to loop back to Sense or exit the subgraph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summarizer&lt;/strong&gt; steps in at the end, packaging key execution info into structured results for the user.&lt;/p&gt;

&lt;p&gt;This collaboration turns the goal from a string in a prompt into a full system that can be referenced, paused, resumed, and cleaned up.&lt;/p&gt;

&lt;p&gt;Where does the state live? Look at &lt;code&gt;state.types.ts&lt;/code&gt;, &lt;code&gt;AgentStateSchema&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;planDocument&lt;/code&gt;: the supervisor-generated plan (Markdown)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;executorInput&lt;/code&gt; / &lt;code&gt;executorOutput&lt;/code&gt;: current subtask input and output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;executor&lt;/code&gt;: internal Executor subgraph state, including screenshot URI, current prediction, loop count, anomaly flags, message history, token usage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;todoFound&lt;/code&gt; / &lt;code&gt;planTodoComplete&lt;/code&gt;: boolean flags for supervisor decisions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isCancelled&lt;/code&gt; / &lt;code&gt;isPaused&lt;/code&gt;: user interrupt state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This state doesn't live in the model's context. It lives in the backend graph's state object, updated by LangGraph's reducer after every step.&lt;/p&gt;

&lt;p&gt;The Android side maintains a WebSocket connection to the backend. &lt;code&gt;StandbySocketManager.kt&lt;/code&gt; handles device standby. &lt;code&gt;GestureService.kt&lt;/code&gt; executes actions on the real device. The device isn't a puppet driven by scripts. It's a worker that feeds back state continuously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anomaly Detection: The Fuse Inside the Executor
&lt;/h2&gt;

&lt;p&gt;The most common v2 failure mode is looping: the screenshot doesn't change, the model taps the same spot again, the screenshot still doesn't change. OpenGUI has explicit anomaly detection in the Post Execute node.&lt;/p&gt;

&lt;p&gt;Look at &lt;code&gt;post-execute.node.ts&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action repetition detection&lt;/strong&gt;: Check the last 10 actions for 5 consecutive similar actions (same type + coordinate distance under 50 pixels). If found, flag as a likely loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action cycle detection&lt;/strong&gt;: Check for A-B-A-B alternating patterns. For example, tap back, then tap in, then tap back, then tap in. The model oscillates between two pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot anomaly detection&lt;/strong&gt;: Compare recent screenshots using perceptual hash (pHash). If 3 consecutive screenshots are identical and the action isn't wait/scroll, the page isn't responding. If screenshots show an A-B-A-B alternating pattern, the page is switching between two states.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consecutive scroll detection&lt;/strong&gt;: More than 8 consecutive scrolls means the current search strategy isn't making progress. Force the executor to exit and let the supervisor replan.&lt;/p&gt;

&lt;p&gt;When an anomaly is detected, the Post Execute node sets &lt;code&gt;needRemind = true&lt;/code&gt; and injects a reminder on the next Vision Model call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remindMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`The current task may be stuck in a loop or drifting from the goal.
Execution anomaly: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remindReason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Original task: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Check whether the execution is drifting from the original goal or stuck in a loop.`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key design choice: &lt;strong&gt;anomalies aren't termination reasons. They're consumed inputs&lt;/strong&gt;. Detect loop → inject reminder → VLM outputs corrected action on next round → execution continues. The entire loop closes inside the system. No human needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Key Difference: State Management
&lt;/h2&gt;

&lt;p&gt;Traditional scripts have no state. They only know "what's the next step". v2 agents have no state. They only know "how do I handle this screen". OpenGUI distributes state across plan documents, current subtasks, execution results, and failure taxonomies. The supervisor makes every decision based on complete state.&lt;/p&gt;

&lt;p&gt;The maze analogy holds. Traditional scripts hold a roadmap. One wrong turn, they're lost. v2 agents look around at every intersection but can't remember how they got there. OpenGUI carries a real-time updated map. It knows where it is, where it's going, which paths were tried and failed.&lt;/p&gt;

&lt;p&gt;Another key difference is &lt;strong&gt;model role separation&lt;/strong&gt;. v2 usually uses one model for all decisions. OpenGUI splits planning, supervision, and VLM execution across different models.&lt;/p&gt;

&lt;p&gt;The README quotes numbers. For a medium-length task (~60 screenshot analyses), all-Opus config runs $8-15. Swap to Qwen 3.6 Plus for Planner and Supervisor, Doubao Pro for VLM, same task drops to $0.6-1.2. A 10-15x cost difference.&lt;/p&gt;

&lt;p&gt;This gap comes from two factors. First, Qwen/Doubao are priced far below Opus. Second, OpenGUI's architecture lets different roles use different models. Planner and Supervisor handle text plans. They don't need multimodal capability, so they can use cheap text models. Only the Executor's VLM needs vision. That cost is isolated inside the subgraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Concrete Example
&lt;/h2&gt;

&lt;p&gt;Here's how the X search task runs in OpenGUI:&lt;/p&gt;

&lt;p&gt;Plan Supervisor generates the plan first: open X, search keyword, browse results, collect viewpoints, summarize. Then it dispatches the subtask "open X" to the Executor. The Executor screenshots. The VLM judges whether we're on the home screen or another app, then taps. Result comes back: X is open, but a login popup appeared. Supervisor judges this as an anomaly that needs login handling. If it can't handle it, mark failed and try to skip. After login is handled, dispatch "search keyword". The Executor searches. Network is slow, the page hasn't loaded. Internal retry: wait, screenshot again, judge again. Search completes, enter "browse results" subtask. A recommended-follow modal appears mid-way. The Executor identifies it as interference, tries to close or skip. All subtasks complete. Supervisor calls Summarizer for structured summary.&lt;/p&gt;

&lt;p&gt;No step assumes the page will proceed in order. Every step judges based on current real state. Failures are consumed as normal inputs, not termination reasons.&lt;/p&gt;

&lt;p&gt;After completion, the Summarizer returns something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Task Summary&lt;/span&gt;

&lt;span class="gs"&gt;**Goal**&lt;/span&gt;: Search X for recent discussions on "mobile AI agents" and summarize key concerns.

&lt;span class="gs"&gt;**Execution**&lt;/span&gt;: 
&lt;span class="p"&gt;-&lt;/span&gt; Opened X app successfully
&lt;span class="p"&gt;-&lt;/span&gt; Searched "mobile AI agents" 
&lt;span class="p"&gt;-&lt;/span&gt; Scrolled through top 20 results
&lt;span class="p"&gt;-&lt;/span&gt; Collected 8 relevant posts/threads

&lt;span class="gs"&gt;**Key Findings**&lt;/span&gt;:
&lt;span class="p"&gt;1.&lt;/span&gt; Privacy concerns dominate: users worried about screen recording and data access
&lt;span class="p"&gt;2.&lt;/span&gt; Reliability: agents failing on non-standard UI patterns (custom keyboards, overlays)
&lt;span class="p"&gt;3.&lt;/span&gt; Cost: VLM per-screenshot pricing makes long tasks expensive
&lt;span class="p"&gt;4.&lt;/span&gt; Latency: 5-15s per action too slow for real-time interaction

&lt;span class="gs"&gt;**Blocked Items**&lt;/span&gt;:
&lt;span class="p"&gt;-&lt;/span&gt; Login prompt appeared; task continued after handling
&lt;span class="p"&gt;-&lt;/span&gt; One result required app switch to Safari; skipped per constraints

&lt;span class="gs"&gt;**Conclusion**&lt;/span&gt;: Mobile AI agents are technically feasible but face UX, cost, and trust hurdles before mainstream adoption.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Cost
&lt;/h2&gt;

&lt;p&gt;This design is heavier. Costs show up in three places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model cost&lt;/strong&gt;. Every VLM screenshot analysis calls an API. A 1080p screenshot encodes to 1000-3000 tokens. A 10-minute task with 60 screenshot analyses might consume 150k-300k tokens total. All-Opus, this is non-trivial. Mixed-model configs bring it to acceptable ranges, but at a capability cost. Qwen plans worse than Opus. Doubao's vision understanding misses details in some scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency&lt;/strong&gt;. Screenshot → backend → VLM inference → action decode → network transmission → device execution → wait for UI stable → screenshot again. One loop is typically 5-15 seconds. A 60-step task spends 5-15 minutes just waiting. v2 can be faster if the VLM runs locally or nearby, but OpenGUI's backend graph architecture naturally adds a network hop. For latency-sensitive tasks (real-time game assistance, for example), this architecture doesn't fit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System complexity&lt;/strong&gt;. You run a backend (NestJS + LangGraph), database (PostgreSQL), cache (Redis), WebSocket gateway, and maintain the Android client's standby connection. Deploying OpenGUI is much heavier than running a Python script. Devices sleep, background processes get killed by the OS, WebSockets drop. Standby isn't "connect and forget". It needs heartbeat (35-second interval), reconnection, and state sync.&lt;/p&gt;

&lt;p&gt;But mobile is hard to simplify. Real apps throw popups, hang on loading, misregister taps, navigate to completely different pages. Pure prompt loops degrade into screenshot-flavored &lt;code&gt;while true&lt;/code&gt; for any non-trivial task length.&lt;/p&gt;

&lt;p&gt;OpenGUI puts complexity explicitly into the system. A missed tap becomes an execution result for the supervisor to consume. Device disconnection is detected by the standby gateway and retries stop. A paused task resumes from the current subtask. The design is heavier, but it gives long-haul tasks a place to debug, recover, and replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checkpoint: Tasks Can Resume After Interruption
&lt;/h2&gt;

&lt;p&gt;LangGraph provides checkpointing. OpenGUI wires it into PostgreSQL (&lt;code&gt;PostgresCheckpointerService&lt;/code&gt;). This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task is on step 20, the backend restarts. After restart, it resumes from the step 20 checkpoint, not from scratch.&lt;/li&gt;
&lt;li&gt;The user pauses the task manually. On resume, the supervisor re-evaluates current state and decides whether to continue or replan.&lt;/li&gt;
&lt;li&gt;Multiple subtasks share the same thread ID. State persists across graph nodes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters for long tasks. A two-hour run losing all progress because of a backend rolling restart is unacceptable. Checkpointing turns "state lives in memory" into "state lives in the database". Performance sacrificed, reliability gained.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;The capability boundary of an automation system isn't defined by "what actions can it execute". It's defined by "how much state can it maintain".&lt;/p&gt;

&lt;p&gt;Scripts maintain one step of state (what's next). v2 agents maintain one round of state (how do I read this screen). OpenGUI maintains the full task lifecycle: plan, progress, anomalies, recovery.&lt;/p&gt;

&lt;p&gt;Codex's &lt;code&gt;/goal&lt;/code&gt; does something similar for coding agents. It turns the goal from text in a conversation turn into recoverable session state. OpenGUI goes further on mobile. It doesn't just save the goal. It wires device feedback, execution results, and failure handling into a complete state flow. Different domain, same problem: long-horizon agents can't just execute the next step. They need to continuously maintain "where am I, where am I going, what are the boundaries".&lt;/p&gt;

&lt;p&gt;Daily check-in? Script is enough. Running an AI on a real phone for tens of minutes, across multiple apps, with complex judgments? Then you need to pull the goal out of the prompt and turn it into continuously maintained state. The choice is heavier. It's also the path from demo to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenGUI: &lt;a href="https://opengui.ai" rel="noopener noreferrer"&gt;https://opengui.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenGUI repository: &lt;a href="https://github.com/Core-Mate/open-gui" rel="noopener noreferrer"&gt;https://github.com/Core-Mate/open-gui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI Codex 0.128.0 release: &lt;a href="https://github.com/openai/codex/releases/tag/rust-v0.128.0" rel="noopener noreferrer"&gt;https://github.com/openai/codex/releases/tag/rust-v0.128.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Simon Willison on Codex goals: &lt;a href="https://simonwillison.net/2026/Apr/30/codex-goals/" rel="noopener noreferrer"&gt;https://simonwillison.net/2026/Apr/30/codex-goals/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Codex /goal and OpenGUI: long-running tasks need state</title>
      <dc:creator>Fenix</dc:creator>
      <pubDate>Tue, 05 May 2026 01:55:19 +0000</pubDate>
      <link>https://dev.to/fenix_23505d14df386c00ced/codex-goal-and-opengui-long-running-tasks-need-state-1c61</link>
      <guid>https://dev.to/fenix_23505d14df386c00ced/codex-goal-and-opengui-long-running-tasks-need-state-1c61</guid>
      <description>&lt;p&gt;Long-running agents tend to fail in the second half.&lt;/p&gt;

&lt;p&gt;The first step is often fine. Fix a CI failure, open an app, tap a button, search for a keyword. Models can produce a reasonable first action. The trouble starts around step ten: what has already happened, where the task is stuck, what the original boundary was, and when the task is allowed to stop. Those details slide out of context.&lt;/p&gt;

&lt;p&gt;Codex CLI 0.128.0 added &lt;code&gt;/goal&lt;/code&gt;. The release note describes a persisted goal workflow: app-server APIs, model tools, runtime continuation, and TUI controls for create, pause, resume, and clear. Simon Willison compared it to OpenAI's version of a Ralph loop: set a goal for Codex, then let it keep executing, checking, and correcting until the goal is done or the budget runs out.&lt;/p&gt;

&lt;p&gt;In the context of long-running tasks, the change is about where the goal lives. It moves from text in a single prompt to state that can be resumed, paused, cleared, and referenced again later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why coding agents need goal
&lt;/h2&gt;

&lt;p&gt;Take a CI failure. The immediate failure may be one broken test. The agent changes the test, then the implementation, then adjusts a type because the code now feels awkward. Each step can be justified, but the final diff is much larger than the original problem.&lt;/p&gt;

&lt;p&gt;Code generation is rarely the hard part here. The run has no stable constraint attached to it. The original goal may have been as small as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/goal 修复当前 failing tests，保持 diff 尽量小，最后跑完 npm test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/goal 处理这个 PR 的 review comments，不改无关文件，最后给出改动摘要
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That kind of goal carries the target, the boundary, and the acceptance condition. It tells the agent where to go, what not to touch, and when to stop.&lt;/p&gt;

&lt;p&gt;Without that state, the agent is easily pulled around by the current error. A type looks awkward, so it changes the type. A test is hard to write, so it changes the test. The structure feels messy, so it refactors. Each local move can make sense, while the whole task drifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  On phones, the hard part is screen state
&lt;/h2&gt;

&lt;p&gt;OpenGUI works on a different kind of long-running task: letting AI operate a real Android phone.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/Core-Mate/open-gui" rel="noopener noreferrer"&gt;https://github.com/Core-Mate/open-gui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a codebase, state can still land in files, tests, and diffs. On a phone, state is a live screen.&lt;/p&gt;

&lt;p&gt;For example, ask the phone to open X, search for discussions about mobile AI agents, collect the main points, and summarize what people care about. As a sentence, this looks simple. On the phone, it becomes a series of state checks: is the app open, is this the home page, is the search box focused, did the results finish loading, did a login prompt, permission prompt, or follow recommendation appear in the middle.&lt;/p&gt;

&lt;p&gt;The loop of screenshot, tap, screenshot can only carry short tasks. If the screen does not change, the system has to decide whether the tap missed, the network is slow, the page is loading, or the action has no visible feedback. If the page jumps somewhere else, it also has to decide whether to go back, retry, or continue from the new page.&lt;/p&gt;

&lt;p&gt;So a goal on mobile has to answer a few concrete questions: which step is the task on, whether the current screen supports the next step, where to recover after a failure, and when the run can end.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenGUI turns the goal into a state flow
&lt;/h2&gt;

&lt;p&gt;I ran OpenGUI and read through the source. It connects the backend graph, device connection, and Android-side action execution instead of leaving phone automation as a script.&lt;/p&gt;

&lt;p&gt;On the backend, the main entry point is &lt;code&gt;server/apps/backend/src/modules/graph-agent/graph/mobile-agent.graph.ts&lt;/code&gt;. Complex tasks go through &lt;code&gt;Plan Supervisor&lt;/code&gt;, where the plan is split into executable subtasks. Concrete actions enter &lt;code&gt;executor.graph.ts&lt;/code&gt;, the device execution subgraph. The execution result goes back to the supervisor, which decides whether to continue, retry, replan, or hand off to &lt;code&gt;Summarizer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On Android, actions are applied to the real device. &lt;code&gt;client/core_accessibility/.../GestureService.kt&lt;/code&gt; executes GUI actions such as taps and typing. The device keeps a WebSocket connection to the backend, and &lt;code&gt;client/core_network/.../StandbySocketManager.kt&lt;/code&gt; handles the standby connection. Feishu/Lark, Telegram, and REST API can sit outside this as remote task entry points, turning the phone from a local demo device into something that can receive work.&lt;/p&gt;

&lt;p&gt;OpenGUI spreads the goal across several pieces of state: the plan document, current subtask, device screenshot, execution result, failure classification, and final summary. After each device action, the backend gets fresh device state and decides the next move.&lt;/p&gt;

&lt;p&gt;A simple script assumes the page will follow the expected order. OpenGUI assumes the page will change, so the executor has to keep reporting real state back to the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost
&lt;/h2&gt;

&lt;p&gt;Putting the goal into a graph makes the system heavier.&lt;/p&gt;

&lt;p&gt;You have to maintain task state, keep WebSocket connections alive, handle device standby, send execution results and screenshots back, and design state transitions for continue, retry, cancel, and summarize. Model calls and screenshot analysis also cost money. The longer the task runs, the more that cost becomes an engineering concern instead of a small detail.&lt;/p&gt;

&lt;p&gt;But on mobile, it is hard to avoid this cost. Real apps show popups, hang on loading screens, misread taps, and send users to completely different pages. A prompt loop alone quickly turns into screenshot-based &lt;code&gt;while true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OpenGUI puts that complexity into the system. A bad tap becomes an execution result for the supervisor to consume. The device keeps reporting state. It behaves more like a worker than a screen being clicked. The design is heavier, but it gives long-running tasks a place to be debugged, recovered, and reviewed.&lt;/p&gt;

&lt;p&gt;The first use cases I would try are community research, mobile flow testing, ops tasks, and App-only workflows that web automation cannot reach. These tasks may not need the strongest model, but they do need an execution system that can keep following the goal, see failure, and send state back.&lt;/p&gt;

&lt;p&gt;In coding agents, Codex &lt;code&gt;/goal&lt;/code&gt; keeps the goal as recoverable state. On phones, OpenGUI connects task progress, device feedback, and failure handling into a state flow. A long-running agent has to keep track of the run, not only execute the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI Codex 0.128.0 release: &lt;a href="https://github.com/openai/codex/releases/tag/rust-v0.128.0" rel="noopener noreferrer"&gt;https://github.com/openai/codex/releases/tag/rust-v0.128.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Simon Willison: &lt;a href="https://simonwillison.net/2026/Apr/30/codex-goals/" rel="noopener noreferrer"&gt;https://simonwillison.net/2026/Apr/30/codex-goals/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenGUI: &lt;a href="https://github.com/Core-Mate/open-gui" rel="noopener noreferrer"&gt;https://github.com/Core-Mate/open-gui&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>cli</category>
      <category>llm</category>
    </item>
    <item>
      <title>OpenGUI：手机上的 OpenClaw</title>
      <dc:creator>Fenix</dc:creator>
      <pubDate>Mon, 04 May 2026 13:24:24 +0000</pubDate>
      <link>https://dev.to/fenix_23505d14df386c00ced/openguishou-ji-shang-de-openclaw-38od</link>
      <guid>https://dev.to/fenix_23505d14df386c00ced/openguishou-ji-shang-de-openclaw-38od</guid>
      <description>&lt;p&gt;OpenGUI 是一个让 AI 操作真实 Android 手机的项目。&lt;/p&gt;

&lt;p&gt;项目地址：&lt;a href="https://github.com/Core-Mate/open-gui" rel="noopener noreferrer"&gt;https://github.com/Core-Mate/open-gui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenClaw 把 AI 接到桌面环境，OpenGUI 则把类似的执行层放到了 Android 手机上。它面向的是手机 App 里的任务：点击、输入、截图、阅读页面、执行流程、返回结果。&lt;/p&gt;

&lt;p&gt;很多任务天然发生在移动端：X、Reddit、Hacker News、Telegram、微信、小红书，还有不少只在 App 内完整存在的业务流程。网页自动化覆盖不到这些场景。&lt;/p&gt;

&lt;h2&gt;
  
  
  基本架构
&lt;/h2&gt;

&lt;p&gt;OpenGUI 由后端和 Android 客户端两部分组成。&lt;/p&gt;

&lt;p&gt;后端负责理解任务、生成计划、监督执行和总结结果；Android 客户端连接后端，在真实设备上执行 GUI 操作。除了点屏幕，它还要处理任务状态、设备状态和失败后的恢复。&lt;/p&gt;

&lt;p&gt;仓库里能看到几块代码：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;后端的任务规划、Executor Graph、复核和总结&lt;/li&gt;
&lt;li&gt;Android 端的 AccessibilityService 动作执行&lt;/li&gt;
&lt;li&gt;设备通过 WebSocket 保持连接&lt;/li&gt;
&lt;li&gt;飞书/Lark、Telegram、REST API 等远程触发入口&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样跑起来后，手机可以保持待命，像一个远程 worker 一样接任务、执行任务、回传结果。&lt;/p&gt;

&lt;h2&gt;
  
  
  本地启动
&lt;/h2&gt;

&lt;p&gt;本地需要先准备 Android 开发环境，并连接一台 Android 设备。&lt;/p&gt;

&lt;p&gt;启动后端：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;server
./start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;启动 Android 客户端：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;client
./start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;后端脚本会准备依赖服务、数据库和 API；客户端脚本会构建 APK、安装到连接的 Android 设备上，并启动应用。&lt;/p&gt;

&lt;p&gt;手机侧还需要手动确认 USB 调试、Accessibility Service、悬浮窗权限，以及模型 API Key / 机器人凭证等。权限授权这部分保留人工确认更合理。&lt;/p&gt;

&lt;h2&gt;
  
  
  难点在哪里
&lt;/h2&gt;

&lt;p&gt;手机 Agent 的麻烦通常出现在后续状态处理上。点一下屏幕只是开始。&lt;/p&gt;

&lt;p&gt;比如一个很简单的任务：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;打开 X，搜索 mobile AI agents 相关的近期讨论，收集主要观点，并总结大家主要关心的问题。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这个任务看起来不大，但手机上会发生很多不确定的事情：App 可能停在旧页面，搜索框可能没点中，结果页可能加载慢，中间还可能弹出登录、权限、推荐关注之类的窗口。&lt;/p&gt;

&lt;p&gt;所以 Mobile Agent 不能只会“看图然后点一下”。它还要知道任务做到哪一步了，当前屏幕是不是符合预期，点错之后怎么恢复，页面没变化时要不要重试，最后怎么把执行结果收回来。&lt;/p&gt;

&lt;p&gt;我实际跑了一下，也仔细看了 OpenGUI 的源码。它的思路还挺不错：后端 graph 管任务状态和计划，Executor Graph 负责把具体步骤派到手机上，Android 端通过 AccessibilityService 执行动作，再通过 WebSocket 把设备状态和执行结果传回来。&lt;/p&gt;

&lt;p&gt;这会把手机放进执行循环里。后端判断任务要继续、重试还是结束；手机端把真实屏幕和动作结果反馈回来。&lt;/p&gt;

&lt;p&gt;这套设计比单纯写脚本靠谱得多。手机可以待命、执行、反馈，更接近一个移动端 worker。&lt;/p&gt;

&lt;p&gt;我能想到的第一批用途，是社区信息收集、移动端流程测试、运营任务执行，以及那些网页自动化碰不到的 App-only 流程。&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>OpenGUI: OpenClaw for phones</title>
      <dc:creator>Fenix</dc:creator>
      <pubDate>Mon, 04 May 2026 13:23:21 +0000</pubDate>
      <link>https://dev.to/fenix_23505d14df386c00ced/opengui-openclaw-for-phones-31d8</link>
      <guid>https://dev.to/fenix_23505d14df386c00ced/opengui-openclaw-for-phones-31d8</guid>
      <description>&lt;p&gt;OpenGUI is a project that lets AI operate a real Android phone.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/Core-Mate/open-gui" rel="noopener noreferrer"&gt;https://github.com/Core-Mate/open-gui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenClaw connects AI to a desktop environment. OpenGUI brings a similar execution layer to Android. It is aimed at tasks inside mobile apps: tapping, typing, taking screenshots, reading screens, moving through flows, and returning results.&lt;/p&gt;

&lt;p&gt;A lot of work already happens on phones: X, Reddit, Hacker News, Telegram, WeChat, Xiaohongshu, and plenty of business flows that only really exist inside apps. Web automation does not reach those surfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic architecture
&lt;/h2&gt;

&lt;p&gt;OpenGUI has two main parts: a backend and an Android client.&lt;/p&gt;

&lt;p&gt;The backend understands the task, creates a plan, supervises execution, and summarizes the result. The Android client connects to the backend and performs GUI actions on a real device. Beyond tapping the screen, it also has to handle task state, device state, and recovery after failures.&lt;/p&gt;

&lt;p&gt;You can see a few pieces in the repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;task planning, Executor Graph, review, and summarization on the backend&lt;/li&gt;
&lt;li&gt;AccessibilityService-based action execution on Android&lt;/li&gt;
&lt;li&gt;WebSocket connections for keeping devices online&lt;/li&gt;
&lt;li&gt;remote entry points through Feishu/Lark, Telegram, and REST API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once it is running, the phone can stay on standby, receive a task like a remote worker, execute it, and send results back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local setup
&lt;/h2&gt;

&lt;p&gt;You need an Android development environment and a connected Android device.&lt;/p&gt;

&lt;p&gt;Start the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;server
./start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the Android client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;client
./start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend script prepares the services, database, and API. The client script builds the APK, installs it on the connected Android device, and launches the app.&lt;/p&gt;

&lt;p&gt;Some phone-side steps still need manual approval: USB debugging, Accessibility Service, overlay permission, and model API keys or bot credentials. Keeping those steps explicit makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it gets hard
&lt;/h2&gt;

&lt;p&gt;The hard part of a phone agent usually starts after the first tap.&lt;/p&gt;

&lt;p&gt;Take a simple task:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open X, search for recent discussions about mobile AI agents, collect the main points, and summarize what people care about.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds small, but the phone can be in many different states. The app may open on an old page. The search box may not receive focus. Results may load slowly. A login prompt, permission prompt, or follow recommendation can appear in the middle.&lt;/p&gt;

&lt;p&gt;So a mobile agent cannot just look at a screenshot and tap once. It has to know where the task is, whether the current screen matches expectations, how to recover after a bad tap, when to retry after no visible change, and how to collect the final result.&lt;/p&gt;

&lt;p&gt;I ran OpenGUI and also spent some time reading the source. The approach is pretty good: the backend graph manages task state and plans, the Executor Graph sends concrete steps to the phone, the Android side performs actions through AccessibilityService, and WebSocket sends device state and execution results back.&lt;/p&gt;

&lt;p&gt;This puts the phone inside the execution loop. The backend decides whether to continue, retry, or finish; the phone reports what actually happened on screen.&lt;/p&gt;

&lt;p&gt;This is much more practical than a plain script. The phone can stand by, execute, and report back. It starts to look like a mobile worker.&lt;/p&gt;

&lt;p&gt;The first use cases I can imagine are community research, mobile flow testing, ops tasks, and App-only workflows that web automation cannot touch.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>NexAgent: a self-evolving AI agent built on Elixir/OTP</title>
      <dc:creator>Fenix</dc:creator>
      <pubDate>Sat, 14 Mar 2026 01:37:33 +0000</pubDate>
      <link>https://dev.to/fenix_23505d14df386c00ced/nexagent-a-self-evolving-ai-agent-built-on-elixirotp-121n</link>
      <guid>https://dev.to/fenix_23505d14df386c00ced/nexagent-a-self-evolving-ai-agent-built-on-elixirotp-121n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;OpenClaw showed the world the future of AI Agents. But it got me wondering: if an Agent is going to stick with me for a decade, what should its architecture look like?&lt;br&gt;
&lt;strong&gt;Links&lt;/strong&gt;: NexAgent GitHub: &lt;a href="https://github.com/gofenix/nex-agent" rel="noopener noreferrer"&gt;https://github.com/gofenix/nex-agent&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;OpenClaw's &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;310k stars&lt;/a&gt; are well-deserved.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It proves that the "personal AI Agent" direction is right—that everyday users are willing to pay for an "AI that can actually do work." As a long-time AI observer, seeing that red lobster logo take over the internet makes me genuinely happy. It means Agents are finally moving from a niche toy to the mainstream.&lt;/p&gt;

&lt;p&gt;I spun one up myself. But using it surfaced some interesting issues that got me thinking: &lt;strong&gt;If an Agent is meant to be a 24/7 "companion" rather than just a "tool"—and if it needs to get smarter over time—how should its underlying architecture change?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's no single right answer. OpenClaw proved the demand using TypeScript/Node.js. I wanted to explore a different path using Elixir/OTP.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/gofenix/nex-agent" rel="noopener noreferrer"&gt;NexAgent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This isn't meant to compete with OpenClaw. It's an experiment focused entirely on the "long-running Agent" niche.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  My Experience Raising a Lobster
&lt;/h2&gt;

&lt;p&gt;Like everyone else, I found OpenClaw on Twitter.&lt;/p&gt;

&lt;p&gt;The red lobster logo, &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;310k stars&lt;/a&gt;, the "AI digital employee" pitch—it felt straight out of Iron Man (hello, JARVIS).&lt;/p&gt;

&lt;p&gt;I installed it right away, hooked up a Telegram Bot, and gave it a task: "Check my GitHub Issues every morning at 8 AM and ping the high-priority ones to Lark."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Early Days: Pure Magic&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Waking up to find my Issues neatly categorized.&lt;/li&gt;
&lt;li&gt;Asking "Any bugs today?" on Telegram and seeing it actually remember yesterday's context.&lt;/li&gt;
&lt;li&gt;Felt a solid 20% bump in my quality of life.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;That Led Me to a Different Question&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if I don't just want to &lt;em&gt;use&lt;/em&gt; an Agent, but &lt;em&gt;raise&lt;/em&gt; it long-term?&lt;/li&gt;
&lt;li&gt;It needs 24/7 rock-solid uptime.&lt;/li&gt;
&lt;li&gt;It should compound its intelligence, not wipe the slate clean on every reboot.&lt;/li&gt;
&lt;li&gt;It needs to self-evolve instead of waiting for author updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s when another tech stack came to mind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Elixir/OTP?
&lt;/h2&gt;

&lt;p&gt;Choosing TypeScript/Node.js for OpenClaw was the right move. It dramatically lowered the barrier to entry and brought more than 310k people into the project. That's how open source wins.&lt;/p&gt;

&lt;p&gt;But I kept wondering: &lt;strong&gt;if the endgame is a "system that never goes down", what else is out there?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That led me to Elixir and OTP. Not for novelty's sake, but because OTP (Open Telecom Platform) was literally built for telecom switches: systems that &lt;em&gt;must&lt;/em&gt; run 24/7, stay resilient, and support hot code upgrades.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Node.js Approach&lt;/th&gt;
&lt;th&gt;OTP Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process Management&lt;/td&gt;
&lt;td&gt;Single process + external restart&lt;/td&gt;
&lt;td&gt;Supervision tree auto-restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory Isolation&lt;/td&gt;
&lt;td&gt;Same process space&lt;/td&gt;
&lt;td&gt;Each task runs in isolated process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot Updates&lt;/td&gt;
&lt;td&gt;Restart the service&lt;/td&gt;
&lt;td&gt;Zero-downtime hot reload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error Recovery&lt;/td&gt;
&lt;td&gt;Manual intervention&lt;/td&gt;
&lt;td&gt;Auto-recovery + graceful degradation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It’s not about which stack is better—it’s about &lt;strong&gt;optimizing for different use cases&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenClaw optimizes for accessibility, putting Agents in everyone's hands.&lt;/li&gt;
&lt;li&gt;NexAgent optimizes for extreme stability, exploring what long-term AI companionship looks like.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Core Experiments with NexAgent
&lt;/h2&gt;

&lt;p&gt;I rewrote the Agent's core in Elixir and ran a few tests:&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment 1: Uptime
&lt;/h3&gt;

&lt;p&gt;I left NexAgent running on my local machine for an extended period:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stability&lt;/strong&gt;: Rock solid, zero memory leaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt;: Consistently fast, no degradation over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience&lt;/strong&gt;: When a tool crashed, it restarted instantly without taking down the main loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero manual restarts. OTP's supervision tree makes the system far easier to operate over long periods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment 2: Hot Reloading in the Wild
&lt;/h3&gt;

&lt;p&gt;My AMap weather tool suddenly broke, throwing API permission errors.&lt;/p&gt;

&lt;p&gt;The Agent self-diagnosed the issue: the API key was bound to iOS, but it was making server-side calls. It autonomously patched its own source code, swapping the logic to read a Web Service key instead.&lt;/p&gt;

&lt;p&gt;Four minutes later, it successfully pulled the weather for Shenzhen. No server restart. The chat session never dropped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the actual screenshot:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxe04pax02fs2v3r94dmu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxe04pax02fs2v3r94dmu.jpeg" alt="Agent auto-fixing AMap weather tool" width="800" height="773"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From diagnosing the bug to writing the fix and hot-reloading the module—zero human intervention.&lt;/p&gt;
&lt;h3&gt;
  
  
  Experiment 3: Self-Evolution Pipeline
&lt;/h3&gt;

&lt;p&gt;NexAgent ships with a built-in pipeline for self-improvement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reflect&lt;/strong&gt;: Read the source code of any internal module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyze&lt;/strong&gt;: Figure out what's broken and draft a fix.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade&lt;/strong&gt;: Apply the patch and hot-reload on the fly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means that when tool logic needs to change, for example because an external API changes its response format, the Agent can inspect its own code, patch it, and update itself in memory without me having to restart the daemon.&lt;/p&gt;

&lt;p&gt;The mechanism works flawlessly. I'm currently exploring more complex "fully autonomous repair" scenarios.&lt;/p&gt;


&lt;h2&gt;
  
  
  Under the Hood of NexAgent
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Supervision Trees: Crash and Recover
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/nex/agent/application.ex&lt;/span&gt;
&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="no"&gt;NexAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;InfrastructureSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;NexAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WorkerSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;NexAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Gateway&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;strategy:&lt;/span&gt; &lt;span class="ss"&gt;:rest_for_one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the infrastructure tier crashes, all dependent Workers restart. If a single tool crashes, only that specific tool's process restarts. The main Agent loop keeps running.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Process Isolation: Each Task Runs in Its Own Process
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/nex/agent/tool/registry.ex:181&lt;/span&gt;
&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Supervisor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;NexAgent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ToolTaskSupervisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;tool_module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every single tool execution gets its own lightweight process. Crashes don't affect the main loop.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Hot Reloading: Upgrades on the Fly
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/nex/agent/code_upgrade.ex:39&lt;/span&gt;
&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;maybe_validate_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;create_backup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;write_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hot_reload&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;compile_and_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="ss"&gt;:ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;maybe_health_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;version:&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;hot_reload:&lt;/span&gt; &lt;span class="n"&gt;hot_reload&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Auto rollback on failure&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Dual-Layer Memory
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MEMORY.md&lt;/strong&gt;: Long-term state (project context, user quirks).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HISTORY.md&lt;/strong&gt;: Grep-able conversation logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Powered by &lt;strong&gt;async consolidation&lt;/strong&gt;: When a chat gets too long, the Agent spins up a background process to summarize the history and extract facts into long-term memory. Zero latency impact on your active chat.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Six-Layer Evolution Model
&lt;/h2&gt;

&lt;p&gt;NexAgent doesn't just evolve in one way. It has six layers of growth:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SOUL&lt;/strong&gt;: Personality and core values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;USER&lt;/strong&gt;: Your profile and how you like to collaborate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MEMORY&lt;/strong&gt;: Long-term context and project domain knowledge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SKILL&lt;/strong&gt;: Reusable workflows it has learned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TOOL&lt;/strong&gt;: Hardcoded integrations and tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CODE&lt;/strong&gt;: The actual Elixir source code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every layer compounds over time, and each can evolve independently.&lt;/p&gt;


&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;Want to take it for a spin?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Install Elixir (~&amp;gt; 1.18)&lt;/span&gt;
&lt;span class="c"&gt;# 2. Clone repo&lt;/span&gt;
git clone https://github.com/gofenix/nex-agent.git
&lt;span class="nb"&gt;cd &lt;/span&gt;nex-agent
mix deps.get

&lt;span class="c"&gt;# 3. Initialize&lt;/span&gt;
mix nex.agent onboard

&lt;span class="c"&gt;# 4. Configure config file&lt;/span&gt;

&lt;span class="c"&gt;# 5. Start gateway&lt;/span&gt;
mix nex.agent gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More docs: &lt;a href="https://github.com/gofenix/nex-agent" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;OpenClaw opened our eyes to what AI Agents can do. That's a massive win for the whole industry.&lt;/p&gt;

&lt;p&gt;NexAgent is simply probing a specific niche: &lt;strong&gt;If an Agent is meant to be a long-term companion, how should we build it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;310k people are raising lobsters&lt;/a&gt;, experiencing what it feels like to have AI at their fingertips.&lt;/p&gt;

&lt;p&gt;I'm planting a tree, waiting for the day it grows into a canopy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two different paths, one shared goal: weaving AI seamlessly into our lives.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
