用户 10-09 两条令:
①「如果 工作区 项目机制 对应的会话异常 导致未解锁的情况下,可以授权 检查会话自解除孤儿锁」
②「其他类型 阻碍 遇到阻碍修改目标状态 暂停机制 让用户决策,不是有这个规则吗」
起因(contentm_agent 实测):一把域锁的持有者会话被 terminated、锁遗留未释放 ⇒ 目标第 4 步 blocked;
R9 一律禁删锁 ⇒ 检查会话连开 11 棒(约 5 小时)每棒都撞同一面墙、零派活,每 30 分钟一棒。
拆开看是三条腿互相锁死:闸③「队列非空」对一条 blocked 恒真;闸① 2026-10-04 收窄时把
sessions-ended 整条腿摘了出去(置了「阻碍」也照样建);结果检查 prompt 又明写「你没有改目标状态的出口」
⇒ 看得见的没权、有权的(目标检查要队列为空)看不见。
改动
- 新增 scripts/lock/orphan-lock.py:孤儿锁「判定 + 解除」的唯一执行面。
判据查宿主库 sessions.status(只读 SQL):working 一律不动;terminated/error/archived 可解除;
completed 须静默 >=30 分钟;查不到 sid / 库读不到一律不动(fail-closed)。
- collabd.py
· 结果检查 prompt 开「一个」状态出口:--set-life 阻碍 --by "<会话名>" --check-why "<一句现状>"
("为什么"的旗标是 --check-why,不是 --why —— 后者是 --retire 的,写错会被静默忽略);
同时把「写明阻碍」的两个出口钉死:台账 --report ... --state blocked --reason +
NEED-USER.md 里明确喊「需用户介入」。
· 闸① 改两档:queue-empty 原样(非「进行中」不建);sessions-ended「只拦『阻碍』」,
「已完成」仍放行(不把 2026-10-04 修好的病搬回来)。docstring 五道闸 -> 六道闸。
· 两条检查 prompt 都加了孤儿锁判定钩子(判「持有者已退出」才可自解除;判「活着」一律不动)。
- R9 条文加「唯一例外」:工作区 CODEBUDDY.md §3(定义处)、references/rules.md 新增 §10、SKILL.md,
以及技能包与文档库两份 handoff-guard.sh 的「抢域锁失败」提示。
- selftest.py:新增两个用例(孤儿锁 16 项,含「working 不许动」的变异对照;阻碍档 6 项,
含「已完成仍要建」的变异对照),并把写在两处的同一判据收成一处(改一处漏一处的实测教训)。
全套 PASS 108 / FAIL 0。
- references/manifest.md:按「改完包必跑」重算(77 份文件,语法失败 0)。
不在本仓(各自仓内提交):工作区 CODEBUDDY.md、文档库 CODEBUDDY.md 与 07-scripts/handoff-guard.sh。
113 KiB
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(),六道闸) |
核对结果/目标,缺口再派活 | 做完自止 |
三条线各归各的(⛔ 别混谈)
- 派活线:主会话 → 任务会话。排期名
[执行]-[<类别>]-<具体>。🔴 不受总开关影响(开关只挡常驻)。 - 检查线:常驻 → 检查会话。排期名
[检查]-[结果检查/目标检查]-…-第N棒。🔴 受总开关管。 - 常驻线:一工作区一条
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.mdP0-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_enabletrue → false(第二道闸,第一道是代码里已删那两段)。二之二、🔴 顺带查清的判据:「改看板要不要重启」有四类答案,混成一句话就是误导 (用户第二句纠正:「看板每次修改都不处理看板」⇒ 我把"改配置要重启"说成了"改看板都要重启",是分类错误): · 改
assets/board.html⛔ 不用(board.py:1293serve()里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.mdP0-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_compile4 份全过 |board.html两个 script 块new Function()全过 |selftestPASS 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()五道闸),⛔ 不经唤醒/跟进转手。 ·selftestPASS 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 活 ∧ 心跳新鲜)──▶ 才允许往下
│
└─ ⛔ **起不来 ⇒ 立刻停下、如实报错**
⛔ 不许"边起边干"(建了会话也没人接)
⛔ 不许只说一句"起好了"就往下走
⛔ 不许编原因(只报**读到的现象**,⛔ 不猜病因)
🔴 **先解决问题,再执行完成目标的任务**
⚖️ 三条边界(越界就是又一次"凭空造需求"):
- ⛔ 没目标 ⇒ 不起、也不报(那是用户在用基础会话方式,正常态)
- ⛔ 只管「常驻在不在位」这一件事 —— ⛔ 不代用户开目标、⛔ 不自己建检查会话
- 🔴 在位判据只有一条:
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 当天栽出来的)
- ⛔ 凡不在上表的状态 ⇒ 问用户,⛔ 不许推断后当成需求或欠项。
- ⛔ 「实体里存在」⛔ 不等于「该有」 —— 台账里 8 条任务会话不在跑 = 上一轮的遗留, ⛔ 不是「缺 8 条」。⚠️ 实测:2026-10-04 我正是把「遗留」读成「欠项」, 而那 8 条对应的轮次早已完成。
- ⛔ ⛔ 不许把自己以为该有的东西摊成「欠项」/「风险」/「待办」 —— 那是凭空造需求(当天三次:开机自启/三类会话/两类会话)。
六、🔴🔴 完成情况判据 · 唯一事实源(2026-10-04 用户定案)
用户原话:「是不是应该统一完成情况的 状态标准,不要换个工作区换个目标,就统计不准确」
病根:同一条「这条验收算不算过」的判据,曾在三处各写一遍,于是必然漂移:
scripts/board.py→acc_is_pass()(唯一实现,改判据只改这里)scripts/collabd.py→_acc_is_pass()(只转发 board,⛔ 不许自己写词表)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 第一段 · 会话机制
这一段不依赖多会话协作;只把「会话活得下去」这套装上。
三件套
- 钩子(
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)。 - 锁(
scripts/lock/):- 开工三步=
scripts/dsh.py open(本项目入口,内部跑 ①状态 ②preflight ③抢锁); - 抢锁
handoff-guard.sh --claim-exec "<会话名>" [--domains <域>]—— ⛔ 机制层必须独占(不带--domains);抢不到 ⇒ 停手 + 报告(红线 R9:⛔ 不删锁、不接管)。 🔴 例外(用户 2026-10-09 授权):卡住的原因是孤儿锁(持有者会话已异常退出、不会再来释放)⇒ 检查会话可自解除, 用scripts/lock/orphan-lock.py(先--dry-run)。判据由脚本查宿主库给,⛔ 不靠自己猜;working的锁一把都不许动。 - 释放必须反序:
--release→--release-exec "<会话名>";⛔ 不带名 ⇒ 拒释放。
- 开工三步=
- 日志闸:文件软 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 --gap13.5 s +--once0.5 s +--tick13.5 s ≈ 27.5 s ⇒ 必超。四条硬规则(已落进wb-result-hook.py):- 开局认领预算:
BUDGET = {UserPromptSubmit: 18, PreToolUse: 25, SessionEnd: 8}(各比注册值少 2 s)⇒ 任何要等的子进程,先问_left()够不够,不够 ⇒ 跳过并留痕。 - 门槛按"实测耗时"给,⛔ 不按硬超时给:
sweep/supervisor实测都是 0.95 s,若按硬超时 8 s/6 s 设门槛,在 8 s 预算下永远跑不到(判据看着在、其实恒假)。 - 不需要结果的活 ⇒ 后台(
_bg():Popen+DETACHED_PROCESS|NEW_PROCESS_GROUP+ stdout 落文件,⛔ 不用 PIPE):实测父进程退出后仍能跑完(15 s 写完 11 KB)。⛔CREATE_BREAKAWAY_FROM_JOB在本机必失败(PermissionError 13)⇒ 别加。 - 一轮只允许一个贵活(
_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 落地):所有常驻/后台subprocessspawn 一律改用同目录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.py5 处 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「⛔ 排期代替不了载体」。⛔ 四条禁止(逐条都有实测/文档依据):
- ⛔ 不许建
recurring排期去「续命/保活/巡检常驻」 —— ✅ 要续命就按需重起(pythonw+NO_WINDOW,见references/00-动手前必过.md)。 ⚠️ 需不需要"目标做完还继续跑" ⇒ 问用户,⛔ 别自己假设要。 - ⛔ 不许把排期当"谁来按点喊一次"的常驻替身 —— 排期喊完会话就结束 ⇒ 跳与跳之间必有空窗,静默窗内无人。
- ⛔ 不许在排期 prompt 里写「不在就起一条
--supervise」 —— 那是把「换载体」偷换成「每次重拉」,且起于会话/工具调用的子进程活不过当轮。 - ⛔ 常驻真死时不许用排期兜底,先查三样:停止标志
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 工作区下面」。⛔ 三条禁止:
- ⛔ 不许把「要去看/要改的那个区」写成
cwds—— 那是对象,不是归属。 - ⛔ 不许用「目标文本里出现了某区名」来定
cwds—— 出现的是被操作对象, 归属看的是「这条命令从哪个工作区发出」(sessions.cwd逐字同形,正斜杠)。 - ⛔ 交付物、台账、修复动作一律落在归属区,⛔ 不许顺手落到对象区。
✅ 正确做法:目标跨区时,
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 23:0x 实测订正(⛔ 推翻当天早些时候的错误结论,本条为准):
一句判据:不是「本机不存在长跑进程」,是「载体不同」—— 从工具调用进程树里起的活不过当轮,宿主后台任务/用户自己从桌面起的活能长跑。
① 实测坐实:同一时刻用
-
🔴 自动唤醒任务的排期名必须带
[执行],否则被认成"主会话"把通知投给自己。 ⚠️ 🔴 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.mdP0-40):peer 格只喂_peer_tasks()/_peer_srows()/_peer_state()(只读对方目录),读不到 ⇒ 空 + 界面如实说明,⛔ 绝不拿本工作区的台账/会话/状态补位(2026-10-03 实测:三格labor/sessionsmd5 完全相同 ⇒ 把本区执行情况挂到了别人名下)。⚠️ 会话还要单独补捞(_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.pyimport 而非抄写)+ 用例是行为级(真造宿主库+真跑扫描)—— ⛔ 只测「那个小函数返回什么」= 测了零件没测装配(2026-10-04 变异验证当场抓到: 拆掉闸后单函数断言照样全绿)。⇒ 见pitfalls.mdP0-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.mdP0-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(应与装前备份逐字节相同)