← 所有文章

Loop X:用我已经付过费的 CLI,搭一个完全私有的 AI 同事

Claude co-worker 和 Claude Tag 都很好用,但算完账单发现不划算,于是我用已经付费的 Claude CLI 搭了 Loop X,一个完全跑在自己 Mac 上的 AI 同事。


一边开着 Claude 的 co-worker 介绍页面,一边开着团队的 GitLab issue 积压列表。那个介绍页写的正是我想要的东西:每天早上自动把队列过一遍,能修的修掉,修不了的标出来,这样没人会让分配给自己的 issue 一直晾在那儿。我之前在 Slack 里已经见识过 Claude Tag 做类似的事,确实好用。然后我按团队实际产生的 GitLab 流量算了一下账单,发现跟团队每台机器上已经在跑的 Claude Code 订阅比起来,这笔钱花得不值。

这件事让我一直惦记着。我想要的能力其实不复杂:列出分配给我的 issue,一个一个处理,能修的修,不用改代码的就回复,弄不清楚的就标出来,最后给我发一份摘要。这几步 claude -p 在终端里本来就能做。真正要额外付费的,是围绕这个命令的调度、安全边界,还有一个仪表盘。于是接下来几周,我就在已经付费的 CLI 之上,把这一层自己搭了出来,而且整套东西就跑在我自己的电脑上。

Loop X 到底是什么

Loop X 仪表盘在 Chrome 里打开,地址是 loop.x,Overview 标签页里有一个对话输入框,侧边栏列着 GitLab Issues、Topic Monitor、Inbox Triage、RSS Watch、Meeting Prep 这几个 loop

这是一个只用标准库的 Python 项目,在我的 Mac 上跑成两个 launchd 守护进程。一个是调度器,每十五分钟轮询一次,到点就触发该跑的 loop。另一个是常驻的本地仪表盘。不管是 issue 分诊、邮箱打标签、CI 排查还是会前准备,每一项都是 loops.json 里的一条 loop 定义,每次跑起来,就用 loop 自己的指令文件拼出一段 prompt,调用 claude -p(如果配置的是 Codex,就调用 codex exec)。没有任何东西托管在外面。没有哪个第三方账号能看到我的 issue、我的邮件或者我的日程,loop 的状态、历史记录、草稿全都存在我自己这台机器上的 ~/.loop-engineering 目录里。

主力 loop 是 GitLab issue 处理。每个工作日上午十点,它会列出我在每个已配置项目里被分配的所有 issue,然后一个一个处理,绝不并行,这点我花了点时间才习惯,因为用 agent 干活的本能反应总是”为什么不开几个并行跑”。对每个 issue,它只会做三件事里的一件:在独立的 git worktree 里修完,等项目自己的 lint 和测试命令都通过后开一个合并请求;不需要改代码的,直接评论回答;需求含糊或者验证失败的,评论请求澄清。处理完一批后会给我发一条 Slack 摘要,并更新 PROGRESS.md,让下一次运行知道上次发生了什么。我自己也能看懂。

然后我就停不下来了

GitLab 那个 loop 干干净净跑了几周以后,我发现生活里到处都是同样形状的问题,于是一个接一个地把它们也变成了 loop。下面这几个现在全都在我自己的机器上跑着,不是什么概念演示。

Gmail 和 Outlook,分诊但绝不动手。 收件箱分诊 loop 会读取上次运行以来的未读邮件,把它们归到几个类别之一(Loop/Urgent、Loop/Action、Loop/FYI 等等),遇到紧急的就起草一封回复,放进邮箱自己的草稿箱里,等我自己看过、改过、发出去。它不能发邮件。不是”被叮嘱不要发”,是真的做不到:Gmail 和 Outlook 两边的集成代码里都没有发送邮件这个功能,而 Outlook 申请的 OAuth 令牌本身就没有 Mail.Send 这个权限,就算代码想发也发不出去。它也不能归档、删除或者改已读状态。它唯一能写的,就是建一个标签、丢一份草稿。

日历,提前十五分钟就知道。 会前准备 loop 盯着我的 Google 日历,只要有别人参加的会议,提前四十五分钟就会整理出一份简报:议程、邀请描述里链接的 GitLab issue 或 MR、跟组织者和参会人往来的近期邮件(从不涉及我自己,也从不看邮件正文,只看主题、发件人、日期),如果是周期性会议,还会附上上一次的小结和待跟进事项。最后这一点比我预想的有用得多。周期性会议很容易越开越散,还没打开电脑,Slack 里就已经躺着”上次我们说要跟进这件事”,这已经救了我好几次,让我没有带着一片空白走进一场本该接着上次讲的会。

话题监控,一句英文就能配一个。 话题监控跟 GitLab、邮箱都没关系,它做的是实打实的联网搜索。每个话题就是一个名字加一句描述,写清楚什么算”值得关注”,所以一个话题可以盯着模型发布和融资新闻,另一个盯着某个具体项目的更新日志,同一个 loop、同一份代码,换一句 brief 就行。它会跟过去七天的记录去重,不会把同一条新闻再发一遍;就算哪天什么都没有,也会老老实实发一句”今天没什么新东西”,而不是悄悄不发,这点比听起来重要,因为一个该出声却沉默的 loop,跟一个坏掉的 loop 从外面根本分不出来。

RSS,排序过的而不是堆在那儿的。 RSS 监控 loop 干的,其实是我这些年一直希望 RSS 阅读器能帮我干的事:抓取新内容,按我在设置框里写的兴趣列表打分排序,挑出最好的十条,各配一句理由发给我,而不是让未读数字一路爬到四位数然后彻底放弃。这个 loop 多了一层额外的警惕,因为订阅内容是整套系统里唯一来自公开互联网、而不是我自己账号的输入。prompt 里明确提醒了这一点,它本身不带任何工具权限、不能碰 shell,写出来的内容在送到 Slack 之前,还要经过跟其他所有通知一样的清洗流程。

两件真正难的事

第一件是决定到底哪些 loop 值得拿到真正的工具权限。GitLab issue 处理需要 shell、git worktree、跑项目自己测试套件的能力,覆盖面相当大,毕竟修代码就得碰代码。但收件箱分诊、会前准备、话题监控和 RSS 监控都不需要这些。它们只需要拿着我已经抓好的数据,调一次模型,要一个判断。我最后把 loop 分成了两类:需要写代码的用带工具和 MCP 服务器的完整运行时;剩下那些本质上只是”看一眼,告诉我重点是什么”的,就用一个封闭插件模式,没有工具、没有 shell,每一条单独隔离,一条出错也不会拖垮整批。这个拆分对成本的影响,比任何 prompt 调优都大,因为我每天大部分的 loop 其实都属于第二类。

第二件是信任问题,这个我一开始明显低估了,而且每个 loop 上这个问题都长得不太一样。对 GitLab 来说,它绝不合并自己开的 MR,没有例外,合并永远是人点一下鼠标的事,每次修复都在独立的分支和 worktree 里进行,验证失败就升级,不会循环重试。对收件箱分诊来说,“这个人的邮件永远标紧急”这条 VIP 规则,是在模型给出回答之后,用一段实打实的 Python 代码强制执行的,排除名单上的发件人,也是在邮件送到 AI 面前之前就已经被过滤掉了。只要是碰真实账号的 loop,我反复回到的都是同一条原则:凡是出一次错都不能接受的事,就别指望光靠 prompt 来兜底。写出每个 loop 真正干活那部分代码,一个下午顶多两个下午就够了。写出”这件事无论之前表现多好,也绝对不许做”的那部分,花掉了剩下的三周。

真正让这一切不再像玩具的那个时刻,其实一点都不戏剧化,这才是它让我记住的原因。有些早上打开 Slack,会看到四份摘要一起躺在那儿:GitLab issue 在夜里被处理完了,一两封紧急邮件的草稿等着我扫一眼再发出去,一份会前简报已经附好了该跟进的事项,一份话题简报那天确实提到了点什么。这些消息没有一条要求我马上去做什么。这正是重点。以前要攒到有空才会去碰的那四堆积压,等我倒完咖啡的时候已经被过了一遍。

让任何人都能用,而不只是我自己在终端里敲命令

我不想让这东西只活在我一个人对 shell 的熟练程度里。bin/scripts/build_macos_app.sh 会把整个仪表盘包进一个带独立 Python 环境的原生窗口,最后得到的是一个 Loop X.app,拖进应用程序文件夹,像打开任何普通软件一样打开它,装好之后就再也不用碰终端了。它会自动连上后台已经在跑的调度器和仪表盘,如果还没启动,就自己把它们拉起来。

Loop X 以原生 macOS app 的形式运行,内容跟上面那张图一模一样,只是窗口标题变成了"Loop X Engineering",不再是浏览器标签页

两种形态里仪表盘内容完全一样,按钮一样,对话框一样,loop 列表也一样。唯一的区别是它到底躺在 loop.x 这个浏览器标签页里,还是躺在自己的窗口里、带着自己的 Dock 图标,而恰恰是这个区别,决定了能不能把它递给一个本来会反问 127.0.0.1:8420 是什么意思的人。

能让”接入一个新账号”变成填一张表单而不是改代码的,是 connector 这个模型。一个 connector 就是一个账号,带一个类型(GitLab、Gmail、Slack webhook、RSS 订阅列表、Jira、Notion、某个日历)和它能提供的能力,比如 issues、mail、notify、feed;一个 loop 只要求某种能力,任何对应类型的 connector 都能满足它。想加个 Telegram 或 Discord 当通知渠道,或者再接一个 Gmail 账号,在 Connectors 页面填个表单、点一下 Test 就行,不用动一行代码。

而且因为整套东西都是无人值守的,真正让它好用的,其实是不用自己去翻就能知道发生了什么。每个 loop 一跑完就会发自己的 Slack 摘要,仪表盘的 Activity 页面里还嵌了一个对话助手,我可以直接问”现在接了哪些账号”或者”GitLab 那个 loop 今天早上干了什么”,拿到的是从真实运行记录里查出来的答案,不是瞎猜。

真正变了的东西

我一开始以为自己在挑的产品是”一个帮你处理 issue 的 AI”。后来发现不是。调用模型这一步从来都不是稀缺资源,我本来就有,反正订阅费都付了。Claude co-worker 和 Claude Tag 真正在卖的,是围绕那次模型调用的编排:调度、安全边界、仪表盘,还有”无人值守的 agent 到底能碰什么、不能碰什么”这套 policy。这是个真实的产品,收费也合理。但它同时也是一个我能用自己已有的权限自己搭出来的产品,因为这些部分没有一个是秘密,都是决定,不是能力。而一旦为第一个 loop 做完这些决定,再给邮箱、日历和一堆 RSS 订阅做一遍,大部分时候就只是重复而已。

没想到的是,整个代码库里,写边界规则的部分比写实际行为的部分多得多,而且加了这么多 loop 之后,这个规律在每一个上面都成立,不只是第一个。真正修一个 GitLab issue、起草一封回复、写一份会前简报的代码,每个都不过是几百行包了一次模型调用。而决定每个 loop 什么时候能动手、什么时候必须停下来问人、不管之前跑得多顺也绝不能碰什么,这部分代码占了整个项目的大头。要是明天重写一遍,我会先写边界那部分,再把实际的任务逻辑当成容易的部分去做,因为连着做了四个 loop,事实证明重要性的顺序一直都是这样的。Loop X 开源在 GitHub 上,github.com/encoreshao/loop-engineering,MIT 协议。

Encore Shao
Encore Shao

全栈工程师 & AI 研究员,就职于上海 Ekohe。10 年以上经验,专注于 Rails 应用与 Agentic AI 系统。