Files
dsh_ai1net_server/交付物/唤醒-统一唤醒出口-20260930.md
T
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

126 lines
7.9 KiB
Markdown
Raw 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)
> 用户口径(本轮):
> **「自动任务(符合条件时)是通过 协作程序去唤醒 主会话,这样流程统一」** +
> **「技能里已写明『投递不靠自动任务』:这个过时了,现在允许自动任务」**
> 落地物:`goalctl.py wake`(新)+ 两条排期改名/改提示词 + **技能侧过时规则纠偏**。全文读数均为实测。
---
## 0. 结论(三行)
1. 🔴 **判据一句话**:**「唤醒(投递)」这个动作只有一个出口 = 协作程序**;区别只在**"谁拨这一下"** —— **钩子**(有会话在动时)/**自动任务**(谁都没动时的时钟缺口)。⇒ 这就是"流程统一"。
2. 自动任务**只准当闹钟**:`goalctl wake` ⇒ `collabd --tick`。⛔ **不自己判条件、⛔ 不自己派活、⛔ 不自己起新会话** —— **除非**闹钟报"**没有可唤醒的对象**",才降级。
3. 技能侧那条旧规则(「投递 ⛔ 不靠自动任务排期」)**已按用户口径改为「允许,但当闹钟」**(4 处)。
---
## 1. 统一后的流程
```
┌─ ① 宿主钩子(事件)──┐ ← 有会话在动时,够用
需求在途 ──需要唤醒─┤ ├─→ 协作程序(唯一唤醒出口)─→ reply 唤醒【主会话】
└─ ② 自动任务(时钟)────┘ ← 谁都没动时(唯一事件缺口)
(只拨钟:goalctl wake)
```
| 谁拨 | 何时用 | 拨完干什么 |
|---|---|---|
| **宿主钩子** | 有会话在动(上报/收尾/用户回话) | 跑 `--tick` ⇒ 判断+投递 |
| **自动任务** | **谁都没动**(钩子的唯一缺口) | 跑 `goalctl wake` ⇒ 同一件事,**同一出口** |
⇒ 条件判断**只在协作程序一处**(不散落在排期提示词里)—— 这正是用户要的"统一"。
---
## 2. 判定三分(=用户说的「符合条件时」)
闹钟(`goalctl wake`)输出 → 拨钟方该干什么:
| 闹钟输出 | 含义 | 拨钟方动作 |
|---|---|---|
| ✅ **`http 2xx`** | **已投递** ⇒ 主会话被唤醒 | **结束**(⛔ 不抢锁、⛔ 不派活、⛔ 不起会话) |
| ✓ **`-`**(无待投)/`same-item`/`too-soon` | **条件不符**(没变化/刚投过) | **结束**(零打扰 —— 这就是"符合条件时才唤"的否定面) |
| ⏸ **`target-busy`** | 主会话**已经在跑** | **结束** —— 🔴 **绝不能降级**(降级=同一件事干两遍) |
| ⛔ **`disabled`** | 本需求已停(`wake_enable=false`) | **结束** |
| 🔴 **`main-not-live`**/`no-main-session`/`no-live-session` | **没有可唤醒的对象** | 🔴 **降级**:拨钟方**自己就是那个会话** ⇒ 抢域锁 + 按状态表干活 |
| 🔴 **`no-token`**/`no-gateway` | 异常(没跑在宿主进程树里?) | 报告一句,**结束**(需人看) |
⚠️ **`target-busy` 与"没有可唤醒的对象"必须分开** —— 前者是"**已经在跑**"(别插手),后者是"**没人可跑**"(要我顶上)。这两者混了,就会出现"同一件事被干两遍"或"没人干"。
---
## 3. 落地清单
| # | 动作 | 结果 |
|---|---|---|
| 1 | 新增 `goalctl.py wake`(**唯一唤醒入口**) | 调 `collabd.py --tick`,把 `deliver=` 字段翻成人话 + **给判定三分**(rc:0 结束/10 降级/2 异常) |
| 2 | 两条排期**改名**为 `[协作]-唤醒轮A/B · ai1net-dsh-server` | 🔴 **必须带 `[协作]`** —— 见 §4.1 |
| 3 | 两条提示词改为**「拨钟三分」** | 默认动作=拨钟即结束;只有"降级"那一条路才抢锁干活 |
| 4 | **纠偏技能侧过时规则**(4 处) | 见 §5 |
| 5 | 自测 | **PASS 28 / FAIL 0** |
---
## 4. 两条会**静默坏**的易错点(🔴 本轮实测)
### 4.1 唤醒轮不能让自己被认成"主会话"
`resolve_main()` 的兜底判据是:**本工作区里 `last_activity_at` 最新、且标题不含 `[协作]`** 的那条。
🔴 **刚起的唤醒轮活动时间必然最新** ⇒ 必然被选中 ⇒ 协作程序把通知**投给唤醒轮自己** ⇒ **主会话永远收不到**。
- **为什么原名会中招**:排期名 `[本机协作]-唤醒轮A` 里**并不含 `[协作]` 这个子串**(它是 `[本机协作]`)⇒ 排除判据**不生效**。
- **修法(已做)**:排期名带 `[协作]` ⇒ 会话标题继承排期名 ⇒ 被排除。 ✅
### 4.2 主会话标题必须以 `主控` 开头(不能加方括号)
代码判的是 `title.strip().startswith("主控")`;实测有条会话标题是 **`[主控]-[机制线]-…`** ⇒ **判 False** ⇒ 只能靠"最近活动"兜底 ⇒ **本工作区任何一条会话活动更新就可能把它顶掉**。
⇒ 命名一律用 `主控 · …`(⛔ 别写成 `[主控] · …`)。这条 SKILL.md §354 已登记,此处再次坐实。
---
## 5. 技能侧过时规则纠偏(用户:「这个过时了,现在允许自动任务」)
| 文件 | 原话 | 改成 |
|---|---|---|
| `references/architecture.md §4` | 「三种节奏(触发源 = 宿主钩子;**⛔ 不引入任何周期排期**、⛔ 不靠常驻)」 | **四种节奏** + 新增 **§4.0「④ 自动任务:允许,但只准当闹钟」**(含判定三分、`target-busy` 禁降级) |
| 同上 §4.1 结论 | 「…**排期更是多余**」+「代价:真正的全员静止期间通知不会自己飞出去」 | 「排期**不是多余**,它专补"**谁都没动**"这个**唯一的事件缺口**」;旧"代价"**已被 ④ 补上** |
| `scripts/collabd.py` 模块用法注释 | 「投递=宿主钩子唤起,**⛔ 不靠排期**」 | 加「**同日用户改口:允许自动任务**」+ `deliver=` 判读三分 |
| `scripts/collabd.py` `--tick` 注释块 | 「**⛔ 不靠自动任务排期**」 | 追加「2026-09-30 用户改口(取代本句)…」 |
| `scripts/selftest.py` 注释 | 「投递⛔ 不靠自动化排期」 | 标注已改口,并写明**下面两条约束与"谁拨这一下"无关、仍然有效** |
🔴 **注意**:改的是**触发源**,不是**约束**。这三条**一条都没放松**:
① `--once` 不许消费队列 ② `--tick` 投不出去就不许消费队列 ③ 投递方唯一。
---
## 6. 验证记录(实测)
```
═══ 拨闹钟 · 协作程序判条件并投递(20:27:44)═══
协作程序结论:tick: item=- awaiting=- phase=- deliver=- consume=- park=-
✓ 判定:**没有需要投的东西**(队列无新变化 / 无停滞)⇒ 静默。
⇒ 本轮**就此结束**(这就是「符合条件时才唤」的否定面 —— 零打扰)。
wake rc=0
```
- 已实测两条分支:**静默(`deliver=-`)** 与 **已停(`wake_enable=false` 直接返回)**。
- "投成"与"降级"两条走的是协作程序**既有且已实战过**的代码路径(`_deliver_str` 的 `delivered` / `skipped`),本轮**未人为制造**(⛔ 不扰动生产队列)。
- 技能自测:**PASS 28 / FAIL 0**;顺带清掉 `py_compile` 生成的 `__pycache__`(技能侧自测的**硬项泄漏**,已消除)。
---
## 7. 能力边界(诚实说)
- 🔴 **能被唤醒的前提是"主会话此刻 live"**。实测 worker 寿命 **30 s–6 min**(干完活就回收)⇒ **大概率不 live** ⇒ 闹钟会报"没有可唤醒的对象" ⇒ **降级**。
- ⇒ 要真做到"**唤醒统一走协作程序**",得让**主会话保持活着**(把那个窗口开着),否则多数时候仍落在降级路径上。
- ⚠️ 降级**不是**绕过统一:省掉的只是一次投递,**判断规则仍来自同一张状态表**。
- ⚠️ 本机"需求↔工作区"仍是 **1:1**(`goal.json` 单份)。
---
## 8. 图
见同目录 `唤醒-统一唤醒出口-20260930-图.html`(① 统一后的流程:两个拨钟方 → 一个出口;② 判定三分图:`target-busy` ≠ 没有可唤醒对象)。