Files
workbuddy_skills/session-mechanism/references/rules.md
T
admin d81f75f173 技能包:配置与代码分家 —— 每包自建 env/ 放本机专属配置,scripts/ 不再写死本机路径
用户 2026-10-11 原话:
· 19:22「这个环境都是针对一台机器的,同步到别的机器把环境都搞坏了」
· 19:28「不用挪出 用env文件夹区分就行,重点是 脚本是脚本 配置是配置」
· 19:29「每个技能 需要环境配置的 都有自己的 env文件夹」
· 19:30「修改完成后要仔细检查 避免调整后 各技能脚本运行失败」

改动:
1、session-mechanism 新增 env/(roots.env=本机真身不入库 + roots.env.example=样例入库),
   install.py 的 ROOTS_ENV 改指 env/roots.env;全包脚本/hook 的配置定位统一为
   「向上 4 层 × env/roots.env 优先 → 包根 roots.env 兼容 → 技能库那份兜底」。
2、去硬编码:collabctl / keepalive / supervise-launch / board-launch 里的本机路径与本机工作区
   清单全清,改为 DSH_AIROOT(兜底 dirname(DSH_WS_ROOT))+ 扫 <AIROOT>/*/.workbuddy/collab/
   自动发现;保活任务名按 collabd-keepalive-<工作区名> 现推;启动器默认值改 ~/.workbuddy 形态。
3、browser-harness:bh_chrome_keepalive.py 默认值全部改 env 驱动,新增 env/bh.env.example。
4、.gitignore:env/ 下只放行 *.example(三行三种层级)+ 兜住旧位置的包根 roots.env。
5、改包纪律.md 新增「四、包结构:脚本是脚本,配置是配置」,立为全仓通用规矩。
6、文档同步:SKILL.md / deploy.md(新增 §3b 配置层级、§6 换机器必查 6 件事)/ rules.md /
   pitfalls.md / supervise-persistence.md。

验收(本机):py_compile 34 个 py 全过;selftest PASS 109 / FAIL 0;
逐文件实证 8 个 loader 命中 env/roots.env;preflight-lock.sh 同目录实测解出两个根;
install.py --manifest 77 份、语法失败 0。

异机验证(包拷成「新克隆」(只有 *.example)+ 另造假工作区 ws-alpha/ws-beta):
install.py --apply 自生成该机 env/roots.env;collabctl/keepalive 自动发现的确实是
ws-alpha/ws-beta、任务名现推(写死清单确已消除);部署副本本地无 roots.env 时靠
CODEBUDDY_CONFIG_DIR 兜底读到技能库那份;整套 selftest 有宿主库时 PASS 108 / FAIL 1
(唯一 FAIL=该区没有常驻心跳,环境所致;本机常驻在跑时同一套为 PASS 109 / FAIL 0)。
2026-10-11 21:49:28 +08:00

127 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 会话机制 · 规矩与判据(照它做,⛔ 别自己另发明)
> ⚠️ 本文件是**作业规矩**;机制全貌 ⇒ `references/architecture.md`;坑 ⇒ `references/pitfalls.md`。
> 🔴 与项目层 `CODEBUDDY.md §1` 冲突时**以项目层为准**(那里有裁决顺序)。
## 1 提问判据(唯一一条)
> **只问「超过现有判断方法边界」的问题。**
- **边界内 ⇒ 自决策,不要问**:技术选型 / 实现路径 / 命名与数据结构 / 调参 / 部署与同步 / 排查方法 / 版本依赖 / 兼容降级 / 方案取舍 / 文档技术内容。 **「部署上线」属此项 ⇒ 做完即上线,不要问**;生产变更**直接做**,只需**动手前一句话说明**。
- **边界外 ⇒ 必须问**:① 业务目标与优先级 ② 花钱与资源承诺 ③ 对外承诺 ④ 需用户提供的凭据或审批 ⑤ 无客观优劣的偏好 ⑥ 影响面超出本平台 ⑦ 红线门禁 ⑧ 判不准。
- **判据**:有没有**客观可判的优劣**?有 ⇒ 自决策;没有 ⇒ 问。
- **提报用户标准 = 存在真取舍**:候选**只有优点或只有缺点 ⇒ 自己拍掉**;各有优劣才提报用户,且**逐项写优点 + 缺点**。
- ⛔ **不许捆包**:要问红线**只问那一句**;技术方案自己定好、当**已定项**陈述。
- ⛔ **禁用征询句收尾**(「要我…吗 / 请确认 / 你看怎么办」)⇒ 按三问重判,没命中就**删掉、自己做完**。
- 🔴 **提报用户内容必须自包含**:① 一句话说清要决定什么(⛔ 不用指代;🔴 **决定对象必须点名**)② 为什么要你定(影响谁 / 断多久 / 花多少钱)③ 每候选写优点 + 缺点,末行给倾向 ④ 一轮一问 ⑤ ⛔ **只服务实现**的内部细节(函数名 / 变量名 / 行号 / sha / 表名字段名 / 内部编号)下沉「技术附录」。
- 🔑 **⑤ 与 ① 的同一句判据**:**少了这个标识,用户还能不能决定?** 不能 ⇒ 必须写进正文;能 ⇒ 必须删。
⚠️ 旧措辞是「⛔ 不出现包名 / 路径 / 变量名 / 类名」——按**词性**一刀切 ⇒ 把"决定对象"也删了
(2026-10-09 实测:把要撤的两份文件写成「那两份」⇒ 用户回「不说具体 我怎么知道」)⇒ **已改成按功能判**。
## 2 回复排版(🔴 用户 2026-10-01 定稿,照抄即可)
- **骨架**:`# 大类`(**已完成** / **待处理任务**)→ `## 任务名` → 每件事两段:**当前状态** + **待处理事项**。
- 🔴 **大类标题必须比任务名大一号**(大类用 `#`、任务名用 `##`;⛔ 不许同号,否则层级压平)。
- **当前状态**:每条一个**圆点**,**一句陈述句说重点**;复杂情况放**句末圆括号**。
- **待处理事项**:用**序号**(`1、2、`,⛔ 不是 `1.`);每条写完整句子。
- **附件**:写在该板块**最末**一行(`附件:<路径>`)。
- **大类顺序**:**已完成的放最前**,`待处理任务`放**最后**。
- **三禁**:⛔ 表格(=省略讲理)|⛔ 长散文(不是写小说)|⛔ 碎标签堆叠。
- ⚠️ 本条只管**给人读的回复**;注释 / 日志 / 解析字段不受限。
## 3 锁
- **开工先抢锁**(是"抢"不是"看");**抢不到 ⇒ 停手 + 报告**(红线 R9:⛔ 不删锁、⛔ 不接管)。
- 🔴 **R9 的唯一例外(用户 2026-10-09 授权)**:「工作区项目机制对应的会话**异常**导致未解锁 ⇒ **检查会话**可自解除**孤儿锁**」。
判据**不靠猜**:`scripts/lock/orphan-lock.py` 去宿主库 `sessions` 表读持有者真身状态 —— **只有**
`terminated`/`error`/`archived`,或 `completed` 且静默 ≥30 分钟 ⇒ 可解除;`working` ⇒ **一律不动**。
⛔ 唯一执行面就是这支脚本(`--dry-run` 先看;⛔ 别自己写 `rm -rf` 删锁);⛔ 例外**只给检查会话**、**只覆盖这一种情形**。
- 🔴 **默认一律带域**(`--domains <本工作区域>/`);⛔ 别省略 —— 省略=**全局独占**,别人连域锁都抢不了。
- 🔴 **只有真正"全平台共用"的改动**(配置 / 加解密 / 隔离 / 锁与钩子**本身**、技能文件)才用**独占**。
- 🔴 **释放必须反序且带会话名**:`--release` → `--release-exec "<会话名>"`;⛔ 不带名 ⇒ 拒释放。
- 🔴 **锁的生命周期 = 任务的生命周期**:⛔ 禁抢锁做一半、不解锁就结束回合(带锁结束=把所有人挡在门外);中途要停 ⇒ 先释放。
- 🔴 **域键算法 shell/hook 必须逐字一致**,否则**静默失效**。
## 4 日志闸(会话"活多久"的物理边界)
- 本工作区闸值:文件**软 5 MiB / 硬 8 MiB**;工具调用**软 200 / 硬 250**。命中 ⇒ 开接续会话。
- 🔴 **宿主侧还有一个 10 MiB 硬上限**:撞顶即**拒写** ⇒ **界面静默哑掉、用户零感知**,而投递方仍记 `ok:true`(**假绿**)。
- 🔴 **病根是"调用次数多",不是"每次调用贵"**;增长只与"工具在跑"成正比(实测:活跃 ≈630 KB/分 ↔ 空闲 34 分钟 0 行)。
- **压增长三条硬纪律**:① 大输出先落盘、只读关键行 ② 命令层限流(`| head -30` / `grep -c` 代替裸 `grep`)③ 让脚本内部聚合、只 print 摘要。⛔ 禁 `cat` 大文件、无 `head` 的 `grep -r`。
- ⛔ **不要在会话里起常驻后台长跑任务**:它每次输出都把会话反复唤醒 ⇒ 永远回不到 idle ⇒ 用户看到"卡死"(已复现多次)。✅ 正确起法=**会话后台任务 + stdout 全重定向到文件**;或干脆做成**独立进程**(见 §6)。
## 5 钩子与脚本的写法约束
- 🔴 **hook 脚本必须走 `sys.stdout.buffer.write(bytes)`** 输出 —— 否则文案含非 ASCII 符号时抛 `UnicodeEncodeError` ⇒ stdout 为空 ⇒ **静默放行**。
- 🔴 **不按位置推导根目录**:钩子脚本被搬一次就会**静默指错**(历史事故:台账写到别处、测试却全绿)⇒ 用外置的 **`env/roots.env`**(本包由 `install.py` 生成,每台机器各一份)。
- 🔴 **配置与代码分家**(2026-10-11 用户令:「脚本是脚本 配置是配置」):「每台机器各不同」的值 → 本包 **`env/`**(入库的只有 `*.example`);`scripts/` 只放通用代码,读 `env/` 里的值、⛔ 不写死本机路径与本机清单。
- ⚠️ `settings.json` 的 hook 条目**没有 `env` 字段** ⇒ ⛔ 别指望用 env 给钩子传参。
- 🔴 **作用域⛔ 不许静默排除**:被跳过必须**留下痕迹**(写跳过清单 + stderr),否则"没动作"与"坏了"长得一模一样。
- ⚠️ **钩子脚本内容改动即时生效**;但 `settings.json` 的 hooks 条目是**应用启动时快照** ⇒ 改条目要**完全重启**(关窗 ≠ 退出)。
## 6 常驻的精确边界
- 🔴 **「协作与投递一直运行(常驻)」是已定案项**(09-29 定案,2026-10-01 用户再确认)⇒ ⛔ **不得拿"进程数=0"去否它**。
- 🔴 **⛔ 禁的是"在会干活的会话里起"**:会话自己的后台任务会**压制该会话的 idle 钩子**(实测被僵尸任务压 **6h20m**)
⇒ 载体应用**专用容器会话**;且**输出必须完全静默**(`stdout` 全重定向到文件,⛔ 否则反复唤醒宿主 ⇒ 看着像卡死)。
- ✅ **独立进程不在禁令内**(输出不接回任何会话 ⇒ 不唤醒宿主);但它**读不到网关口令 ⇒ 不能投递**,只能做只读判定 / 告警。
- **存活边界一句话**:「**后台任务跟着应用活,不跟着会话活**」⇒ 要"关了应用也还在"必须做成独立进程(⚠️ 但那就没口令了)。
## 7 其他硬约束
- 🔴 **技术讨论不谈法规**:⛔ 不引条文当论据、⛔ 不主动提合规、⛔ 不前置提报用户;只在你问起、或对象就是"对外承诺 / 资质 / 合同"时才谈。
- 🔴 **红线 R5 / R7 / R9 / R10 永远硬约束**(内容见项目层 `CODEBUDDY.md §3` 红线全表)。
- 🎯 **要解决问题,不将就妥协**:降级 / 延期 / 静默兜底**都不算解决**;🟢 **只做正向迭代**。
- 🔴 **接手前人结论先做最小取证**,⛔ 别拿旧结论当既成事实;**取现状一律按 mtime 取最新那份**。
## 8 🔴 验证通过的机制 ⇒ **必须写进技能**(2026-10-05 用户明令)
> 用户原话:「**这个也是会话规则,验证通过的机制要写进技能中**」。
- 🔴 **一条机制实测跑通之后,落点只能是技能**(`SKILL.md` 或 `references/`),⛔ **不许只留在工作区日志或当次对话里**。
- **判据**:换一个新会话、**不读任何历史**,只看技能能不能照着做出来。做不到 ⇒ 等于没立。
- **⚠️ 写的位置决定会不会被读到**:`SKILL.md` 第一屏/「必读三篇」/索引表 ⇒ **每次都会读到**;长文档正文底部 ⇒ 基本没人会翻到(等于没写)。
- ⛔ **反例(10-05,同一件事栽到第四次)**:常驻的起法只写在当天日志里 ⇒ 之后每一轮都重新折腾一遍、每一轮都得出同样的结论。
✅ 正解:结论**同时**落三处 —— `SKILL.md` 第一屏(会被读)+ `pitfalls.md` 一条(有来龙去脉)+ 本档一条规矩(约束以后怎么写)。
- 🔴 **配套**:凡"换会话容易忘、忘了就会重来"的结论,**优先放第一屏**,⛔ 不要塞进长文档。
## 9 🔴 一律用肯定表述("要什么"优先)(2026-10-07 + 2026-10-09 用户令)
**写或改技能与规则文件时,一律写"要什么、怎么做"** —— **新建、生成时同样适用**(⛔ 不只管"修改")。
落点(都是「自动匹配面 / 每轮注入面」):技能的 `description` 与首屏 · **每轮注入的排版块与钩子文案** ·
技能与文档的「用途/范围」段 · 其它规则正文。⛔ 不写「不要 A/B/C」这类负向描述。
- **为什么(机制性,不只"占地方")**:负向词进匹配面 = **把 X 这些词喂进去** ⇒ 反而更容易命中 X
(实测形态:`draw-ui` 描述里列「不用于普通插画/海报/故事板」⇒ 用户说"做个海报"时它更可能被选上);
进**注入面**是同一回事 —— 同样的反向拉高。
- **例外(按文档性质定,⛔ 不按句子定)**:**以「经验沉淀」或「红线/避坑」为目的**的技能与规则
(踩坑记录 · 红线清单 · 反例库 · `pitfalls.md` 这类)**可以写负向** ——
写法=「**正向目标 + 依据(曾栽过什么)**」,⛔ 不把禁令单独摆在那儿。
- 🔴 **落地(2026-10-09)**:这条口径已写进**每轮注入块**(工作区规则文件的 `REPLY-CORE` 段
+ 包内 `03-回复排版-核心块.md`,两份逐字一致),并在 `改包纪律.md` 立为第 5 条硬纪律。
此前它**只在本文里**、且只覆盖"用途/范围 段" ⇒ 注入面看不到 ⇒ 我又在注入块里写了一整段负向表述
(用户 2026-10-09 点破)。防漂移判据=`selftest.py::t_reply_core_same_source`。
- ⛔ **别与"事实陈述"混**:「配置里不含本工作区」「该目录不含 `board.py`」是在**描述事实**,
不在本条管辖内 —— 一刀切会把它们误删。
- ⚠️ **硬禁令(「三禁」这类)不在本条改写范围** —— 它们是约束本体,强度要留住;
本条管的是**描述性文字**与**可选的举例**。
## 10 🔴 遇阻碍 ⇒ 把目标状态改成「阻碍」⇒ 机制暂停 ⇒ 让用户决策(2026-10-09 用户令)
**口径**(用户原话:「**其他类型 阻碍 遇到阻碍修改目标状态 暂停机制 让用户决策**」):
阻碍**解不了**时,正确动作不是反复重试,而是**改目标状态把机制停住、交回用户**。
- **什么叫"阻碍"(三条全中)**:① 队列里那条 `blocked` **确认解不了**
(含跑过孤儿锁判定仍判「不动」的);② 它是在**等人**,不是等某个会话;③ 原因已写进台账与 `NEED-USER.md`。
- **谁去做**:🔴 **结果检查会话** —— 它是**唯一能看见 `blocked`** 的那条腿(靠"队列非空"触发);
「目标检查会话」虽有权改状态,却要**队列为空**才触发 ⇒ 队列里躺着 `blocked` 时它**永不触发**。
⇒ 不给结果检查这一个出口,两条腿会互相锁死、只剩空转(2026-10-09 `contentm_agent` 实测:
连开 **11 棒、约 5 小时、零派活**,每 30 分钟一棒)。
- **怎么做**:`collabd.py --set-life 阻碍 --by "<会话名>" --check-why "<一句现状>"`
⇒ 闸① 随即生效、**不再建检查棒**(这就是"暂停机制")。
⚠️ 「为什么」的旗标是 **`--check-why`**,⛔ 不是 `--why`(那是 `--retire` 的,写错会被**静默忽略**)。
⚠️ 写完**必须回读** `goal.json` 确认(本项目栽过「命令不存在却 rc=0 静默放行」)。
- **怎么恢复**:用户处置完 ⇒ **主会话**跑 `--set-life 进行中` ⇒ 机制自动复跑。
⛔ 阻碍时**不关**后台任务与协作程序(用户口径第②条),只是不派活、不建检查。
- ⛔ **别越界**:「已完成」不归结果检查 —— 那是「目标检查会话」的活(判据是完成度,不是队列)。