# 唤醒机制 · 现行工作机制说明 +「能否正常运行」判定 > 日期:2026-10-02(07:0x 取证) > 触发:用户两条指令 —— ①「把唤醒框图的线 **连接到 协作程序上**,然后分析 **唤醒 后台任务机制是否可以正常运行**」②「**详细说明当前唤醒的具体工作机制**」 > 取数方式:**只读**宿主机库(`sqlite3 file:...?mode=ro`)+ 工作区台账/日志文件。⛔ 未写库、⛔ 未拷库文件、⛔ 未起任何进程。 --- ## §0 一句话结论 **唤醒机制「能准时醒、能走完流程、但送不出去」——而且送不出去的时候,它一声不响。** | 环节 | 判定 | 一句话 | |---|---|---| | ① 时钟(排期) | ✅ **正常** | 宿主每小时真触发、真开一条新会话,连续 8 轮有记录 | | ② 执行(会话内三步) | ✅ **正常**(有 1 次例外) | 让位检查通过、三条判据都取到真读数、跑了一次 `--tick`;⚠️ 近期 8 跳里 **1 跳被中断** | | ③ 送达(`--tick` 投递) | ❌ **结构性不达** | 投了,但被自己的**去抖 `same-item`** 挡掉;且**不写日志、不喊用户** ⇒ 静默空转 | ⇒ 「是否可以正常运行」的答案是:**排期与执行这两段可以,整条链路不行** —— 卡在**最后一段**,而且是**无声地卡**。 --- ## §1 现行机制 · 六个环节(每环都附一手证据) ### 环节 1 · 时钟 = **宿主的一条每小时排期**(⛔ 不是常驻进程、⛔ 不是网关注册的定时任务) | 项 | 实测值 | |---|---| | 排期名 | `[唤醒]-会话协作自检-脉冲` | | id | `7fe0fe82…` | | 类型 | `recurring` / `FREQ=HOURLY;INTERVAL=1` / `status=ACTIVE` | | 下次到点 | `automations.next_run_at` = **10-02 07:10:34**(⇒ 这一项**读得到**,见 §4 的看板假话②) | | 工作目录 | `E:/ProgramData/AIProject/ai1net-dsh-server`(正斜杠形态) | 复跑:`sqlite3 file:E:/ProgramData/.workbuddy/workbuddy.db?mode=ro "select id,name,rrule,next_run_at,status from automations where name like '%唤醒%'"` > ⚠️ **同线的两条旧排期仍是 `PAUSED`**:`[协作]-唤醒机制-唤醒轮A/B`(`caca9a89` / `16bec5ce`)⇒ **在册但不生效**,⛔ 别把它们当成"还有两条钟"。 ### 环节 2 · 触发 = **每跳开一条新会话**(⛔ 不是"复用同一条") `automation_runs` 逐跳有记录;`sessions` 里同标题会话**每小时新增一条、id 各不相同**: | 建(开新会话) | 完 | 会话 id | |---|---|---| | 10-02 06:08:00 | 06:10:34 | `a5af050c` | | 10-02 05:06:24 | 05:07:34 | `01d55d84` | | 10-02 03:55:52 | 05:09:09 | `f9ad6a46` | | 10-02 02:47:55 | 02:55:41 | `cf2f3fe5` | | 10-02 01:34:04 | 01:47:23 | `1495f547` | | 10-02 00:25:36 | 00:33:50 | `945bb847` | ⚠️ **点火时刻是漂的**(01:34 / 02:47 / 03:55 / 05:06 / 06:08)—— 不是固定 `:xx`,**迟到量 4~47 分钟**。 ⇒ 所以**任何"唤醒准点就会推"的假设都不成立**;靠它报时不可行。 ### 环节 3 · 会话内三步(这是"机制本体") 从最近一跳(06:08,会话 `a5af050c`)**自己的自述**逐字取: 1. **第 0 步 · 让位检查** —— 「本工作区除本会话外没有正在执行的 `[唤醒]` 会话(两道判据都指向本会话)⇒ 本轮照常执行」。 2. **取三条判据**(真读数): - ① 除本会话外正在执行的会话 = **0**; - ② `queue.json` **有待处理项** —— `head=S9` / `pending=1` / `doing` 空 / `gate=free`; - ③ `goal.json.acceptance_state` **仍有非 pass 项** —— 只剩 `V2-主会话可响应=待重验`。 3. **跑一次 `collabd.py --tick`**(="推一次投递轮")。 ### 环节 4 · 投递 = 「唤醒只负责**跑一次 tick**,推给谁由**协作程序**决定」 - 唤醒会话**不直接找跟进会话**;投递目标 `target=follow` 由协作程序判。 - 最近一次**成功**投递:**10-02 04:06:52 → `19e6e204`(`[跟进]` 会话),`http=200`,hash `512943bd519bbf1d`**。 - ⚠️ 此后**再无成功投递**(台账 `wakeups.jsonl` 最后一条就是它)。 ### 环节 5 · 三条兜底出口(推不成时写文件) | 文件 | 什么时候写 | 现状 | |---|---|---| | `NEED-USER.md` | 需用户介入 | mtime **10-02 05:01**(停在 05:01 那一版) | | `READY.md` | 「可派未派(浪费)」 | mtime **10-02 06:54**,内容=派 **S9** | | `STALL.md` | 有会话在跑但长时间无成果 | 06:06:31 生成 | 🔴 **但是**:`--tick` 被**去抖**挡掉时,**这三份一份都不写**(下详 §3-①)。 ### 环节 6 · 它与另外两台"钟"的分工 | 角色 | 谁在驱动 | 出口 | |---|---|---| | **唤醒**(本文件) | 宿主排期,每小时,**开新会话** | 跑一次 `--tick` | | **跟进** | 宿主排期,每小时,**开新会话**(`055ffdd1`,next `10-02 07:34:23`) | 收队列上报、判"该不该派活" | | **钩子**(补充) | 宿主在 `PreToolUse ^Bash$` / `UserPromptSubmit` 时机起**短命子进程** | 事件驱动地补一次 `--tick` | ⇒ 三者的**唯一"推"出口都是协作程序**;⛔ 唤醒**不叫主会话**(主会话只由用户触发)。 --- ## §2 「能否正常运行」的判定 —— 三段分开答 ### ✅ 段一:能点火 - `automation_runs` 里连续 8 跳有记录,`result_success=1`;会话按预期被创建。 - ⚠️ **例外 1 次**:**05:06 那一跳被中断** —— 该跳的 `thread_title` 原文是 `Automation prompt interrupted: unknown_i…` ⇒ **那一小时的唤醒没跑完**。 ⇒ 结论:**"每小时必醒"成立,但"每小时必跑完"不成立**(近期 8 跳 1 次中断)。 ### ✅ 段二:能执行(会话内三步照跑) 06:08 那跳把三步全做完了,并且**自己写出了体检**:闸门在册、各闸门日志末行是 06:08、看板进程在跑。⇒ 会话内部的**流程**是好的。 ### ❌ 段三:**送不出去**(这是"整条链路不行"的地方) 真因**不是**"没人投",而是**投了、被自己的去抖静默挡掉**。三重叠加: **① 去抖 `same-item` 无时限(直接原因)** - 机制自己的状态文件 `tmp/supervise-inbox/collabd-state.json` 里: `"wake_info": { "skipped": "same-item" }`(mtime 07:01,最新一跳)。 - 含义:队首这条内容的 hash 与**上一次已投递的完全相同** ⇒ 判定"重复"、**跳过**。 - 🔴 它**没有时限**:只要内容不变就**永远不再投**。实测后果 —— **队首从 04:06:52 起约 2 小时没出过件**。 - 🔴 **而且它静默**:生产日志 `_collabd.log` 的**最后一行停在 05:01:23**,`NEED-USER.md` 也停在 05:01 ⇒ **被挡掉的每一轮,日志、喊话都不写**(="卡住但一声不响")。 **② `target-busy` 判据(已登记、未修)** - `_session_status()` 只回 `sessions.status` 小写串;"活"与"忙"**是同一根字符串 `working`** ⇒ **有活跟进会话时也会被判"忙"而延后**(S9 待修)。 - 旁证:日志里 04:06 那两行 `忙判据读不到(19e6e204)⇒ 按**没在跑**处理` —— 它是靠"读不到"才**绕过去**投成的。 **③ 常驻投递缺席(放大静默窗口)** - 全机进程扫描:**没有任何 `collabd.py --supervise`**(只剩看板 `board.py --serve 8788`)。 - ⇒ 本该由常驻投递承担的高频投递没了,**唯一投递源只剩"每小时唤醒会话 + 宿主钩子"**; 一旦上面 ①/② 命中,**没有人补投**,静默窗口就是一小时起。 **④ 顺带发现:看板服务此刻也已停** - 06:08 那跳的自述里还写着「看板进程 `board.py --serve 8788` 在跑」; **07:0x 实测**:`netstat` 里 **8788 无监听**,`tasklist` 里 **一个 `python.exe` 都没有**(`curl` 回 `HTTP 000`)。 - ⇒ **投递与看板"两个常驻"现在都不在** —— 它们起在会话里 ⇒ **会话一结束就被带走**(=S8 记过的那个根因)。 > 附带旁证(同样指向"读数曾经过期"): > · 生产 `_collabd.log` 尾部还留着 10-01 14:09–15:07 一串 `fatal 'gbk' codec can't encode '\u26d4'` > —— 即"编码未声明 ⇒ GBK ⇒ 整轮 fatal"那类故障的**历史痕迹**。 > · 弃用的 `wake-pulse.sh` 仍在盘上(`.workbuddy/collab/`)。 --- ## §3 本轮"顺带"修掉的(都是同一个病:**看板在说假话**) | # | 位置 | 原来写什么 | 实测是什么 | |---|---|---|---| | ① | `board_ext.py` 唤醒格 tip | 「唤醒已从『排期开新会话』改成『**脉冲复用同一条会话**』」 | **反的** —— 排期每小时**开一条新会话** | | ② | `board_ext.py` 唤醒格 tip | 「『下次几点到点』**⛔ 读不到** —— 脉冲是会话自己起的**后台任务**」 | **两半都错** —— 是宿主排期;`next_run_at` **读得到**(`10-02 07:10:34`) | | ③ | `board_ext.py` `WAKEUP_KINDS` | 用 `上报·唤醒`/`监督程序·心跳` 当"唤醒痕迹" | **两类 kind 都已退役**(最后一条 **09-30T10:44:43**)⇒ 照它读会得出「两天没响过」的**假结论**;现役真读数在**会话表** | | ④ | `board_ext.py` 唤醒格数据 | 死字段 `"edge": "唤醒"`(注释"箭头指主会话") | **零消费方**(`board.html` 已改成固定文案)⇒ "在册 ≠ 生效"的坑,**已删** | | ⑤ | `board_ext.py`(技能侧模板) | 改名替换过头留下的乱码 4 处(`(原来叫"唤醒")`、`("唤醒" not in nm) and ("唤醒" not in nm)` 等) | 已逐处订正 | | ⑥ | `references/architecture.md` | 「常驻投递进程…它同时提供**唤醒时钟**」/「**唤醒不是一个定时自动化**…不引入任何周期性排期」 | 🔴 **这两句是口径(定案),不是错的** —— 我 07:0x 一度把它们当"与现状相反"给废掉了,**同日已回改**(见 §7) | --- ## §7 🔴 同日自我更正:我把「口径」当「实测」废掉了(2026-10-02 07:2x) **起因**:用户问「**为什么唤醒会话还是定时任务呢,不应该是一个会话靠自己的后台任务定时唤醒吗**」⇒ 回查口径原文,发现**我上一轮的"订正"订错了**。 **定案口径(⛔ 一直没变,多处同款)**: | 出处 | 原文 | |---|---| | `architecture.md` §0 | ⛔ **已废弃**:**「用自动任务当闹钟」** —— 2026-10-01 用户明确废弃(原话:「**定时任务的方案已经废弃了**」) | | `architecture.md` §4.0/§4.1 | 两节**整节作废** ⇒ 「时钟由**常驻投递**提供」 | | `architecture.md` §5-1/`deploy.md` §5/`collabd.py` 用法头 | **常驻投递(`collabd.py --supervise`)= 投递本体 + 唤醒时钟**;⛔ 不用自动任务、⛔ 不靠宿主排期 | | `architecture.md` §8 | 「—— 它同时就是**唤醒时钟**」 | **⇒ 正确记法**:口径**不动**;**现状**是偏差 ——「**唤醒时钟缺位,由一条已作废的旧路代偿**」。 **我错在**:把"现状与口径不符"读成了"口径已被推翻",于是把两处**口径**当**实测**给订正掉了。⇒ 已回改(原文保留,另加「实测偏差」行,并写明⛔ 不许据此改口径)。 **现场读数(07:10 那一跳,正在跑)**:`[唤醒]` 会话 `477ab6ca` **建 07:10:53、status=working**;台账最后一条仍停在 **04:06:52**;去抖仍 `same-item` ⇒ **每小时开一条新会话、每次都投不出去**,此刻就摆在眼前。 ⚠️ **另有一条同样被误会的禁区**:用户问的「一个会话**靠自己的后台任务**定时唤醒」—— 这条路是 **2026-10-01 明令禁止**的(`architecture.md` §4.0 末:「⛔ 禁止(原「第四种」,仍然禁):把长跑服务放进**会话后台任务**」,理由=每轮输出唤醒宿主 ⇒ 会话永不空闲 ⇒ 用户看到"卡死",历史复现 6 次)。⇒ 它不是备选方案,⛔ 别照它照做。 🔴 **订正③值得单独记一笔**:它意味着**看板原样会告诉用户"唤醒两天没响过",而事实是它每小时都在响** —— 又一处「在册 ≠ 生效」的镜像(这回是"**在册的是旧的**")。 --- ## §8 相关引用的全量清扫(2026-10-02 07:2x · 对应「修复错误概念时要排查相关引用」) **缘起**:用户原话「修复错误概念的时候 要排查相关引用 确保更新完全」—— 同日的「口径 vs 实测」自我更正(§7)若不把**引用**一起改,下一个人读到的仍是旧概念。 **做法**:全量枚举 ⇒ 逐处**只加标注、⛔ 不删原字**(保留取证)⇒ 复核(判定窗口=该行**或其后 3 行**内是否带标注)。 | 落点 | 处数 | 说明 | |---|---|---| | `architecture.md` | 10 | §3 投递/派活行、§5-1 时钟行、§4 ④时钟行、§7.3 标题、排期名前缀 + **本轮 §4.1 的标题/红字「⇒ 🔴 结论」/「旧『代价』已被 ④ 补上」段/红字「⇒ 🔴 立场」** | | `collab-detail.md` | 8 | `:95/:235/:404/:425/:512/:513/:778/:891` | | `collab.md` / `SKILL.md` | 3 | 措辞「**唤醒**轮排期名」⇒「**自动唤醒任务**的排期名」(旧措辞会被读成"唤醒本该用排期") | | `board_ext.py` | 3 | 死函数 `_triggers_gw` 的旧名「心跳」;运行期文案「在等脉冲到点」⇒「现状由排期代偿到点叫醒;⚠️ 定案是常驻投递直接叫醒」 | | `MEMORY-全文-20261001.md` | 2 条 | **整条改写**(见下) | | `MEMORY.md` | 1 | 补入「🔴🔴 **唤醒时钟=这个常驻进程本身**」 | **🔴 最重的一处(差一点漏掉)**:状态层 `⇒ 全文` 的**指针目标** `MEMORY-全文-20261001.md:35`,原文**整条与定案相反** —— 「监管+投递**都=钩子事件驱动**」/「⛔ **不常驻**(常驻天生拿不到口令)」/「两个拨钟方:① 钩子 ② **自动任务**」/「⚠️ **常驻走不通**」。 ⇒ 已整条改写为定案口径 + 附「**2026-10-02 订正记录(三项全错)**」;同文件 `:34` 两处同步订正。 ⇒ **教训(新)**:查残留 ⛔ **不能只查"正文"** —— **指针目标(`⇒ 全文`/`⇒ 细则`)必须一起查**,它们在读者眼里就是正文。 **复核结论("确保更新完全"的证据)**:技能库 md 侧疑似残留 **30 → 13**;13 条**逐条判过 = 全部是正确口径/历史引用/用户原话**(如 `:546/:548`「常驻投递=唤醒时钟」正是**定案**、`pitfalls.md:363`「不建当闹钟的自动任务」正是**修法**)⇒ **无遗留**。 已退役技能名(`workbuddy-session-forensics`/`multi-session-collab`):**活引用零**(命中全在 `归档/技能-退役-20261001/`、带日期接续包、`归档/配置与备份/`)。 **回归**:`install.py --manifest` = **35 份 / 语法失败 0**;`--verify` = **全绿(PASS 45 / FAIL 0)**;两份 `board_ext.py` md5 一致(技能侧为源、cp 到工作区实跑份)。 **改前备份**:`tmp/bak-wake-sweep-20261002/`(4 份 md 改前原样)。 ⚠️ **顺带发现(既存,非本轮引入)**:`MEMORY.md` **8039 字符**(自设上限 7,650)/`MEMORY-全文-20261001.md` **10,453**(上限 7,800)—— **两者改前即已超**,建议专做一次压缩(涉及信息取舍,⛔ 不顺手做)。 ## §4 缺口 / 待决(⛔ 我**没有**擅自改) | 级别 | 缺口 | 影响 | 建议 | |---|---|---|---| | **P1** | 去抖 `same-item` **无时限** | 队首一旦投出过一次、内容不变 ⇒ **永不再投**(实测停摆 ≈2h) | 并入 **S9**(同方向:投递判据);至少加时限/退避 | | **P1** | 去抖跳过时**不写日志、不写 `NEED-USER.md`** | 卡住但**一声不响** ⇒ 只能靠人凑巧看见 | 静默跳过也必须**留痕**(已有 `skipped.jsonl` 那套写法可循) | | **P1** | 常驻投递缺席(`--supervise` 全机无进程) | 投递没有主时钟 ⇒ 静默窗口一小时起 | S8(载体)待决,⛔ 受"⛔ 不在会话里挂常驻后台任务"约束 | | **P2** | **看板服务同样已停**(8788 无监听、无 `python.exe`) | 用户看不到看板;且它是"读数打架"的又一例(06:08 报在跑、07:0x 已无) | 与 S8 同根因(起在会话里 ⇒ 会话结束被带走),一并治 | | **P2** | `target-busy` 判据把"活"与"忙"判成同一个值 | 有活跟进会话也会被判忙 | S9 | | **P2** | 唤醒格**还没读**排期表的 `next_run_at` | 看板给不出"下次几点醒" | 已登记,未实现(本轮只订正文案) | | **P2** | 同线的 `唤醒轮A/B` 两条 `PAUSED` 排期仍在册 | 容易被误读成"还有两条钟" | 收敛清单 | | **P3** | `_triggers_gw()` 无调用方(历史件) | 改它不会有任何效果 | 已加"无调用方"护栏注释 | --- ## §5 我没做的(如实) - ⛔ 未起常驻投递、⛔ 未派新会话、⛔ 未改 S9/S8/S5 任何一件的代码(都属机制层,且**本轮执行锁由本会话持有**,应由下一棒在抢到锁后做)。 - ⛔ 未删任何排期、未动线上任何文件(除本文档与 §3 列的订正处+看板技能文件)。 - ⛔ 未对 `_collabd.log` 的历史 fatal 做清理(那是取证痕迹)。 --- ## §6 复跑(全部只读) ```bash PY=E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe # ① 排期与运行记录(⛔ 别 SELECT *:automation_runs 有大字段会淹输出) $PY tmp/_q_wake3.py # ② 唤醒/跟进是不是各开新会话 $PY tmp/_q_wake4.py # ③ 唤醒最近一跳的自述(机制自己写的账) $PY tmp/_q_wake5.py # ④ 去抖现状 $PY -c "import json,io;print(json.load(io.open('tmp/supervise-inbox/collabd-state.json',encoding='utf-8'))['wake_info'])" # ⑤ 生产日志尾 tail -6 .workbuddy/collab/logs/_collabd.log ```