Files
dsh_ai1net_server/交付物/唤醒机制-现行工作机制与能否运行判定-20261002.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

242 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 唤醒机制 · 现行工作机制说明 +「能否正常运行」判定
> 日期: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
```