Files
workbuddy_skills/session-mechanism/SKILL.md
T
admin 805f1ba2d7 对外叫法落定:机制叫「项目机制」,用法「使用项目机制完成目标:XXXX」;角色名不变
一、用户令(逐字)
`后续说 使用项目机制完成目标:XXXX,创建会话还是叫做 执行会话 和 检查会话`
⇒ 口径终于对齐:**「项目机制」= 这套机制的名字**(不是会话名),
  而它建出来的会话**仍叫「执行会话」「检查会话」**(连同「主会话」)—— **⛔ 角色名一律不改**。

二、改法(4 处)
· `SKILL.md` H1 后新增「**对外叫法**」块,逐字记下用户原话,并写清:
  **项目机制 = 机制的名字;执行会话/检查会话 = 会话的名字**,两者⛔ 别混。
· 第 0 步触发句置顶加 **`使用项目机制完成目标:XXXX`**(标注 2026-10-08 用户令的现行写法)。
· `skill-load-guard.py` 词表加「项目机制」族(`使用项目机制`/`项目机制完成`/`项目机制`),
  ⛔ **旧词一个不删**、⛔ **角色名不加进词表**(它不变)。
  复核:`使用项目机制完成目标:XXXX` 命中 `['使用项目机制','项目机制完成','项目机制']` ✓;
  旧写法 `使用执行会话完成 X 目标` 仍命中 ✓;`按决策方法处理` 仍命中 ✓。

三、与上一轮的关系
上一轮我按「执行会话(协作会话)改为 项目会话」把目标词改错了(已全量撤回,见 `90e892e`);
本轮按用户更正后的**正确目标词「项目机制」**落地,且**不再动角色名**。

四、验收
selftest.py rc=0 PASS 106 / FAIL 0;manifest 76 份、语法失败 0。
2026-10-08 22:24:30 +08:00

991 lines
113 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.
---
name: session-mechanism
description: 「**会话机制 + 多会话执行**」的合并总入口(原 `multi-session-collab` + `workbuddy-session-forensics` 已并入本包)。🔴 **2026-10-05 起会话只三类:主会话 + 任务会话 + 检查会话**(唤醒/跟进/队列上报**已整套退役**;**任务会话=旧称「执行会话」,同一个角色**)|🔴 **2026-10-03 起「上报」整套真删**——现行机制=**常驻程序 `collabd.py` 接收(`--report` 写台账)+ 处理(建检查会话排期)**(⛔ 「协作程序」是 10-03 已退役的旧称,见文首术语表),⛔ **队列变化不再自动通知任何人**,要落事得**显式建执行会话**。两段可分别加载:① **会话机制** —— 管「会话怎么活下去、怎么不哑掉、怎么不失忆、跨机器怎么一键装好」:治「会话卡住 / 一直转圈 / 发消息没反应 / 界面不刷新」「会话日志涨到上限把界面顶死」「上下文爆了要换会话 / 接续会话怎么开」「抢锁 / 执行锁 / 并发 / 别的会话在动」「钩子没生效 / 钩子把我也拦住了」「换一台电脑要重装这一堆钩子」「某个历史会话当时到底干了啥」。② **多会话执行** —— 管「一个主会话带多个执行会话把需求做完」:治「多个会话协同但工期被拖长 / 有会话在干等 / 链条断了没人接 / 分不清『真完成』还是『只排了下一棒』/ 问清主会话·执行会话两类怎么分(**接续会话是撞阈值时的「形态」,⛔ 不是第 3 类**)」。🔴 **主触发句(2026-10-03 用户定案)**:用户说「**使用执行会话完成 XXXX 目标**」「**继续 XXXX 目标**」时**直接走本技能**(=要派执行会话去把一个目标做完/接着做)。其他触发:「怎么协同多个会话」「别的会话都在干等」「任务没推进」「链条断了」「你监督这些会话」「会话卡住」「日志要爆了」「换机器怎么配」「升级 / 安装这套机制」。核心=**会话机制三件套(钩子 + 锁 + 日志闸)全局生效** + **执行两条通道各走各的**(派活靠自动化/收结果直读宿主库/机械判定下沉到常驻程序/人只看一个看板)+ **一键配置 `install.py`**(换机器不手抄绝对路径)。
version: 1.3.0
updated_at: 2026-10-05
last_change: 2026-10-05 10:5x · 🔴🔴 **`lifecycle` 判定收敛为「单一事实源」**:抽 `goal_life_of(root)`(剥全角/半角括号后缀 + 认不出时写日志),`goal_life()` 降为薄封装,`peer_supervise_sweep()` 改调它 —— 此前它**自己又抄了一遍**精确匹配(不剥后缀)⇒ **同一份 `goal.json` 两条路两个答案**。⛔ 判「目标状态」一律问代码(`--check-status`/`goal_life_of()`),⛔ 不许肉眼读原文比对。⚠️ 端到端实测坐实**夹具坑**:合成区只复制配置字节、不改 `workspace` 字段 ⇒ 钩子读的是**真区心跳** ⇒ 正确地"在位沉默",而我差点误判成"钩子失效"(P0-53)。 🔴 起不来先报错这条红线已落进 T 表 §1.6(用户原话「必须首先启动好在执行」)+ **`supervise-ensure-hook.py` 两个真缺陷已修并变异验证报红**:① 工作区原先只读 `DSH_WS_ROOT`(宿主 env 三级全空 ⇒ **钩子长期空转**)② `env=dict(os.environ)` 对 `exec_module` 里的 `load_cfg()` **无效** ⇒ 会验到别的区。⛔ 教训=**「零输出」既可能是"正确沉默"也可能是"根本没看见"⇒ 必须造合成区做反向对照**。 🔴 10-04 17:3x · **文档索引已建**:`references/01-文档索引.md`(干什么事→看哪篇 + **文档四条规则**:分类索引/结论在最前/历史倒排·新的在前/单条 ≤6 KB)+ SKILL.md 第一屏挂「改机制前必读三篇」(⛔ 只写进工作区日志的规矩=没立:10-03 立的「⛔ 写会怎样前先取证」10-04 复发)。 🔴 **本行是「现行口径台账」,⛔ 不是变更日记**:只记**现行结论**,实体与来路在 `references/`(下面每条都给指向)。⛔ **不再在这里堆叠「本条为准」式的旧口径**(那会让同一件事在两处各存一份、改一处忘另一处就打架)。
· **常驻形态(2026-10-03 定案)**:每个工作区的常驻**由它自己的主会话起**(主会话开工第0 步:确认 pid 活 ∧ 心跳 <90 s);看板**全平台只保留一份**,其它区用 `peer_workspaces` 并列看。→ `architecture.md`《运行形态》+`pitfalls.md` **P0-41**(载体与三条封死路)。
· **常驻存活唯一机读判据**:`pid 活 ∧ 心跳新鲜(<90 s)`—— ⛔ 别看退役旧戳。→ `pitfalls.md`。
· **看板 tab 跨工作区并列**:一样数据源跟着格走(⛔ 只换标题不换数据源 ⇒ 看板在说假话)。→ `pitfalls.md` **P0-40**。
· **改看板要不要重启?四类答案**(读前部的真相)。→ `pitfalls.md` **P0-38**。
· **钩子超时是「拦截」不是「变慢」**:会报阻塞、不会只慢。→ `pitfalls.md`。
· **开工第 0 步:会话机制体检**(不是规划)**—— 用户原话「不是规划 是 规则」。→ `references/collab.md`。
· **诊断口径**:删看板里「负责类别:—」那行(它会说假话)、会话类别与主体的两轴消歧。→ `references/collab-detail.md`。
· **看板标签用「简称」**:简称只是压缩版面,⛔ 不是又多了角色。→ `collab-detail.md`。
· **常驻被杀的真因结案**:「释放锁」≠「删文件」(**P0-6**);载体与长期在线→ `references/supervise-persistence.md`。
· **投赴与队列投递**:「投递」整套已真删(2026-10-03,用户原话「没用了就删除」);实际机制=**常驻接收(`--report` 写台账)+处理(建检查排期)**,⛔没有「推送」环节。→ `pitfalls.md`。
· 🔴🔴 **术语统一**(用户「语言全部统一」)——**同一件事只许有一个词,现行以第三代为准**:
| 代 | 时间 | 被派的会话 | 常驻程序 | 状态 |
|---|---|---|---|---|
| 一 | 10-02 前 | 协作会话 | **协作程序** | ⛔ **退役**(10-03 用户下令改,⛔ 包内未同步) |
| 二 | 10-03 | 执行会话 | 目标检查 | ⚠️ **过渡名**(代码与看板仍在用) |
| 三 | **10-05** | **主会话/任务会话/检查会话** | **常驻程序** | ✅ **现行** |
🔴 **任务会话 = 旧称「执行会话」,同一个角色**(⛔ 不是第四类)。
🔴🔴 **2026-10-05 用户定案**:**「协作 全部 改为执行」**(连说两遍)⇒ **新建前缀一律 `[执行]`**,包内现行文案一律用「**执行**」(旧「协作」只作历史沿革保留)。
🔴🔴🔴 **2026-10-05 追加定案(同一话题,用户逐字)**:「**兼容个毛线,今天兼容一个明天兼容一个 过不了一周就成大杂烩了**」
⇒ **本包禁用「兼容」二字描述任何前缀/字段**。凡看见旧前缀,**必须一句话答出"删了会坏在哪"**;
答不出 ⇒ **它就是该删的**(不许以"兼容"为由留)。
· ✅ **留得住的唯一理由 = 历史行解析正确性**:`[协作]`/`[协作目标]`/`[任务会话]` 三条**不许删** ——
实测(2026-10-05 跑 `board.py::_role_of_title()`)删任一条 ⇒ 存量旧标题行解析成 `role=""`
⇒ **看板画不出、派活漏管,且不可逆**。⇒ 这是"**删了会坏**",⛔ **不是"兼容"**。
· ⛔ **不许进「活类别表」**:`session-rules-check.py::ROLES_LIVE` **只有 `[执行]`**。
旧前缀只出现在**扫描面** `SESS_PREFIXES`(捞历史行用)+ `_PFX` 映射表(解析用)。
· 🔴 **机制层闸门(已落地,防"明天又长一条")**:`session-rules-check.py` **第 ⑬ 项自检**
—— 扫**活排期名**与**活会话标题**,出现旧前缀 ⇒ **当场 fail**(判据只扫"活件",⛔ 不扫注释/考古段)。
变异对照已跑:全 `[执行]`⇒ok|混 1 条 `[协作]`⇒fail|混 1 条 `[任务会话]`⇒fail|历史行不以旧前缀开头⇒ok。
🔴 **改名落地位置**:发源地 `collabd.py::_gap_plan()` 的 `_zh = {"worker": "执行"}`(**唯一出处**)+ 文首提示词模板 9 处 + 本文件现行规范句。
🔴 **待办(未做)**:一/二代旧词在包内仍有 ~174 处(`collabd.py` 37、`board.py` 27、`board.html` 28、`selftest.py` 22、`goalctl.py` 15…)。
⛔ **改名不能盲替换**,先分三类:① 注释/文档 ⇒ 可直接改;② `selftest.py` 的 `@case` 标题 ⇒ 改了会断 `-k` 用例引用;③ 看板显示文案 ⇒ 改了用户可见,且可能有字面判据(`board.py:191` 那条就是防两处漂移的)。→ 待办清单 `references/manifest.md`。
· **本包自身**:`install.py`(干跑/安装/校验)+`references/manifest.md`(清单);⛔ 包外**不应**有第二套会话机制代码。
agent_created: true
---
# session-mechanism — 会话机制(含多会话执行)
> 🔴 **对外叫法(2026-10-08 用户令 · 逐字:「后面说 使用项目机制完成目标:XXXX,创建会话还是叫做 执行会话 和 检查会话」)**
> · **这套机制叫「项目机制」**(旧称「会话机制」);用法说「**使用项目机制完成目标:XXXX**」。
> · ⚠️ **它建出来的会话仍叫「执行会话」「检查会话」**(连同「主会话」)—— **⛔ 角色名一律不变**。
> · 一句话:**项目机制 = 机制的名字;执行会话/检查会话 = 会话的名字**,两者⛔ 别混。
## 🔴🔴 改流程/改机制前,**先看这四类**(10 分钟,⛔ 别通读文档)
| 序 | 必读 | 为什么是它 |
|---|---|---|
| 1 | **`references/00-动手前必过.md`** | 六条动作红线(⛔ 开工前只读这一篇就够) |
| 2 | **`references/rules.md`** | 现行规则本体(⛔ 历史在 `pitfalls.md`) |
| 3 | **`references/manifest.md`** | 清单:哪个文件是**权威**(改之前先确认) |
| 4 | **`references/作业规矩/`** | **会话必须遵守的作业规矩**(原 `agent-operating-rules`,2026-10-06 按用户令整包搬入):`00-作业总规矩` · `02-工作区纪律` · `03-多棒接力编排` · `04-去AI味与说话方式`。⚠️ 原 `01-协作与提报用户判据` 与包内正文**同题** ⇒ **2026-10-07 已并入**「判据 → `references/02-功能优先协作协议.md`(§3.2 冲突裁决 / §3.3 语言转换表 / §5.3 铁律 6 + 只做正向迭代 / §5.5 提报前三问)」「排版 → `references/03-回复排版-核心块.md` 完整版段」 |
**📇 完整索引(干什么事 → 看哪篇):`references/01-文档索引.md`**
> ⚠️ **第 4 项是一整包(四份)**,⛔ 不是"顺手扫一眼":其中 **`04-去AI味与说话方式.md` 管的是语气(像不像人话)**、
> **`03-回复排版-核心块.md` 管的是结构**(那一份的核心块由排版钩子**每轮自动注入**)。
> 🔴 2026-10-07 订正:本表标题原写「**先看这三篇**」而表里**有 4 行** ⇒ 第 4 项被顶在"三篇"之外、
> 常被跳过(用户质问「说人话技能 加载会话基础规则中了吗」时查到的)。
⚠️ 那篇里还有**文档四条规则**(分类索引/结论在最前/历史倒排·新的在前/单条 ≤6 KB)——
**⛔ 只写进工作区日志的规矩 = 没立**(10-04 实证:一条红线立在对的地方,换会话照样读不到)。
## 🔴🔴 第 0 步:加载门槛(2026-10-05 立 · ⛔ 这是**第一条**,在十条禁令之前)
> **判据(一句话)**:**用户说了触发句 ⇒ 第 0 步是先加载本技能,然后才动手。⛔ 没有"我大概知道"这条捷径。**
**触发句(命中任一条即须加载)**:🔴 `使用项目机制完成目标:XXXX`(**2026-10-08 用户令的现行写法**)/
`使用任务会话完成 X 目标` / `使用执行会话完成 X 目标` / `继续 X 目标` /
`用执行会话完成 X` / `派任务会话` / `建任务会话` / 任何"让我去把某个目标做完/接着做"的表述。
**⛔ 禁止的三种"跳过理由"**(都真实发生过):
| 跳过理由 | 为什么不算数 |
|---|---|
| 「我已经很熟这套机制了」 | 熟悉 ≠ 现行。机制 10-03/10-04/10-05 连续改过三轮(会话类别、常驻形态、术语),**记忆里的版本大概率是旧的**。 |
| 「上下文里已经有规则片段了」 | 片段 ≠ 全文。**只读片段最容易漏「自决策白名单」这一节**(见下)。 |
| 「注入通报里说本轮不需要」 | 🔴🔴 **用户原话 > 注入通报**。通报是机制层的**旁路信号**,⛔ 不能拿它反驳用户当轮的明确指令。 |
🔴🔴 **最关键的一条(2026-10-05 实测栽过)**:**用户明确说了"使用任务会话完成目标",就不许再回头问"要不要建任务会话"。**
- 用户原话:「**任务会话的目的不就是 建立任务会话执行嘛,不然我调用任务会话技能干什么**」
- 用户原话:「**我想知道你哪来的这么多问题啊,你去执行不行啊,会话技能里面没有告诉你自决策的规则和机制嘛**」
- ⇒ 说这句话**本身就是授权**:直接建排期、拉起任务会话。把"怎么执行"当待拍板项 = 把已授权的事重新要一遍签字。
**自决策白名单(九类,命中即永不问)** —— 全文在 `references/02-功能优先协作协议.md` §2:
技术选型/实现路径/命名/调参/部署/排查/版本/兼容降级/文档技术内容。
**只准提报三类**:① 功能语义分叉(做 A 还是做 B,影响用户看到什么)② 红线门禁 ③ 超出决策边界(花钱、对外承诺、要凭据)。
## 🚨 十条禁令(2026-10-05 立 · ⛔ 每条都栽过,栽一次就重来一遍)
> 用户 2026-10-05 原话:「**你不要给我知道了,你给我记下来,每次都是知道了,知道了,过一会儿又忘了**」。
> ⇒ 所以这些话不放日志、不放事后复盘,**放在每次加载都会读到的地方**。⛔ 改任何东西之前先过一遍。
1、⛔ **不清楚就先读代码/日志/任务定义,⛔ 不许推断后当结论说**。栽过:把"5 分钟一次"编出来(实际每分钟)、把"已经停了"说出口(实际任务还在跑)。
2、⛔ **报数字必须先数**(日志行数、次数、间隔),⛔ 不许从现象反推节奏。
3、⛔ **说"已修复"之前必须复核一次现场**(进程/心跳/端口/日志时间戳),复核读数贴进回复。
4、⛔ **⛔ 不许在用户机器上做起停实验**。为取证起一个可能弹窗的东西 = 本末倒置。栽过:为"证明留不住"起了个循环脚本。
5、⛔ **任何"改成交给用户做"的方案,必须先有用户原话**。栽过两次:脑补"开机自启"、脑补"手动启动"。
6、⛔ **改动只许往一个方向收敛**:先只读盘点 → 说明 → 再动手。⛔ 不许一边分析一边改,那样会把现场越改越乱。
7、⛔ **禁用不等于停止**(任务禁了老进程还在跑);**杀进程不等于停止**(任务到点又拉起)。要么都做,要么不做。
8、⛔ **凡"东西自己反复触发",第一步读它的调度定义**(触发条件/重复间隔),⛔ 别从现象反推。
9、⛔ **验证通过的结论,落点只能是技能**(`SKILL.md` 第一屏 或 `references/`),⛔ 不许只写工作区日志。
10、⛔ **用户说"你知道了"时,⛔ 不许回答"知道了"** —— 去把它写成上面这种条文。
## 🗺️ 现状地图(2026-10-05 · ⛔ 看机制**先读这一屏**,读完再往下)
**三类会话(⛔ 只有三类,没有第四类)**
| 角色 | **谁创建它** | 它干什么 | 怎么停 |
|---|---|---|---|
| **主会话** | 用户手动开 | 管目标与方向、**派活** | 用户关窗 |
| **任务会话**(旧称执行会话) | 🔴 **派活产生** —— 主会话判缺口后登记自动化 → 宿主到点拉起(⛔ **不是常驻建的**) | 一棒一线、一次一件,做完即上报 | 做完自止 |
| **检查会话** | 🔴 **常驻程序** `collabd.py`(`maybe_spawn_check_agent()`,六道闸) | 核对结果/目标,缺口再派活 | 做完自止 |
**三条线各归各的(⛔ 别混谈)**
1. **派活线**:主会话 → 任务会话。排期名 `[执行]-[<类别>]-<具体>`。🔴 **不受总开关影响**(开关只挡常驻)。
2. **检查线**:常驻 → 检查会话。排期名 `[检查]-[结果检查/目标检查]-…-第N棒`。🔴 **受总开关管**。
3. **常驻线**:一工作区一条 `collabd.py --supervise` + 看板**全局只一条**(端口 20099)。
**🔴🔴 目标口径(2026-10-05 用户定案 · ⛔ 这是"关注哪个目标"的唯一判据)**
> 用户原话逐字:**「一个工作区 历史的旧的目标会有很多个,当前要求完成什么目标 就因该关注和检查那个目标」**
> **「一个工作区 同时只能执行一个目标,如果要切换目标,需要用户确认,然后切换和关注检查切换后的目标」**
拆成**两条硬规矩**:
1、**同时只有一个"当前目标"** —— `tmp/supervise-inbox/goal.json` = **唯一当前目标的载体**(⛔ 不存在"多目标并行")。
历史旧目标**移到 `tmp/supervise-inbox/goals/` 归档**(⛔ 不许原地堆在 `goal.json` 里)。
2、**切目标 = 用户确认的动作** —— 「声明新目标」**不是**随便覆盖;**必须先问用户**,得到确认才切,切完**关注和检查的是切换后的那个**。
⛔ **机制不许自作主张换目标**(同 `lifecycle` 那条铁律:**宁可等,不可动**)。
✅ **三条落点现状(2026-10-05 已全补,⛔ 别再照抄旧缺口表)**:
| 项 | 现状 |
|---|---|
| 当前目标载体 | `goal.json`(单文件,✅ 天然只有一个) |
| 切目标需确认 | ✅ `declare` 加**确认闸** —— 标题变了 ⇒ **默认拒绝**(rc=3),必须带 `--switch-goal` |
| 旧目标归档 | ✅ 换目标时**整份**存 `goals/<旧标题≤40字>__<时间>.json` + 新目标留 `_前身目标` 指针(⛔ 不再靠人手写) |
🔴 **改前取证结论**(P0-78 留档):① 载体**本就满足**;② 归档**零实现**(全文搜 `goals/` 零命中 ⇒ 那份归档件是**手工造的**);③ 确认闸**零实现**(`declare` 直接覆盖 `title`)。
⚠️ **加闸带出的连带缺陷**:`declare` 没给 `--title` 时**回落成工作区名** ⇒ 把"纯细化"误判成"换目标" ⇒ 已修成「没给 ⇒ 保持现有标题」。
📂 全文 ⇒ `references/pitfalls.md` **P0-78**;⚠️ `lifecycle` 那条独立铁律 ⇒ **P0-79**(从 P0-77 拆出)。
**🔴🔴 换目标的正确动作(⛔ 照这个做,别自己发明)**
1、**先问用户** —— 「当前目标是 X,要放弃它换成 Y 吗?」(⛔ 机制不许自作主张换目标)。
2、用户**确认后**才执行:
```
python .workbuddy/collab/goalctl.py declare --title "<新目标>" --switch-goal --yes
```
3、**紧接着同步 `lifecycle`**(🔴 `declare` **不碰它**,不同步 ⇒ 检查机制会空转):
```
python .workbuddy/collab/collabd.py --set-life 进行中 --by "主会话"
```
4、切完**关注和检查的是切换后的新目标** —— 旧目标已归档,⛔ 不再建它的检查会话。
⚠️ **只细化同一目标**(补 `--why`/`--kpi`/改错别字)⇒ **不带 `--switch-goal`**,正常放行(⛔ 不该被拦)。
⚠️ `--switch-goal` ⛔ **不等于** `--yes`:前者=「用户已同意换目标」,后者只是「干跑转真写」。**混用 = 伪造用户授权**。
**🔴🔴 机制新建会话「用哪个模型」(2026-10-05 用户点破 · P0-80)**
> 用户原话:**「为什么 vibe-product 检查进程 创建的 执行和检查会话没有使用 主会话相同的模型」**
一句话判据:**机制另建会话时,模型与思考档一律「跟本区主会话」—— ⛔ 不许在代码里写死。**
- **改前事实**:`collabd.py::create_check_schedule()` 的 INSERT 里**字面写死** `"space-bunny"` + `model_is_thinking=0`
⇒ 机制建的 49 条会话/9 条排期全是 `space-bunny`,而**主会话是 `deepseek-v4.1-flash`(thought=high)**。
- **两条建会话的路,模型来源不同(⛔ 修的时候别只修一条)**:
· **常驻直连 SQLite**(`create_check_schedule()`)⇒ 这次已改成**取本区主会话**;
· **会话侧走 `automation_update`** ⇒ 天然跟主会话同档(宿主默认)。
- ✅ **现在的实现**:`collabd.py::preferred_model(cwd)` —— 取 `sessions` 里**人开的**(`is_background_automation <> 1`)、
**本区**、**最近一条**的 `model` + `thought_level`;取不到才回落常量,且**必打日志**(⛔ 不许静默)。
⚠️ **必须排除 `bg=1`** —— 否则会取到"机制自己上次建的那条" ⇒ **自我强化**,永远锁死在历史值上。
- 🔴 **配套判据**(`session-rules-check.py` ⑧):报「**机制建的排期模型 ≠ 本区主会话模型**」。
⚠️ 扫描面**必须含 `once`** —— 机制建的排期全是 `once`,旧判据只扫 `recurring` ⇒ **一条都看不见**(恒绿)。
**常驻的唯一管理入口**:`python collabctl.py <status|on|off|ensure>`(加 `--out <文件>` 取结果)
**用户可见入口(双击)**:`会话机制-一键开关.bat`(工作区根 + 桌面各一份)= 启动/全部停止/看状态
**🔴🔴 常驻机制的真实形态(2026-10-05 实测定性 + 用户拍板 · ⛔ 别再凭印象设计)**
**定案形态(两层,⛔ 结束"三层嵌套"):**
1、**常驻本体** `collabd.py --supervise`:**内部自带循环**(每 10~30 秒一轮),这是干活的那层,✅ 必要。
2、**计划任务**(`collabd-keepalive-<工作区>` / 看板 `dsh-board-keepalive`):
动作 = **`pythonw.exe` 起"启动器"**(⛔ **不许直起 `collabd.py`/`board.py`**),每 **5 分钟**触发一次**判活**。
🔴🔴🔴 **为什么要夹一个"启动器"(2026-10-05 血的教训 · P0-72+P0-73)**:
**计划任务的"动作"里没有 env 字段** ⇒ 一切环境变量**只能由启动器在进程内设**。两个启动器**对称存在**,缺一个就静默半瘫:
| 启动器 | 服务的程序 | 进程内必须设 | ⛔ 缺了会怎样 |
|---|---|---|---|
| `scripts/supervise-launch.py` | 协作程序(`collabd.py --supervise`) | **`CODEBUDDY_CONFIG_DIR`** + `COLLABD_CONFIG` + `PYTHONIOENCODING` + 兜 `stdout/stderr` | **检查程序静默失效**(读 0 字节空库 ⇒ `no such table: sessions` ⇒ fail-safe 恒判"有会话"⇒ **再也不建检查会话**) |
| `scripts/board-launch.py` | 看板(`board.py --serve 20099`) | `COLLABD_CONFIG` + `PYTHONIOENCODING` + 兜 `stdout/stderr` | **看板崩溃重启循环**(`已拒跑`)、**20099 从没绑上** |
🔴🔴 **两条"起法"必须收敛成同一个(P0-74)**:机制里**曾经有第二条起法** ——
`collabd.py` 的 `_escalate_to_keeper()`(自我供给旁路)会建 `collabd-supervise-<区>` 任务,
动作写死 **`powershell.exe -WindowStyle Hidden -File start-supervise.ps1`**
⇒ PowerShell 是**控制台程序** ⇒ 每次触发分配 `conhost.exe` ⇒ **闪黑窗**(用户 2026-10-05 报「又弹了窗口」)。
✅ 已改指 `pythonw.exe + supervise-launch.py`。⇒ **凡"同一种东西有第二条起法",收口时必须全文搜一遍**:
判据 = `grep -n "New-ScheduledTaskAction"` 与 `grep -n "\.ps1"`(本次就是靠这个抓到的)。
🔴🔴 **2026-10-06 二次收口(上一次只改了一半)**:只换"动作"不够,**名字与旁路也得一起换**。三处:
1、**任务名统一** —— `_escalate_to_keeper()` 原建 `collabd-supervise-<区>`,而 `collabctl.py` 建
`collabd-keepalive-<区>` ⇒ 前者**恰好落在 `collabctl.py` 的 `SCHED_TASKS` 禁用名单里**
⇒ **自愈起的常驻会被下一次 `off` 当"旧形态"顺手干掉**(自愈链断在这里)。✅ 已统一为 `collabd-keepalive-<区>`。
2、**`init_workspace.py` 是漏网的第二个建任务者** —— 它自带一份 `_register_keeper_task()`,
还用着 `powershell.exe -File start-supervise.ps1` 与 `assets/start-supervise.ps1.tpl`。
✅ 整份去掉:建任务**只许有一处实现**=`collabctl.py`(受 `supervise.switch` 总电闸管辖),
该脚本改为只检查启动器在位并提示入口。模板 `assets/start-supervise.ps1.tpl` 一并删除。
3、**探针曾打偏** —— 守形态的自检用例**只扫 `collabd.py`**,同族的 `init_workspace.py` 整份没被扫到。
✅ 已扩成扫**全族**(`collabd.py` / `init_workspace.py` / `collabctl.py`),并新增判据
「**任务名口径唯一**」与「旧形态死代码已清」。
⚠️ **教训**:凡"这一族文件都不许有 X",必须**枚举整族**,⛔ 不许只挑一个代表 —— 挑一个就等于没扫。
⚠️ 该判据**扫文本**,故必须先剥掉 `SCHED_TASKS` 那种"故意列旧名去禁用它"的**否定语境块**
(同一坑本项目踩过三次)。
- ⚠️ **变量名坑**:`roots.env` 给的是 **`COLLABD_PROD_CONFIG`**,而 `board.py`/`collabd.py` 读的是 **`COLLABD_CONFIG`** ⇒ 名字对不上 ⇒ `roots.env` **兜不住**。
- 🔴 **启动器里写死最稳**(⛔ 别指望 `roots.env` 兜住各区 —— 它只在技能目录,而各区副本在 `<ws>/.workbuddy/collab/`,`_sm_load_roots()` 向上 4 层**找不到**)。
- 🔴 **重构"起法"时,旧起法里的 env 必须逐条搬过去**(P0-73 就是改成两层形态时**丢了 `CODEBUDDY_CONFIG_DIR` 这一句**)。
- 🔴 **各区 config 的 `host_db` 写死绝对路径最稳**(vibe 一直这么写 ⇒ 只有 ai1net 炸)。
🔴🔴 **三条硬约束(用户 2026-10-05 逐字重述:「这些常驻程序每次创建之前,要先检查是否已经存在。如果已经存在了,就不需要重复创建,而且各工作区是各工作区的。不共用,共用的只有看板。」)**:
1、**先查后建(幂等)**:创建前必须判「已经有了吗」;已有 ⇒ **什么也不做**。
载体=`--supervise` 进场即判(`supervise_alive()`:`pid` 活 ∧ 心跳新 ∧ `_pid_is_collabd` 身份核验),
已在 ⇒ 新起的**写心跳前原子抢位失败 ⇒ 立刻让位退出**(实测连触两次,进程数恒为 3,⛔ 不叠加)。
2、**各工作区各工作区的**:一区一条 `collabd-keepalive-<工作区>`,动作指向**本区副本** `.workbuddy/collab/collabd.py`;
心跳里的 `argv0` 必须指向本区路径(实测 ai1net/vibe 各自 argv0 互不相同)⇒ **⛔ 不许一区去起另一区**。
3、**共用的只有看板**:看板任务**全局唯一一条** `dsh-board-keepalive`(端口 20099,动作指向技能目录那份共用 `board.py`)⇒
⛔ 不许每区各起一个(每区一个 = 白多 N 个进程 + N 个端口)。
🔴🔴 **一句判据(这是本条的**全部价值**)**:**常驻靠什么活着 —— 不在「在不在作业对象里」,在「谁拉起它」。**
- 会话树里起的(父链穿到 `WorkBuddy.exe`)⇒ **会话一收工就死**(表现为「有些区能常驻、有些不能」)。
- **计划任务起的**(父链**断在自己身上**,父进程已退出)⇒ **真常驻**。
- ⚠️ **`IsProcessInJob` 在本机恒为真、没有鉴别力**(四组对照全部 `IN-JOB`)⇒ ⛔ 别拿它当判据。
**⛔ 两个致命细节(掉一个就"看着配好了、其实没跑"):**
1、🔴 **`WorkingDirectory` 必须是「工作区根」**,⛔ **不是脚本所在目录**。
设成脚本目录 ⇒ 配置查找路径变成 `<ws>/.workbuddy/collab/.workbuddy/collab/…` ⇒ **找不到** ⇒ 拒跑。
2、🔴 **常驻不需要网关口令**(`--supervise` 只写 SQLite,不走网关)⇒ **保活用 `--supervise`**,
⛔ 用 `--ensure` 会在任务环境里"起了就退"。
**⛔ 一条虚警(别误判成故障)**:看板任务的 `LastResult=**1**` 是**正常的** ——
`board.py` 有全局单例守卫("看板全局只允许一个"),已在跑时它打印一行后 `return 0`,
但 **`pythonw` 在任务环境下没有 stdout** ⇒ 退出码被顶成 1。⇒ **判据看「端口在不在听」,⛔ 不看 `LastResult`**。
🔴 **弹窗真因(已封死)**:旧形态任务动作是 `powershell.exe -WindowStyle Hidden -File …` —— PowerShell 是**控制台程序**,
`-WindowStyle Hidden` 只是把窗口藏起来,**仍会分配控制台并闪一下** ⇒ 要完全不闪,必须**由 `pythonw.exe` 起**(GUI 子系统,不分配控制台)。
🔴 **判据**:任何"自动触发"的东西,**只许由 `pythonw.exe` 启动**;⛔ 不许拿 `powershell.exe` / `cmd.exe` 当动作。
⛔ **绝不允许再出现"每分钟创建一次进程"**(那本身就是设计错误,不是参数没调好)。
🔴🔴🔴 **怎么判「两套程序都正常」(2026-10-05 用户点名 · 判据写死)**:
⛔ **"进程活着" ≠ "它在干活"** —— 常驻本体活得好好的,内部某一环读错库、被 fail-safe 兜成"什么都不做",**外表零报错**。
| 要验的 | ✅ 看什么(唯一判据) | ⛔ 别只看 |
|---|---|---|
| **协作程序** | 心跳文件新(`logs/supervise-heartbeat.json` 的 `ts` < 90 s)∧ `pid` 活 ∧ **`argv0` 指本区** | 进程在不在、日志在不在走(钩子也写同一日志,**会骗人**) |
| **检查程序** | `logs/_collabd.log` 里**有没有产出预期分支**:`检查会话:目标状态=… ⇒ 不建/已建` 或 `已建检查会话排期 …` | 同上;尤其**别只看"没报错"** |
🔴 **fail-safe 读法**:`all_sessions_idle` 读库失败 ⇒ 判「有会话在跑」⇒ 不建检查会话(安全但**不可见**)
⇒ **凡出现 `读库失败`/`no such table` 就是真故障**,不是"安静很正常"。
🔴 **前置闸是合法的**:本区有 `status='working'` 的会话(含**你自己**被排掉后仍有别人的)⇒ **正确地不建**
⇒ 想观察 `检查会话:…` 分支,**得等本区会话全结束**(判据:`_collabd.log` 出现 `all_sessions_idle:还有 N 条 working`)。
🔴 **用户可见入口(用户点名要的「一键能关掉」)**:
`会话机制-一键开关.bat`(工作区根 + **桌面各一份**)⇒ 双击选 `1 启动 / 2 全部停止 / 3 看状态`。
它的"停止"=**一个动作做全**:关开关 + 禁任务 + 杀进程 + 复核(⛔ 不再"禁了任务却没杀进程")。
→ 章法与事故复盘:`references/pitfalls.md` **P0-70 / P0-71**(P0-71 是载体定案的完整实测)。
> 🔴🔴 **2026-10-03 07:2x 口径改(本条为准,排在 10-02 那条之上)——「上报」整套退役 + 看板两处几何改动**
>
> 用户逐字:「**没用了就删除,现在的机制是 协作程序接收和处理队列**」。
>
> **一、现行机制只剩两条腿(都已实测活着)**:
> · **接收** = `--report` 把任务会话的执行状态写进 `tasks.json`(四态台账,唯一权威)—— `task_report()`。
> · **处理** = 建**检查会话排期**(`maybe_spawn_check_agent()`,五道闸)让会话去读队列干活
> —— 常驻 `--supervise` 每 2 轮判一次,是这条腿的**唯一载体**(⛔ 检查会话只能靠排期开)。
> 🔴🔴 **2026-10-04 用户口径(本节为准)——「检查程序必然要去处理队列」**:
> 逐字:「首先执行程序要把执行结果 的文本地址或引用写入检查程序的队列,
> **检查程序必然要去处理队列的情况**(是等待工作区所有会话停止后,把队列情况一并处理)」
> ⇒ 三条逐条对:
> | 用户的话 | 机制里的对应 | 状态 |
> |---|---|---|
> | 执行结果要写**地址或引用**入队列 | `--report --state done` 缺 `--artifact` **直接拒收** | ✅ 早已落地 |
> | 检查程序**必然**处理队列 | → 见下面「闸① 收窄」 | ✅ 2026-10-04 修 |
> | **等所有会话停止后**一并处理 | `_all_sessions_idle()`(闸②) | ✅ 早已存在 |
> 🔴 **闸① 收窄(当天修)**:原来 `if life != GOAL_LIFE_RUN: return None` 让
> 「目标已标完成」成为「**处理队列**」的前置条件 ⇒ 队列非空时**照样不建** ⇒ **活永远没人接**。
> ✅ 现口径:
> · **`sessions-ended`(队列非空 = 有活没人干)⇒ ⛔ 不受目标状态限制**(活没干完就是没干完);
> · **`queue-empty`(队列空)⇒ 仍须「进行中」**(它的职责正是**判目标该不该收口**,只在进行中才有意义)。
> 📌 台账实体 ⇒ `references/pitfalls.md` **P0-56**。
> · ⛔ **没有"推送"这一环**:队列变化**不再自动通知任何人**。要落一件事,**显式建一条任务会话**。
>
> **二、本轮真删了哪些(⛔ 不是"标成已退役")**:
> · `collabd.py::supervise()` 里的 **①′待反馈序列 + ②③单条握手 + ④唤醒** 四段,
> 以及两个 `_deliver_str()` 调用点 —— **203 行 → 71 行(-132)**。
> (原文随历史备份一并清理;⛔ 要恢复得按 `collabd.py::supervise()` docstring 里那四条清单重写。)
> · `--tick` 里的 `check_delivery_consumed()` 调用(判据 `st["wake"]["expect"]` 只由 `_deliver_str()` 写
> ⇒ 投递删后**恒返回 `{}`**)。
> · 看板「**最近上报 `wakeups.jsonl`**」整张卡(该文件**已不再被写**、实测根本不存在 ⇒ 恒空假面板)。
> · 看板两处几何改动,**都是用户当面纠正后落地的**(⛔ 不是我第一版那么写的):
> ① 架构图 ④ 那格 —— 我第一版把「上报」**改名成「常驻」**,用户逐字驳回:
> 「**怎么又把上报 改成常驻了 常驻什么,不是 协作程序常驻吗**」。
> **根因=把「角色名」与「运行形态」混成一个词**:那一格的角色本来就叫**常驻程序**(`collabd.py`),
> "常驻"只是它的一种跑法(`--supervise`)—— 用模糊词替代具体词 = 越改越糊。
> ✅ **处置=两格合并成一格**(`PX=280, PW=720`;删 `UX`、删随之悬空的 `RKX`、`TICKX` 改算为 `PX+PW/2+120`):
> 标题「**协作程序**」/副标题「**接收:--report 写台账 · 处理:建检查会话排期 · 常驻一直运行**」
> /右侧小字「⛔ 队列投递已于 2026-10-03 退役(曾叫「上报」)」。
> ⚠️ 删变量在**代码行**零引用(用自写检查器逐个 `grep` 过 `UX`/`RKX`)。
> ② 「最近上报 `wakeups.jsonl`」整张卡删除(该文件**已不再被写**、实测根本不存在 ⇒ 恒空假面板)+其渲染块。
> · 另 4 处用户可见「上报」文本替换:`② 上报 · --report` → 「② 写台账 · --report」(空态/有会话两处);
> `Hook进程` 框内 `--tick(上报)` → 「--tick(补检查排期)」;台账空态文案 → 「用 --report 把状态写进这里(四态…)」;
> 常驻程序格副标题「只维护不上报」→「**只维护不推送**」。
> · `collabd.config.json`:`wake_enable` **true → false**(第二道闸,第一道是代码里已删那两段)。
>
> **二之二、🔴 顺带查清的判据:「改看板要不要重启」有四类答案,混成一句话就是误导**
> (用户第二句纠正:「**看板每次修改都不处理看板**」⇒ 我把"改配置要重启"说成了"改看板都要重启",**是分类错误**):
> · 改 `assets/board.html` ⛔ **不用**(`board.py:1293` `serve()` 里 `html_p.read_bytes()` **每请求实时读盘**;
> `/board.json` 带 `html_sig`(`mtime.size`)⇒ 页面自动重载)—— 现跑实证 `tmp/_probe_hotreload.py`:
> 改 `<title>` 插标记 → ⛔ 未重启 → 页面立即含标记(md5 已还原一致 ✔)。
> · 改 `.workbuddy/collab/board_ext.py` ⛔ **不用**(`EXT_CACHE` 按 `(mtime,size)` 签名**热重载**,`board.py:855-870`)。
> · 改 `scripts/board.py` 自身 ✅ **必须**(进程里是启动那一刻载入的旧代码)。
> · 改 `collabd.config.json` ✅ **必须**(`C = _cfg()` 在 `board.py:81` **模块级执行一次**,⛔ 无 re-read 路径)。
> ⇒ **判据=先答"这个文件是谁在读、什么时候读"**;⛔ 别把"改了 X"与"改看板"当同一件事。
> ⇒ 全文 ⇒ `references/pitfalls.md` **P0-38**(与 P0-17/P0-33 同族但**方向相反**:那两条讲"该重启却没重启",
> 本条讲"⛔ 不该重启却去重启了" —— **重启不是万能药**)。
>
> **三、🔴 删除前的实测读数(这才是"为什么该删"的证据)**:
> | 读数 | 值 |
> |---|---|
> | 日志 `follow-retired` 行数 | **1 313 行且每 20 s +1**(每轮试投、每轮失败) |
> | `notify_pending` | `["S8=done","S9=done","S5=done","S12=done"]` —— **4 条全 `done` 堆在队首** |
> | `wakeups.jsonl` | **文件不存在**(投递早已没成功过) |
> | `TO-MAIN.md` | 每轮被覆写成 `S8 -> done`(那份通知永远送不出去) |
> | `NEED-USER.md` | 每轮刷新「投递链路没有收件人」 |
>
> **四、🔴 它是三重死锁,不是一条腿断**:
> ① 投递失败 → `collabd.py`「**只有真投出去才允许消费队列**」⇒ 不消费;
> ② 不消费 ⇒ 队首永驻一条投不出去的死件(`S8=done` 从 23:55 堵到 07:08);
> ③ 队首非空 ⇒ `no_fb` 恒 False ⇒ **唤醒的条件②「队列无待反馈」永不成立** ⇒ 唤醒那条路跟着一起死。
>
> **五、⛔ 保留的零调用点函数(本体不动,是"将来重建收件人"的实现)**:
> `_deliver_str()` / `follow_for_topic()` / `wake_round()` / `check_delivery_consumed()`。
> 🔴 **为什么不删**:`selftest.py` 有 4 处**真调用**前两个 ⇒ 删函数体 = `NameError` 崩自测。
> ⚠️ **判据必须是 AST 而不是字面 grep** —— 退役说明的 docstring 里**必然**提到被删的函数名
> ⇒ 任何 `grep "_deliver_str(" in <函数体切片>` 都**永久假红**(见 `selftest.py::_calls_in()`)。
>
> **六、验收(全部现算)**:`py_compile` 4 份全过 | `board.html` 两个 script 块 `new Function()` 全过
> | **`selftest` PASS 47 / FAIL 1**(回到基线;那个 FAIL 是域锁锚点词表的项目名,⛔ 既有问题)
> | **60 秒观察窗内 `follow-retired` 增量 = 0**(改前每 20 s +1)
> | 常驻新实例 pid 49324、心跳 `round` 递增、心跳 JSON 里 `wake` 键**已消失** ✔
> | 看板 `--takeover` 后 `/board.json` 的 `meta.deliver_retired == "2026-10-03"` ✔
> | Edge headless 截图(`tmp/board_merged.png`)实证:④ **一格「协作程序」**+「队列 2 件 · 已完成 2 · 未完 0」
> +「接收:--report 写台账 · 处理:建检查会话排期 · 常驻一直运行」+ 右侧退役小字;
> `--once`(x=440) 与 `--tick`(x=640) **两条线都落在框内**;**「最近上报」卡已消失** ✔
> | 两格合一后**再跑一次 `selftest` 终检**= PASS 47 / FAIL 1(与合并前同基线,⛔ 合并没引入新问题) ✔
> 🔴🔴 **2026-10-02 23:4x 口径改(本条为准,全文其余「四类」表述按此读)**
>
> **会话类别从四类收敛为两类**:① **主会话** ② **任务会话**。
> **唤醒会话 / 跟进会话 / 队列上报机制 —— 整套退役**(用户逐字,三句):
> ① 「按照之前的讨论 唤醒会话 跟进会话 和 上报程序 都去掉才对」
> ② 「上报机制也不需要了」
> ③ 「按照新的逻辑整体修改」
> (口径**早就在** `scripts/collabd.py:2293` 写着:「创建检查会话的为 协作程序
> **(现在不需要上报机制了、之前已经去掉 唤醒会话和跟进会话机制)**」—— 本轮是**把代码与文档对齐到已有口径**。)
>
> **已落地(现算)**:
> · 角色表 `collabd.py::parse_session_name()` 与 `board.py::_role_of_title()` **两处同款**收到 `主/执行`
> ⇒ `[唤醒]`/`[跟进]` **判空**(旧 `sessions` 行不删,但不再是活类别,也**不会被当主会话候选**)。
> · 主会话候选排除元组**两处**收到 `("worker",)`。
> · `follow_for_topic()` **短路退役**(恒返回 `why="follow-retired"`、`sid=""`)⇒
> ⛔ **不投主会话**(主会话只由用户触发),⛔ 不盲投。
> · `goalctl.py::_WHY` 登记新档 `follow-retired`(「需人看」)⛔ 不降级去抢锁自己干。
> · 两台周期钟 `[唤醒]-…-脉冲` / `[跟进]-…-队列上报` + 3 条一次性跟进排期 ⇒ `ACTIVE→PAUSED`、
> **零删除**、`integrity_check=ok`、周期排期未误伤。
> · **检查会话由常驻程序建**(`maybe_spawn_check_agent()` 五道闸),⛔ 不经唤醒/跟进转手。
> · `selftest` **PASS 47 / FAIL 1**(那个 FAIL 是域锁锚点词表里的项目名,⛔ 既有问题、⛔ 故意不动)。
>
> ⚠️ **旧正文里「四类」「跟进会话」「唤醒会话」的大量表述按本块读** ⛔ 不是"没改",
> 是**刻意保留**(那是历史证据与踩坑记录;与本块冲突处以本块为准)。
> 🔴 **`board_ext.py` 里的「链路前置」已停用**(讲的是 2026-09-30 随概念退役的手机接入线 20090 链路)
> ⇒ `collabd.config.json` 的 `board_ext` 指向 `.RETIRED-20260930.py`。
> ⚠️ **改了 `collabd.config.json` 必须重启看板才生效** —— `C` 是**模块级加载一次**,
> ⛔ 不每次快照重读(这是 P0-17「改了看不见」漏记的一面)。
> 🔴 **一句话**:**会话要先活得下去(钩子 + 锁 + 日志闸),再谈协作(派活靠自动化、收结果直读宿主库、机械判定下沉、人只看一个看板)。**
> ⚠️ **包自包含**:所有脚本、参考件、资产都在本包内(`scripts/` `references/` `assets/`),⛔ 不再依赖文档库 `07-scripts/`。换机器 = 拷本包 + 跑一次 `install.py`。
---
## §0 怎么用(先读这一节)
**① 我要装 / 要换机器** ⇒ 跑 `python install.py --dry-run` 看 diff,再 `python install.py --apply`,最后 `python install.py --verify`。
(`--apply` 会:写 `roots.env` → 按**声明表**接线全局钩子 → 初始化工作区 → 记 `install.log`。⛔ 不硬编码 python 路径与盘符。)
**⓪ 我要动手(建判据/写验收/报"已完成"/任何带"现在·已·还差"的结论)** ⇒ **先读 `references/00-动手前必过.md`**(🔴 **动手层**,2026-10-04 建,只有三条动作:① 查状态**问程序自己**,⛔ 不许 glob/手拼路径 ② 引用任何读数**先看它什么时候写的** ③ 写判据**先让基线全绿**再谈抓得住 —— 每条都对应当天真实翻车,且都已落判据不许退化)。
**② 我要查会话机制** ⇒ 读 `references/architecture.md`(唯一权威:机制全貌)+ `references/rules.md`(规矩与判据)。
**③ 我要查多会话执行** ⇒ 读 `references/collab.md`(执行四条通道 + 派活模板)。
常驻程序可独立使用:`python scripts/collabd.py --where` / `--tick` / `--report`。
**④ 我要复盘某个历史会话** ⇒ 读 `references/forensics.md`,取证脚本 `scripts/forensics/proc-parent.py`。
**⑤ 踩过坑 / 要避坑** ⇒ `references/pitfalls.md`;**包内文件清单与来源** ⇒ `references/manifest.md`(逐文件 md5 + provenance)。
**⑥ 我要让常驻长期在线(⛔ 会话/工具调用起的活活不过当轮)** ⇒ 读 **`references/supervise-persistence.md`**(唯一权威:计划任务 `collabd-keepalive-<区>` → `pythonw.exe` → `supervise-launch.py` · 三条封死路 · 两个秒退坑 · 心跳三判据验收)。
 🔴 **先分场景再动手**(该文档 §〇 有对照表):**用户手动创建主会话** / **定时任务创建主会话** —— 两者都要**同一个载体**(计划任务 + 永不返回的守护循环);⛔ **排期代替不了载体**(跑完即 `completed`,下一跳之前是空窗);⚠️ `status=ACTIVE` **≠ 在跑**(`once` 排期过期即哑:`next_run_at=None`)。
**⑦ 我要判断「这件事该自己定,还是该问用户」/ 要给用户一个「待拍板」清单** ⇒ 读 **`references/02-功能优先协作协议.md`**(2026-10-04 由 `dsh-decision` 搬入本包,**内容守恒**)。
 它答的是**会话里做事时的自决策口径**:**功能卡 4 问**(用户只填「谁用/在哪用/要做什么/怎样算成功」)· **9 类自决策白名单**(技术选型 / 实现路径 / 命名 / 调参 / 部署 / 排查 / 版本 / 兼容降级 / 文档技术内容 ⇒ **永不问**)· **只准提报用户 3 类**(功能语义分叉 / 红线门禁 / 超出决策边界)· **提报用户格式**(⛔ 不许出现包名 / 路径 / commit / 代码标识符)· **拆包提报用户**(红线问题⛔ 不得与技术方案捆着问)· **答复与交付格式**。
 ✅ **判据实体就在本档正文里**(2026-10-04 改,原文写「权威在 `agent-operating-rules §1.6`」——现已改为**本档即权威**)⇒ 🔴 **只复制这一个技能到别的机器,这些功能全部可用,⛔ 不依赖任何其他技能**。📌 来路:2026-10-04 从 `dsh-decision/references/01` 逐行搬入;⛔ `dsh-decision` 那份视为副本,**改判据以本档为准**。
 🔴 **配合任务会话的六阶段一起用**(见 `references/collab.md` §5):**需求识别 → 调研 → 规划 → 执行 → 验证 → 归档**——每个阶段都能撞上"该自己定还是该问用户",本档就是那一刻的判据。
---
## 🔴🔴 T 表 · 状态 → 我该做什么(2026-10-04 用户口述,⛔ **唯一权威,不许 AI 推断**)
> 📌 **为什么有这张表**:技能原先只有「你要干什么 → 去看哪篇」(知识组织),
> **没有「现在什么状态 → 下一步做什么」(行动组织)** ⇒ AI 查到一条知识
> 就以为「知道该做什么了」⇒ **2026-10-04 一天连栽三次**(凭空造出「开机自启」
> 「三类会话」「两类会话」三个需求,全是 AI 自己推断的,⛔ 用户从没要求过)。
> ⇒ 这张表是**堵这个洞**的。⛔ **不在表里的状态 ⇒ 问用户,⛔ 不许自己推断。**
### 一、执行会话的**生命周期**(用户原话逐字)
> 「**使用执行会话完成目标的时候 主会话 才建立一轮执行会话,
> 后续无意外都是 检查进程创建**」
```
主会话收到「用执行会话完成 X」
│
├─ ① **建一轮任务会话** ← 🔴 只有这一步是「主会话建执行会话」
│ └─ 它们干活 → 完成后**向检查程序队列投递执行情况**(`--report`)
│
├─ ② **后续无意外 ⇒ 只建检查会话** ← 🔴 检查会话**由常驻建**(`maybe_spawn_check_agent()`)
│ └─ ⛔ **主会话到这一步就收手**,⛔ 不再自己建任务会话
│
└─ ③ **有意外**(链条断了 / 推不动 / 检查会话判不过)
└─ 才需要再建任务会话去接 —— ⛔ 建不建、建几条 ⇒ **问用户**
```
### 🔴 1.5 前置条件(2026-10-04 用户补充,⛔ **顺序不能颠倒**)
> 用户原话:「**不只是创建执行会话,如果主会话下没有 后台任务 或 检查程序
> 还需要启动常驻程序**」
⇒ **三层是串起来的**,⛔ 不许跳过中间那层直接建会话:
```
第 0 层:常驻(collabd.py --supervise) ← ⛔ 一切的前提
└─ 它是「建检查会话」那条腿的**唯一载体**(`maybe_spawn_check_agent()`,每 2 轮判一次)
└─ 🔴 实测依据:`collabd.py` 文首「处理 = 建检查会话排期 …… 由常驻 --supervise
每 2 轮判一次,是这条腿的**唯一载体**」⇒ **没有常驻 ⇒ 检查会话永远建不出来**
第 1 层:检查会话([结果检查]/[目标检查]) ← 常驻建的,⛔ 你自己建不了
第 2 层:任务会话([执行]-…) ← ⛔ **只有主会话在第①步建一轮**,之后不再建
```
🔴 **因此开工第 0 步的真顺序是**:
| 步 | 查什么(**现取,⛔ 别看快照**) | 不在位时 |
|---|---|---|
| **0-a** | **常驻在不在**:`supervise_alive()` = `pid 活 ∧ 心跳新鲜(<90 s)` | 🔴 **先起常驻**,⛔ 不许直接建检查会话 |
| **0-b** | **有没有目标** + 生命周期 | 🔴 **没有目标 ⇒ 用户在用「基础会话方式」,⛔ 那不是故障、⛔ 不必修**;
| | | 有目标但 `lifecycle` =「等待」⇒ **`queue-empty` 那条不动**(闸①,⛔ 别替他改);
| | | 🔴 `lifecycle` =「已完成」(**含带后缀**形态)⇒ **先看台账**:还有未完成件 ⇒ **照建 `[结果检查]`**;全 `done`/空 ⇒ **收工** |
| **0-c** | 五道闸其余(会话全结束/队列对得上 reason/无待执行排期/同名未在册) | ⛔ 缺一即静默不动 |
🔴🔴 **0-b 的读数必须看"标准值",⛔ 不看原文**(10-04 实测栽到,`vibe-product`):
`goal.json` 里可能写着 **带后缀**的 `已完成(机器可判部分)`,而 `goal_life()` 只做精确匹配
⇒ **静默回落成「等待」** ⇒ 「已完成」被读成「等待」(**语义相反**,排查被直接带偏)。
✅ 已修:`goal_life()` **剥掉全角/半角括号后缀再判**,且**认不出时写日志**(⛔ 不静默回落)。
⇒ 📌 判 0-b 一律**问代码**(`python collabd.py --check-status` 或 `goal_life()`),
⛔ **不要自己读 `goal.json` 的原文再肉眼比对** —— 那正是被带偏的入口。
📌 **「没有目标」不是缺口,是「用户用基础会话方式」(用户 2026-10-04 口径)**:
⛔ **别把「没启用机制」说成「没配好」**。
代码同向:`goal_life()` 读不到 `goal.json` ⇒ 判「等待」,注释原话
「**默认必须是「等待」而不是「进行中」**(只有进行中才建检查会话)⇒ 默认给进行中 ⇒
用户还没开口机制就开始自动建会话 ⇒ **越权**。**宁可等,不可动**」。
⇒ 🔴 **不设目标就不会有检查会话,这是设计**;⛔ 想让它有 ⇒ **用户开目标**,⛔ **不许我代设**。
⚠️ 另:`acceptance_state` 记着 **pid 8024 / 10-02 13:44**(两天前快照),
实际是 **pid 19424 / 心跳 4 s 前** ⇒ ⛔ 判据只用 `started_ts` 与 `ts`,⛔ 别读那个字段。
📌 **一句话**:`⛔ 顺序是「先常驻 → 再检查会话 → 才是主会话那一轮执行会话」;
⛔ **跳过常驻直接建会话 = 建了也不会被接上**(检查会话只能靠排期开,而排期只有常驻会建)。
### 🔴 1.6 起不来 = **先报错、先解决,再执行目标**(10-04 用户原话)
> 用户原话:「**加个规则 后台任务和检查程序如果不能启动,就先报错解决了问题
> 在执行完成目标的任务**」「**必须首先启动好在执行**」
⇒ 这是**顺序红线**,⛔ 不是"尽量起一下":
```
判断「常驻/检查程序在不在位」
│
├─ 在位 ────────────────────────────▶ 才允许往下建会话、派活
│
└─ 不在位 ──▶ 🔴 **先起**
│
├─ 起成功(pid 活 ∧ 心跳新鲜)──▶ 才允许往下
│
└─ ⛔ **起不来 ⇒ 立刻停下、如实报错**
⛔ 不许"边起边干"(建了会话也没人接)
⛔ 不许只说一句"起好了"就往下走
⛔ 不许编原因(只报**读到的现象**,⛔ 不猜病因)
🔴 **先解决问题,再执行完成目标的任务**
```
⚖️ **三条边界(越界就是又一次"凭空造需求")**:
1. ⛔ **没目标 ⇒ 不起、也不报**(那是用户在用**基础会话方式**,正常态)
2. ⛔ **只管「常驻在不在位」这一件事** —— ⛔ 不代用户开目标、⛔ 不自己建检查会话
3. 🔴 **在位判据只有一条**:`pid 活 ∧ 心跳新鲜(<90 s)`,⛔ 别看 `supervise.pid` 文件本身
📌 **谁来执行这条**:**不是靠我记得**,是 `scripts/hooks/supervise-ensure-hook.py`
(挂 `UserPromptSubmit`)—— 它每轮自动问 `supervise_alive()`,不在位就起,
**起不来就注入一条报错**让本轮对话看见。
⚠️ 🔴 **2026-10-04 实测:这个钩子曾长期空转**(只读 `DSH_WS_ROOT`,
而宿主 env 三级全空)⇒ 详见 `references/pitfalls.md`。
### 二、**「这轮结束了」的判据**(用户原话:靠投递,不靠我判断)
| 判据 | 怎么查 |
|---|---|
| 任务会话**做完了** | 台账出现它那条的 `state=done` |
| ⛔ **`done` 必须带 `--artifact`** | `collabd.py --report` 拒收没产物的 `done`(10-03 用户逐字要求)⇒ **`done` 存在 = 产物已登记** |
| 🔴 **`done` 的产物要真读得到** | `artifact_state()`(**唯一事实源**)判 `ok`/`missing`/`gone` ⇒ **读不到也拒收**(10-04 加,见 `pitfalls.md` P0-58) |
| 卡住了 | `state=blocked` **必须**带 `--reason`(⛔ 不许只标「卡了」不说卡在哪、谁在等) |
| **这轮结束** | 台账该轮全部 `done` ⇒ ⛔ 此时**零活任务会话是正常的**,⛔ **不许报「缺执行会话」** |
### 二·补 🔴 **文档合同 —— 「谁写哪份文档」**(用户 2026-10-04 口径逐字)
> 「之前还说过 **执行会话的结果要形成文档,目标也要完成情况的文档**,
> 这样后续检查会话和后续执行会话都可根据文档继续处理,**避免全工作区到处找信息**」
🔴 **为什么必须有这份合同**(实测**三区三种形态** ⇒ 缺的**不是能力,是合同**):
某区只有 `目标执行状态.md`(420 B,**执行产物没落这儿**)/某区有**手工**写的「过程记录」(⛔ 非机制要求)/
本区 `S12_*.md` 落对了 —— **同一套机制、三个区三种形态**。
| 文档 | 谁写 | 什么时候写 | 谁读 |
|---|---|---|---|
| `<目标目录>/目标执行状态.md` | **目标检查会话** | 核对完目标状态后 | 下一个检查会话/用户 |
| `<目标目录>/<棒次>_<事项>_<日期>.md` | **任务会话(执行棒)自己** | 本轮做完时(=`--report done` **之前**) | 检查会话照 `artifact` 读 |
| `tasks.json` | `--report`(机器) | 每次状态转移 | 全部环节 |
🔴🔴 **目标目录的完整形态(2026-10-05 用户定案,逐字:「`<工作区根>/执行会话/目标-xxx-xxxxxx/` 改成这样」)**:
```
<工作区根>/执行会话/目标-<简称或标题前缀≤20字>-<sha1(title)前6位>/
```
- **加了一层 `执行会话/`** —— ⛔ 目标文件夹**不再直接散在工作区根**(用户诉求:根目录干净、历史目标归拢一处)。
- 单一真源:常量 `_GOAL_DIR_PARENT = "执行会话"` + `goal_dir_name()`(⛔ 别在调用方各拼一遍)。
- ⚠️ 短哈希算的是 **`title`(全量标题)**,⛔ 不是 `short` ⇒ **改标题 = 换目录名**(存量里已有漂移案例,见 `pitfalls.md`)。
- ⚠️ **域目录不受影响**:独立域仍是「工作区第一层目录」;`执行会话/` 是**目标文件夹的父层**,⛔ 不参与域键计算。
🔴 **落点是死的**:执行产物的 `--artifact` **必须**指进 `<目标目录>/` 里
(⛔ 不许写回 `交付物/`、`docs/`、工作区根 —— **那正是「到处找」的由来**)。
⚠️ **落点=软判据**(`artifact_dir_ok()` 只**提示**):存量里有落在别处但有效的产物,
硬拒会误杀既有工作流;但**必须说出来 + 写日志**(⛔ 静默通过=合同等于没立)。
🔴 **存在性=硬闸**(`artifact_state()`):**读不到就拒收**(两层判据 ⛔ 别混)。
📌 **`artifact_state()` 是唯一事实源** —— ⛔ 不许在 prompt/看板/CLI 里各写一段 `if not artifact`。
📌 **台账是唯一权威**:`tmp/supervise-inbox/tasks.json`(四态 `pending/running/blocked/done`)。
⛔ 判「有没有在做的事」一律现取它,⛔ 别读看板/文档里的快照(`acceptance_state` 是快照,
⚠️ 实测它记着 10-02 的 `pid 8024`,而 10-04 实际是 `pid 19424`)。
### 三、检查会话**不能固定席位**(用户原话)
> 「**没办法固定要是有好了,为什么不行有记录吗**」
⇒ 🔴 **不许预先摆一条「固定检查会话」**。理由(有记录就不怕丢):
**记录在台账里** ⇒ 检查会话是**可丢弃的** ⇒ 有好了就换一条,⛔ 不必留一个常设席位。
⇒ ⛔ **反过来的推论**:台账空 = **没有在做的事** ⇒ ⛔ **不许**因此去建任务会话。
### 四、状态 → 动作(**这张表就是「什么时候该做什么」的答案**)
| 当前状态(**现取台账**) | 我该做的 | ⛔ 不许做的 |
|---|---|---|
| 台账有 `pending` | 取那条执行;没意外就交给检查会话 | ⛔ 另起一批任务会话 |
| 台账有 `running` | 让它跑;检查会话去核 | ⛔ 催它、⛔ 重建 |
| 台账全 `done` | **这轮结束** ⇒ 报「已完成 + 剩什么」 | ⛔ **不许**编「欠项」、⛔ 不许找活干 |
| 台账空 | 报「**没有在做的事**」 | ⛔ **不许**建任务会话、⛔ 不许造需求 |
| **目标 `lifecycle=已完成`**(含 **带后缀**形态,如 `已完成(机器可判部分)`) | 🔴 **先看台账,⛔ 不是一律收工**:<br>· 台账**还有未完成件**(`pending`/`running`/`blocked`)⇒ **判定=还有活没人干** ⇒ **照常建 `[结果检查]`**(闸① **不**拦它)<br>· 台账**全 `done`/空** ⇒ **判定=收工** ⇒ 报「做完了 + 还差什么人工确认」 | ⛔ **不许**把「已完成」说成「等待/没点头」(**语义相反**);⛔ 不许自行续活 |
| **没有目标**(`goal.json` 无 / `lifecycle=等待`) | **判定=用户在用「基础会话方式」** ⇒ 正常,⛔ 什么都不用起 | ⛔ **不许**说成「缺目标/缺配置」、⛔ **不许**代他设目标、⛔ 不许起检查会话 |
| 有 `blocked` | 报「卡在 X,等谁」⇒ **问用户** | ⛔ 不许自己替用户决定绕过去 |
| 用户明确说「用执行会话完成 X」 | 才走上面第 ① 步建一轮 | ⛔ 不许替用户决定开不开 |
### 五、⛔ 三条由此推出来的红线(2026-10-04 当天栽出来的)
1. ⛔ **凡不在上表的状态 ⇒ 问用户,⛔ 不许推断后当成需求或欠项。**
2. ⛔ **「实体里存在」⛔ 不等于「该有」** —— 台账里 8 条任务会话不在跑 = **上一轮的遗留**,
⛔ **不是**「缺 8 条」。⚠️ 实测:2026-10-04 我正是把「遗留」读成「欠项」,
而那 8 条对应的轮次早已完成。
3. ⛔ **⛔ 不许把自己以为该有的东西摊成「欠项」/「风险」/「待办」** ——
那是**凭空造需求**(当天三次:开机自启/三类会话/两类会话)。
### 六、🔴🔴 **完成情况判据 · 唯一事实源**(2026-10-04 用户定案)
> 用户原话:「**是不是应该统一完成情况的 状态标准,不要换个工作区换个目标,就统计不准确**」
**病根**:同一条「这条验收算不算过」的判据,曾在**三处各写一遍**,于是必然漂移:
1. `scripts/board.py` → `acc_is_pass()`(**唯一实现,改判据只改这里**)
2. `scripts/collabd.py` → `_acc_is_pass()`(**只转发 board,⛔ 不许自己写词表**)
3. `assets/board.html` → `accIsPass()`(前端跑不了 Python ⇒ **抄同一份白名单**)
**白名单(全库唯一)**:`pass` / `过` / `通过` / `达` / `达标` / `合格` / `完成`
(Python 侧常量 `ACC_PASS_WORDS`;JS 侧常量 `ACC_PASS_WORDS` —— 两处**必须逐字相同**。)
**算法(三处同款)**:① 掐掉括注(`(`/`(` 起)② 按 `:`/`:` 切段 ③ 逐段 `startswith` 白名单词
④ 一段都没命中 ⇒ `False`(**fail-closed**)。
⚠️ ⛔ **不许「只取最后一段」** —— 那是 collabd 的一版漂移(`过:1440` 与 `过:390` 会判相反)。
**分母口径(三处同款)**:`_` 开头的键是**说明行**(`_说明`/`_更新`…),
⛔ **不许进分母** ⇒ 分母 = **有效判据条数**(否则通过率永远到不了 100%)。
**新增判定词的正确姿势**:改 `board.py::ACC_PASS_WORDS` **一处** ⇒ 同步 `board.html` 同名字符串
⇒ 跑 `selftest.py`(`t_acc_is_pass_chinese` 已含 `达/达标/合格/完成` 与反例 `未达/待达标`)
⇒ 跑 `sync` 到各工作区副本 ⇒ **看板要重启**才吃到 `board.py`(`board.html` 实时读盘、不用重启)。
**为什么当初没抓到**:自检用例**只喂了 11 种写法、⛔ 没有 `达`**,而真源恰恰主用 `达`
⇒ **用例盲区 = 判据盲区**(同族:`pitfalls.md` P0-13/P0-20「判据写死期望值 ⇒ 永远不命中真源」)。
## §1 第一段 · 会话机制
> 这一段**不依赖**多会话协作;只把「会话活得下去」这套装上。
**三件套**
1. **钩子**(`settings.json` 全局生效,`scripts/hooks/` 6 份):日志闸(`session-log-guard.py`)/锁闸(`lock-guard-hook.py`)/输出闸(`bash-output-guard.py`)/叫停闸(`stop-dialog-guard.py`)/技能闸(`skill-load-guard.py`)/结果回报(`wb-result-hook.py`)。
2. **锁**(`scripts/lock/`):
- 开工三步=`scripts/dsh.py open`(本项目入口,内部跑 ①状态 ②preflight ③抢锁);
- 抢锁 `handoff-guard.sh --claim-exec "<会话名>" [--domains <域>]` —— **⛔ 机制层必须独占(不带 `--domains`)**;抢不到 ⇒ **停手 + 报告**(红线 R9:⛔ 不删锁、不接管)。
- 释放必须反序:`--release` → `--release-exec "<会话名>"`;⛔ 不带名 ⇒ 拒释放。
3. **日志闸**:文件软 **5** / 硬 **8 MiB**;工具调用软 **200** / 硬 **250**。命中 ⇒ 开接续会话。
**🔴🔴 执行会话「独立域」硬规则**(用户 2026-10-02 定案,逐字)
> 「创建协作会话还要加个判断:协作会话必须是独立域运行的,就是做所有修改操作都在单独的文件下运行
> (比如某个项目要开发 webserver,desktop,phone app,独立插件或产品原型)这些文件都可以放在工作区对应
> 独立文件夹下,**只能只读的方式访问别的文件夹内容**。应为会话有锁的机制,开多个会话都操作一个域的文件
> 只有一个会话能执行,别的只能干等。」
- **域目录=工作区下的第一层目录**(`<工作区>/webserver/`、`<工作区>/desktop/`…)。
🔴⛔ **绝不许套公共父目录**(`domains/xxx`、`projects/xxx` 那种)—— 实测那样所有子目录会算出
**同一个域键** ⇒ 域锁等于没有、并行直接失效(本机制实测踩过并已改正)。
- **域键算法只认第一层**:域键=`<工作区名>/<第一层目录名>`,与再往下钻几层**无关**。
⇒ 想让两个会话真并行,就给它们**两个不同的第一层目录**;⛔ 在同一目录里再分层**不能**解锁并行。
- **开工第 0 步必须先抢域锁**:`handoff-guard.sh --claim-exec "<会话名>" --domains "<域目录>"`;抢不到 ⇒ **停手报告**,⛔ 不许硬写。
- **写只许在域目录内,跨目录一律只读**;必须写到外面时 ⇒ **不写**,在 `tmp/supervise-inbox/NEED-USER.md` 写明要谁批准。
- 三条命令(`collabd.py`):
```bash
python "<包>/scripts/collabd.py" --domain-status # 域现状体检(在册域锁 + 锚点词表一致性)
python "<包>/scripts/collabd.py" --domain-suggest "<类别>" # 推荐一个**当前空闲**的域名
python "<包>/scripts/collabd.py" --domain-check "<域名>" # 判这个域能不能派(⛔ 被占时 rc=1)
python "<包>/scripts/collabd.py" --domain-block "<域名>" # 打出派活要嵌的门禁块,⛔ 别手抄
```
- **门禁已自动嵌进派活**:任务会话 prompt(`GAP_PROMPT["worker"]`)与检查会话 prompt(`CHECK_PROMPT`)都带上了这段,
⛔ 跟进/唤醒**不带**(它们只读+建排期,给域目录是反向约束)。
**关键判据(踩过才写在这)**
- 🔴🔴 **用这套技能的第一件事=查环境配置,配没配决定后面全部动作**(用户 2026-10-02 定案:「重点是**技能的使用时要检查环境配置是否已配置,如果没有配置就要先配置**」)。
```bash
# 体检(只读,⛔ 无副作用)—— 判技能库根能否定位 + 已注册钩子逐条目标是否存在
python "<包>/scripts/hooks/_env.py" --ws "<工作区绝对路径>"
# 写环境标记(状态变量+时间),scope 决定落在哪个文件夹
python "<包>/scripts/hooks/_env.py" --scope global --stamp --fixed "<修了什么>"
python "<包>/scripts/hooks/_env.py" --ws "<WS>" --scope workspace --stamp
```
- **作用域要问用户**(用户 2026-10-02 原话:「可以询问是配置在**全局**还是**本工作区**」)——
🔴 **默认问、别默认写**:**全局**=一次配好所有工作区共用(改它影响**所有**工作区 ⇒ 属影响面变更);
**工作区**=只管本工作区(换机器/别的项目要各自重配)。
判据:影响面超出本工作区 ⇒ **必须问**;纯本工作区 ⇒ 自决策并**一句话说明**。
- 标记落点:全局 ⇒ `<配置目录>/env-stamp.json`;工作区 ⇒ `<工作区>/.workbuddy/env-stamp.json`。
字段=`scope`/`checked_at`(ISO 到秒)|`ok`|`config_dir`|`skills_root`|`hooks`(逐条存在性)|`fixed`。
- 🔴 **定位不到技能库根 ⇒ 报错 + 非零退出,⛔ 绝不「静默零输出」**(2026-10-02 实测事故的病根):
`reply-style-guard.py` 被注册成文档库里的旧副本 ⇒ 沿 `__file__` 上溯够不到 `skills/`
⇒ 落进 `os.path.expanduser('~/.workbuddy/skills')`,而 **Windows 上 `~` 不是真配置目录**(真值在
`CODEBUDDY_CONFIG_DIR`)⇒ **静默零输出**,日志只留一行 `core=0 字符`
⇒ **「机制坏了」与「没配规则」表现完全一样**(真实代价:另一工作区为此绕了两轮,在「规则文件在不在」上打转)。
⛔ `~` 回落已从 `_skills_root()` 删除;**每一档 env 都要验目录真存在**(env 给错要继续往下找,⛔ 不猜)。
- 工作区 `state.py` 已有 `§5c [钩子环境]` 一项跑它 ⇒ **开工跑状态就能看见**,⛔ 不必另记命令。
- 🔴 **各脚本的根目录一律先读包内 `roots.env`,再回落按位置推导**(已贯通 **16 份**:9 钩子/锁 + 7 非钩子脚本)—— 因为 `settings.json` 的 hook 条目**没有 `env` 字段**,包内脚本**搬一次就会静默指错**(历史事故:台账写到别处、测试却全绿;近期又复现一次:`wb-result-hook.py` 按 `__file__` 推三层 ⇒ **把技能包当工作区**,在包里长出 `tmp/supervise-inbox/`)。**⛔ 不要靠 `mv` 搬迁,⛔ 不要靠"改壳转发"**(转发会改 `$0`,同样挪走根)。**⛔ 代码里不留盘符字面量**:外部根只能来自 `roots.env`/宿主 env。**🔴 2026-10-02 起再收一层**:「配置目录/技能库根」的取法统一走 `scripts/hooks/_env.py`(7 个钩子已改),⛔ 各脚本不再自己拼 `~`。
- 🔴 **出口一律声明编码**:钩子脚本写 stdout 走 `sys.stdout.buffer.write(bytes)`(文案含 `⛔` ⇒ 否则 `UnicodeEncodeError` ⇒ stdout 空 ⇒ **静默放行**);**非钩子脚本**(`collabd` / `board` 等,会被重定向到文件或 `DEVNULL`)在文件头加**输出编码兜底**(`sys.stdout/stderr.reconfigure(encoding="utf-8", errors="replace")`)—— 实测:重定向 + 本地 GBK ⇒ `print("⛔…")` 抛异常 ⇒ 被顶层 handler 记成 `fatal`、**整轮失败**(常驻必踩)。
- ⚠️ **本机**:裸 `bash` 可能落到 WSL 启动器 ⇒ 要跑 shell 一律显式用 `PortableGit/.../usr/bin/bash.exe`。
- 🔴🔴 **钩子总预算(2026-10-02 事故)**:**钩子超时 ≠ 钩子变慢,而是用户这一句话被拦下**(`UserPromptSubmit operation blocked by hook: Hook timed out after 20000ms` ⇒ 提交失败,不是慢)。根因=**`UserPromptSubmit`(宿主注册 20 s)上一个脚本里串了三个子进程**:`collabd --gap` **13.5 s** + `--once` 0.5 s + `--tick` **13.5 s** ≈ **27.5 s** ⇒ 必超。四条硬规则(已落进 `wb-result-hook.py`):
1. **开局认领预算**:`BUDGET = {UserPromptSubmit: 18, PreToolUse: 25, SessionEnd: 8}`(各比注册值少 2 s)⇒ 任何**要等**的子进程,先问 `_left()` 够不够,不够 ⇒ 跳过并留痕。
2. **门槛按"实测耗时"给,⛔ 不按硬超时给**:`sweep`/`supervisor` 实测都是 **0.95 s**,若按硬超时 8 s/6 s 设门槛,在 8 s 预算下**永远跑不到**(判据看着在、其实恒假)。
3. **不需要结果的活 ⇒ 后台**(`_bg()`:`Popen` + `DETACHED_PROCESS|NEW_PROCESS_GROUP` + stdout **落文件**,⛔ 不用 PIPE):实测**父进程退出后仍能跑完**(15 s 写完 11 KB)。⛔ `CREATE_BREAKAWAY_FROM_JOB` 在本机**必失败**(`PermissionError 13`)⇒ 别加。
4. **一轮只允许一个贵活**(`_HEAVY_DONE`),其余后台;**慢的产物落缓存**(`gap-cache.json`,后台写 `.tmp` → 下轮 `os.replace` 收割,钩子只读缓存=毫秒级)⇒ 实测钩子 **27 s → 0.5~1.9 s**。
⚠️ 附带发现:`SessionEnd` 注册只有 **10 s**,而它上面挂着 `--tick`(13.5 s)+`--once`(25 s 硬超时) ⇒ 那条**从来就没跑完过**(被掐)⇒ 别拿它的缺失当"机制没装好"。
- 🔴🔴 **后台子进程一律无窗口(pythonw)**:`collabd.py --supervise`(投递常驻,每 10 s 一轮)+ 看门狗 `guard.py`(每 15 s 探活、子进程一死就重生)原本以 `python.exe`(**控制台子系统**)起来,被宿主/钩子/看门狗拉起时 Windows **新分配一个控制台窗口** ⇒ 每次重生 / 每轮 `netstat` 就闪一下黑窗(用户原话「一会弹出来一会弹出来的,影响我操作」)。🔴 **根因**:`guard.py Child.ensure()` 拉起子进程 `creationflags=0x00000008`(**只有 DETACHED_PROCESS,漏 CREATE_NO_WINDOW**);`wake-session.py` 的 `netstat` **没有任何 creationflags**。`collabd.py` 的 `netstat` 已于 09-29 修(带 `0x08000000`)。✅ **根治(2026-10-02 落地)**:所有常驻/后台 `subprocess` spawn 一律改用同目录 `pythonw.exe`(**GUI 子系统,Windows 永不为其分配控制台**)+ `creationflags` 补 `NO_WINDOW|DETACHED|NEW_PROCESS_GROUP`:① 三个脚本加 `_win_pythonw()` 解析器(模块级 `PYW`);② `collabd.py ensure_supervise` 与 `guard.py Child.ensure` 的 `sys.executable`→`PYW`;③ `wb-result-hook.py` 5 处 spawn(含 `_bg`/sweep/once/tick)`sys.executable`→`PYW`;④ `wake-session.py netstat` 补 `0x08000000`。⛔ **今后任何新加的常驻/后台 spawn 都走 `PYW` + NO_WINDOW**,⛔ 别再用 `sys.executable` 起会长期存活的子进程(备份 `*.bak-flicker-20261002.py` ×4)。
- 🔴🔴🔴 **R 红线:⛔ 严禁用「排期/自动任务」当常驻的载体或替身**(2026-10-04 用户定案,当场立)
**一句话**:「**常驻要死**」⛔ **不是**建一条排期去续它的命。
✅ **正确处置=按需重起**(`collabd.py --supervise` + `pythonw` + `NO_WINDOW`)。
📌 **口径(2026-10-04 用户订正)**:**常态=调用技能完成目标时起后台任务 + 检查程序**;
「开机自启/计划任务」只是**想做"目标做完程序还继续跑"时**的可选做法,⛔ **不是需求、不是欠项**。
🔴 **实测的反面案例**(`vibe-product`,`id=e181b51b`):`[执行]-界面交互-常驻续命`,
`FREQ=HOURLY;INTERVAL=1` —— prompt 明写「不在 ⇒ **后台任务**起一条 `collabd.py --supervise`」。
**15:23 真跑过一次**,会话结论是「**✅ 常驻存活,本轮未做续命**」
⇒ **每小时唤醒一个新会话,只为看一眼常驻活没活**;而常驻本来就活(pid 19424 连跑 17.7 h)。
⇒ **零产出、纯烧钱,且违背本技能自己写的 `supervise-persistence.md`「⛔ 排期代替不了载体」**。
⛔ **四条禁止**(逐条都有实测/文档依据):
1. ⛔ **不许建 `recurring` 排期去「续命/保活/巡检常驻」** ——
✅ 要续命就**按需重起**(`pythonw` + `NO_WINDOW`,见 `references/00-动手前必过.md`)。
⚠️ 需不需要"目标做完还继续跑" ⇒ **问用户**,⛔ 别自己假设要。
2. ⛔ **不许把排期当"谁来按点喊一次"的常驻替身** —— 排期喊完会话就结束 ⇒ **跳与跳之间必有空窗**,静默窗内无人。
3. ⛔ **不许在排期 prompt 里写「不在就起一条 `--supervise`」** —— 那是把「换载体」偷换成「每次重拉」,且起于**会话/工具调用**的子进程**活不过当轮**。
4. ⛔ **常驻真死时不许用排期兜底**,先查三样:停止标志 `guard.stop`(在=正常收工,⛔ 不是故障)|
心跳年龄(`supervise-heartbeat.json` 的 `ts`)|pid 还在不在(`_pid_alive`)。
✅ **常驻真死时的正确四条**:
① 先看**停止标志** `guard.stop`(在则是「被正常收工」,⛔ 不是故障);
② 看**心跳年龄**(`ts` 距今 > `SUPERVISE_STALE` 即已陈旧,⛔ 别看退役旧戳);
③ 宿主钩子按需**一次性**唤起 `collabd.py --tick`(**补充**,⛔ 不是「常驻」);
④ 需要长期跑 ⇒ 问用户要不要登记**计划任务**(`references/supervise-persistence.md`,⛔ **不默认要**)。
📌 **判据**:`selftest.py` 的 `t_no_schedule_as_supervisor`(⛔ 扫技能文档里"排期当常驻载体"的表述 + 扫脚本里"排期里起 --supervise"的写法),改动前后都报红即通过。
📌 **用户 2026-10-01 已定**:「**协作与投递一直运行(常驻)**」+**「自动任务当闹钟」方案已废弃** —— 本条只是把它**升格为红线 + 落判据**。
- 🔴🔴🔴 **S 红线:⛔ 严禁把「被监控对象所在的工作区」当成「目标归属的工作区」**(2026-10-04 用户当场纠正)
**一句话**:**目标在哪个工作区下达,执行它的会话就属于那个工作区** —— 被它操作/观察的**别的**区只是**对象**,⛔ 不是归属。
🔴 **实测的反面案例**:用户在本工作区(`ai1net-dsh-server`)下达
「使用执行会话完成目标:持续监控 **vibe-product** 工作区主会话使用会话技能的情况」,
我把排期 `cwds` 写成 `E:/ProgramData/AIProject/vibe-product`
⇒ 会话 `c88a157c` 落在 `vibe-product` ⇒ **本工作区看板/台账里直接看不见它**,
而本工作区恰恰是机制问题最集中、最需要它的地方。
⇒ 用户原话:「**本工作区下面的目标 为什么执行会话要创建到 vibe-product 工作区下面**」。
⛔ **三条禁止**:
1. ⛔ **不许把「要去看/要改的那个区」写成 `cwds`** —— 那是**对象**,不是**归属**。
2. ⛔ **不许用「目标文本里出现了某区名」来定 `cwds`** —— 出现的是**被操作对象**,
归属看的是「**这条命令从哪个工作区发出**」(`sessions.cwd` 逐字同形,正斜杠)。
3. ⛔ **交付物、台账、修复动作**一律落在**归属区**,⛔ 不许顺手落到对象区。
✅ **正确做法**:目标跨区时,`cwds`=**下达目标的那个工作区**;prompt 里显式写
「⛔ `<对象区>` 是被监控对象,**不是**你的工作区 —— 别去那儿建会话、别改那儿的东西」;
同步技能改动用 `scripts/workspace_mirror.py --sync <对象区副本>`(⛔ 只读对象区 + 写**全局**技能)。
⚠️ **唯一例外**(需用户在**当轮**明确要求):用户说「在 X 工作区建」⇒ 才建在 X。
⚠️ 机械背景:`automations.cwds` ⛔ **不落** `sessions.cwd`(后台会话可带独立 cwd)⇒ 判断归属看 `cwds` 字面 + 会话 `cwd`。
📌 **判据**:`selftest.py` 的 `t_ws_attribution`(扫技能文档里「对象区 / 归属区」的口径是否在位)。
---
## §2 第二段 · 多会话执行
> 这一段**可单独使用**(不装钩子与锁也能跑常驻程序本体)。
**四条通道各走各的**
· **派活** ⇒ 自动化(**唯一能开新会话的通道**;⛔ 钩子做不到)。
· **收结果** ⇒ 直读宿主库(0 token)。
· **机械判定** ⇒ 下沉到本地只读程序(`collabd.py` / `board.py` / `goalctl.py`)。
· **人看的** ⇒ 只有一个看板(`assets/board.html` + `board_ext.py`)。
**铁律(全部实测得来)**
- ⚠️ **【2026-10-03 口径已改 · 本条「四类」部分作废】** 现行=**两类**(① 主会话 ② 任务会话;唤醒会话/跟进会话/队列上报**整套退役**)⇒ 以**文首口径块**为准,⛔ **别照本条去建会话**。(原文保留仅为留痕,⛔ 不删。)
- 🔴 **同工作区 = 四类会话**(2026-10-01 用户口径,⛔ **已于 2026-10-03 作废**):**① 主会话(只管目标和方向)② 执行会话 ③ 唤醒会话 ④ 队列上报的跟进会话**。四类**同处一个目录**,**靠标题两级前缀区分**(第 1 级=角色/第 2 级=任务类别),⛔ **不按 `cwd`**。✅ 第 ④ 类**解析层 + 投递路由都已落地** —— 投递目标=**跟进会话**(`follow_for_topic()`),解析不出 ⇒ **喊用户**,⛔ **不降级投主会话**(用户:「**换新会话 唤醒的是 跟进会话,主会话只能是用户触发**」)。🔴🔴 **第④类的职责只有一件事:创建执行会话**(用户 2026-10-01 22:5x 细化,逐字:「**跟进会话 只负责 ,创建协作会话(1、跟进上报后判断是否创建 2、被唤醒后 跟进目标情况 判断是否创建)**」)⇒ 两条触发、同一个动作:**收到队列上报** 或 **被唤醒** ⇒ 跟进目标情况 ⇒ **判断是否建一条 `[协作]` 会话**。⛔ **它自己不做具体活**(⛔ 不改台账 `state`、⛔ 不写 `blocked.json`、⛔ 不派活、⛔ 不抢锁)—— 那些是**它建出来的那条执行会话**的事。细则 ⇒ `architecture.md §2.3.0c`。⚠️ **看板图上这三处写的是简称**(用户 2026-10-01 23:1x 定):**唤醒**=唤醒会话、**跟进**=队列上报的跟进会话、**协作**=目标检查 —— 简称**只是压缩版面**,⛔ **不是又多了角色**(尤其「**协作**」⛔ 别读成「**协作会话**」,那是另一层)。对照表 ⇒ `architecture.md §2.3.0c-2` / `collab-detail.md`。
- 🔴 **`board.html` 的"虚线大框"=分组,⛔ 不是节点、也⛔ 不是"新一层"**(用户 2026-10-01 第四改:「用一个**虚线大框**把 主会话 唤醒会话 和 跟进会话都框起来,这个虚线大框 **连接 协作会话 虚线大框**就好」)⇒ `UA`=主会话+唤醒+跟进、`UB`=任务会话那一排,两组之间**只有一条线 = ① 派活**(⛔ 不再从主会话往每一格画射线)。⚠️ 两个框的尺寸**都从里面的格子现推**(⛔ 不写死坐标)—— 改了格宽不重算框 ⇒ 虚线**横穿文字**,而**那种图照样能渲染**(两道自检守着它)。细则 ⇒ `architecture.md §2.3.0c-2`。
- 🔴🔴 **开工第 0 步 = 会话规则机制体检**(用户 2026-10-02 明令)⇒ **先查清,再动手**(补建会话是它**后面**一步)。用户给了**两句,第二句是纠正**:
· ① 原话逐字:「**这个会话和协作会话的技能包 运行的第一件事 ,就应该是检查清楚 所有会话规划是否配置完整且生效,然后标记一个状态**」
· ② 原话逐字:「**就应该是检查清楚 所有会话规则机制 是否配置完整且生效, 不是规划 是 规则**」⇒ 对象 = **规则机制**(钩子 / 闸门 / 技能指针 / 常驻 / 编排…),⛔ 不是"排期规划"。🔴 首版按①的字面做成「会话规划体检」、**只查排期那一面** ⇒ 当天实测出的三类失效(钩子注入指向**已退役技能名** / 快照**写进幽灵目录** / **每轮注入的记忆**里指针悬空)**一条都查不到** ⇒ 旧脚本已退役到 `<WS>/归档/技能包-旧件-20261002/`,**同包内只剩一个入口**(两个入口 = 「在册 ≠ 生效」本身)。
⇒ **怎么跑**:`"$PY" "<本包>/scripts/session-rules-check.py" [--ws <工作区>]` —— ✅ **已接进工作区 `state.py`**(跑状态快照就自带这一段,⛔ 不必另记一条命令)。
⇒ **查三类、十二项**:**A 机制装没装好** ① 关键钩子在册 ② 钩子脚本路径存在 ③ 钩子注入里引用的技能名**是否还存在** ④ 钩子**真在被调用**没(闸门日志新鲜度)|**B 规则载体同没同步** ⑤ **每轮注入的记忆**里引用的技能名存在 ⑥ 常驻规则快照**不比权威旧**|**C 编排在不在跑** ⑦ 唤醒 / 跟进两台**周期钟**(缺 = 没人推 / 没人收)(⚠️ 其中「唤醒」这台是**代偿形态** —— 定案的唤醒时钟=**常驻投递**(⑩ 那一项查的才是它);「唤醒排期在册」⛔ **不等于唤醒时钟已就位**,两件事要分开读)⑧ 排期绑的模型**会不会被服务端拒**(`model_is_thinking=0` + flash 系 ⇒ 每触发必拒,2026-10-02 实测)+ 🔴🔴 **机制建的排期模型是否与「本区主会话」同值**(2026-10-05 加,P0-80 —— 此前 `collabd.py` 把模型**写死**成 `space-bunny`,而主会话是 `deepseek-v4.1-flash` ⇒ 「同一件活在两种模型上跑」;⚠️ 该判据**扫描面必须含 `once`**,机制建的排期全是 `once`,只扫 `recurring` 就恒绿)⑨ `cwds` **归属同形**(错一字面 ⇒ 裂组且自我强化)⑩ **投递(常驻)**心跳 ⑪ 三类会话**当前有没有活的** ⑫ 有没有「**从未运行**就失效」的一次性排期。
⇒ **标记**:结论写成 `<WS>/.workbuddy/collab/session-rules.json`(`verdict` = `ok`/`warn`/`fail` + 逐项 `detail`)—— 后续会话与看板**读它**,⛔ 不靠人复述。
🔴 **为什么必须是第一件事**:2026-10-02 实测——排期**都在册**、模型**都可用**、cwds **都同形**,**却三类会话一条活的都没有**(=配置在、机制没在跑);同一天还查出钩子注入文本指着**已合并退役的技能名**、常驻快照脚本**写到没人读的幽灵目录** ⇒ 全是「**看着有配置、其实没生效**」。这类状态**不问就不会知道**,等它表现成"卡住"时已经晚了。
🔴 **判据本身也要能报出问题**:⑨ / ⑫ 这两项(以及 `cwds` 判据的边界)用**合成样本 + 四个变异体**做过红绿对照(夹具 `<WS>/tmp/rules-check-mutate.py`,跑完即弃)—— 变异体=判据恒空 / 判据放宽成"同父目录即报" / 把"从未运行"当"跑完了" / 不排除"还有下次触发"的排期,**逐一按预期报红**。⛔ **别拿"实跑一次没报错"当验收** —— 判据恒空时那次实跑**同样是绿的**。
⛔ **只标记、不设卡**:体检 `rc≠0` 也照常开工 —— 它的职责是**把状态问清楚**,不是拦人。
- 🔴🔴 **开工清单:主会话开工的第 0 步不是"派活",是"把三类会话摆好"**(用户 2026-10-01 明令:「**开始会话完成需求的时候,主会话需要创建 唤醒会话 以及根据分工类别 创建 协作会话 和 跟进会话呢 不然整个机制跑不起来**」)⇒ 建 **唤醒会话** `[唤醒]-<类别>-<具体>`(少建=**没人推**,需求原地静着)+ **每个分工类别一条任务会话** `[执行]-<类别>-<具体>`(少建=**没人干**)+ **跟进会话** `[跟进]-<具体>`(少建=**没人收**)。🔴 **跟进会话是全局唯一席位、⛔ 不按类别各建一条**(2026-10-02 用户订正);它是**收口者**:任务会话干完活把**待核对状态**写进执行队列,上报给**这固定的一个**跟进会话处理。⚠️ **只有自动化能开新会话** ⇒ "建会话"=登记一条自动化,⛔ 不是自己 spawn;标题**第 2 级必须带方括号、值取 `goal.json` 的 `topics`**(⛔ 用 `short` ⇒ 静默漏管)。细则 ⇒ `architecture.md §2.3.0d`。
- 🔴🔴 **缺会话 ⇒ 自动拉起**(用户 **2026-10-02** 口径,逐字:「**是用户说 使用协作会话方式 完成目标 或 继续完成目标**」)
⇒ 用户说这两句(或队列堵住)时:**先查三类会话齐不齐、活不活**;**缺 ⇒ 机制自己补建排期把它拉起来**,
⛔ **不许把"你去开一条会话"甩给用户**(旧行为=写 `NEED-USER.md` 喊人开会话,本条**取代**它)。
· 判据 + 现成排期参数 ⇒ `collabd.py --gap [--json]`(**只读**:判缺 + 给 `automation_update` 的 name/prompt/scheduledAt)
· 把结论**送进会话** ⇒ 钩子 `wb-result-hook.py::maybe_inject_session_gap()`(`UserPromptSubmit` 注入,触发词命中即查)
· ⛔ **脚本不许写 `automations` 表**(双红线)⇒ 建排期只能由**会话**用 `automation_update` 执行
—— 这一步**就是"自动"的全部通路**(用户只需照常说那句话,什么都不用做)。
· 🔴 **跟进会话只有一条、不带类别**(2026-10-02 用户订正,逐字:「**跟进会话只创建一个,跟进的内容
来自 执行会话执行完成 后 把 待核对状态 写入 执行队列,上报给那个 固定的 跟进会话处理**」)
⇒ `collabd.py --gap` 对 `follow` **不按类别分桶**(桶键恒 `(follow, "")`,显示成「全类别(固定席位)」),
拉起的排期名=`[跟进]-队列上报(固定席位·不分类别)`;`worker`/`waker` **仍按类别**分。
细则 ⇒ `architecture.md §2.3.0g`。
- 🔴 **「接续会话」是「形态」,⛔ 不是第 5 类**(用户 2026-10-01 原话:「**接续会话 不是单独的一类会话,是这几类会话到达阈值时 创建的接续会话**」)⇒ **角色继承被接续的那条**(主会话的接续仍是主会话候选)。
- 🔴 **执行与投递「一直运行」(常驻)** —— 09-29 定案,**2026-10-01 用户再确认**(理由:「**可能不是所有队列都是钩子产生的**」)。
⛔ **不要用自动任务当闹钟**(用户 2026-10-01:「**定时任务的方案已经废弃了**」);宿主钩子只作**补充**,⛔ 不是主路径。
- **一棒一线**;**派活 ≠ 结束**,要建监管棒并跟进。
- **自动化四律**:开机第 0 步跑状态 | prompt ⛔ 不抄任务细节 | **下一棒 id 只来自工具返回值** | 排期=收口+3~4 分钟、每条线只挂一个。
- **`cwds` 逐字同形**(去重键=`path.trim().toLowerCase()`);**入口头部声明的「工作区」决定归属**。
- 🔴 **⛔ 别用 OS 文件锁做并发**:本机实测「隔离目录全绿、上生产即 `rc=124` 卡死」⇒ 用**临时文件 + `os.replace` 原子替换 + 回读核对(最多 3 次)**。
- 🔴 **⛔ 不在「会干活的会话」里起常驻后台任务**:它会**压制该会话的 idle 钩子**(实测被僵尸任务压 6h20m);
要常驻 ⇒ 用**专用容器会话** + `stdout` **全重定向**(⛔ 否则输出反复唤醒宿主 ⇒ 界面静默哑掉)。
- 🔴🔴 **2026-10-02 23:0x 实测订正(⛔ 推翻当天早些时候的错误结论,本条为准)**:
一句判据:**不是「本机不存在长跑进程」,是「载体不同」—— 从工具调用进程树里起的活不过当轮,宿主后台任务/用户自己从桌面起的活能长跑。**
① **实测坐实**:同一时刻用 `start /b` 与 `Popen+DETACHED` 各起一条每秒打点的探针 ⇒ 两条**活到约 11 分钟后停在同一 tick**(67/64 行)⇒ **差别不在起法关键字,在父链**。
② **反证(⛔ 就是它让我误判的)**:MCN 工作台 `<mcn-short-video>/.../mcn-work-shop/start.bat` = `start "" node.exe server.js 8900`,用户**从桌面双击**、属用户登录会话 ⇒ **一直活着**;另有 4 个 `node.exe`(会话名 `Console`)长期存活。
③ ✅ **正解(本轮已跑通,两条都现算)**:走**WorkBuddy 自己的后台任务**(工具的 `run_in_background`,⛔ 别用 `subprocess` 自己造)⇒ 载体由宿主管理、不由当轮工具调用决定:
`python tmp/start_board.py 8788` → `board.py --serve --takeover` ⇒ **`127.0.0.1:8788 LISTENING`(pid 43176)/`HTTP=200`/119 704 B**;
`python tmp/start_mcn_board.py 8900` → **`LISTENING`(pid 14056)/`HTTP=200`/1 797 B**。
⚠️ 一次**带 `--takeover`**(静默并存会看到旧图);⚠️ 用 `pythonw.exe` 起子进程(GUI 子系统 ⇒ Windows 永不分配控制台 ⇒ ⛔ 不闪窗);⚠️ `pythonw` 无 stdout ⇒ **显式重定向到 `tmp/board-serve.log`**,否则静默无痕、连"起没起"都查不到。
④ ⛔ **因此撤回**当天那条「改用排期当时钟」的建议 —— 它建立在错误前提上。**排期仍保留,但身份是复活兜底**,⛔ 不是时钟。唤醒时钟**回到常驻进程本身**。
⑤ ✅ **S8 那套自愈仍有效**(`--supervise` 心跳 + `--tick` 顺手续命 + 存活判据 `pid 活 ∧ 心跳新鲜(<90 s)`)—— ⛔ 但它**不是**因为"没法长跑"才需要,而是**常驻总会被各种事打断**(重启、换会话、用户手动收),所以要能自动补回来。
🔴 **判"常驻在不在"只看两样**:`pid 活 ∧ 心跳新鲜(<90 s)`(`logs/supervise-heartbeat.json`);
⛔ **不许拿 `_tick.stamp`/投递日志当证据** —— 那些轮次是**钩子**写的(`pitfalls.md P0-22`)。
🔴 **启完必查三样**(⛔ 别凭"打印了启动消息"当成了):`netstat` 里有 **`LISTENING`**(⛔ `TIME_WAIT`/`FIN_WAIT_2` 是历史连接残留,不算)+ `curl` 有 `200` + `tasklist` 里进程在。
- 🔴 **自动唤醒任务的排期名必须带 `[执行]`**,否则被认成"主会话"把通知投给自己。
⚠️ 🔴 **2026-10-02 标注(⛔ 不是口径变更)**:该规则**仅代偿形态适用**(定案时钟=常驻投递,不开会话);🔴 **定案口径**=「协作与投递**一直运行(常驻)**」+「**定时任务的方案已废弃**」(2026-10-01 用户原话)。
- 🔴 **看板 tab 可并列查看别的「工作区」的目标**(用户 2026-10-03 报障,逐字:「**为什么 会话协作看板 tab 选项不能切换看另外两个工作区的目标**」)。
· **真因(看代码,不猜)**:`board.py::goal_files()` 只扫 `INBOX/goal.json` + `INBOX/goals/*.json`,而 `INBOX` 由**部署配置的 `workspace`** 决定 ⇒ **一个 `--serve` 实例天生只看见一个工作区**。
· **修法**:部署配置加 `peer_workspaces`(要并列查看的其它工作区,**正斜杠**)⇒ 把对方的 `goal.json` 读进来当**额外一格 tab**(打 `peer` 标记,前端显示在标题旁)。
· 🔴 **严格只读**:⛔ 不写对方文件、⛔ 不起对方进程、⛔ 不改对方状态;**活跃目标仍然只有本工作区那份** ⇒ `collabd.py` 行为零改动。
· 🔴🔴 **那一格的数据源必须跟着格走**(⛔ 否则=**假数据**,见 `pitfalls.md` **P0-40**):peer 格只喂 `_peer_tasks()`/`_peer_srows()`/`_peer_state()`(**只读对方目录**),读不到 ⇒ **空 + 界面如实说明**,⛔ **绝不拿本工作区的台账/会话/状态补位**(2026-10-03 实测:三格 `labor`/`sessions` md5 完全相同 ⇒ 把本区执行情况挂到了别人名下)。⚠️ 会话还要**单独补捞**(`_session_rows()` 只取全库最近 50 条,对方会话不在里面)+ 把 `sc["workspace"]` 换成对方根(`_sessions()` 有 `cwd` 硬过滤)。
· ⚠️ **它只是「只读概览」,⛔ 不是对方的完整看板**:peer 格的 `sessions` 目前为空(卡在 `in_project()` 判据,⛔ 不改那条 —— 它与 `collabd.py` 有**逐条同款**约束,历史已踩三次)。
· 🔴🔴 **2026-10-03 16:3x 用户定案:⛔ 不再为每个工作区各起一个看板** —— **看板只保留一份**(就是主工作区这一个),其它工作区靠 `peer_workspaces` 并列查看 ⇒ 各区的目标、验收、台账、常驻心跳**都在这份上看**(实测:三区常驻同时在跑,各自的 `heartbeat_age_min` 都能在这份看板上读到)。
· ⛔ 配置里**不含本工作区**(它已是 active 那格,重复列 ⇒ 出现两格);⛔ 去重键必须带**工作区前缀**(同名目标会互相顶掉)。
· 🔴🔴 **某格工作区「已经不在会话列表里」⇒ ⛔ 不再占 tab**(用户 2026-10-04 20:59 报障,逐字:
「**tab 要把已经不再 会话列表的工作区 目标移除,不然都放不下了**」)。
- 🔴 **判据(唯一)**:宿主库 `sessions` 表里该 `cwd` 的**未删会话数**(`deleted_at is null or 0`)
= **0** ⇒ 这个工作区在会话列表里**已经不存在了** ⇒ 不列 tab。
实现在 `board.py::goal_files()`(peer 循环里 `_peer_session_rows()` 为空就 `continue`)。
- 🔴🔴 **⛔ 别拿「心跳新不新鲜」当判据**:心跳只说明那个区的**常驻进程**在不在,
与「这个工作区还有没有人在用」**是两件事**。实测反例:测试2/3 心跳早停 ⇒ 但**未删会话也为 0**
(用户把会话全删了)⇒ 确实退场;而 vibe-product 心跳 1.5 小时前、**未删会话 5 条** ⇒ **还在用** ⇒ 必须留。
- ⚠️ **读不到库 ⇒ 保留**(fail-open):宁可多一格,⛔ 不因自己的读数失败就把别人的格子吞掉。
- ✅ **零删除、可逆**:只影响"要不要画这一格",⛔ 不动对方任何文件、⛔ 不替用户改 `peer_workspaces`。
- ⚠️ 改 `board.py` ⇒ **必须重启看板**(本节上一条);只改 `collabd.config.json` 里的 `peer_workspaces` 也**必须重启**(`C = _cfg()` 模块级只执行一次)。
· 🔴🔴 **「已退役角色」⛔ 不许当主会话候选**(用户 2026-10-04 21:5x 拍板「候选一」)。
- 🔴 **判据(唯一)**:标题的**一级方括号里是退役角色词**(`[跟进]`/`[唤醒]`)⇒ ⛔ 不进主会话候选。
实现在 `collabd.py::is_retired_role_title()`(+表 `_RETIRED_PFX`;`board.py` 从该模块**取**,⛔ 不另写一份)。
- 🔴 **病根**:`[跟进]`/`[唤醒]` 两键在 2026-10-02 从映射表摘掉后 ⇒ 角色解析成 **`""`**
⇒ 而候选排除元组是 `_role not in ("worker",)` ⇒ **`""` 恰好放行** ⇒ 这两类**已退役的干活的棒**
被收进主会话候选 ⇒ `_role_label()` 判据①命中 ⇒ 看板「主会话」位上坐着它。
**实测现网**:`main_by_topic` = `{"机制排查与修复": "6ab1463e", "会话协作自检": "57f58ecf"}`
—— 两个任务类别**全都指向 `[跟进]` 退役会话**(`6ab1463e`「[跟进]-机制排查与修复-队列跟进」等 11 条)。
- ⛔ **修法不是「排除空串」**:空串里还有**真主会话**(现役 `a80f300d`「复盘失败并避免重犯」无前缀)
⇒ 一刀切会把真主会话一起排掉。也⛔ **不是「恢复 `[跟进]`/`[唤醒]` 映射」**(那等于让退役类别复活,与 10-02 口径相反)。
- ⚠️ 闸**只认一级方括号里的整词**:⛔ 不许误杀「类别名里恰好含『唤醒』」的在役会话
(`[协作]-[唤醒机制]-…` 一级是「协作」⇒ 是 worker,本就该排,但**理由不该是"退役角色"**)。
- 🔴🔴 **⛔ 危害不止"标签难看"**:主会话候选=**投递/派活的收件人** ⇒ 会**投错窗口**。
- ⚠️ 判据**两侧同源**(`board.py` import 而非抄写)+ 用例是**行为级**(真造宿主库+真跑扫描)——
⛔ 只测「那个小函数返回什么」= **测了零件没测装配**(2026-10-04 变异验证当场抓到:
拆掉闸后单函数断言**照样全绿**)。⇒ 见 `pitfalls.md` P0-54。
· ✅ 现算:`/board.json` 的 `goals`=**3 格**,`peer`=`会话协作测试1`/`会话协作测试2`(⛔ 区名是历史名,不改),`active` 恰好 **1** 个。
· ⚠️ 改 `board.py`/配置 ⇒ **必须重启**(**P0-38**),且用 `--takeover`(⛔ 否则新旧实例静默并存、同一个 URL 随机应答)。
- 🔴🔴 **自测的「现网真读数」类判据 ⛔ 不许复用 `imp()` 注入的测试环境**(2026-10-03 实测栽的,同族第 2 次):`imp()` 会把 `COLLABD_CONFIG`/`DSH_COLLAB_WS` 指到 `tmp/selftest/` ⇒ 用例里**无论怎么加载 `board.py` 都只读到测试那 1 格**(同一时刻命令行直接加载是 3 格)⇒ **恒绿与恒红都是假象**。正解三条:**加载前换 env、用完 `finally` 还原** + **在真磁盘上造目标**(`tempfile.mkdtemp()`)+ **变异对照**(打掉接线必须报红;还原后 md5 校回原值)。⚠️ 「配置缺省 ⇒ 回落旧行为」那条**只守配置侧**(代码被破坏时它照样绿)⇒ ⛔ 别把它当证伪项写进标题。详 ⇒ `references/pitfalls.md` **P0-39**。
- 🔴🔴 **每个工作区的常驻由「它自己的主会话」起**(用户 2026-10-03 定案,逐字:「**每个工作区 会话协作机制的主会话自己创建后台任务 启动常驻协作程序**」,同句「**后台看板不用运行这么多 共享一份就可以**」)。
· **主会话开工第 0 步 = 确认本工作区的常驻在跑**:判据=`.workbuddy/collab/logs/supervise-heartbeat.json` 里 **`pid` 活 ∧ 心跳距今 < 90 秒**;在跑 ⇒ **跳过**(⛔ 别起第二个,同端口两个实例会互相顶掉);不在 ⇒ 用**后台任务**启动一次,**输出必须重定向到文件**(⛔ 否则它的日志会把会话日志顶满)。
· ⛔ **不是由别的会话代起** —— 谁的工作区谁负责:跨区代起会让「哪个进程属于哪个区」彻底糊涂,而且**代起方一结束,被代起的那个区立刻失联**(实测就是这么断的)。
· 🔴🔴 **本工作区的会话 ⛔ 绝不许去起「别的区」的常驻**(用户 2026-10-03 16:4x 当场纠正,逐字:「**不是 为什么这个会话 要创建别的会话的 常驻任务,让那边的会话自己创建啊**」)—— ⚠️ **我自己就违反了这一条**:写完禁令后转头在本会话里替两个测试工作区起了后台任务(看着是"帮忙让它跑起来",实际是**代起**),当场被纠正后已停掉。
⇒ **正确做法**:那边的常驻**只能由那边的会话起**;我这边唯一能做的是**把它那条排期的触发时间提前**(改 `scheduledAt`,或等它周期触发),⛔ **不是替它执行**。
⇒ **怎么判断有没有越界**:问一句「**这个进程归哪个工作区?起它的会话又归哪个工作区?**」—— 两个答案不一致 ⇒ 就是代起。
· 🔴 **看板只保留一份**:各工作区配置里**没有** `board_port`;只有主工作区起一个 `board.py --serve`,靠 `peer_workspaces` 并列看其它区。⛔ 每区一个看板=白白多 N 个进程+N 个端口。
· 🔴🔴🔴 **全局只允许一个看板 / 一个常驻(用户 2026-10-04 21:0x 定案,逐字:「看板 包括常驻 按现在的方式启动,全局只允许一个,把规则记录下来」)**:
**现状即标准** —— 就是本轮已经跑通并取证的那一套,⛔ 不要再发明第二套。
- **看板载体**:计划任务 **`dsh-board-keepalive`(🔴 全局唯一一条,⛔ 不每区一个 —— 2026-10-05 收敛,旧名 `dsh-board-20099` 已禁用)**。
· 🔴🔴 **动作 = `pythonw.exe` 起"启动器" `scripts/board-launch.py`**(⛔ **不许直起 `board.py`**)。
**为什么(2026-10-05 实测)**:直起 ⇒ **崩溃重启循环、20099 从没绑上**。现场输出:
`⚠️ collabd:未找到部署配置(COLLABD_CONFIG 未设)⇒ 已拒跑` —— `board.py` 顶层用
**`COLLABD_CONFIG`** 定位配置(它要 `import collabd.py`),而**计划任务的动作里没有 env 字段**
⇒ 该变量只能由**启动器在进程内设**。⚠️ **名字别混**:`roots.env` 给的是 `COLLABD_PROD_CONFIG`,
而 `board.py` 读的是 `COLLABD_CONFIG` ⇒ 名字对不上,`roots.env` **兜不住它**。
· ⚠️ 启动器的 `WorkingDirectory` =**技能 `scripts/` 目录**(启动器自己定位 `board.py`);
看板**配置恒指主工作区**那份(看板是全局共用一份)——这与 `--supervise` 必须指**工作区根**是两回事。
· 🔴 **`ExecutionTimeLimit` 必须 `0`(无时限)** —— 看板是**长驻服务**;
⛔ 设 2 分钟 ⇒ 到点被调度器掐死 ⇒ 又变成"每 5 分钟重建一次"的抖动(与 `--supervise` 同理)。
· 🔴🔴 **⛔ 动作绝不许写成 `.cmd`/`.bat`/`powershell.exe`** —— 它们是**控制台程序**,
计划任务每次触发都分配 `conhost.exe` ⇒ **弹黑窗**;更糟:`.cmd` 里若**前台**跑 pythonw
(没 `start`)⇒ `cmd.exe` **不退出** ⇒ 黑窗**常驻**(实测 `cmd.exe` + `conhost.exe` 挂在看板树上)。
· 🔴 **为什么用 launcher `.py` 而不是直接起 `board.py`**:`COLLABD_CONFIG` 等环境变量
原本靠 `.cmd` 的 `set` 传(`board.py` 在**模块顶层**读它们,`roots.env` 只兜底 `COLLABD_PROD_CONFIG`,
**名字对不上**)⇒ 现在改在**进程内** `os.environ[...] = ...`,等价且零 shell。
· 🔴 **launcher 必须用 `runpy.run_path(BOARD, run_name="__main__")`,⛔ 不许用 `exec(compile(...))`** ——
实测:`exec` 版从 `C:\Windows\System32`(=任务默认 cwd)起时 **rc=1 且零输出**,
换 `runpy` 后从任意 cwd 都正常(`runpy` 会正确设好 `__file__`/`__name__`/`sys.path[0]`,
`board.py` 顶部的 `_sm_load_roots()` 靠 `__file__` 往上找 `roots.env`)。
· 🔴 **任务设置**:`ExecutionTimeLimit=0`(无时限)|`MultipleInstances=IgnoreNew`|
`RestartCount=999`/`RestartInterval=1min`|`RunLevel=Highest`(⚠️ `Limited` 实测 `LastTaskResult=1`)。
· ✅ **验收(三条全绿才算成,⛔ "打印了启动消息"不算)**:`netstat` 见 `127.0.0.1:<port> LISTENING` ∧
`curl /` = 200 ∧ **父进程是 `svchost.exe -s Schedule`**(⇒ 已脱离会话,⛔ 不是会话树里的 bash)。
🔴 **且要看进程树下**:**零 `cmd.exe`/零 `conhost.exe`** ⇒ 黑窗彻底消除,这才算对。
- **常驻载体**:同规矩 —— **`pythonw.exe` 起"启动器" `scripts/supervise-launch.py`**(⛔ **不许直起 `collabd.py --supervise`**),
⚠️ 启动器的 `WorkingDirectory` =**工作区根**(⛔ 与看板相反,看板指 `scripts/`);参数由启动器给(⛔ 动作里不再自带 `--supervise`);
存活判据仍是 `pid 活 ∧ 心跳 <90 s ∧ argv0 指本区`(⛔ 不换判据)。**为什么必须经启动器**:见本节开头「为什么要夹一个启动器」(P0-73)。
- 🔴 **「全局只允许一个」怎么守**:看板=`board.py` 内置单实例护栏(端口上已有实例 ⇒ **拒绝启动**并提示;
换代码要 `--takeover`)。**端口即互斥锁**,⛔ 别再加一层自造锁。
常驻=`ensure_supervise()` 心跳判据(已有活的 ⇒ 幂等 `exit 0`)。
- ⛔ **看着"要起两个"时的正解不是多起,是查为什么第一个没起**(`LastTaskResult`/launcher 日志/端口占用)。
· 🔴🔴 **实测警告(决定这个口径能不能落地)**:**后台任务的寿命 ≈ 发起它的那个会话的寿命** —— 2026-10-03 实测两条:由**一次性会话**启动的那份只活 **5 分钟**(14:42:00 起 → 14:47:10 最后心跳,`round=32`);由**长期存在的会话**启动的那份已连续运行 **4.5 小时**(12:03 起仍在跑,`round=1624`)。⇒ 所以主会话必须是**周期性**的(现配 `FREQ=HOURLY;INTERVAL=1`)才能反复补,⛔ **别指望"起一次就一直活着"**。
· ⚠️ 由此推出的**残留**:仅靠周期性主会话 ⇒ 常驻每小时只在主会话那几分钟在线 ⇒ 要**真正长期在线**还得有一层**脱离会话**的载体(计划任务/桌面启动/系统服务)⇒ **尚未定,见本节末待定项**。
- 🔴🔴 **各工作区的「程序」也独立 —— 看板是唯一共用的一份**(用户 2026-10-03 17:0x 定案,逐字:「**跨工作区使用会话协作技能,除了看板共用,其余都是独立的,包括程序和相关文件**」)。
· **形态**:技能目录 `scripts/collabd.py` = **源(唯一真身)**;每个工作区 `.workbuddy/collab/collabd.py` = **它自己的一份副本**(另含 `goalctl.py`)。⛔ 各区**不再跑技能目录那份**。
· **一键分发**:`python scripts/deploy_code.py --ws <工作区>` —— 覆盖前**自动备份**、打印源与副本的 md5;支持 `--dry-run` 与 `--only collabd.py`。
· 🔴 **代价(明说)**:一处改动要分发 N 处 ⇒ **改完代码必须重跑分发**;⛔ 漏了**不报错**,只是"某个区行为不对",最难查。
· ✅ **可观测性(关键)**:`collabd.py` 每次写心跳都带 **`argv0`**(启动入口绝对路径)⇒ 「这个区跑的到底是哪份文件」一眼可查,**也能证伪**"改了副本却没重启"。
⚠️ **改副本不重启=没生效**(P0-17 同族)⇒ 主会话开工第 0 步要核 `argv0`:指向的不是本区路径就**自己换掉**。
· ⛔ **`board.py` 不进副本清单** —— 看板是**共用一份**的(用户同句定案);⛔ 每区一个看板=白白多 N 个进程与端口。
· 🔴🔴 **启动脚本/载体也必须在清单内 —— 只分发程序、不修启动入口 = 这条规则形同虚设**(2026-10-04 实测事故,P0-57):
- **病根**:某区的 `tmp/start_supervise.py` 第 11 行写死 `WS + "/.workbuddy/skills/session-mechanism/scripts/collabd.py"`
⇒ **它指的是「技能副本」,不是「本区发布物」** ⇒ `.workbuddy/collab/collabd.py` 成了**孤儿副本**
(**全工作区只有 0 处引用它**,落后一版也没人发现)。判据②(`argv0` 指向本区)实测**不过**。
- 🔴 **这是"旧口径的启动脚本 + 新口径的目录结构"的典型症状**:目录改对了、**引用没跟着改**。
⇒ ⛔ **别只盯 `.workbuddy/collab/` 里文件在不在** —— 文件在 ≠ 有人跑它。
- ✅ **正解**:启动脚本里**必须**写**本区发布物**路径,并**优先+回落**两段式(缺了才用技能副本,且要**打印告警**,⛔ 静默回落=用户以为跑的是副本)。
```python
COLLABD_OWN = WS + "/.workbuddy/collab/collabd.py" # ✅ 首选
COLLABD_SKILL = WS + "/.workbuddy/skills/session-mechanism/scripts/collabd.py" # ⚠️ 仅回落
COLLABD = COLLABD_OWN if os.path.isfile(COLLABD_OWN) else COLLABD_SKILL
```
- 🔴 **开工第 0 步的自查(三条一起做,⛔ 少一条就漏)**:
① `deploy_code.py --ws <工作区> --dry-run` ⇒ 副本与源 **md5 一致**;
② 心跳 **`argv0` 指向本区发布物**(⛔ 指向 `skills/` 或全局技能都算**没接线**);
③ **grep 启动脚本**(`tmp/*.py`、`*.ps1`、计划任务的 `Args`)⇒ 引用的路径**逐字是本区发布物**。
⛔ 只做①② ⇒ 会漏掉「副本是孤儿、跑的是另一份」这种形态 —— 今天就是这么漏的。
- 🔴🔴 **分发是"手动"的 —— 这是 2026-10-05 用户拍板(选 A,⛔ 不做"保存即分发"钩子)**:
**技能目录是源、各区 `.workbuddy/collab/` 是发布物**;改完源**必须显式跑** `deploy_code.py --ws <区>`
(覆盖前自动备份、打印 md5 对照),⛔ **没有 watcher、不会自己流到各区**。
· 为什么不自动:`collab/` 是**生产面**,静默覆盖一旦**源改错**会**同时打歪所有区**,且不备份时机难控;
显式一条命令=**可回滚、可审计、不易误伤**。
· ⇒ **收口口径**:改完本包脚本 ⇒ ①跑 `deploy_code.py`(**两区都跑**)②**重启常驻**(见下)。
- 🔴🔴 **"部署完" ≠ "生效了" —— 必须重启常驻**(2026-10-05 实测):
常驻进程**启动时就把代码读进内存**,改文件**不影响正在跑的进程**。
· ✅ **判据**:心跳里的 **`started_h`(进程启动时间)必须晚于部署时间**;
⛔ 只看副本 md5 一致 ⇒ 会误判成"已生效"(实测 vibe 副本 md5 早就是新的,但进程还是 12:15 起的旧代码)。
· ✅ **重启做法**:`Stop-Process` 杀掉常驻 ⇒ 触发该区计划任务(或等 5 分钟自动判活)⇒ 新进程读新副本。
· ⚠️ 重启前确认 **无 `guard.stop`**(有 ⇒ 拉起后立即自停,看着像"没起来")。
· 🔴🔴 **跑自测的位置(2026-10-05 实测踩到)**:**⛔ 别在本区 `.workbuddy/collab/` 目录里跑 `selftest.py`**。
- **为什么**:`collab/` 是**生产部署面**,只有 `collabd.py` + `goalctl.py`(⛔ **不含** `board.py`/
`judge_audit.py` 等**包内依赖** —— 看板是共用一份的)⇒ 在那里跑会大面积 `FileNotFoundError`,
实测 **PASS 42 / FAIL 45**,看着像"回归",其实是**跑错地方**(⛔ 假警报,会把人带偏去查不存在的 bug)。
- ✅ **正确跑法**:在**技能目录**跑源那份、把 `cwd` 设成目标工作区即可(`selftest.py` 用 `cwd` 推工作区):
`cd <工作区> && python <技能目录>/scripts/selftest.py`
- 📌 **验收基线**:本区正式截面应 **PASS ≥92 / FAIL 0**(另 1 条报告型)。
- ⚠️ 判据:**"FAIL 一片" 先问一句"我是不是在 `collab/` 里跑的"**,再去怀疑代码。
---
## §3 目录与依赖
```
SKILL.md install.py roots.env(装后生成) install.log(装后生成)
references/ architecture collab collab-detail rules deploy pitfalls taskgraph forensics manifest
scripts/hooks/ 6 份宿主钩子(10 条接线)
scripts/lock/ handoff-guard.sh preflight-lock.sh handoff-status.py op-lock.sh
scripts/ collabd.py board.py board_ext.py goalctl.py guard.py wake-session.py
stop-collab.py deliver-gateway-token.py selftest.py
collabd.config.example.json
scripts/forensics/ proc-parent.py(查进程父链 · 纯 ctypes · 只读)
assets/ board.html design-tokens.css
```
⚠️ 逐文件清单 + 来源(provenance)⇒ `references/manifest.md`(**唯一权威**,⛔ 别按本树猜)。
**⛔ 已无必需外部依赖(2026-10-04)**:原先声明「依赖 `agent-operating-rules`」——现已把**排版核心块收进本包并定为权威** ⇒ `references/03-回复排版-核心块.md`
(2026-10-06 校正:原措辞是「内联副本」,与 03 号自称「权威源」打架 ⇒ 权威声明分裂)、把 `_env.py` 的技能库识别特征改为**候选数组(首个=本包自己)** ⇒ **本包自包含**。📌 **2026-10-07 订正**:上面末句原写「**那个技能**若同时装着,属可选增强(多了跨项目排版/去 AI 味的完整版),⛔ 不装也能跑」——
那个技能(`agent-operating-rules`)**已于 2026-10-06 整包并入本包并删除**,⛔ **现在没有它可装**(照旧句去找必然扑空)。
本包自带的对应物:排版 → `references/03-回复排版-核心块.md`(权威);语气/去 AI 味 → `references/作业规矩/04-去AI味与说话方式.md`(**会话场景加固版**,含中文会话专属加固 + 交付前清单 + 质量评分)。📌 **通用版**(示例更全)在独立技能 `humanizer-zh`,属**可选增强,⛔ 不装也能跑**。📌 🔴 **2026-10-07 按用户令把英文硬核版整包纳入本包**:`references/humanizer-en/`(原独立技能 `humanizer` 逐字搬入,6 文件;含 **55 个模式** + **5 种语气档** + **0–100 AI 痕迹打分** + `--file` 就地改)—— 要更硬的检测/打分或要指定语气档时读它。
---
## §4 验证(装完必跑)
```
python install.py --dry-run # 看 settings.json 将怎么变
python install.py --apply # 真装
python install.py --verify # 10 条接线空载荷 rc=0 + collabd --where + selftest PASS 39/0
python install.py --manifest --note "<本轮:…>" # 重算 references/manifest.md 的逐文件表(**改完包必跑**)
python install.py --uninstall # 还原 settings.json(应与装前备份逐字节相同)
```