Files
workbuddy_skills/session-mechanism/SKILL.md
T
admin 2ed7766007 说人话落地:加每轮注入 + 英文硬核版整包纳入 + 第①段补写作口径
一、用户令(逐字)
「humanizer(英文那份) 也要纳入」「重点就是做到说人话就行了」「但是 content_marketing_agent 的会话生成的文档并没有说人话,还是我手动要求的」

二、根因(两处,都不是"没整合")
· **语气那一档只有指针、没有注入** ⇒ 实测没被读到(`SKILL.md` 必读表标题原写「三篇」而表里有 4 行,
  第 4 项=作业规矩整包,被顶在"必读"之外;已订正为「四类」)。
· **写文档的执行会话那条链上一句写作口径都没有** —— 产品规划技能里「反 AI 味」只出现在**第③段界面**
  (且指的是界面不是文字),**第①段出文档那节零口径** ⇒ 产物自然是 AI 味。

三、改法
1. **每轮注入**(照排版那条现成机制,⛔ 不改 `main()`):
   · `04-去AI味与说话方式.md` 顶部加 `<!-- VOICE-CORE -->` 紧凑块(≈450 字符:核心原则/四种高频 AI 味/交付前三问);
   · `reply-style-guard.py` 里**原地重定义** `_core`(`_core_reply = _core` 后用新 `_core` 包住它并追加语气块)
     ⇒ 注入正文自动多一段,排版那条**不受影响**。
2. **产出侧补口径**:`product-planning` 第①段(stage-discovery)在 1b 表后新增「写作口径」块 ——
   落笔前读 `humanizer-zh` 或本包 04,**定稿前过「交付前快速清单」**,并列出六种禁用 AI 味;**适用本段全部五份产出**。
3. **英文硬核版整包纳入**:原独立技能 `humanizer` **逐字**搬入本包 `references/humanizer-en/`(6 文件、
   md5 逐个一致):55 个模式 + 5 种语气档 + 0–100 AI 痕迹打分 + `--file` 就地改;
   在 `04 §9` 关系表与 `SKILL.md` 对应物段登记(冲突以本包会话场景版为先)。

四、验收
· 新增 `selftest.py::t_voice_core`(4 项);全量 **PASS 106 / FAIL 0**;manifest 70 → **76** 份、语法失败 0。
· 端到端喂真钩子:注入正文已含说人话块、排版块仍在。
· ⛔ 改的是钩子与技能目录(宿主直读)⇒ **不需要分发/重启**。
2026-10-07 22:00:13 +08:00

112 KiB
Raw Blame History


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 — 会话机制(含多会话执行)

🔴🔴 改流程/改机制前,先看这四类(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 步是先加载本技能,然后才动手。⛔ 没有"我大概知道"这条捷径。

触发句(命中任一条即须加载):使用任务会话完成 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=已完成(含 带后缀形态,如 已完成(机器可判部分)) 🔴 先看台账,⛔ 不是一律收工:
· 台账还有未完成件(pending/running/blocked)⇒ 判定=还有活没人干 ⇒ 照常建 [结果检查](闸① 不拦它)
· 台账全 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):
    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 定案:「重点是技能的使用时要检查环境配置是否已配置,如果没有配置就要先配置」)。

    # 体检(只读,⛔ 无副作用)—— 判技能库根能否定位 + 已注册钩子逐条目标是否存在
    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/ 里文件在不在 —— 文件在 ≠ 有人跑它。
    • ✅ 正解:启动脚本里必须写本区发布物路径,并优先+回落两段式(缺了才用技能副本,且要打印告警,⛔ 静默回落=用户以为跑的是副本)。
      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(应与装前备份逐字节相同)