Skip to content

docs: record communication dog live TUI UAT - #837

Draft
vansin wants to merge 4 commits into
agent/grok-commdog-recoveryfrom
agent/grok-commdog-live-uat
Draft

docs: record communication dog live TUI UAT#837
vansin wants to merge 4 commits into
agent/grok-commdog-recoveryfrom
agent/grok-commdog-live-uat

Conversation

@vansin

@vansin vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What changed

  • replace the current CommHub recovery probe contract from send_message to lifecycle-backed send_task
  • record the exact single-node communication-dog runtime, rollback, hashes, session, and separate TUI/inbound/outbound UAT evidence
  • record the 2026-08-14 failure root cause: Grok auto-update deleted the supposedly pinned binary under ~/.grok/bin
  • require an immutable CommHub-owned Grok pin, version/hash verification, tmux remain-on-exit, and multi-signal health checks
  • explicitly disclose that ordinary text, web_search, and send_task coexist, while composer navigation/history/slash editing is not yet a complete native-TUI experience
  • keep A站狗 and P站狗 untouched

Why

The prior recovery could pass once and later degrade into a false-online node: the vendor-managed ~/.grok/bin directory was treated as immutable, the TUI/leader/socket process group disappeared, and the agent-node remained connected to Hub. A restart then failed deterministically with ENOENT.

The runbook now distinguishes deployment bytes from their Git recovery record, and distinguishes Hub presence from attributable runtime behavior. It also records the safe operating boundary instead of calling the current composer proxy a fully covered native TUI.

Scope

  • docs-only Draft PR
  • source/runtime evidence commit: 433b4af44bdcc09145c75b697634f74aec42a7df
  • latest runbook commit: 677709bb91813e1ef7852cfc5d3d32f7cb98f072
  • one runbook file; no package publish, merge, DB write, A/P node change, or fleet rollout

Validation

  • git diff --check
  • Grok pin 0.2.93 (f00f96316d) and SHA-256 4e0738d3b5550f3c842bc0ae69f468815c6329c008a110d0c27a694dc3401135
  • visible TUI web_search returned TOOL_TUI_OK OpenAI | Research & Deployment
  • CommHub task 398d11bd-edad-4eea-9418-0d241841ffff reached terminal replied with COMM_DOG_POST_PIN_OK
  • node/TUI panes and both sockets remained live after both probes
  • no secret values are stored; machine-local paths identify deployment/recovery coordinates only

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Identity correction (append-only): the earlier draft text said Vincent had attached to 通信狗. That attribution was not supported. The only observed fact was a tmux client on /dev/pts/171; tmux metadata cannot identify the human operator, and capture-pane showed no new operator input. Commit f22fd68653ba27c6141312eec4fe71cb8e3b1b96 corrects the runbook and requires both Vincent's direct rollout instruction and attributable UAT evidence (operator, time, session, input, visible response, Dashboard-origin task id and terminal state) before A站狗/P站狗 are changed. An arbitrary “正常” message is explicitly insufficient.

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

结论:记录本身经得起核,建议合并。一条 BLOCKER 级的合并顺序提醒(不是内容问题)

这是纯记录类 PR,此前只有一条 bot 评论。记录类最该核三件事:
有没有把凭据写进公开文档、记的事实存不存在、结论有没有超出证据。三条我都跑了。

一、密钥扫描:干净

对新增行做凭据模式扫描(ntok_ / utok_ / sk- / cli_… / -----BEGIN 等):

命中 0 个

grep 命中的那些行全是规则文字本身,例如
「不得从会话记录、旧日志或别的节点复制 token」「回复和事件中不得出现任何 token 或正文」——
是在规定不许泄密,不是在泄密。方向正确。

文档还明确写了「配置目录与 token 是运行数据,不提交仓库」「Git 只恢复软件和非密钥流程,
不恢复 Hub 数据库、node token、Grok 登录/会员状态」—— 这条边界写得对。

二、记的事实存在

新增内容里的 40 位 hex 共 15 个 → 仓库里**全部存在**,0 个悬空
正文的两个 head  c6c4de30… / f22fd686…  → 都是真 commit

三、结论没有超出证据(这是这份记录最值钱的地方)

三处自我设限我都读了,写法是对的:

  • 「这证明了该次节点注册、入站消费、连续 Grok turn、会话上下文、WebSearch 和 Hub 终态回执。
    它不证明长期稳定性、heavy 会员的完整配额,也不授权生产写操作」;
  • 「Grok 尝试 run_terminal_command 被安全边界拒绝后 TUI 子进程退出,而 agent-node 一度仍显示 idle
    …… 不能把 Hub 的 idle 状态单独当成可用性证明」——
    这条尤其好,它记的是一个观测陷阱,不是一次成功;
  • 操作者归因:/dev/pts/171 只能证明有客户端连接,不能证明是谁做的手工 UAT,
    并据此禁止把任意一句「正常」当成 rollout 授权。

记录类文档最容易犯的错是"把一次成功写成能力证明",这份反着来,把边界写在了每个结论后面。

🔴 合并顺序:它 stacked 在 Draft #808

git merge-base --is-ancestor pr/808 pr/837   → 是(#808 确实是 #837 的祖先)
gh pr view 808 → isDraft=true  state=OPEN

所以:

  1. 合并 docs: record communication dog live TUI UAT #837 会把 docs: record GrokTUI dog node recovery #808 的内容一起带进 main,而 docs: record GrokTUI dog node recovery #808 的作者还把它标着 Draft;
  2. 反过来,docs: record communication dog live TUI UAT #837 合了之后 docs: record GrokTUI dog node recovery #808 就变成空 PR(它的改动已在 main 里),
    届时不要再去合 docs: record GrokTUI dog node recovery #808 或以为它还有内容。

两个 PR 改的是同一个文件 deploy/fleet/grok-commdog.md,#837 的版本是最终态,
所以内容上不会打架 —— 但**"Draft 会被顺带合入"这件事得由 #808 的作者点头**,不该由 #837 的绿灯代劳。
这不是内容问题,是流程问题,写在这里让合并的人先确认一下。

四、顺带的现状核对

文档说「A站狗 与 P站狗 在 Vincent 直接授权前保持不动」。查 hub 实时:

通信狗  idle   A站狗  idle   P站狗  idle      (updated_at 均为分钟级新鲜)

三个都在线且 idle,与"未动"一致。(hub 的 runtime 字段这三个都是 None ——
那是首次注册值不回写的已知陷阱,不能用它判断节点实际 runtime,与本 PR 无关。)

(只读:未改本 PR、未 approve、未 merge;未连任何节点、未发任何探针消息。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

现场证据:本文那句「不能把 Hub 的 idle 单独当成可用性证明」今天真实复现了一次

本 PR 正文写过:

Grok 尝试 run_terminal_command,安全边界拒绝后 TUI 子进程退出,而 agent-node 一度仍显示 idle;
不能把 Hub 的 idle 状态单独当成可用性证明。

2026-08-14 这句话在 通信狗 上完整发生了一次,时间线如下(全部只读采样,带时刻)。

一、坏的那一段

08-13 16:28:13  启动,[grok-cli] execution mode=co-presence TUI
08-13 16:28:14  [grok-copresence] TUI ready session=bb0b01f0
08-13 16:31:11  正常注入一个网络任务(此时可用)
        …之后 TUI 子进程退出(run/ 下只剩过期锁,agent-node 无子进程)…
08-13 23:36:20  ✗ grok copresence observed an unowned or automatically resolved permission request
                → [grok_failure:approval_boundary]
08-14 00:20:06  同上,再失败一次;此后 8 小时无活动

而在这 16 小时里,hub 上 通信狗 一直是 idle,updated_at 分钟级新鲜。
我 08:37 采样时的判据:

~/.anet-grok/node-1a793d6b…/run/   只有 .lock,**没有 attach.sock、没有 leader.sock**
agent-node 进程                     无任何子进程
最后一次 TUI ready                  停在 08-13 16:28:14

二、好的那一段(2 分钟后)

08-14 08:39:04  新进程启动,日志首行 [grok-cli] execution mode=co-presence TUI
08-14 08:39     run/ 下 attach.sock 与 leader.sock **重新出现**
08-14 08:39:55  新 agent-node pid=3836269(旧的 2302597 已退出);6 个 grok-0.2.93 进程、5 个子进程
08-14 08:40:36  processTask returned **failed=false**
08-14 08:40:37  回复:COMM_DOG_TUI_COPRESENCE_OK

此刻 hub 上仍然是 idle 前后两种 idle 在 hub 视图里完全一样,
一种是"16 小时不可派活",一种是"真能干活"。

🔴 三、建议补进 runbook 的:怎么用三行区分这两种 idle

# 1) 有没有活的 TUI 端点(最直接)
ls ~/.anet-grok/<grok-home>/run/          # 要看到 attach.sock 与 leader.sock,只有 .lock = 没有 TUI

# 2) agent-node 有没有子进程(TUI 应该挂在它下面)
pgrep -P <agent-node-pid>                 # 空 = 没有 TUI

# 3) 日志里最后一次 TUI ready 与最后一次 approval_boundary 谁更新
grep -E 'TUI ready|approval_boundary' <node>/logs/*.log | tail -5

🔴 四、一个我踩过的坑,值得写进 runbook

<grok-home> 不是 CommHub 的 node_id

我第一次拿 config.json 里的 CommHub node_id(形如 n_72be30e0)去搜 grok leader 进程,
得到 0 命中,差点把"搜不到"当成结论。正确的键在日志里:

[grok-copresence] TUI ready session=… attach=/home/vansin/.anet-grok/node-1a793d6b1274fe11f641001c/run/attach.sock
                                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 这个才是

两个 id 长得都像"节点标识",但分属两套命名空间
用错键的失败形态是"查不到 → 以为没有",和真的没有长得一模一样

(只读:ps / ls / readlink / 日志 tail;
本次恢复不是我做的 —— 我未执行 kill / send-keys / node start|stop|restart 中的任何一个,
上一轮我也写明"重启在跑节点属红线,须 Vincent 点头"。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

补一条节点侧的自察症状(来自 通信狗 本人,与上面那三行宿主侧判据互补)

上一条我给了三行宿主侧判据来区分"两种 idle"。今天(2026-08-14 11:27–11:28)我按 Vincent 指示
把整段情况发给 通信狗 本人,它两次回执都 failed=false,并给出了它自己视角的边界:

我这边能确认的只有:当前会话在响应、是共存 TUI 形态;
attach.sock / leader.sock、子进程、hub 上的 idle 时间戳、approval_boundary 历史,
我这边看不到,也没法对账,没有和你叙述冲突的"亲身观测"

若以后本会话又出现"人能聊、任务却 approval_boundary",
以你宿主观测为准来判 TUI 是否又掉了。

为什么这句值得写进 runbook

它给出了一个节点侧唯一能自察的症状对:

可以对话(人在 TUI 里聊、或 SSE 任务能被 inject)
        +
任务却以 [grok_failure:approval_boundary] 失败
        ↓
就是"TUI 掉了但 agent-node 还活着"这一形态

这解释了为什么这个故障能安静存在 16 小时:从节点自己的视角看不出来 ——
它照常收任务、照常进入 grok-copresence 路径,只是每次都在审批边界上失败。
hub 侧同样看不出来(一直 idle、时间戳新鲜)。

所以三方视角要合起来用:

节点自己   → 只能给出上面那个症状对(能聊 + approval_boundary)
hub        → 给不出信息(两种状态的 idle 完全一样)
🔴 宿主侧   → 唯一能直接判定的:run/ 下有没有 attach.sock + leader.sock、
              agent-node 有没有子进程、日志里 TUI ready 与 approval_boundary 谁更新

附:本次双向验证的证据(它自己的日志)

11:27:49  ← task 11b67768  → [grok-copresence] injected network task
11:27:59  processTask returned **failed=false**
11:28:18  ← task 089c6ca4(1569 chars)
11:28:30  processTask returned **failed=false**

与 08-13 23:36 / 08-14 00:20 那两次 approval_boundary 失败形成对照 ——
同一条共存路径,在 08:39 重起 TUI 之后是通的。

(只读:读它的日志与 commhub 回执;本次恢复不是我做的,我未执行 kill/send-keys/node restart。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

🔴 更正 + 补一个此前没用过的证据源:grok 侧的 unified.jsonl

本 PR 的 runbook(以及我上面那两条评论)一直只用 agent-node 侧的日志判 TUI 状态。
2026-08-14 又掉了一次,我先按老办法归因,归因错了;换用 grok 自己的日志才看清。记下来。

一、我先怎么错的

通信狗 的 agent-node 日志里,11:31:03–11:33:49 连续出现:

[grok-copresence] unknown terminal control sequence was blocked:
    Grok co-presence keeps its runtime-owned always-approve policy immutable
[grok-copresence] slash command was blocked:  (同一句)

而三条宿主侧判据同时显示 TUI 没了(run/ 下 0 个 .sock、agent-node 无子进程)。
我据此报了「与拦截时间高度吻合的强相关」。

二、grok 侧日志说的是另一回事

~/.anet-grok/<grok-home>/logs/unified.jsonl     # 每行一个 JSON:{ts,src,pid,ver,lvl,sid,msg,ctx}

那次事件前后:

11:33:58  grok-pager  prompt.drain
11:33:58  grok-pager  prompt.acp_send.start
11:34:00  grok-pager  turn.first_activity
11:34:03  shell       shell.turn.inference_done
11:34:03  grok-pager  **pager quit**            ← info 级
11:34:03  shell       leader.client.disconnected

拦截发生在 11:31–11:33,而它在 11:33:58–11:34:03 又完整跑了一个 turn,然后才 pager quit
全文件 2006 条事件里没有任何 exit / fatal / panic / crash

这是一次正常退出(多半是有人关掉了 TUI),不是被拦截搞崩的。 我的第一版归因是错的。

三、为什么老办法必然看不出来

agent-node 的日志只记录它自己看得到的:任务注入、回执、以及共存策略的拦截。
TUI 自己的生命周期(启动/turn/退出)不在它的视野里。
所以:

判"TUI 现在在不在"  → 宿主侧三条判据(socket / 子进程 / TUI ready 时间)足够
判"TUI 为什么没的"  → 必须看 grok 侧 unified.jsonl,agent-node 日志给不出

区分"被人退出"与"崩了"的判据:

有 `pager quit`(info) 且无 exit/fatal/panic  → 正常退出
无 `pager quit` 但 socket 已消失            → 才往崩溃/被杀方向查

四、仍然成立的那半条

在共存 TUI 里敲 / 开头的命令确实会被拦(策略要保住 runtime-owned always-approve 不可变),
这一条本次实测成立;但它不会因此崩掉。两件事要分开说。

五、排查纪律

unified.jsonl 每行带 ctx 字段,那是会话正文
排查只取 ts/lvl/src/pid/msg,不要打印 ctx,也不要整文件贴出来。

(只读:读日志、pstmux capture-pane(未 send-keys);未重启节点 —— 是否重起在等 Vincent 一句话。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

把这件事从"轶事"变成"频次":26 小时里 TUI 退出 9 次,其中 5 次与 approval_boundary 同秒

上一条我用 grok 侧 unified.jsonl 更正了今天那次的归因。既然有了这个证据源,
就把整份日志的历史也算了 —— 结果比单次事件有意义得多。

一、频次

日志跨度:2026-08-13 09:31 → 2026-08-14 11:34 CST(约 26 小时)
`pager quit` 事件:**9 次**

CST 时刻:09:46:06 / 10:18:22 / 10:43:43 / 10:56:12 / 13:01:34 / 14:14:41 / 14:38:10 / 23:36:20
以及次日 11:34:03

这不是"偶尔掉一次",是一天掉九次。

🔴 二、与 approval_boundary 的时间关系:5 次同秒

把 grok 侧的 pager quit 与 agent-node 侧的 approval_boundary 逐个对时:

pager quit(CST) 最近的 approval_boundary 关系
08-13 09:46:06 09:46:06 🔴 同秒
08-13 10:18:22 09:47:35 无关联(0.5 小时)
08-13 10:43:43 10:56:12 无关联(0.2 小时)
08-13 10:56:12 10:56:12 🔴 同秒
08-13 13:01:34 13:01:34 🔴 同秒
08-13 14:14:41 14:14:41 🔴 同秒
08-13 14:38:10 14:14:42 无关联(0.4 小时)
08-13 23:36:20 23:36:20 🔴 同秒
08-14 11:34:03 23:36:20 无关联(12 小时)

9 次里 5 次与一次 approval_boundary 失败发生在同一秒。
同秒不是巧合,是同一事件的两侧记录 —— 印证了本 PR 正文那句
「安全边界拒绝后 TUI 子进程退出」。

三、这同时收窄了我上一条的更正范围

我上一条说"今天这次是正常退出,不是被拦崩的" —— 那句话只对今天这一次成立
(11:34:03 那次最近的 approval_boundary 在 12 小时以外,且它退出前刚跑完一个正常 turn)。

不能推广成"pager quit 都是正常退出":另外 5 次明确与边界拒绝同秒。
准确的说法是:

9 次退出里
  5 次  与 approval_boundary 同秒   → 就是正文描述的那个形态(边界拒绝 → TUI 退出)
  3 次  与任何 approval_boundary 都差 0.2~0.5 小时 → 原因不明
  1 次  今天 11:34:03,差 12 小时,退出前刚完成一个正常 turn → 看起来是人为退出

四、对 runbook 的建议

  1. 正文那句「边界拒绝后 TUI 退出」不是偶发,建议标注频次(实测 26 小时 9 次、其中 5 次同秒);
  2. 排查时两份日志要一起看:
    agent-node 日志给 approval_boundary 的时刻,unified.jsonlpager quit 的时刻,
    同秒 = 边界拒绝导致的退出;差很远 = 另有原因;
  3. 这也解释了为什么这个节点"总要重起":不是某一次意外,是一天九次
    要根治得看那条 always-approve 策略为什么会被触发到,那是产品侧的事,不在本 runbook 范围。

(只读:两份日志 + ps;unified.jsonl 只取 ts/lvl/src/msg,未打印 ctx 正文;
未重启节点 —— 是否重起仍在等 Vincent。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

根因方向:不是"TUI 不稳定",是每次要用工具就撞一次边界

顺着上一条的 5 次同秒往下查,把触发条件也定位了。这条只陈述日志显示的事实,
不提修法 —— 那是产品侧的取舍。

一、单次事件的完整链条(取 08-13 09:46 那次)

09:46:00  ← SSE new_task,← [通信牛] (task/low)
09:46:00  → processing [grok]
09:46:00  [grok-copresence] injected network task 67b5585c…
09:46:06  ✗ grok copresence observed an **unowned or automatically resolved** permission request
09:46:06  processTask returned failed=true
09:46:06  → 回 [通信牛]:[grok_failure:approval_boundary]

grok 侧同一秒:shell.turn.inference_done —— 模型刚推理完、正要用工具

🔴 二、关键在那句措辞:unowned or **automatically resolved**

它拒绝的不只是"没人认领的审批",还包括已经被自动批准的审批
而 grok 共存这一侧的策略恰恰是 runtime-owned always-approve
(这句在 slash 被拦时的文案里也出现过:
Grok co-presence keeps its runtime-owned always-approve policy immutable)。

于是形成一个结构性冲突:

grok 侧:always-approve —— 权限请求被**自动解决**
anet 共存守卫:拒绝**自动解决**的权限请求(它要的是人拥有的审批)
        ↓
任务里只要出现一次工具调用 → 守卫判边界违规 → 任务 failed → 同秒 pager quit

这解释了"一天九次":不是 TUI 脆弱,是每一个需要工具的任务都会触发一次
不需要工具的任务(比如今天我发的那两条纯文本问答)就能正常 failed=false 走完 ——
这与 11:27/11:28 那两次成功、以及 16:31 那次成功完全吻合。

三、可验证的推论(供接手的人复核)

任务需要工具   → 必然 approval_boundary + 同秒 pager quit
任务纯文本对话 → 正常完成(failed=false)

本次日志里两侧都成立:5 次同秒失败都在任务注入后数秒内;
而 08-14 11:27:59 / 11:28:30 两次纯文本任务 failed=false,TUI 也没退。

四、边界

  • 这是产品侧的策略冲突(grok 的 always-approve vs anet 共存守卫要求人拥有审批),
    不是 runbook 能解决的,也不该在 runbook 里"绕过";
  • 守卫本身的方向是对的 —— 它防的正是"自动批准把人排除在外";
  • 不建议为了让任务跑通去放松这条守卫:那等于把共存的安全意义去掉。
    真正的问题是两边对"谁拥有审批"的假设不一致,得由产品侧定。

(只读:两份日志;unified.jsonl 只取元字段,未打印 ctx。未改任何配置、未重启节点。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

🔴 撤回上一条的根因结论:"每次用工具就撞边界"被我自己的数据推翻了

上一条我写:「不是 TUI 不稳定,是每个需要工具的任务都会触发一次」。
我随后去数了一下,这句是错的,现在撤回。

一、推翻它的数字

grok 侧 `shell.tool.exec_done` 事件: **33 条**
approval_boundary 失败:              **5 次**
pager quit:                          **9 次**

工具执行了 33 次,只有 5 次撞边界。 所以"每个需要工具的任务必然触发"不成立 ——
绝大多数工具调用是正常跑完的。

再看那 5 次同秒的现场,shell.tool.exec_done 也只出现在其中 3 次:

09:46:06   inference_done            (无 tool.exec_done)
10:56:12   inference_done + tool.exec_done
13:01:34   inference_done            (无 tool.exec_done)
14:14:41   inference_done + tool.exec_done
23:36:20   inference_done + tool.exec_done

连"失败时一定在用工具"都不成立。

二、仍然站得住的部分

  • 9 次 pager quit 里 5 次与 approval_boundary 同秒 —— 这条是逐秒对时得出的,不受影响;
  • 那 5 次确实是"边界拒绝"与"TUI 退出"的同一事件两侧记录;
  • 今天 11:34:03 那次(差 12 小时、退出前跑完正常 turn)看起来是人为退出。

🔴 三、我现在不知道触发条件是什么,而且这份日志答不了

我去 grok 侧日志里找权限/审批类事件,整份 2006 条里只有 1 条包含 permission 字样,
而且是无关的(auth lock: failed to open …auth.json.lock: Permission denied)。

也就是说:

grok 侧日志       —— **不记录**权限请求/审批事件
agent-node 侧日志 —— 只记录守卫的**判决结果**(observed an unowned or automatically
                     resolved permission request),不记录**是哪个请求、来自哪个工具**

两侧都没有"是什么请求触发的"。所以触发条件在现有日志下不可判定 ——
我上一条把"措辞里有 automatically resolved"推成"always-approve 导致每次工具调用都触发",
那一步是推理,不是观测,而且和 33 vs 5 的数字矛盾。

四、要定这件事,需要的是什么证据(给接手的人)

在守卫判定处记录:被拒的那个 permission request 的**工具名/请求类型/来源**
    (现在只输出一句结论,拿不到区分 5 次失败与 28 次成功的字段)

有了这个字段,才能回答"为什么同样是工具调用,大多数过、少数不过"。
在此之前,任何关于触发条件的说法都只是猜测,包括我上一条那个。

五、我为什么会错

我从一句错误信息的措辞(automatically resolved)推出了机制,
然后只用"能解释一天九次"来自我验证 —— 没有去数它应该同时解释的那 33 次成功
一个能解释坏结果、却和好结果矛盾的机制,不是机制,是巧合。

(只读:两份日志计数;unified.jsonl 只取元字段。未改配置、未重启节点。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

横向对照:这个故障不是共存的普遍病,通信狗 是离群点

前面几条都只看 通信狗 一个节点。既然本机还有 3 个共存节点,就横着比了一下 ——
结论推翻了一个容易顺手得出的假设("共存 TUI 就是这么不稳")。

一、四个共存节点的原始计数

节点 approval_boundary pager quit
A站狗 0 1
P站狗 0 1
指挥狗 1 15
通信狗 7 9

二、先归一化,否则是拿不同窗口比

日志覆盖天数不同,直接比次数会得出错误结论:

节点 agent-node 日志覆盖 天数 approval_boundary 每天
指挥狗 2026-07-31 → 08-12 8 1 0.1
A站狗 2026-08-13 1 0 0.0
P站狗 2026-08-13 1 0 0.0
通信狗 2026-08-13 → 08-14 2 7 3.5

三、结论

  1. approval_boundary 不是共存的固有属性:
    A站狗 / P站狗 各自 0 次,指挥狗 8 天只有 1 次;
    通信狗 每天 3.5 次,比其它节点高一到两个数量级。
    → 它取决于这个节点被派什么活,不是"开了共存就会这样"。
  2. pager quitapproval_boundary 不是同一件事:
    指挥狗 退出 15 次却只有 1 次边界失败 —— 退出还有别的原因(与我前面那 3 次"原因不明"一致);
    A站狗/P站狗 各只退出 1 次。
  3. 所以此前那句「一天九次」只对 通信狗 成立,不能推广成共存节点的常态。

四、这对下一步的影响

要查根因,应该看 通信狗 与另外三个在"被派什么活"上有什么不同,
而不是去改共存机制本身 —— 后者对 A站狗/P站狗 显然是好用的。

(只读:四个节点的 agent-node 日志 + 各自 grok 侧 unified.jsonl 计数;
未打印任何 ctx 正文、未改配置、未重启节点。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

🔴 更正上一条:我按「天」归一化,但正确的分母是「任务数」

上一条我说 通信狗approval_boundary3.5 次/天 vs 其它节点 0~0.1 次/天,
"高一到两个数量级"。换成正确的分母后,这个结论要大幅缩水。

一、把工作量也量出来(这是我上一条漏掉的)

节点 收到任务 平均任务长度 tools 配置
指挥狗 17 303 字 None
A站狗 1 97 字 ['WebSearch']
P站狗 1 97 字 ['WebSearch']
通信狗 44 1625 字 ['WebSearch']

通信狗 是唯一被大量使用的节点 —— 任务数是别人的 2.644 倍,单条任务长度是 517 倍。

二、两种分母,两个结论

节点 AB 次数 按天 按任务
指挥狗 1 0.12 0.059
A站狗 0 0.0 0.0 (n=1)
P站狗 0 0.0 0.0 (n=1)
通信狗 7 3.5 0.159
按天    通信狗 3.5   vs 0~0.13   → 高 1~2 个数量级
按任务  通信狗 0.159 vs 0.059    → **只有约 2.7 倍**

A站狗 / P站狗 各只收到 1 个任务,0 次失败 —— n=1,统计上说明不了任何事,
不能用它们的"0 次"去论证"机制没问题"。

三、修正后的结论

  • ❌ 上一条那句"通信狗 比其它节点高一到两个数量级、是单点异常" —— 过强,撤回;
  • ✅ 站得住的是:通信狗 是唯一被真正压过负载的共存节点
    (44 个任务、平均 1625 字);其它三个的样本量根本不足以比较;
  • 按任务归一后差距约 2.7 倍,而且只有 指挥狗 一个可比对象(n=17)——
    这个差距可能是真的,也可能是任务性质不同(1625 字 vs 303 字)带来的,现有数据分不开。

四、我为什么又错

上一条我已经意识到要归一化,并特意按"天数"归了 —— 但没问一句:
这个现象真正随什么变化? approval_boundary 发生在处理任务时,
所以分母应该是任务数,不是日历天数。节点空转一天不会产生边界失败。

归一化选错分母,比不归一化更危险:它看起来严谨,数字也算对了,但量的是另一件事。

(只读:四个节点的 agent-node 日志计数 + 各自 config 的 tools 字段;
未打印任务正文、未改配置、未重启节点。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

一个干净的反例:两种归一化给出相反的排序

上一条我说"分母选错比不归一化更危险"。这里有一个当场的例子,值得贴出来,
因为它说明这份数据本身判不了"谁更容易掉"

指挥狗 那 15 次退出

grok 日志跨度:2026-07-31 → 2026-08-14(14 天,5279 条事件)
pager quit:15 次,其中只有 1 次与 approval_boundary 同秒
每次退出前一条事件(统计):
  turn.phase_transition 5 / session.create.done 2 / auth refresh 2 /
  turn.complete 2 / marketplace handle_list 1 / session.load.done 1 /
  shell.handle_prompt.done 1 / auth refresh completed 1

没有单一模式 —— 退出前在干什么,八种情况都有。

🔴 两个分母,两个相反的结论

节点 quit 次数 任务数 天数 quit/天 quit/任务
指挥狗 15 17 14 1.07 0.88
通信狗 9 44 ~1.1 8.18 0.20
按天    通信狗 8.18 vs 指挥狗 1.07  →  通信狗 差 7.6 倍
按任务  指挥狗 0.88 vs 通信狗 0.20  →  **指挥狗 差 4.3 倍**

排序反了。 同一份数据,换个分母,谁"更容易掉"的答案就调个个儿。

这说明什么

不是某个分母对、另一个错 —— 是我们还不知道 pager quit 真正随什么变化:

若退出主要由「处理任务」触发   → 该按任务归一 → 指挥狗 更差
若退出主要由「时间/空闲/人为」  → 该按时间归一 → 通信狗 更差
而现有日志里,那 15 次退出前的事件八种都有,分不出属于哪一类

所以正确的结论是"判不了",而不是挑一个看起来支持我想法的分母。

要判,需要的是退出事件里带上原因(是收到退出信号、还是超时、还是异常),
现在 pager quit 只是一条没有原因字段的 info 日志。

(只读:两个节点的 grok unified.jsonl 计数与前序事件统计;未打印 ctx、未改配置、未重启节点。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

把"需要退出原因字段"从推测变成已验证的事实

上一条我写:「要判 quit 属于哪一类,需要 pager quit 带上原因字段,现在它只是一条 info 日志」。
那句当时是印象,没验。现在验了,结论成立,而且比我说的更明确

一、pager quit 事件确实没有任何附加字段

对两个节点的 grok 日志,把所有 pager quit 事件的键名枚举出来(只看键,不读值):

node-1a793d6b…(通信狗)  pager quit  9 条 → ctx:不存在
node-a2f3e13f…(指挥狗)  pager quit 15 条 → ctx:不存在
两者顶层键都只有:['lvl','msg','pid','src','ts','ver']

二、而这份日志格式本身是支持 ctx 的 —— 所以这是这个事件的空白,不是格式的限制

全文件 2006 条,其中 1848 条(92%)带 ctx 字典
带 ctx 的事件:turn.phase_transition / shell.turn.inference_start /
              shell.turn.inference_done / model catalog … 等
ctx 里出现过的键(Top 10):
  prompt_id / loop_index / elapsed_since_turn_start_ms / from / to /
  phase_elapsed_ms / kind / ok / elapsed_ms / len

92% 的事件都带上下文,唯独"TUI 退出"这条什么都不带。
而它恰恰是排查时最需要上下文的那一条。

三、建议(产品侧,一行字的事)

pager quit 加一个 ctx.reason,取值至少区分:

signal      —— 收到退出信号(人按了 q / Ctrl-C / 关窗口)
idle        —— 空闲超时(启动行里已经声明了 idle timeout=300000ms)
boundary    —— 因审批边界拒绝而退出
error       —— 异常

有了它,前面那些"5 次同秒 / 3 次原因不明 / 两个分母排序相反"的问题都不用再猜

四、方法上的一句

我这次是枚举键名得出"没有这个字段",不是搜某个字段名没搜到 ——
后者会因为字段叫别的名字而给出假阴性。
(同理:上面"格式支持 ctx"也是枚举出来的,不是假设的。)

(只读:两份 unified.jsonl,只读键名与事件名,未读取任何 ctx 的值;未改配置、未重启节点。)

@vansin

vansin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

第二个样本已闭环:掉线 176 分钟、零任务损失;判据红绿两态都在真实翻转中见证过

按我此前给自己定的约束,只有"通信狗 恢复"这类触发条件才回来补一条。现在补完这条就收尾。

一、第二次掉线的完整区间

11:34:03  pager quit(grok 侧,info 级;退出前刚跑完一个正常 turn)
   …这 176 分钟里 hub 上一直是 idle、updated_at 分钟级新鲜…
14:30:49  重新启动(execution mode=co-presence TUI)
14:30:54  [grok-copresence] TUI ready session=272e16a7
          新 agent-node pid 4169429(旧 3836269 已换);socket 2 个 / 子进程 5 个

期间新任务 0 条 → 零实际损失。 这一点从头到尾都成立,所以本次的代价只是"如果有人派活会失败"。

二、那三条判据在这次事件里红绿两态都翻过了

11:34–14:30   socket=0 / 子进程=0  → 判"不可派活"(每次巡检都稳定复现,不是抖动)
14:30 之后    socket=2 / 子进程=5  → 判"可派活"

判据是随真实状态自己翻的,不需要任何调整。
同期把它推广到全部 4 个共存节点:

掉线时  copresence_total=4  ok=3  fake_idle=1(通信狗)
恢复后  copresence_total=4  ok=4  fake_idle=0

三、两次掉线的对照(本 PR 的两个样本)

第一次 第二次
08-13 16:28 TUI ready 08-14 08:39 TUI ready
某刻退出,23:36 首次任务失败 11:34:03 pager quit
时长 约 16 小时 176 分钟
期间任务 2 次,均 approval_boundary 失败 0 次,零损失
发现方式 事后追溯 判据在 5 分钟内报出

差别不在故障本身,在于有没有判据。

四、本条之后我不再追加

这条线上可执行的东西都已落地:三条判据、全舰团扫描、grok 侧 unified.jsonl 的用法、
ctx.reason 的具体建议、以及"判不了"的几处诚实留白。
剩下的都是决定题(ctx.reason 加不加、always-approve 与共存守卫的假设冲突怎么定),不是分析题。

(只读:日志与 ps;本次恢复不是我做的 —— 我未执行 kill / send-keys / node start|stop|restart。)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants