← Silent Star monitor running source notes · 2026.08.25

Agent monitor source notes

三个 Coding Agent 怎么等长任务

一张 Grok 截图里,主会话已经停下,输入框上方却还显示 1 monitor still running。我想知道后台任务怎样让 Agent 继续工作,于是对照 Grok Build、Codex 的公开源码和 Claude Code 2.1.241 的发布二进制,沿着进程输出、通知队列和模型调用往下查。

Grok Build 1.0.5 · Claude Code 2.1.241 · Codex CLI 0.149.1 · 记录于 2026 年 8 月 25 日

Grok 和 Claude Code 可以让普通进程等状态,再由事件推进主会话。Codex 0.149.1 的普通后台终端仍由模型调用 write_stdin 查询。

三者都能运行后台进程,区别在于等待是否需要模型继续发起查询。Grok 和 Claude Code 的 Monitor 可以先由本地脚本等条件,再通知会话;这里检查的 Codex 普通后台终端路径,仍由模型调用工具读取结果。

01 · The question

轮询本身没有错,模型陪着轮询才贵

这次问题来自一张 Grok 终端截图。Agent 已经工作了九分多钟,输入框上方还挂着 1 monitor still running。主会话看起来停了,后台的监控却没有消失。它像是在等 monitor 自己结束,随后再继续做事。

两分钟的测试查几次状态,影响可能不大。训练、视频生成或大型构建运行半小时以后,查询方式就值得单独看:shell 定时查进程只消耗本地资源,模型反复调用工具则还会增加请求、上下文和推理开销。轮询频率相同,参与等待的组件可能不同。

为避免混淆,下面把本地检查、模型查询和事件触发分别列出。

Local polling

普通进程轮询

shell 每隔一段时间查文件、进程或接口。没有对话上下文,也不调用模型。

Tool polling

Agent 查询工具

模型调用工具读后台状态,拿到结果以后判断要不要继续等。

Event wake

事件推进会话

普通进程先等条件,状态变化以后发通知,主会话才重新开始工作。

例如,每 45 秒通过 SSH 查询 worker,可以一直由脚本完成。只有脚本报告状态变化时才调用模型,与每次检查后都让模型判断是否继续等待,消耗的模型回合数不同。

02 · Grok Build

Grok 把等待交给 monitor,主会话等输出

Grok Build 的公开代码中,配置注册表把 Feature::AutoWake 映射到 features.auto_wake,默认开启。后台命令完成、subagent 返回或 monitor 产生事件后,空闲会话可以继续执行。

Monitor 工具的说明写得很直接。脚本的每一行标准输出都会成为一次主 Agent 唤醒。说明同时要求脚本控制输出量,只打印完成、失败或取消这类值得处理的状态。原始日志不能整段灌进去,带管道的过滤还要用行缓冲,免得事件在用户态缓冲几分钟。

01MonitorTool.run把命令作为后台任务启动,并保存输出文件
02run_monitor_pipeline读取新增内容,按行送进速率限制器
03MonitorEvent每个保留下来的事件进入会话通知
04AutoWake空闲主会话收到事件后重新运行

截图里的 GPU 监控脚本正好符合这个设计。它每 45 秒通过 SSH 进入机器和容器,检查四个 worker。全部完成时打印 ALL_DONE,进程提前消失时打印 WORKERS_DEAD,其余时间保持安静。

bash
while true; do
  check_workers

  if all_finished; then
    echo ALL_DONE
    exit 0
  fi

  if workers_died_early; then
    echo WORKERS_DEAD
    exit 1
  fi

  sleep 45
done

截图中的脚本仍在每 45 秒轮询,只是没有在每次检查后输出。全部完成或进程异常时,它才打印状态,让 Grok 检查后续产物;等待期间,用户仍可继续输入其他问题。

界面上的 1 monitor still running 表示后台还有监控任务。

它不要求主模型一直执行。接下来是否开启回合,还要看脚本输出或退出事件怎样进入会话;这条运行时路径在后续文章中继续检查。

03 · Claude Code 2.1.241

Claude Code 先整理 stdout,再把通知放进主队列

Claude Code 没有公开同等范围的 CLI 源码。只看 Agent SDK 类型,可以知道 Monitor 接受哪些字段,却看不到进程怎样启动、输出怎样进入会话。我对本机安装的 2.1.241 可执行文件做了只读静态检查,没有执行未知代码,也没有附加调试器或拦截网络。

目标文件位于 ~/.local/share/claude/versions/2.1.241。它是 arm64 Mach-O,可执行文件大小为 325,055,632 字节。SHA-256 如下。

sha256
1495eb7c42d3b4451f5f1cd38b6d498d22a4a38c802bc2be5c1cf1795e64820d

这个哈希与 Anthropic 随 Agent SDK 0.3.241 发布的 darwin-arm64 清单一致。清单对应 Claude Code 2.1.241,Git 提交为 c87e2742fc9ad269ec8920460d00a091b1e410f0。文件签名的标识是 com.anthropic.claude-code,签发方为 Anthropic PBC。

实际代码藏在 __BUN 段里

这个 Mach-O 有一个约 255 MB 的 __BUN 段,里面保存了 Bun 打包后的 JavaScript。变量名经过压缩,Monitor 的入口、stdout 回调和消息队列仍能连成一条可达路径。下面是几个关键函数在文件里的字节偏移。

函数字节偏移负责什么
PBi296308601处理 stdout、分批和速率限制
PTT296867034启动命令型 Monitor
U3t298622624注册本地后台任务并接上完成回调

命令型 Monitor 从 MTT.call 进入 PTT。PTT 调用 QFe 启动 Bash,把 PBi.onData 交给 onStdout,随后用 U3t 把任务登记为 type: "local_bash" 和 kind: "monitor"。

minified js
onStdout: p.onData
// ...
kind: "monitor"
01MTT.call → PTT选择命令型 Monitor,并启动 Bash
02QFe → PBi.onDatastdout 进入本地事件处理器
03HSl → xwe按行切开、分批、限流,再生成 Monitor event
04enqueuePendingNotification通知以 task-notification 模式进入队列
05getCommandsByMaxPriority("next")主查询循环接走通知,开始或继续模型回合

stdout 怎样合并成通知

SDK 的注释说每一行 stdout 都是事件。实际实现多了本地整理。HSl 按换行切开内容,非空行成为事件候选。短时间内连着到达的几行可以合成一条通知,原始缓冲区也有硬上限。

200 ms事件合并窗口
500单行字符上限
3,000单批字符上限
1 MiB原始缓冲上限
30 s持续超限停止

PBi 还维护速率桶:允许短时间连发 10 次,之后每两秒恢复一个名额。超限持续 30 秒时,客户端停止 Monitor 并生成内部提示。这些限制控制进入通知队列的事件量,因此监控脚本不宜直接转发全部训练日志。

next 决定事件什么时候进入模型

xwe 生成 Monitor event,再调用 enqueuePendingNotification。下面的两个队列字段说明它采用哪种通知优先级。

queue item
mode: "task-notification",
priority: "next"

主查询循环调用 getCommandsByMaxPriority("next"),把通知折进待发送的消息。最高优先级的 now 可以中止当前查询,Monitor 使用的 next 通常会等当前回合走到边界。它不需要模型反复查询,也不会在 stdout 一有字符时就打断正在进行的请求。

进程退出以后,ALm 等到命令结果,YIl 根据退出状态生成完成、失败或停止通知,仍以 task-notification 和 next 进入同一个队列。中途事件和最终完成由此汇到一处。

公开 SDK 类型能证明哪些事

Agent SDK 0.3.241 的 MonitorInput 定义了 command、timeout_ms 和 persistent。默认超时五分钟,最大一小时。SDKTaskNotificationMessage 公开了 completed、failed 和 stopped 三种最终状态。

这些类型与二进制里的路径一致。CLI 怎样启动进程、怎样合并输出、怎样限流和入队,仍以实际发布代码为依据。

04 · Codex 0.149.1

Codex 把一次 poll 等得很久,下一次仍由模型发起

Codex 的实现可以直接在公开 Rust 源码里追。我没有用不断变化的 main 分支代替本机版本,检查的是 rust-v0.149.1 标签,对应提交 ff29a44391deccde0aba0f8390337d7f3c319ea4。

write_stdin 处理器把空输入定义为后台 poll。继续进入 process_manager.rs,空输入会走单独的等待范围。

rust
if request.input.is_empty() {
    time_ms.clamp(
        MIN_EMPTY_YIELD_TIME_MS,
        self.max_write_stdin_yield_time_ms,
    )
} else {
    time_ms.min(MAX_YIELD_TIME_MS)
}

unified_exec/mod.rs 给出的空查询下限是 5,000 毫秒,默认后台终端上限是 300,000 毫秒。本机配置同样是五分钟。一次空查询因此可以挂住很久,原始命令若在这段时间结束,当前 poll 能直接拿到最终状态。

Start

后台命令返回 session ID

终端进程继续运行,模型拿到后续读写所需的会话标识。

Wait

空 write_stdin 挂起

一次查询可以等待五秒到五分钟,减少短间隔刷状态。

Continue

模型读取结果再决定

没结束就安排下一次查询,结束后继续检查产物。

长 poll 减少了短间隔查询,但续接仍由工具调用推动。若当前回合已经交还用户,这里检查的普通后台终端完成路径通常只更新状态,不会单靠退出事件创建新模型回合。

源码里还能看到一个 awaiter.toml。它把后台终端上限提高到一小时,并要求 awaiter 重复调用工具,逐步增加等待时间。agent/role.rs 上方写着 Awaiter is temp removed,整段角色注册已经注释。0.149.1 默认角色表没有启用它,普通后台终端继续走 write_stdin。

Codex 也能运行安静的 watcher。

watcher 可以只在成功或失败时打印,减少日志噪声。主会话仍要处在等待这条终端的回合里,或者等用户下次输入后回来读取结果。watcher 改善输出量,没有改变普通后台终端由工具查询续接的事实。

05 · Side by side

三家都能等,模型参与等待的程度不同

项目Grok Build 1.0.5Claude Code 2.1.241Codex 0.149.1
主路径Monitor event + AutoWakeMonitor event + next queuewrite_stdin poll
谁观察状态本地监控脚本本地监控脚本和 stdout 处理器后台终端加模型工具调用
没有变化时脚本继续等待脚本继续等待,不生成通知一次 poll 挂起,超时后模型决定是否再查
状态变化时一行输出唤醒主 Agent输出经过分批和限流后进入 next 队列当前或下一次查询读到结果
当前回合边界空闲时可自动开新回合next 通知通常等当前回合结束需要当前等待回合或新的用户输入
噪声保护工具端有限流,文档要求只打印关键事件200 ms 合并、速率桶、持续超限停止长挂起减少查询次数,输出仍会进入上下文
跨会话存活受当前会话生命周期约束受当前会话生命周期约束普通 terminal session 同样不是外部调度器

对截图这类长任务,我更倾向让普通进程先等明确条件,再由 Grok 或 Claude Code 的 Monitor 通知模型。Codex 的长 poll 也能减少查询频率,但两种方式实际省下多少请求,需要结合任务时长和等待参数测量。

这个偏好限于文中检查的版本和监控场景。Grok 的公开代码展示了事件语义,Claude Code 的二进制包含输出整理与入队逻辑,Codex 的 Rust 源码则能直接追踪 poll;本文没有据此比较三款工具的整体能力。

06 · Practical watcher

长任务监控脚本应该尽量少说话

事件驱动也会被坏脚本拖累。训练进度每变一次就打印,Monitor 便会不断制造事件。实用的 watcher 只关心能改变 Agent 下一步动作的条件。

值得通知留在日志里
完成文件已经写好进度从 42% 变成 43%
worker 提前退出每轮 loss 的普通波动
质量门槛没有通过重复出现的健康状态
外部任务进入终态没有改变下一步动作的中间值
bash
while true; do
  if test -f output/final.json; then
    echo DONE
    exit 0
  fi

  if ! kill -0 "$worker_pid" 2>/dev/null; then
    echo FAILED
    exit 1
  fi

  sleep 30
done

远端接口可按响应时效设置检查间隔,本地文件和进程另行处理;使用管道时还要留意行缓冲与 stderr。脚本应只报告会改变下一步动作的状态,否则事件通知也会变成高频查询。

07 · Boundary

Monitor 适合一段会话,长期作业仍要调度系统

Grok 和 Claude Code 的 Monitor 都受会话生命周期约束。关闭终端以后,后台任务不会随着恢复会话自动重建。Codex 的后台 terminal session 也不适合承担跨设备、跨重启的长期可靠运行。

半小时训练、一次 CI、一个 PR 状态或当前会话里的日志观察,很适合交给 Monitor。几天后的定时任务、机器重启后还要继续的训练、需要审计和重试的生产作业,应该交给 CI、守护进程或调度系统。Agent 接收终态,再做解释和后续动作。

这次先定位了等待和通知发生的位置。还需要继续追问:进程结束以后,事件只更新了界面,还是进入模型输入并开启了下一回合?后续检查会沿这条路径展开。

08 · Sources

源码与版本依据

文章目录8
Silent Star约 10 分钟