← Silent Star monitor running source notes · 2026.08.25

Agent monitor source notes

三个 Coding Agent 怎么等长任务

我从一行 1 monitor still running 往下查,读了 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 可以把轮询留在本地脚本里,不让模型陪着醒。Codex 能把一次查询挂起很久,回合数会少一些,下一次读取仍要由模型发起。

01 · The question

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

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

把一条耗时两分钟的测试放到后台,几次查询没有多大影响。换成模型训练、视频生成或大型构建,任务一跑就是半小时。Agent 每隔十几秒读一次日志,每次都要发起工具调用,输出随后进入会话,模型再判断一次。GPU 没快一秒,聊天记录已经长了一截。

这里要分清三个动作。

Local polling

普通进程轮询

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

Tool polling

Agent 查询工具

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

Event wake

事件推进会话

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

前两种都叫轮询,成本差得很远。一个安静的 shell 循环每 45 秒查一次远端 worker,只花本机进程和一次 SSH。模型每 45 秒醒一次,会多出工具调用、上下文和推理。本文比较的就是这个位置。

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 秒醒来。状态没变,shell 继续睡。最后一行出现以后,Grok 才回来检查产物。用户这时也可以继续输入别的问题,后台 monitor 不会占着当前对话回合。

1 monitor still running 是一条活任务提示。

它表示后台还有能够唤醒 Agent 的工作。脚本还在跑,主 Agent 已经可以停下来。monitor 完成或产生事件时,会话再接着走。

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 进入 PTTPTT 调用 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")主查询循环接走通知,开始或继续模型回合

一行输出不会粗暴地变成一次立即中断

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

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

PBi 还维护一个速率桶。Monitor 可以短时间连发 10 次,之后每两秒恢复一个名额。输出过快时,多余事件会被压住。超限状态持续 30 秒,Claude Code 会停掉 Monitor,并送出内部提示。训练脚本即使疯狂刷日志,也不会无限制造模型回合。

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

xwe 生成 Monitor event,随后调用 enqueuePendingNotification。实际代码里的队列字段只有两行,却把唤醒方式交代得很清楚。

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

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

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

公开 SDK 类型能证明哪些事

Agent SDK 0.3.241 的 MonitorInput 定义了 commandtimeout_mspersistent。默认超时五分钟,最大一小时。SDKTaskNotificationMessage 公开了 completedfailedstopped 三种最终状态。

这些类型与二进制里的路径一致。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

模型读取结果再决定

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

这套实现已经比每十秒刷一次状态安静很多。它仍由查询推动。当前回合若已经交还用户,普通后台终端完成通常不会仅凭这个状态自行开一个新回合。

源码里还能看到一个 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。普通进程负责盯明确条件,模型只处理有意义的变化。Codex 的长 poll 已经把查询频率压得很低,长任务跑得越久,事件通知省下的回合越明显。

这个判断只覆盖后台长任务监控,不代表三款工具的整体能力排名。Codex 的公开源码最容易核验,Grok 的事件语义最直观,Claude Code 在发布二进制里做了最细的输出整理。三家处理的是同一类麻烦,取舍各有侧重。

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

远端接口可以把间隔放到 30 秒以上,本地文件和进程可以更短。管道要留意缓冲,stderr 也要按工具语义处理。最要紧的一条很朴素。没有需要 Agent 处理的事,就别打印。

07 · Boundary

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

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

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

那行 1 monitor still running 的意思到这里已经很清楚。任务还在运行,主 Agent 已经可以停下来。等值得处理的事件出现,它再回来。

08 · Sources

源码与版本依据

文章目录8
Silent Star约 10 分钟