Files
dsh_ai1net_server/交付物/唤醒-上报即唤醒与门控归属-20260930.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

102 lines
7.6 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-09-30)
> 起因(用户原话):**「会话协作项目中有任何会话在执行时,唤醒定时任务跳过处理(应该是通过 hook),无任何协作队列无任务,且无任何会话执行时,检查目标状态(完成:同时暂停,阻碍:反馈给协作程序 同时暂停,未完成:反馈给协作程序),这样等队列信息上报给主会话时 是不是就唤醒了」**
> 判据:文中每条都标了源码位置(`~/.workbuddy/skills/multi-session-collab/scripts/collabd.py`)。
---
## 0. 结论(三行)
1. **是** —— 但前提是**主会话此刻活着**。因为在这套机制里,**「上报」不是写文件,是投递**;投递落地 ⇒ 主会话多跑一轮 ⇒ 这就是唤醒。
2. **主会话已退出 ⇒ 投不出去 ⇒ 不会唤醒**。那一格只能由**唤醒轮**(自动任务起新会话)覆盖。
3. 你这条规则里,**"有会话在执行就跳过""无队列才查目标""目标三态判定"大半已经是现成实现**(协作程序 `supervise()` / `_deliver_str`);真正**缺**的是两格:**「完成/阻碍 ⇒ 自动暂停」** 与 **「有队列但无会话执行 ⇒ 派棒」**。
---
## 1. 把「上报」拆成三段 —— 只有第二段会唤醒
| # | 段 | 具体动作 | 会不会唤醒谁 |
|---|---|---|---|
| ① | **内容** | 写 `tasks.json`(队列本体)、`TO_MAIN.md`(投影)、`blocked.json`、`NEED-USER.md` | ❌ **一个人都叫不起来**(纯落盘) |
| ② | **投递** | 钩子 → `POST /api/v1/sessions/<主会话>/reply {text}`(`collabd.py:1058`) | ✅ **这才是唤醒**:主会话多跑一轮 |
| ③ | **起会话** | 自动任务(唤醒轮) | ✅ 主会话不在时的**唯一**通道 |
> 判据:`supervise()` 先写 `TO_MAIN.md`(`collabd.py:1770`),**紧接着**才 `_deliver_str(txt, "上报·单条", st)`(`collabd.py:1777`)。
> ⇒ **只做了 ① 没做 ②,等于没上报。** 你问的"上报时是不是就唤醒了",答案取决于走没走 ②。
---
## 2. 你的表述 vs 现有实现(逐格对照)
| 你的表述 | 现有实现(位置) | 状态 |
|---|---|---|
| 有会话在执行 ⇒ 跳过 | `_session_status(目标)=="working"` ⇒ `target-busy` **延后**(`collabd.py:1050`) | ✅ 已有 |
| 队列无任务 | `tasks.json` 四态 `pending/running/done/blocked`,**唯一权威**(`collabd.py:852`) | ✅ 已有 |
| 无任何会话执行 | 宿主库 `sessions.status='working'`(`_main_state` `collabd.py:1616`;`reconcile` `collabd.py:1644`) | ✅ 已有 |
| 检查目标状态 | `goals_open()`:台账非 done **∪** 任务图非 done **∪** `goal.json.acceptance_state` 任一非 pass(`collabd.py:1403`,三路取并集) | ✅ 判据已有 |
| 完成 ⇒ 同时暂停 A、B | 现有只有「任务图全 done ⇒ `skipped=all-done` 不再投递」(`collabd.py:754`)——**它⛔不会去暂停自动任务** | 🔴 **缺** |
| 阻碍 ⇒ 反馈协作程序 + 暂停 | `task_report(state="blocked", reason=…)`/`need_user()`(`collabd.py:878`/`728`) | ⚠️ 接口有;⛔ 见 §3-② |
| 未完成 ⇒ 反馈协作程序 | `goals_open()==True` ⇒ 写差距;现有只在"有活可派/僵住"时才报(`collabd.py:786-798`) | ⚠️ 部分 |
| **有队列任务 且 无会话执行** | —— | 🔴 **你的规则没定义这一格** —— 而这正是**最该动**的那一格(有活没人干) |
---
## 3. 落地必须先知道的三个硬约束(源码级)
1. **`reply` 只认 live** ⇒ 主会话退出后投不出去;程序此时**拒绝盲投**(不投给任意会话),只按**工作区**解析,解析不出就 `need_user("找不到主会话…")`(`collabd.py:1038-1049`)。
⇒ **"上报即唤醒"对已退出的主会话结构性不成立**,且程序的处置是**喊人**,不是自己想办法。
2. **队列只有「投递方」才配改**(`mutate`,`collabd.py:1690-1696`):`--tick`(宿主钩子)是唯一推进方;`--once`/常驻只做投影。⛔ 唤醒轮**不许手改 `tasks.json`**,要用 `task_report` 口径上报,否则会重演「把待反馈项消费掉却不投递」的静默丢件。
3. **"跳过"是零 token 的**:投递前的否决(`same-item` 内容哈希去重 / `too-soon` 最小间隔 300s / `target-busy`)都在**钩子进程内**完成,不碰会话、不烧 token(`collabd.py:960-967`)。⚠️ 但钩子**拦不住自动任务起会话**——automation 无条件触发,所以唤醒轮"起来后发现有人在跑就立刻退"仍靠**域锁**。
---
## 4. 完整闭环:两半合起来才闭合
| 主会话此刻 | 谁把它叫起来 | 延迟 | 成本 |
|---|---|---|---|
| **活着 · 空闲** | 钩子**投递**(`reply`) | 秒级~分钟 | 到投递前零 token |
| **活着 · 执行中** | **不打扰**(`target-busy` 延后,等它空闲) | 等它空 | — |
| **已退出**(worker 已回收) | **唤醒轮**(自动任务起新会话) | ≤30 分钟 | **一个新会话** |
> ⇒ 所以「等队列信息上报给主会话时就醒了」——**对"活着"那行成立,对"已退出"那行不成立**;后者靠唤醒轮。
⚠️ **代价(源码里写着的一句话,必须知道)**:`collabd.py:857-860` 注释——
> 「每次投递都会唤起主会话跑一轮 ⇒ 用户发消息撞 busy ⇒ 体感"卡"」
⇒ **"上报即唤醒"=会占用主会话 ⇒ 必须节流**。现有的四道节流:**单条+握手**(一次只反馈一条,等它执行完才发下一条)/**内容哈希去重**/**最小间隔 300s**/**真停滞门槛 15 分钟**(`HEARTBEAT_MIN_IDLE`,有进展一条都不发)。⛔ 落地时不许把这四道拆掉。
---
## 5. 落地:要补的两格(其余照抄现成)
```
唤醒轮起来 → ① 抢域锁(抢不到 = 有人在推进 ⇒ 立即结束,零成本跳过)
→ ② 读队列 tmp/supervise-inbox/tasks.json
→ ③ 读会话:宿主库 sessions.status='working'(cwd=本工作区,排除自己)
→ ④ 读目标:tasks.json 非 done ∪ taskgraph.json 非 done ∪ goal.json.acceptance_state 任一非 pass
```
| 格 | 条件 | 动作 |
|---|---|---|
| A | 有队列任务(pending/running)**且** 无会话执行 | **派一棒**(起一个会话去干)← 🔴 **你原规则缺的这格** |
| B | 无队列 **且** 无会话执行 · 目标**未完成** | 把差距写进 `tmp/supervise-inbox/`(⛔ 不手改 `tasks.json`) |
| C | 无队列 **且** 无会话执行 · 目标**有阻碍** | 写清"要用户做什么" + **A、B 两条排期一起置 `PAUSED`** |
| D | 无队列 **且** 无会话执行 · 目标**已完成** | **A、B 两条排期一起置 `PAUSED`** ← 🔴 **现成实现没有这一格** |
| E | 判不准 | 按 B 处理(宁可空转一轮,⛔ 不许造活) |
**A/B 排期 id**(自我暂停时用;⛔ 只改 `status`,不动 `rrule`):
- 唤醒轮 A `caca9a89-fa82-48e1-a55c-7e0f3a06bd48` | 唤醒轮 B `16bec5ce-98c0-470c-afa9-6f5a06218068`
---
## 6. 一句话速记
> **上报(写文件)≠ 唤醒;投递(钩子把文字推进活着的会话)才=唤醒。**
> 主会话活着 ⇒ 秒级唤醒(但要节流,否则把它顶"卡");主会话退出 ⇒ 钩子也没了,只剩唤醒轮起新会话(≤30 分钟)。
> 判据一句:**"有会话在执行就跳过"= `sessions.status=='working'` 或 域锁被占。**
---
## 附:图
见同目录 `唤醒-上报即唤醒与门控归属-20260930-图.html`(① 三段拆解图:标出哪一段才唤醒;② 门控矩阵图:标红两格缺失)。