LifeOS CMUX Monitor 工作流:基于轮询的 Agent 状态监控与语音通知实战指南
2026/9/14 18:25:21 网站建设 项目流程

LifeOS CMUX Monitor 工作流:基于轮询的 Agent 状态监控与语音通知实战指南

【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS

导读

monitor是 LifeOS 的 CMUX 技能中负责"观察—改进"闭环的工作流:它定期轮询一个工作区(workspace)内的所有 surface,把每个 Agent 的状态分类为 idle / working / done / awaiting-input,并在某个 Agent 刚刚完成或需要你输入时,通过 Pulse 的/notify接口触发 {{DA_NAME}} 语音播报。读完本文,你将掌握 monitor 的完整调用方式、四态分类的底层判定逻辑、状态迁移触发语音的实现原理,以及如何把它接入 Agent 竞速、多团队编排等实战场景,做到"只看该看的,不盯不需要盯的"。

为什么需要 monitor:看不见的 Agent 无法被改进

在 cmux 组成的多 Agent 工作区里,一个你看不见的 Agent 就是一个你无法改进的 Agent。当一个 8 个 Agent 的团队分散在多个你从不查看的窗格中时,它们实际上只是 8 个静默的黑盒——只有当你碰巧瞥一眼时,才会发现它们卡住了、死循环了,或者早就完成了。

monitor正是为填补这一空白而设计:它替你在每个 surface 上持续盯守,并在"值得你注意"的状态迁移发生时主动发声,从而把你的注意力导向真正需要它的 Agent,而不是花在照看那些不需要照看的 Agent 上。这一点与 CMUX 技能的定位一脉相承——SKILL.md 开篇即强调:"an agent you can't see is an agent you can't improve",而monitor就是让"看得见"这件事自动化的工具。

设计前提:poll-not-event 的现实

cmux 没有任何 push 或事件订阅命令,也没有"完成时通知我"的回调可以注册。因此monitor采用轮询模型:

  • 每隔--interval秒,遍历工作区中的每个 surface;
  • 对每个 surface 调用surface-health,并读取屏幕尾部(read-screen);
  • 将本次状态与上一轮的结果做 diff。

所谓"通知",是轮询循环检测到的状态迁移,而不是应用主动发出的事件。这是 cmux CLI 本身的设计约束,而不是封装层的缺陷。这一设计取舍在 DESIGN.md 中被明确记录:"cmux has no push or event-subscribe command... 'Agent finished' is discovered by pollingsurface-health+read-screenand matching idle/done markers. We design the monitor as a poll loop, full stop."(cmux 没有订阅命令,"Agent 完成"只能通过轮询 surface-health + read-screen 并匹配 idle/done 标记来发现,monitor 就是一个彻头彻尾的轮询循环。)

核心用法:启动监控循环

在封装层 Tools/cmux.ts 中,monitor子命令的入口为commandMonitor(cmux.ts),基础用法如下:

bun ~/.claude/skills/CMUX/Tools/cmux.ts monitor --workspace beta --interval 3

参数说明:

参数含义默认值
--workspace <ref>要监控的工作区引用(如workspace:7beta全部 surface
--interval <N>两轮轮询之间的间隔秒数,必须是正整数3
--once只做一轮分类便退出(适合脚本化定点巡检)循环运行直至 Ctrl-C

间隔参数由parsePositiveInteger校验(cmux.ts),非法值会返回错误--interval must be a positive integer。循环期间按 Ctrl-C(SIGINT)会输出{"ok":true,"stopped":true}后干净退出。

每一轮中,monitor 对每个 surface 依次执行surface-healthread-screen(读取尾部40行,常量MONITOR_TAIL_LINES = 40,见 cmux.ts),然后将结果交给分类器,得到以下四类状态:

状态含义典型标志
idleshell 提示符,没有活跃任务行尾为$%#等提示符
working输出仍在滚动 / 进程仍在运行不匹配其他任何类别的兜底状态
done尾部出现完成标记绿色测试通过、"done"、"completed"、exit code: 0、✓/✔ 符号
awaiting-input有一个提示正等着你回应y/n(y/n)press enterconfirmcontinue?或行尾?

四态分类的源码级实现

分类逻辑集中在classifyScreen函数(cmux.ts),判定顺序本身就是优先级设计:先判awaiting-input,再判done,再判idle,最后兜底为working。注意done的优先级低于awaiting-input——如果屏幕尾部既有完成标记又有提问,优先视为需要你介入。

function classifyScreen(text: string): MonitorStateName { const tail = text.trimEnd(); if (/(do you want|\[y\/n\]|\(y\/n\)|press enter|confirm|continue\?)/i.test(tail) || /\?\s*$/.test(tail)) { return "awaiting-input"; } if (/(^|\n)(done|completed|exit code:\s*0)\b/i.test(tail) || /[✓✔]\s*(done|complete|completed)?/i.test(tail)) { return "done"; } if (/(^|\n)[^\n]*([$%#❯])\s*$/.test(tail)) { return "idle"; } return "working"; }

分类的输入来自readMonitorStates(cmux.ts):

  1. 先调用surface-health拿到工作区所有 surface 的引用列表;
  2. 若拿不到任何 surface 引用,则退化为对整体聚合输出分类(以 workspace 引用代替 surface 引用);
  3. 否则对每个 surface 单独执行read-screen --lines 40
  4. 读取失败的 surface 一律标记为working(保守处理,避免误报完成);
  5. 输出结构为{ ok: true, states: [{ ref, state, textTail }] },循环模式下每轮输出一行 JSON。

这里可以做一个值得注意的推断:分类完全依赖屏幕尾部的文本启发式,因此它在不同 shell、不同 Agent CLI 之间的表现会有差异——这正是 DESIGN.md 风险清单中明确指出的"done 检测是启发式的,且跨 shell 容易脆弱"。在真正的生产依赖落地前,建议用send → read回读验证来双确认。

状态迁移触发与语音通知

monitor 的核心价值不在于"知道状态",而在于只在状态发生迁移时通知。循环内部维护了一个previous: Map<ref, MonitorStateName>(cmux.ts),每一轮对比新旧状态,仅当满足以下条件时才发声:

const oldState = previous.get(state.ref); if (oldState !== state.state && (state.state === "done" || state.state === "awaiting-input")) { const label = state.state === "done" ? "done" : "awaiting input"; await notifyVoice(`cmux surface ${state.ref} is ${label}`); } previous.set(state.ref, state.state);

即:只有"从其他状态迁移到 done 或 awaiting-input"才触发通知。这带来两个重要的工程收益:

  • 状态必须跨轮保持,一次画面闪烁不会立刻产生通知——相当于内置了一重去抖(debounce)机制,避免"一帧抖动就刷屏";
  • 已经处于 done / awaiting-input 的 surface 不会重复播报,只有新的迁移才会发声。

通知动作由notifyVoice(msg)(cmux.ts)完成,它是一个 fire-and-forget 的 HTTP POST,目标地址由常量VOICE_URL = \${PULSE_BASE}/notify`` 决定:

POST http://127.0.0.1:31337/notify { message, voice_enabled: true }

请求体携带{ message, voice_enabled: true },并设置了 5 秒的超时(AbortSignal.timeout(5_000)),失败时静默返回false,不会拖垮轮询主循环。

文档中描述的语音内容是beta/worker-2 finishedbeta/lead awaiting input这类带角色语义的消息;从源码看,当前封装层实际发送的模板为cmux surface <ref> is donecmux surface <ref> is awaiting input,即"surface 引用 + 状态"的机器可读格式——如果你希望播报更人性化(比如带上 Agent 的角色名),可以在外层把 workspace/surface 引用映射为团队角色后再调用。

Pulse 端如何接收

PULSE_BASE定义在 endpoint.ts:

export const PULSE_BASE = process.env.PULSE_URL ?? "http://127.0.0.1:31337"
  • 默认硬编码为 IPv4 字面量127.0.0.1:31337,这是有意为之:Pulse 只绑定 IPv4,macOS 上localhost会同时解析到::1127.0.0.1,Happy-Eyeballs 可能先尝试失效的 IPv6 地址造成卡顿,硬编码 IPv4 可消除该停滞;
  • 可用环境变量PULSE_URL覆盖(如 Pulse 运行在其他主机/端口、或远程 fleet 成员);
  • endpoint.ts刻意零依赖,方便 hooks 与技能工具直接 import。

语音端点在 VoiceServer/voice.ts 中处理POST /notifymessage缺省为"Task completed"voice_enabled !== false决定是否发声,title缺省为"LifeOS Notification";POST 路由还带有基于客户端 IP 的限流(checkRateLimit)与 CORS 预检处理。TTS 的播放通过串行队列(playbackQueue)排队,保证并发 /notify 不会在扬声器上重叠(voice.ts)。

单轮巡检:--once 模式

如果不想长期驻留循环,而只是做一次"定点体检"(例如放在另一个工作流内部、或作为boot-team后的即时确认),使用--once

bun ~/.claude/skills/CMUX/Tools/cmux.ts monitor --workspace beta --once

该模式完成一轮surface-health+read-screen+ 分类后立即输出结果并退出(return 0),不进入sleep等待。它同样执行迁移判定——第一轮previous为空,因此不会触发任何语音通知,只会输出{ ok: true, states: [...] }的 JSON。典型用法见 BootTeam.md 的实战示例:向 lead 派发任务后,用monitor --workspace workspace:5 --once快速确认整个团队的状态,再把长期盯守交给循环模式的monitor

monitor 如何喂给 Pulse 与语音

需要强调的是:monitor不是要替代 LifeOS 的 Pulse 仪表盘,而是喂给它。分类出的 surface 状态流向 Pulse(默认127.0.0.1:31337),与旧的 Kitty 标签状态层上报 working/done/awaiting 的方式同源;完成消息则走既有的/notify → {{DA_NAME}} TTS语音通道。也就是说:

  • cmux 是"新被盯守的 surface 层";
  • Pulse 与语音保持原样不动;
  • 整体链路是"状态进,仪表盘 + 语音出"。

这与 DESIGN.md 的核心设计判断一致:几乎整个 LifeOS 都构建在终端之上——Pulse 读 work.json、语音打 HTTP 端点、算法写 phase 到注册表,都不关心终端是 Kitty 还是 cmux;唯一真正耦合 Kitty 的是标签状态绘制层,而 cmux 只替换终端盯守这一层。monitor 对应的正是设计文档中 Phase 2 的"cmux 状态 → Pulse 桥接"(DESIGN.md 的 Phased rollout 一节),其风险被标注为中等:轮询成本与标记启发式误报——分类器太吵会刷爆语音,这正解释了上面"只在迁移时发声"的设计。

实战示例:无值守看护一场 Agent 竞速

monitor最典型的落地场景是 Agent 竞速(race):多个 Agent 同时进攻同一个问题,谁先解决谁赢(详见 AgentRace.md)。当你同时跑 5 个 Agent 时,不可能一直盯着屏幕等第一个完成者——交给 monitor 即可:

# 一个 5 Agent 的竞速正在 workspace:7 中运行(参见 AgentRace.md) bun ~/.claude/skills/CMUX/Tools/cmux.ts monitor --workspace workspace:7 --interval 2 # ... 你可以去忙别的事 ... # {{DA_NAME}}: "cmux surface surface:32 is done" <- 第一个完成,语音立即响起

听到播报后,拉取胜者并标记:

# 读取胜者 surface 的尾部 80 行(--lines 缺省即为 80) bun ~/.claude/skills/CMUX/Tools/cmux.ts read --surface surface:32 --lines 80 # 对整个工作区触发视觉闪烁,把注意力引导过去 bun ~/.claude/skills/CMUX/Tools/cmux.ts flash --workspace workspace:7

read子命令对应源码中的commandRead(cmux.ts),底层调用read-screen --lines N--lines缺省 80;flash底层调用trigger-flash(cmux.ts)。竞速场景的完整闭环(启动 → 轮询 → 读取胜者 → 关闭败者 surface)在 AgentRace.md 中有完整的五步说明,其中第 3 步正是 monitor 轮询。

与团队编排工作流的协同

monitor 盯守的"团队"来自两条路径:

  • 分层团队(tiered)boot-team --tiers orchestrator,lead,worker,worker在一个工作区铺开三层团队,见 BootTeam.md;
  • 网格舰队(grids)fleet --name beta --grid 2x2搭建本地网格,mini-fleet打开远程 SSH 窗格,见 Fleet.md。

在 Fleet.md 的实战示例中,一个完整的全栈特性团队(本地 2x2 网格 + 远程 mini-fleet)最终统一用一条命令盯守:

bun ~/.claude/skills/CMUX/Tools/cmux.ts monitor --workspace beta --interval 3

而 SKILL.md 的 Quick Reference 与示例也把monitor --workspace <ws>作为"lead 回报时语音通知"的标准收尾动作。

前置条件与已知局限

在真正把 monitor 投入生产前,需要了解以下来自 SKILL.md 与 DESIGN.md 的事实与限制:

  • Mac-only:cmux 是 macOS 应用,没有 Linux/WSL 版本,对应路径是 tmux;远程 fleet 通过本地 SSH 窗格驱动,不需要远端安装 cmux。
  • Socket 默认拒绝(#1 Gotcha):cmux 的 socket 默认 deny,外部进程会被拒绝连接。两种通过方式:(a) 在 cmux surface 内部运行编排器(继承认证);(b) 在 cmux Settings 设置 socket 密码并导出为CMUX_SOCKET_PASSWORD(封装层通过buildCmuxCommand自动附加--password,见 cmux.ts)。安全提示:持有 socket 密码的本地进程可以驱动你的整个 Agent 舰队,务必谨慎设置且不要提交到公开文件。
  • 自动拉起:若 socket 不存在,封装层会自动open -a cmux拉起应用,并以 750ms 间隔轮询ping最长 15 秒(PING_WAIT_MS = 15_000),超时则明确报错而非静默失败(cmux.ts)。
  • 状态验证状态:据 SKILL.md 的 2026-07-07 状态说明,封装层本身已通过类型检查(tsc/bun build)、voice已实测可用,但 monitor 的 live 驱动(socket-auth 握手)尚未执行线上验证,属于"离线验证通过、线上待证"状态——部署前请先在 cmux surface 内部或设置好CMUX_SOCKET_PASSWORD的情况下跑通一次。
  • 轮询成本与调优:DESIGN.md 指出间隔是一个调优旋钮:太密烧 CPU 且容易刷屏语音,太疏则"完成"通知滞后(默认约 3 秒)。分类器需要去抖,而"仅在迁移时发声"的 previous-map 判定正是该去抖的实现。
  • 引用是位置性的workspace:1/surface:2这类索引会随开合而漂移;对于长期存活的盯守对象,建议先用tree配合--id-format uuids解析出 UUID 并持有(SKILL.md Gotchas)。
  • 优先 hooks,轮询是兜底:SKILL.md 特别说明,对 Claude Code 启动的 Agent,cmux 会自动注入生命周期 hooks(SessionStart/Stop/Notification/UserPromptSubmit → cmux claude-hook <event>),Agent 会主动上报状态;monitor 的surface-health+read-screen轮询是非 Claude Agent 的兜底路径,而非首选通道。

小结

monitor把"盯着屏幕看 Agent"这件重复劳动,抽象成一条可审计、可脚本化的观察循环:轮询surface-healthread-screen→ 四态启发式分类 → previous-map 迁移判定 →/notify语音播报。它不替代 Pulse 仪表盘,而是作为新的 surface 状态源把 cmux 接入既有的"状态进、仪表盘 + 语音出"链路。无论是无值守看护 Agent 竞速、巡检三层团队,还是盯守远程 mini-fleet,一条monitor命令即可把注意力还给真正需要它的 Agent——这正是"observe-to-improve"闭环的落地点。

【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询