Files
workbuddy_skills/dsh-workflow/references/01-多棒自动接力.md
T
admin e03465c398 按用户令提交:把此前未纳管的 9 个技能目录一并入库
用户令逐字:「E:/ProgramData/.workbuddy/skills  提交仓库是指的这里」——
即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。

本次入库(9 个技能,46 个文件):
1、`AI HOT`
2、`draw-ui`
3、`dsh-diagnose`
4、`dsh-knowledge`
5、`dsh-local-env`
6、`dsh-opensource-release`
7、`dsh-workflow`
8、`oil-motion`
9、`skills-security-check`

提交前核对:
· **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓;
· 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 ——
  仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库);
· 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
2026-10-08 22:29:08 +08:00

480 lines
58 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.
# 多棒自动接力编排法(规划棒 ↔ 执行棒)
> **归属**:技能 `dsh-workflow` · 详情档(主干 `../SKILL.md`)
> **本档覆盖**:原技能 `dsh-auto-handoff-chain` **全文**(正文 308 行 + frontmatter 变更历史)
> **provenance**:本机版(WorkBuddy 实况)
> **搬运方式**:**逐行未改**;内部附件链接已改写为 `dsh-auto-handoff-chain/<附件名>`(附件在本目录下)
> ⚠️ 原技能 `dsh-auto-handoff-chain` **已合并退役** ⇒ 见到该名按本档读。
> 🔴 **2026-10-01 用户口径(最新)**:**「接续会话 时间缩短 3-4 分钟即可」** ⇒ 本档中一切「收口 + 5~8 分钟」**一律按 3~4 分钟读**(原值作废)。
---
# dsh-auto-handoff-chain — 多棒自动接力编排法
> **一句话**:不要用「一条 prompt 跑完整条工作线」,而是让**每一棒只做一件事**,做完**自己把下一棒排上**。
用户原话(2026-09-17 立项):
> 「每次执行完毕后 自动根据方法决策 自动新建会话接续处理,**在处理多步骤任务的时候非常好**」
---
## 0. 与其他技能 / 文档的分工
| 谁 | 管什么 |
|---|---|
| **本技能** | **怎么把长任务排成一条自动接力的链条**(编排层) |
| `dsh-change-workflow` | 单次改造怎么落地(六阶段) |
| `session-mechanism` | 单次取舍怎么定得对(**规划棒应加载**) |
| 工作区 `会话接续规范_20260916.md` | 上下文超限时的**接续包 v2 模板 / 开机四步 / 多线并行**(本技能是其"多棒编排"的补充,**不重复**它已写的接续包字段与成本公式) |
| 工作区 `接续入口_<线名>_<日期>.md` | **每一条工作线的唯一执行依据**(本技能的核心依赖) |
### 0.6 🔴 工作区归属五律(2026-09-21 立①②③④ / 2026-09-22 加⑤ / **2026-09-23 修⑤ 盘符大小写** / **2026-09-24 修⑤ 判据:实测 `sessions.cwd` + 正斜杠** —— 治「参考别的工作区的规则 ⇒ 会话落错家」+「同一工作区裂成两个同名分组」)
**背景实测**:某工作区会话「参考接续会话的规则」时照抄了 `aliyun-dsh-server` 的路径 ⇒ 把**入口文件**和**接续任务**都落到了平台工作区,而它那条线自己的家是 `dsh-plugin-forge`。同一份入口出现两处、md5 相同 = **第二真相源**;平台工作区的 `state.py` 因此把**别线**报成了自己的线(实测报"2 条工作线",其中一条不是它的)。
| 律 | 内容 | 反例(都真发生过) |
|---|---|---|
| ① **入口只允许一份** | 位置 = **那条线自己的工作区根**;头部必须写 `> 🔴 **工作区**:<该工作区绝对路径>` | 两处同改(forge + aliyun 各一份,md5 相同) |
| ② **`cwds` = 本工作区** | automation 的 `cwds` 只认**那条线自己的工作区**;⛔ 不因"脚本在别的工作区"就把 `cwds` 设过去 | 照抄"第 0 步跑 `state.py`" ⇒ `cwds` 写成脚本所在的工作区 |
| ③ **参考规则 = 加载本技能** | 要副本就复制**规则文档**到本工作区 `docs/会话与接续/`;⛔ **入口文件不复制** | 直接读别的工作区的 `接续入口_*.md`,照抄其中路径 |
| ④ **`cwds` 写对 ≠ 会话落对**(2026-09-21 加) | `cwds` 只是**登记项**,管不到会话从哪个目录被拉起 ⇒ prompt 里**额外**写死「开工前先 `cd` 到 `<工作区绝对路径>`」+「收尾校验产物落点」 | **乙类(下)**:6 棒 `cwds` 全写对,会话仍全落在 `E:\ProgramData\WorkBuddy\<时戳>` |
| ⑤ **`cwds` 路径写法必须归一化**(2026-09-22 加;**09-23 修盘符方向**;**09-24 修判据**) | ⛔ **只写「与手动开会话的 cwd 逐字同形」**:**当前正解 = `E:/ProgramData/AIProject/ai1net-dsh-server`(正斜杠)**。**判据 = 实测 `sessions.cwd`**(⛔ 不凭记忆定「绝对写法」—— 09-22 / 09-23 各翻烧饼一次);⚠️ 宿主去重键 = `path.trim().toLowerCase()` ⇒ **只小写、不统一斜杠**:斜杠风格不同即裂同名分组(09-24 代码级实证);⚠️ 改路径字面必须**枚举全部复制面**(技能正文 + 本技能 `description` + `MEMORY.md` + 入口声明行 + automation prompt) | **写法一变就当两个 cwd** ⇒ UI 里**同一工作区裂成两个同名会话分组**(实测:09-22 = 斜杠方向,aliyun 151 vs 6、capability 5 vs 4;09-23 = 盘符大小写,aliyun 158 vs 2) |
**律⑤ 的实测事故·一(2026-09-22 排查,「又产生了两个同名的会话分组」|污染维度 = 斜杠方向)**
| 环节 | 事实 |
|---|---|
| 现象 | UI 里同一个工作区出现**两个同名分组**(用户原话:「又产生了 两个同名的会话分组」) |
| 宿主行为 | 会话 `cwd` **逐字抄** `automation.cwds`,**不做任何规范化**(实测 10/10 逐字一致) |
| 污染源 | **10 条 automation 的 `cwds` 写成正斜杠** `E:/ProgramData/AI技能/…`,集中在 09-21 06:48 → 09-23 09:52 的接续棒 |
| 影响面 | `aliyun-dsh-server`:反斜杠 151 会话 vs 正斜杠 6 会话|`dsh-ai1net-capability`:5 vs 4 |
| 传染路径 | 前任棒次在**日志里把正斜杠当规范记下**(`2026-09-23.md` 的 `cwds = E:/…`)⇒ 后棒照抄 ⇒ 一路正斜杠 |
**判据**:⛔ 凭记忆定「绝对写法」必错(09-22 定「大写」/ 09-23 改「小写」,各裂一次)⇒ **一律取实测 `sessions.cwd` 里条数最多的那条字面逐字复制**。会话已经裂开 ⇒ 只能**发现后清理**(旧组不会自动消失,且会自我强化);⛔ **新建 automation 一律写「与手动开会话的 cwd 逐字同形」**。
**自查一句**:我这个 `cwds` 与**手动新建会话实际落库的 cwd** 是否**逐字同形**(斜杠方向也必须同形)?(当前正解 = `E:/ProgramData/AIProject/ai1net-dsh-server`)
🔎 **不确定就现查存量**(比记忆可靠):在 `CODEBUDDY_CONFIG_DIR` 下的 `workbuddy.db` 里跑 `SELECT cwd, COUNT(*) FROM sessions WHERE cwd LIKE '%<工作区名>%' GROUP BY cwd ORDER BY 2 DESC` ⇒ 取**条数最多**的那一条**字面**逐字复制。
**律⑤ 的实测事故·二(2026-09-23 排查,用户原话「为什么又出现了多个 aliyun-dsh-server 会话分组」|污染维度 = 盘符大小写)**
| 环节 | 事实 |
|---|---|
| 现象 | 同一工作区**再次**裂成两个同名分组(距事故·一只隔一天) |
| 污染源 | **3 条接续棒(IM 线第 12 / 13 / 15 棒)的 `cwds` 写成大写盘符** `E:\ProgramData\AI技能\aliyun-dsh-server`;三条**全部建在 09-23 18:55 automation 库被 app 重建之后**(记录全丢 ⇒ 重登时按技能里残留的**错误字面**输入)。⚠️ **21:5x 复核更正:原记「第 12 / 13 / 14 棒」有误** —— 第 14 棒(`2d059ec8` · 20:15)实为小写、落 `e:\`;**真凶是第 15 棒(`4e08cc77` · 20:45),当时漏改** ⇒ 20:45 又裂 1 条 |
| 实测分布(09-23 21:5x 复核) | `sessions.cwd`:小写 `e:\…` = **160 条** | 大写 `E:\…` = **5 条**(3 条 automation 拉起 19:10 / 19:49 / 20:45 + **2 条 IDE 手动** 20:54 / 21:47)| 旧路径 `d:\AI技能\aliyun-dsh-server` = **11 条**(09-08~09-12 迁移前,末次活动 09-13,已冻结)| `workspaces.path` = 小写(**53/53 全小写**) |
| 🔴 教训一:凭记忆规定"绝对写法"必错 | 律⑤ 原文「**只写大写盘符** / **小写盘符必错**」**方向反了** —— 存量与工作区登记都是小写。⇒ 正解**不是**"与存量逐字对齐"(存量被污染成 3 个值之后,这句话无法执行),而是 **跟随宿主自己的登记值 `workspaces.path`** ⇒ 判据由"我们的约定"变成"客观事实",⛔ **不再翻烧饼** |
| 🔴 教训二:改规则必须枚举**全部复制面**并对账 | 同一路径字面同时存在于 **技能正文 + 技能 frontmatter `description` + 用户级 `MEMORY.md` + 项目 `MEMORY.md` + 4 个入口文件的「工作区」声明行 + automation 的 prompt 正文 + `USER.md` + 历史日志/交接单**。09-23 那次只改了 3 处,**漏掉 `description`(曝光度最高 —— 每次新会话的技能清单都会读到它)** ⇒ 当天 19:10 起照旧裂 3 条。⇒ **说"已同步改正"必须附一条对账查询**,否则修复质量无法自证 |
| 🔴 教训三:已裂的组会**自我强化** | 手动新建会话**继承所在分组的字面** ⇒ `E:\` 组一旦存在就自己长(21:5x 实测该组 5 条中 2 条为手动会话)。⇒ 只堵源头不够,**必须清掉已裂组**,否则残留组持续复制自己 |
| 已止损(09-23 21:5x 补全) | ① 第 14 棒(20:15)小写 ✅ ② **第 12 / 13 / 15 棒**(原为大写)一并改回小写 `e:\…`,三条已确认落库 ✅ ③ 技能 `description` 方向已补改 ✅ ④ 两处 `MEMORY.md` 判据已改为「真源 = `workspaces.path`」并**冻结** ✅ |
| 待处置 | ① 已裂的 **5 条大写 + 11 条 `d:\`** 会话字面归并 ⇒ 需**停 app + 改活库**(WAL 三件套)⇒ 属红线门禁,⛔ 未擅自动 ② 4 个入口文件第 3 行「工作区」声明行的**大写字面**(登记时的诱导源)统一为小写 ③ `USER.md` 里工作区仍写 `D:\AI技能\aliyun-dsh-server`(过时,`d:\` 组来源) |
**律④ 的实测事故(2026-09-21,6 个碳线棒次 100% 命中)**
|cwds(登记)| 会话实际 `cwd`| 对得上|
|---|---|---|
| `E:\ProgramData\AI技能\dsh-ai1net-capability` | `E:\ProgramData\WorkBuddy\2026-09-21-20-20-06` | ❌ |
| 同上(连续 5 棒) | `…-20-22-44` / `…-20-25-47` / `…-20-30-24` / `…-20-33-35` | ❌ |
| 同上(执行棒⑤) | `…-22-11-36` | ❌ |
**危害**:入口 / 记忆 / 日志 / md5 全写进错目录,主工作区看起来「什么都没发生」;**每跑一棒多一个**时间戳目录(不是"两份")。
**判据**:会话记录每条都有 `cwd` 字段 —— 开机第一件事比对它;不等 ⇒ **停手报告**,⛔ 不许就地开工。
**对策**:登记方 prompt 加行「① 先 `cd` 到 `<工作区绝对路径>`,校验 cwd 相等」;本棒收尾校验**产物落点**是否在该路径下,越界则报告并搬回。
⚠️ **别与本类混淆 —— 同名重复的六类成因(排查按此顺序)**:
| 类 | 成因 | 特征 | 处置 |
|---|---|---|---|
| **甲·盘符迁移留旧份** | 项目目录名 = 盘符+路径连字符化,路径一变就多一份 | 目录名不同(`d-AI技能-…` vs `e-ProgramData-AI技能-…`) | 发现后清理,旧份不自动消失 |
| **乙·cwd 写法变体**(律⑤) | ① **斜杠方向**(`e:\…` vs `E:/…`)② **盘符大小写**(`e:\…` vs `E:\…`)—— 两个维度都**各自实测裂过**(09-22 / 09-23) | 目录名**看着像同一个**,实为两个 cwd | 新建一律「**反斜杠 + 小写盘符**」;旧份发现后清理 |
| **丙·会话标题重名** | 不同线起了同名 automation | 标题完全相同、cwd 也相同 | 改标题加线名/序号(本项目已普遍带线名,风险低) |
| **丁·cwd 落到时戳目录**(律④) | prompt 没 `cd`,宿主按默认目录拉起 | 一堆 `E:\ProgramData\WorkBuddy\<时戳>` | 见律④ |
| **戊·同名镜像目录**(2026-09-27 加) | **磁盘上真的有两个目录**,路径段里只差一个词(`…\ProgramData\…` vs `…\ProgramDSH\…`),且镜像目录里**另有一份同名入口文件**、还有 `bundles\`/`docs\` 骨架 | 目录名**看着像同一个**、只差一段;两侧都可能被当「本工作区」;⛔ 不是写法变体(乙类)、也**不是**空壳(光看"目录里有东西"判不出来) | 定**唯一权威**(判据 = 实测 `sessions.cwd` 条数最多那条 + app 当前工作区 + `.workbuddy\memory\` 只长在哪侧)→ 镜像侧入口改**跳转 stub** → 改全部 automation 的 `cwds` 与 `--ws` → 已落错的会话搬回权威分组。**只留一处** |
| **己·入口声明的工作区与磁盘不符**(2026-09-27 加) | 搬家或改名后**只搬了目录、没回填入口头部**:入口第 3/10 行仍写着旧路径 —— 实测 `ai1net-decision-laya` 线入口写「工作区 = `E:/ProgramData/AIProject/session-mechanism-laya`」,而**该目录在两个根下都不存在**(真实目录名是 `ai1net-decision-laya`,入口内 33 处引用全是旧名) | 与本表甲/戊**正相反**:那两类是"多了一份",这一类是"**指向的那份根本不在**"。照抄登记 ⇒ 宿主**凭空新建一个幽灵分组**(`projects\e-ProgramData-AIProject-session-mechanism-laya`),比同名镜像更难发现(镜像至少目录存在、还能靠 `ls` 撞见) | 判据 = 把入口里每处「工作区 = …」逐条 `Test-Path`;**任一不存在 ⇒ 以实测 `sessions.cwd` 为准回填入口头部**,⛔ 绝不按入口里的路径去**新建**目录(那会把幽灵坐实) |
⚠️ **「停维护」≠「停用」**(2026-09-27 实测,比上表任何一类都容易漏):定完唯一权威后,只写一句「旧副本停维护」**不算收口** —— 旧侧那份入口仍是**完整正文**,两侧还会各自继续被写。实测当晚:`ai1net-dsh-anywhere` 两侧入口已分叉到 **24903 B vs 23816 B(差 1087 B / 落后 8.5 小时)**、`ai1net-dsh-desktop` 差 **121 B / 20 分钟**,而两条线**都早已拍板"只用 ProgramData 那份"**。⇒ 收口动作必须落到「**旧侧入口就地改成跳转 stub**」(明示"这不是入口"+权威路径+停用原因),否则下一棒读旧侧就会照着过期内容重做。
**律①/② 的实测事故·三(2026-09-27 排查,用户原话「又开始出现接续会话 创建重复的 会话分组了 ai1net_ui」|污染维度 = 同名镜像目录)**
| 环节 | 事实 |
|---|---|
| 现象 | `ai1net_ui` 在 UI 里裂成两个同名分组:`projects\e-ProgramData-AIProject-ai1net_ui`(3 会话)与 `projects\e-ProgramDSH-AIProject-ai1net_ui`(1 会话,建于 23:45:19) |
| 污染源 | 第 1 棒登记第 2 棒时把 `cwds` 写成镜像路径 `E:\ProgramDSH\AIProject\ai1net_ui`。它这么判是因为:入口 `来源会话` 那栏写着「工作区 `E:\ProgramDSH\AIProject\ai1net_ui`」、正文几十处**资产**路径都在 ProgramDSH 下、上一棒给它的抢锁 `--ws` 也是镜像路径 |
| 隐蔽后果一:**锁不互斥** | dsh 锁按 `--ws` 分命名空间 ⇒ 权威 `.locks\e-programdata-aiproject-ai1net_ui-2e350e23` vs 镜像 `.locks\e-programdsh-aiproject-ai1net_ui-97d0d29d`,**两处各一把锁、互不排斥**(第 2 棒持锁期间从权威侧查 `status` 报「空闲 ✅」)。本轮两棒没真并发,纯属运气 —— 这条比 UI 观感危险得多 |
| 隐蔽后果二:**入口分叉** | 第 2 棒只写了镜像那份 ⇒ 45052 B vs 40720 B,md5 `c3f0afa2…` ≠ `07ad6380…`;再走一棒就会读到过期的「本轮动作」 |
| 处置(已落地) | ① 新版入口同步到权威侧 ② 权威入口头部立「本线工作区(唯一权威)」四条约束表 ③ 镜像侧入口改**跳转 stub** ④ 第 3 棒 automation 的 `cwds` + prompt 入口路径 + `--ws` 全改权威侧(排期不动)⑤ 会话 `cc1c1205` 搬回权威分组(先等它 `status=completed`,再 SQL 单行 `UPDATE sessions SET cwd=…` + jsonl 移位 + 删空目录;`integrity_check=ok`;`group by cwd` → 1 行 3 条) |
| 🔴 教训一 | **判「本工作区」⛔ 绝不能读入口里的「来源会话」栏和资产路径** —— 那些是**历史**与**资产位置**,不是工作区。唯一判据 = ① 实测 `sessions.cwd` 条数最多那条字面 ② app 当前打开的工作区 ③ `.workbuddy\memory\` 只长在哪一侧 |
| 🔴 教训二 | **镜像目录不是空壳**(内有 `bundles\`/`docs\` 骨架,看着极像正经工作区)⇒ 光凭"目录里有东西"定不了归属;**入口里出现两个绝对根路径时,先问「磁盘上到底有几个同名目录」** |
| 自查一句 | 我这条线的工作区路径,**在磁盘上是否只有一个目录**?(同名不同根 = 戊类,必须先定权威、再开工;若已裂 ⇒ 清掉错误分组,旧组不会自愈) |
**跨工作区取脚本的正确姿势**(不违反律②)—— 本工作区没有脚本时,用**绝对路径**调、并指定 `--ws`:
python "E:/ProgramData/AIProject/ai1net-dsh-server/state.py" --ws "E:/ProgramData/AIProject/<自己的工作区>"
⛔ 把 `state.py` 复制到每个工作区 = 多一份要维护的代码,`--ws` 是正解(2026-09-21 加;`state.py` 同时新增**归属校验**:头部声明的 `工作区` ≠ 本工作区 ⇒ 标「外来线」并排除出 §2 / 口令)。
---
### 实测事故·四:全库体检(2026-09-27 23:5x · 用户原话「检查 workbuddy 下所有其他会话 看是否也会出现相同的问题」)
**范围**:`projects\` 全部 7 个分组 / `sessions` 全部 11 条 / `automations` 全部 4 条 / `.locks` 全部 12 个键 / 各工作区入口文件。复跑脚本 = `dsh-auto-handoff-chain/audit-mirror.py`(只读;读 `workbuddy.db` 走 `file:…?mode=ro`)。
**结论**:**当前只有 ai1net_ui 裂过、且已修**(分组↔cwd **7:7 一一对应,孤儿 0、缺组 0**,去重键冲突 0)。但**三条线具备同款结构条件**:
| 工作区 | 同名镜像 | 入口双份 | 入口是否分叉 | 锁命名空间 | automation `cwds` | 判定 |
|---|---|---|---|---|---|---|
| `ai1net_ui` | 有 | 有 → **已 stub** | 已修(1883 B stub) | 仅旧侧残留键 | 3 条全=权威侧 | 🟢 已收口 |
| `ai1net-dsh-desktop` | 有 | 有 | 🔴 **分叉 19039 B(23:35) vs 18918 B(23:15)** | 🔴 **两套键**(旧 15:00 `…-3fe5ab6f` / 新 23:36 `…-547001a0`) | 无 | 🔴 P1 |
| `ai1net-dsh-anywhere` | 有 | 有 | 🔴 **分叉 24903 B(23:37) vs 23816 B(15:00),差 1087 B / 8.5 h** | 🔴 **两套键**(旧 15:00 `…-5ee837d7` / 新 23:38 `…-6bce3721`) | 无 | 🔴 P1 |
| `ai1net-decision-laya` | 有 | 有(md5 一致,在同步) | 否 | 自建 `scripts/lock.py`(不涉 dsh 锁) | 无 | 🟠 P1(己类) |
| `aigc-idea-impression` | **无** | 无 | — | — | 1 条=本工作区 ✓ | 🟢 |
**三个新事实(写进判据)**:
1. **`E:\ProgramDSH\AIProject\` 是 `E:\ProgramData\AIProject\` 的整树镜像**(4 个工作区,连 `.workbuddy\memory\automations\` 的 16~24 个棒记录副本都被复制过去)⇒ 这是甲/戊/己三类的**共同温床**:只要这棵镜像树还在,任何一条线把 `cwds`/`--ws` 写到 ProgramDSH 侧就立刻复现。**⛔ 看单个目录判不出来,必须整根比对**。
- 🔴 **2026-09-28 现状**:该镜像树**已整树删除** ⇒ 本条**暂不适用**,保留作**复现判据**。⚠️ 但同一份 `cwds` 裂缝**换了个形式仍会发生**:工作区 09-28 由 `aliyun-dsh-server` 改名为 `ai1net-dsh-server` ⇒ 凡沿旧字面登记的 `cwds` 都会落到(已不存在的)旧目录。
2. **`dsh-anywhere` 的权威侧入口头部第 10 行仍写「工作区 = `E:\ProgramDSH\AIProject\ai1net-dsh-anywhere`」,而同一文件 §24 早已记「唯一权威 = 本目录」** —— 同一份文件内部自相矛盾。⇒ 戊类的"污染源"(第 1 棒据入口头部判错工作区)**在别的线上原样待触发**。
3. **「登记下一棒写错 `cwds`」这条路径全库只发生在本线**:逐条解析 `automation_update` 入参,6 条其他会话的 `mode=create/update` 计数**全为 0**(其余为 `list`/`view`)⇒ 没登记过接续棒的线不会踩。**这才是可复用的判据:先看 `automations` 表里有没有指向镜像侧的 `cwds`,而不是先看 UI。**
**一条被证伪的假设**(省下次排查):`~/.workbuddy` 与 `E:\ProgramData\.workbuddy` **不是** junction 关系,且只有**一个** `workbuddy.db`(`C:\Users\Administrator\.workbuddy\` 下**无** db)⇒ 不存在"双库"问题,分组只有一套。
🔎 **收尾自检一句**:本棒 `cwds` 是否 = 我这条线自己的工作区?入口文件是否只在我这个工作区存在一份?**本次写入的产物是否都在该工作区路径下(律④)?**
---
### 0.7 🔴🔴 活库(`workbuddy.db`)改动铁律(2026-09-23 血的教训 —— ⛔ 违反必炸库)
> 🔴 **纠偏(2026-09-23 22:0x 实证)**:「**必须停 app**」只对**文件级操作**(`cp`/`mv`/覆盖 WAL 三件套)成立,⛔ **不要套到 SQL 变更上**。
> **实证**:**在 app 运行中**用 `sqlite3.Connection.backup()` 做在线一致性备份(产出单文件自洽副本 1.83 MB)→ `UPDATE sessions SET cwd=…`(**16 行**)→ 改前/改后各跑一次 `PRAGMA integrity_check` 全 `ok` → **app 全程正常**(执行者本身就跑在 app 里)。
> ⚠️ 把"必须停 app"套到 SQL 变更会形成**死锁**:执行清理的 AI **永远跑在 app 内** ⇒ 这条规则一读死,「清理已裂组」就永远做不成(用户原话点破:「**关了你咋运行 你就是 workbuddy**」)。**正解 = ⛔ 不碰三件套,只用 SQL + 官方 `backup()` API/`VACUUM INTO`。**
**事故**:为清理「同名会话分组」,`cp` 覆盖了 `E:\ProgramData\.workbuddy\workbuddy.db` ⇒ **WorkBuddy 应用当场读不了库**(`DatabaseError: file is not a database`),且**持续不可读**、app 还在往上写。
**机理**(这才是要记的):`workbuddy.db` 是 **WAL 模式 + 多进程并发**的活库,同一逻辑库由**三个文件**共同构成:
```
workbuddy.db 主库(已 checkpoint 的基线)
workbuddy.db-wal 预写日志(增量,带 salt 世代)
workbuddy.db-shm 共享内存索引(被 app 进程持有)
```
三者必须**同一世代**才自洽。文件级 `cp` 只换其中一部分 ⇒ **salt 世代错位** ⇒ SQLite 判定「不是数据库」。
**铁律四条(⛔ 不可破)**:
| # | 律 | 理由 |
|---|---|---|
| ❶ | **先确认 app 未运行**(`Get-Process WorkBuddy`)⇒ 有进程就**停手**,⛔ 不碰文件 | app 持有 `-shm` 句柄,`cp` 覆盖必然三件套错位 |
| ❷ | **⛔ 禁止文件级 `cp`/`mv`/`rsync` 动活库** | 即使 app 已关,逐文件 `cp` 仍有中间态风险;要备份就三件套一起、要恢复就三件套一起 |
| ❸ | **改数据只走 SQL 连接**(`sqlite3.connect` + `busy_timeout`),⛔ 不碰字节 | 连接路径由 SQLite 自己维护锁与世代 |
| ❹ | **改前必备份三件套**,改后**立即 `PRAGMA integrity_check`** | 备份要含 `-wal`/`-shm`,否则备份本身不自洽 |
**判据(出事时怎么定位)**:
- `file is not a database` ≠ 文件坏了 ⇒ 先看**三件套 salt 是否同世代**:主库 `change counter`(off 24) 与 `version-valid-for`(off 92) 相等 ⇒ **主库自洽**
- 用 `?mode=ro&immutable=1` 打开可**完全绕开 WAL/SHM**:读得出 ⇒ **数据在,只是 WAL 错位**
- ⛔ **`immutable=1` 只能读,不得用于写**(会跳过锁 ⇒ 与 app 打架)
**修法(唯一)**:停 app → **删 `-wal` 与 `-shm`** → 重启 app(SQLite 从自洽主库重建)⇒ 数据零丢失。
**自检一句**:我要动的这个库,**app 现在开着吗**?备份是**三件套一起**吗?改完跑 `integrity_check` 了吗?
---
## 1. 形态:规划棒 ↔ 执行棒 交替
```text
规划棒①(出交接单)→ 执行棒①(照单落地)→ 规划棒②(出下一单)→ 执行棒② → …
↑ 每棒 = 一个全新会话 + 一条一次性 automation,做完自己登记下一棒
```
**为什么拆成两种棒(不是随便切段)**
| 棒 | 只做什么 | 成本量级(实测) |
|---|---|---|
| **规划棒** | **只出交接单**:不改服务器、不改代码、不部署 | 23–56 次调用 / 5.7–9.4 积分 / 3–10 min |
| **执行棒** | **照单落地**:按单子的 S0–Sn 逐条执行、逐条验收 | 93–215 次调用 / 23–29 积分 / 21–41 min |
⇒ 这是「**规划与执行分离**」从纪律变成**机制**:规划棒物理上碰不到生产,执行棒物理上不必做取舍。
**⛔ 什么时候不要用这套**
- 一次性小任务(直接做,别排链条)
- 步骤可并行(用并行会话 + **域锁**:域不重叠即真并行;机制层任务仍须独占,见 §3.5)
- **任务形态本身就贵**(例:批量改写 N 份文档)—— 那只该**先写脚本一次跑完**;接力**救不了**贵的活,只换场地
- 单棒做不完(会超出上下文预算)⇒ 说明**棒还要再切细**,或改用「接续包 v2 + 开机四步」
- 🔴 **链条上存在「需要用户拍板」的决策点** ⇒ 可自决的段落照常接力,但**必须在拍板点前停下**,不许自动跨过去(**见 §3.3 登记门禁**)
---
## 2. 六件套 prompt 骨架(照抄填空)
> **位置**:每棒的 prompt 写在 `automation_update` 的 `prompt` 字段里。
> **要点**:整段 400–1,300 字符。**越长越糟** —— 每多抄一个技术细节,就多一个漂移源。
```text
<线名> · <第 N 棒:规划棒 / 执行棒>(本轮只做这一件事,做完即停)。
第 0 步:跑 `<绝对路径>/state.py` 看状态(只读、免抢锁、1 次调用拿到锁/git/入口/收口)。
第 1 步(开工门禁):先跑 `bash "<绝对路径>/preflight-lock.sh" "<线名>-<第N棒>" <目标文件...>` —— **【D】未归类 或【E】机制层非空 ⇒ rc=1 ⇒ 拒开工**(先去归类/改独占)。
第 1b 步:抢锁 `bash "<绝对路径>/handoff-guard.sh" --claim-exec "<线名>-<第N棒>" --domains <域...>`;**抢不到 = 冲突域被占 ⇒ 只报告并立刻停**(⛔ 不得删锁,R9)。
⚠️ 机制层任务(`scripts`/`config`/`crypto`/`isolation`/`index`/`CODEBUDDY.md`/锁本身)**⛔ 不加 `--domains`** ⇒ 退化全局独占。
第 2 步:读**唯一执行依据** = `<入口文件绝对路径>` 的 **§2「🎯 本轮动作」块**,按它点名的那份交接单开工。
(规划棒专属)本轮任务:出可执行交接单,落盘 `<路径>`,按 8 段模板:目标 / 只读前置 / 范围 / 决策点 / 步骤 S0–Sn / 逐条验收判据 / 回滚 / §8 回报格式。
(规划棒专属)⛔ 开工前先加载技能 `session-mechanism`(取舍判据以它为准,不凭记忆)。
约束:⛔ 不 commit / push;⛔ 不做 <明确点名的排除项>;⛔ 不重做 <已收官的序号>;⛔ 不扩大单子范围(单外发现的缺陷先报告、不动手)。
成本纪律:批量活先写脚本再让脚本跑;取证最多 3 条命令;⛔ 不要 Glob/Grep 全库摸底;一轮内工具调用次数尽量压低。
纪律:技术实现项**自决不上抛**;只有「没有客观优劣」的取舍才列候选,且每个候选必须写「优点 / 缺点」、**候选竖排成段**(不横排);判据必须可被第三方复现。
收尾(缺一即算未完成):① 释放锁 `--release-exec "<会话名>"`(⛔ 不带会话名 ⇒ **拒绝释放** · 2026-09-25 修);② **先过登记门禁(见 §3.3)**——只有「下一棒可自决」才登记,**`scheduledAt` = 此刻 + 5~8 分钟(见 §3.1.1 铁律①,⛔ 不许留长等待窗口)**,且**同一时刻只挂一个接续棒**(§3.1.1 铁律②:⛔ 不预登记队列,后续项写进入口 §2 的「⏭️ 本线下一项」由下一棒自己排);用**陈述句**告知「已登记自动接续、约 5~8 分钟后自动开新会话、接续点 = X」;③ 把入口 §2「🎯 本轮动作」推进到再下一棒;④ 写工作区日志。
```
### 2.1 三条骨架为什么长这样(都有实测出处)
| 骨架 | 治什么 | 实测依据 |
|---|---|---|
| **第 2 步 = 指向入口,不抄细节** | **细节漂移**(细节有两个来源 ⇒ 必然打架) | 09-16 那版 prompt 重述了整段技术细节 ≈1.2 KB,反而**挤掉了"开机四步"那一行** ⇒ 40 次调用 / 9.37 积分(立项口径 ≤10 次 / ≈1 分) |
| **「本轮只做这一件事,做完即停」** | 无人值守时的**自我扩权** | `b08b1c35`:用户只说「先确认待办」,第 28 次调用**已在写代码** |
| **「取证最多 3 条命令」** | 防御性过度取证 | 同一会话第 7–24 次**连续 17 次取证**,reasoning 里三连自我加码「取证非常完整了」 |
| **纪律块(自决不上下抛 + 候选写优缺点)** | **把决策方法内联**(新会话读不到旧上下文,方法论不会自己进来) | 09-17 实测:9 棒里 `Skill` 调用 **0 次** ⇒ 判据全靠这段内联文字撑住 |
### 2.2 ★ 已知缺口:Skill 调用 = 0(本技能给出的修法)
**实测**(09-17 全部 9 棒):`function_call.name == "Skill"` **一次都没有**。判据是 prompt 里的**内联摘要**在起作用,完整方法论从未进上下文。
**修法**(分棒区别对待,别一刀切):
- **规划棒必须加**:`⛔ 开工前先加载会话技能 session-mechanism`(规划棒基数只有 23–56 次调用,+1 次可接受,且它确实要做取舍、要出单)
- **执行棒可以不加**:单子已经把判断写死了,再加载方法论是纯开销(执行棒基数已 93–215 次)
---
## 3. 收尾四件套(缺一即算未完成)
> 这四件里**第 ② 件是唯一会"断链"的地方**,也是钩子**做不到**的地方(钩子不能创建会话、不能创建自动化)。
> 🔴 第 ② 件的两条硬约束(详见 §3.1.1):**只排"下一棒"这一棒**(⛔ 不预登记队列)+ **`scheduledAt` = 收口 + 5~8 分钟**。
| # | 动作 | 判据 |
|---|---|---|
| ① | 释放锁 `--release-exec "<会话名>"`(⛔ 不带名 ⇒ 拒绝释放) | 跑一次信息模式确认已释放 |
| ② | **先过 §3.3 登记门禁** → 登记下一棒的一次性 automation(**`scheduledAt` = 此刻 + 5~8 分钟**,见 §3.1.1 铁律①;**同一时刻只挂一个**,铁律②)+ **在给用户的回复里用陈述句告知** | 门禁不过 ⇒ **不登记,改为告知"链条已暂停待拍板"**;陈述句三要素:约 5~8 分钟后自动开新会话 / **不用你操作** / 接续点 = X |
| ③ | 把入口文件 §2「🎯 本轮动作」**推进到再下一棒** | 入口 = 下一棒的**第一信息源**;不推进 ⇒ 下一棒照旧口径做,做重工 |
| ④ | 写工作区日志(当日 `memory/YYYY-MM-DD.md` 追加自己的小节) | 只追加自己的小节,⛔ 不重写别人的段落 |
### 3.1 收尾陈述句模板(实测原文,照抄)
```text
已登记自动接续:一次性 automation `<id>`,约 5~8 分钟后(08:37)自动开新会话,不用你操作;
接续点 = 序 ④「443/TCP 兜底」的规划棒(出 `05-交接单/交接单_443兜底_20260917.md`,只出单、不改服务器)。
收口:锁 抢 ✓ → 释放 ✓(08:32)|未 commit / 未 push|入口 §0/§2 已刷到「③ 已完成 → 下一棒 ④」|产出物已交付。
```
**⛔ 只登记不告知 = 缺陷**:新建会话是**用户可感知的状态变更**。实测:`408636f2` 登记了自动化却一字未提,用户两小时后自己发现才追问。
#### 3.1.1 🔴 排期两条铁律:**首个(也是唯一的)接续棒** = 收口 + **3~4 分钟**;⛔ **同一时刻只挂一个**(2026-09-18 定稿;🔴 **2026-10-01 用户口径改值:5~8 ⇒ 3~4 分钟**)
> 用户 2026-09-18 原话:「**首个接续任务 5-8分钟**」+「**最好不要建立多个接续任务,一个会话结束时在排下一个**」
- **铁律①(间隔)**:`scheduledAt` = **收口时刻 + 3~4 分钟**(🔴 2026-10-01 用户口径,原 5~8 作废)。⚠️ 这 3~4 分钟是「**从本会话收口,到那"唯一一个"接续棒开跑**」的间隔 —— ⛔ **不是"棒与棒之间的间隔"**,后者根本不存在(由铁律②,任何时刻只该有一个待跑接续棒)。
- **铁律②(唯一)**:**同一时刻只挂一个接续棒**,下一棒**只能由"正在收官的那一棒"自己排**。⛔ **禁止预登记队列 / 堆叠**("我先把后面两棒都排上" = 违规)。
- **后续项不会丢**:把它写进入口文件 §2 的 **「⏭️ 本线下一项(⛔ 本棒不预登记)」** 行(含要点 / 根因 / 验收基线),**由当前那一棒收官时照此立棒**。
- **⚠️ 「重挂」的唯一正确做法 = 新建,不是改时间**(2026-09-21 实测,见 §4 第 ⑦ 条):棒因为**抢不到锁**等原因空转、你想"过 8 分钟再来一次"时,⛔ **不要** `mode="update"` 改 `scheduledAt` —— 已跑过的 once 型棒改时间**不会产生新的运行记录**(链条静默死亡)。正确做法:`mode="create"` **新建一条同内容 automation**,旧棒置 `PAUSED`(保住铁律②的"只挂一个"),并在 prompt 里写明"抢不到就照此法自助重挂"。
**边界(哪一头都不能越)**
| 方向 | 判据 |
|---|---|
| 下限为何是 5(不是 2) | 避开调度器最小提前量与文件落盘竞态 |
| 上限 8 何时可越 | ⛔ 只有「**要等一个外部窗口**」才允许拉长(对方服务重启完 / 另一条棒在跑 / 用户拍板),且**必须在陈述句里写明在等什么** |
| ⛔ 不许的两条理由 | 「**给用户留追改窗口**」(追改可在任何一轮直接打断 ⇒ 等价于主动空转)· 「**怕它跑不完**」(**排期按实测基线算,不按猜不确定性算**:规划棒实测只需 **6–13 分钟**,序⑦/⑨/⑪/⑯/⑱/㉔) |
**实测事故 2026-09-18(一棒之内两处都犯过 ⇒ 本节因此重写)**
| # | 我做了什么 | 用户原话 | 性质 |
|---|---|---|---|
| ① | ㉘ 规划棒排到 **收口 + ~2 h**,又给 ㉙ 留 **1.5 h 余量** | 「为什么时间要定在3:00」→「㉘ 规划棒 = 01:30 **也还有1个多小时呢**」 | 违反铁律①:长空转 + **用"猜不确定性"代替实测基线** |
| ② | 用户说「**5-8分钟即可**」后,我把它读成 **"棒与棒之间的间隔"**(㉘→㉙ = 8 分钟),于是**同时挂了 ㉘ + ㉙ 两个接续棒** | 「我说的是**首个**接续任务 5-8分钟,**最好不要建立多个接续任务,一个会话结束时在排下一个**」 | 违反铁律②:预登记队列。**根因 = 改规则时只在旧句子上换了个数字,没有回到本节核对规则的完整定义** |
⇒ **判据**:任何一次"排期调整"都要**回到本节逐条对照两条铁律**;⛔ 不许只换数字就交差 —— 数字与"口径落在哪个对象上"会一起漂移。
#### 3.1.2 🔴 下一棒的 **automation id 只能来自工具返回值**(2026-09-17 实测踩坑)
- **实测反例**:序⑧ 执行棒收口时,我先把「下一棒 id = `ad8d1c4f-…`」写进了自动化记忆文件,**之后**才调 `automation_update` 建单 —— 真实 id 是 **`0c3feee5-…`**(工具生成,无法预知)⇒ 记忆文件里留下一条**指向不存在的自动化**的假 id,必须回头再改一次。
- **正确顺序**(不可颠倒):① 先 `automation_update(mode=list)` 查重 ⇒ ② `mode=create` 建单 ⇒ ③ **从返回值取真 id** ⇒ ④ 再写记忆文件 / 入口文件 / 日志。
- ⛔ **不许"先占位再补"**:id 是**外部生成的事实**,编造 = 判据级污染(下一位读者会拿它去 `mode=view`,查不到就会误判"链条断了")。
- ⚠️ 同理适用于:`scheduledAt` 的实际生效值(以返回的 `nextRunAt` 为准)、被工具规范化过的 name。
#### 3.1.3 🔴 跨线交付:**「投单 ≠ 启动」** —— 工单不会自己变成动作(2026-09-28 实测漏)
**事实**:给**另一条线**(另一工作区)投了对接单、还在对方入口加了指针之后,那条线**不会因此开工**。
依据就是你本技能的第一律 —— **「自动化 = 开新会话的唯一通道」**:没有 `cwds` 指向那个工作区的 automation,**就没有任何机制把对方拉起来**;工单只是「文件里的留言」,得等某个会话去读它。
**实测(2026-09-28)**:平台线给 `ai1net-dsh-anywhere` 投了对接单(15:10 落地),并对用户说「让相关会话都执行起来」——
但 automation 全表 6 条里**没有一条指向那个工作区**,对方**当天零活动**(根目录 mtime 停在 09-27 23:38、无当日日志)。
⇒ 用户后来直接问「**为什么另一个会话还没开发 app**」,根因就是**投单之后没排棒**。
**判据(投完单必自问一句)**:这张单**由谁、在什么时候**读?
- 有 automation ⇒ ✅ 已闭环
- 没有 ⇒ **立刻补一条 `once` automation**:`cwds` = 对方工作区,prompt 写「读 X 单并执行」(⛔ 别指望对方"自己会看到")
⚠️ **投单前还要核对对方的「开工依据」是否仍然成立**:实测那条线入口的「下一步」依赖的宿主目录**已被删除** ⇒ 就算把它拉起来,按自己的入口办也会失败。
⇒ **投单 + 排棒 + 在棒里写明「局面已变、⛔ 别按旧入口办」**,三件一起才算送到。
⚠️ **跨线排棒要防"撞车"**:若对方要动的东西正被自己这条线用着(例如今晚要端到端验证的那批文件),**把对方那一棒设成「只读核对 + 出结论」**,⛔ 不给写权限 —— 否则两条线同时改同一批文件。
### 3.2 ★ 登记前必须先看有没有在跑的(防双开)
实测(序③规划棒,09-17 07:56):
> 「执行棒会话**已经起来了**(07:53 触发,现在 07:56),所以我**不再新建接续 automation** —— 链条已经接上,多建一条只会重复开工。」
**判据**:登记前先确认下一棒是否**已经存在**(会话列表 / `automation_runtime_state.running`)。已存在 ⇒ **不登记,只报告**。
### 3.3 🔴 登记门禁:要拍板的,等拍了再登记(用户 2026-09-17 明令)
用户原话:
> 「**需要我拍板时,等拍了再新建接续会话就行**」
**动作只有两句**:
- 下一棒若含**需要用户拍板**的事 ⇒ ⛔ **不登记**;本棒正常收口,停在「待你拍板」节,并告知「**链条已暂停,等你拍板**」
- **拍板到手后** ⇒ 再由当时的会话登记下一棒
**什么算「需要拍板」** = 边界外六类 + 不可逆操作:
业务目标 / 优先级 · 花钱或承诺资源 · 对外承诺与合规 · 需要用户提供凭据或审批 · 无客观优劣的偏好 · 影响面超出本平台 · 不可逆操作(删数据 / 迁库 / 清目录)。
⚠️ **判据顺序**:先判「是不是要拍板」;**未命中**才轮到「候选排不排得出优劣」。顺序反了就会自我扩权 —— 实测事故见 §3.3.1。
**唯一例外**:条件式非阻塞项(不做决定也能先开工)⇒ 可以登记,但要把「遇到即停」写进下一棒 prompt。
> 实测正例(序④规划棒):「只有 S3 实测证明『地址覆盖 + 指定 SNI』不可行时,这一条才成为唯一缺口」⇒ 不阻塞开工 ⇒ 正常登记。
### 3.3.1 实测事故:序⑥ → 序⑦「拍板未定,接续已跑」(2026-09-17 用户报障)
用户原话:
> 「**最新会话遇到需要我拍板 还是自动创建接续会话的情况,导致拍板还没定,接续会话已经开始执行**」
**经过**:09:52 序⑥**规划棒**把「真机来源」(要用户出设备 = 边界外)自己拍了 ⇒ 登记执行棒 → 10:58 序⑦规划棒按链条自动开跑 → **11:00 用户才给拍板** → 11:09 序⑦执行棒又自动开起来 → 11:11 用户手动喊停。**多烧 ≥2 轮,且要用户自己介入收拾。**
**三条留档(别再犯)**
1. **边界外的事,候选再明显更优也不许自己拍** —— 这次就是「A 零成本零等待」看着更优,但选项本身越界。
2. **拍板项一旦上抛,本棒就不许再登记下一棒** —— 机器定时与人的拍板节奏**没有同步点**,不等就必然错位。
3. **判「某棒是否在跑」看锁的时间戳**(域锁/全局锁皆可),⛔ 不是 `automation.status` —— 12 条已跑完的一次性任务,`status` 全是 `ACTIVE`。
---
## 4. 七条实测防护(都是真踩过的)
| # | 现象 | 后果 | 处置 |
|---|---|---|---|
| ① | **漏登记下一棒**(链条断在收工处) | 无人接着做,**要等用户发现** | 实测:07:2x 那棒漏登记 ⇒ 用户 07:39 主动追问「为什么还不创建新会话接续任务」。⇒ 修法 = 把「收尾四件套缺一即算未完成」**写进每棒 prompt** |
| ② | **双开 / 重复登记** | 同一份活被跑两遍,白烧一轮 | 见 §3.2(登记前先看有没有在跑的) |
| ③ | **once 型 automation 跑完不自动转完成态** | 列表里堆积"已过期但 ACTIVE"的一次性任务;调度器补跑窗口 **12 小时** ⇒ 理论上可能被扫到、**多开一个会话** | 实测 09-17:列表里躺着 4 条过期仍 ACTIVE 的 once 型任务。处置:**保持原样**(不动既有配置)或**设为暂停**;⛔ 不要用 shell 去改库 |
| ④ | **自动跨过"需要拍板"的点** | 拍板未定就跑,**要等用户自己发现**(已有实测事故 §3.3.1) | **要拍板的,等拍了再登记**(§3.3 门禁) |
| ⑤ | **下一棒 `scheduledAt` 拉太长**(实测 ~25 min,甚至 ~2 h) | 链条白白空转,**用户当场追问** | 实测:序⑦执行棒 12:2x 收口、下一棒排 12:50 ⇒ 用户 12:26 追问「**为什么要等20多分钟才执行接续会话**」;09-18 又排过「收口 + ~2 h」。⇒ 修法 = §3.1.1 铁律①:**= 收口时刻 + 5~8 分钟**,且**按实测基线算**(规划棒 6–13 分钟) |
| ⑥ | **预登记多个接续棒**(队列堆叠) | 链条上挂着 ≥2 个待跑棒 ⇒ 抢锁空转;后续项要么白烧要么被撤,**用户当场纠正** | 实测 09-18:㉗ 棒同时挂了 ㉘+㉙ 两条 ⇒ 用户原话「**最好不要建立多个接续任务,一个会话结束时在排下一个**」。⇒ 修法 = §3.1.1 铁律②:**同一时刻只挂一个**;后续项写进入口 §2 的「⏭️ 本线下一项(⛔ 本棒不预登记)」,由当棒收官时立棒 |
| ⑦ | **「重挂」= 改 `scheduledAt` ⇒ 根本不触发**(棒被锁挡回、想重排一次时最容易踩) | 链条**静默死亡**:以为"5~8 分钟后会自动重试",实际调度器**从未创建运行**,要等用户追问才发现 | 实测 09-21 carbon 棒③:06:24 把 `scheduledAt` 改成 06:34 ⇒ 06:42 仍零痕迹;`daemon.log` 里**不存在** `:1789943*` 形态的运行记录,只有原 06:23 那一笔 ⇒ **已跑过的 once 型棒,改时间不会重新入队**。⇒ 修法 = **重挂必须新建 automation**(`mode="create"`),并把旧棒置 `PAUSED`(保「同一时刻只挂一个」);⛔ 别用改时间重排。取证方式 = 运行记录形如 `<automationId>:<epochMs>`,去 `E:/ProgramData/.workbuddy/logs/daemon.log` 里按 `<id>:<期望时间戳前 7 位>` 搜 |
### 3.5 🔴 域锁四个已实证的「假绿」坑(2026-09-22 落地时全踩过)
> 域锁 = 锁携带**资源声明**(`DOMAINS`),可算交集 ⇒ **域不重叠即可真并行**(取代旧的「全局只放一个」)。
> 机制层(`scripts`/`config`/`crypto`/`isolation`/`index`/`CODEBUDDY.md`/锁本身)**全平台共用 ⇒ 必须独占**。
| # | 坑 | 症状 | 修法 |
|---|---|---|---|
| ① | **域键算法 shell / hook 两侧不一致** | 一侧取"首个锚点段"、另一侧取"前两段" ⇒ 同一路径算出不同域 ⇒ **交集永远算空** ⇒ 该拦的全放行 | 锚点表**逐字一致**且**先长后短**(`dsh-server-docs` → `aliyun-dsh-server` → `src`/`web`/`test`…);改一侧必须同步另一侧 |
| ② | **非 ASCII 会话名塌成同一目录**(本平台**必现**) | `tr -c 'A-Za-z0-9._-' '_'` 把每个非 ASCII 字符压成 `_` ⇒ 「域锁-A」「域锁-B」「域锁-C」**全映射成同名目录** ⇒ 一锁被多人共用、域声明被并进同一份 ⇒ **域隔离整体失效** | 会话名含非 ASCII ⇒ 目录名**附 `cksum` 摘要**(唯一、确定、无外部依赖);ASCII 名直接留用(可读+兼容) |
| ③ | **同会话二次抢锁 `rm -rf` 重建** | 锁目录名只由会话名派生 ⇒ 第二次抢 `src/net` 把第一次的 `src/im` 声明**冲掉** ⇒ 异会话可抢进来 | 改为**合并**:`grep -qxF` 去重后追加进 `DOMAINS` ⇒ 一会话一把锁、多域累积 |
| ④ | **`trap` 单槽位互相覆盖** | bash 的 `trap` 是**单一槽位** —— 先后注册两次,后者**覆盖**前者 ⇒ 必然漏清一样(实测漏 `.gate`) | 收敛成**一个**清理函数同时清所有临时物,全流程**只注册一次**;⚠️ 且**只清自己创建的**(`.gate` 可能是别人的,无脑删 = 破坏别人的互斥) |
**⚠️ 验收 hook 的前置条件(⛔ 不满足就是在测错的东西)**:`_is_mine()` 要求**会话名与 `session_id` 同时吻合**。
若不设 `DSH_SESSION_NAME`,且多把锁写着**同一个** `CODEBUDDY_SESSION_ID`(同进程批量抢锁必然如此)⇒ 会话名为空 ⇒ 退到「只看 id」分支 ⇒ **把别人的锁认成自己的** ⇒ 假绿放行。
⇒ **验收必须带 `DSH_SESSION_NAME=<会话名>`**,并覆盖四场景:我改我域(放行)/我改他域(拦)/他改我域(拦+报精确冲突域)/他改他域(放行)。
**⚠️ 收尾顺序铁律**:`--release-exec "<会话名>"` **必须放在所有文件改完之后**(⛔ 不带会话名 ⇒ 拒绝释放)。
提前放锁后再改文件 ⇒ 被 hook 以「没有持任何锁」**硬拦**(fail-closed,按设计工作)⇒ 只能重新抢锁再改。
⇒ **改完 → 回归 → 才放锁**。
---
## 5. 实测成本基线(2026-09-17 链条,连续 8 棒零断链)
| 棒 | 工具调用 | 积分 | 时长 | 用户轮数 |
|---|---|---|---|---|
| 序③ 规划棒 | 50 | 6.66 | 10 min | 1 |
| 序③ 执行棒 | 215 | 24.65 | 41 min | 4 |
| 序④ 规划棒 | 56 | 5.73 | 6 min | 1 |
| 序④ 执行棒 | 123 | 24.90 | 23 min | 3 |
| 序⑤ 规划棒 | 25 | 7.66 | 3 min | 1 |
| 序⑤ 执行棒 | 121 | 28.76 | 21 min | 1 |
| 序⑥ 规划棒 | 23 | 9.35 | 7 min | 1 |
| 序⑥ 执行棒 | 93(进行中) | 22.99 | — | 9 |
**对比旧形态**(同一条线、09-16「一条 prompt 跑完整条线」):
| 会话 | 工具调用 | 积分 | 时长 |
|---|---|---|---|
| 规划覆盖网络任务落地步骤 | 207 | 77.99 | 195 min |
| R3 接续(relay 常驻 + via 切换) | 641 | 72.22 | 166 min |
⇒ **规划棒成本降到旧形态的 1/8–1/12**;且**每棒用户轮数 ≈ 1**(只有 automation prompt 本身,**零人工介入**)。
**取数方法**(已固化为脚本,一条命令出全套):
```bash
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" \
"E:/ProgramData/.workbuddy/skills/dsh-workflow/references/dsh-auto-handoff-chain/chain_report.py" "<线名关键词>"
```
它一次输出四段:① 逐会话(工具调用 / 积分 / 积分每次 / **Skill 调用数** / top tools)② 合计 ③ **自动化运行链 + 断链检测**(相邻两棒间隔 > 90 min 报 ⚠️)④ **过期但仍 ACTIVE 的一次性 automation 清单**。
> **实测判读**:09-17 那 8 棒间隔全部 < 90 min(**零断链**);09-16 旧形态连续 6 次 > 90 min(人工介入、无接力)。
底层口径(要自己写脚本时看这三行):
- 会话/自动化元数据 → `<配置目录>/workbuddy.db`(`sessions` / `automations` / `automation_runs`)
- 逐次积分 → 转录 `projects/<工作区目录名>/<sid>.jsonl`,**递归**收集 `rawUsage` 节点求和(⛔ 顶层 `d.get("rawUsage")` 抓不到)
- 工具调用次数 → 同一份 jsonl 里数 `type == "function_call"` 的行;文本元素类型是 `input_text` / `output_text`(⛔ 不是 `"text"`,写错会静默抽空)
---
## 6. 落地清单(新开一条接力链时)
1. **建入口文件** `接续入口_<线名>_<日期>.md`,把状态、定序、`§2「🎯 本轮动作」块` 写进去
2. **让 `state.py` 能读到它**(状态脚本的 `[入口]` 段只展开入口 §2 ⇒ 钉在 §2 顶部才有用)
3. **定序**:把整条线切成「规划棒①→执行棒①→规划棒②→…」,并**写进入口 §0**
4. **起第一棒**:`automation_update` 建一次性 automation(`+5~8 分钟`,见 §3.1.1 铁律①),prompt 照 §2 骨架填
5. **每棒收尾走完 §3 四件套**(其中第 ② 件**前置 §3.3 登记门禁**)—— 推进 §2 + 登记下一棒;🔴 **同一时刻只挂一个接续棒**:本棒只排"下一棒"一棒,后续项写进入口 §2 的「⏭️ 本线下一项」行,⛔ 不预登记队列
6. **收口**:链条跑完时,最后一棒不登记下一棒,改为**明确告知用户"链条已完结"**
7. **遇到拍板点**:停在「待你拍板」节(候选竖排 + 优缺点 + 我的倾向),**并明确告知链条已暂停**;等用户拍板后再由当时会话登记下一棒
---
## 变更历史(原 frontmatter · 逐字保留)
```text
name: dsh-auto-handoff-chain
description: 长任务「多棒自动接力」编排法 —— 把一个跨越多个上下文窗口的大任务,拆成「规划棒 ↔ 执行棒」交替的一次性自动化链条,每棒做完自动开新会话接下一棒,**全程零人工点击**。当用户说「自动新建会话接续处理」「接力跑下去」「多步骤任务自动推进」「跑完一棒自动接下一棒」「无人值守推进」,或一个任务预计要跨 ≥3 个会话 / 超过一个上下文窗口时触发。核心 = 六件套 prompt 骨架(状态单点 → 唯一执行依据指针 → 全局锁 → 单一动作 → 成本纪律 → 收尾四件套)+ **登记门禁(★要拍板的,等拍了再登记 —— 用户 2026-09-17 明令,当天已有实测事故 §3.3.1)** + 七条实测防护(断链 / 双开 / once 不转完成态 / 跨过拍板点 / **下一棒定太晚** / **预登记多个接续棒** / **重挂改时间不触发(必须新建)**,排期纪律见 §3.1.1)⛔ **投单 ≠ 启动(§3.1.3,09-28 实测)**:给别条线投工单+在对方入口加指针**不产生任何动作**(automation 全表无一条 `cwds` 指向那个工作区 ⇒ 对方**当天零活动**,用户直接问「为什么另一个会话还没开发 app」)⇒ 必须**补一条 `once` automation**,`cwds` = 对方工作区;⚠️ 投单前还要核对对方「开工依据」是否仍成立(实测其依赖目录已删),并在棒里写明「局面已变、⛔ 别按旧入口办」。+ 实测成本基线 + 复跑脚本 `scripts/chain_report.py`。⛔ **排期两条铁律(§3.1.1,用户 2026-09-18 明令)**:首个(唯一)接续棒 = 收口 + **5~8 分钟**;**同一时刻只挂一个接续棒**,下一棒由当棒收官时再排。⛔ 两条关键判据:**prompt 里绝不抄任务细节**(细节只有一个漂移源 = 入口文件的「本轮动作」块);**先判「是不是要拍板」,未命中才轮到「候选排不排得出优劣」—— 顺序颠倒就会自我扩权**。⛔ **工作区归属五律(§0.6,2026-09-21 立①②③④ / 09-22 加⑤)**:入口文件只允许一份(在本工作区)· automation 的 `cwds` = 本工作区 · 参考接续规则用本技能(不是去读别的工作区的文档)· 🔴 **`cwds` 只写「与手动开会话的 cwd 逐字同形」(判据 = 实测 `sessions.cwd`;**当前正解 = `E:/ProgramData/AIProject/ai1net-dsh-server` 正斜杠**)**(`E:/…` ✅/`E:\…` ❌/`e:\…` ❌ ⇒ 同一工作区在 UI 里裂成**两个同名会话分组**,实测 09-22 斜杠方向、09-23 盘符大小写**两次**;⚠️ 宿主去重键 = `path.trim().toLowerCase()` ⇒ **只小写、不统一斜杠**(09-24 代码级实证))。⛔ **同名镜像目录(§0.6 戊类 · 2026-09-27 实测)**:入口文件同时存在于**两个真目录**(如 `…\ProgramData\…` 与 `…\ProgramDSH\…`)⇒ ① 同一工作区裂成**两个同名会话分组** ② dsh 锁按 `--ws` 分命名空间 ⇒ **两处各一把锁、互不排斥(等于没上锁)** ③ 入口分叉;判据 = **磁盘上到底有几个同名目录** + 实测 `sessions.cwd` 条数最多那条;⚠️ 镜像目录**不是空壳**(可能有 `bundles\`/`docs\` 骨架),⛔ 别看入口里的「来源会话/资产路径」判工作区。⛔ **己类(2026-09-27 实测)**:入口里声明的**工作区路径在磁盘上不存在**(搬家或改名后只搬目录、没回填入口头部 —— 实测 `session-mechanism-laya` vs 真实目录 `ai1net-decision-laya`,入口内 33 处旧名)⇒ 照抄登记会**凭空新建一个幽灵分组**;判据 = 把入口里每处「工作区 = …」逐条 `Test-Path`,任一不成立即以**实测 `sessions.cwd`** 为准回填(⛔ 别按旧名去新建目录)。🔴 **「停维护」≠「停用」**:定完唯一权威后,旧侧那份入口**必须就地改成跳转 stub**,只写一句"旧副本停维护"**不算收口** —— 实测两条线因此各留了一份完整旧正文,当晚已分叉(24903 B vs 23816 B)。全库自查一条命令:`scripts/audit-mirror.py`。🔴🔴 **活库改动铁律(§0.7,09-23 血教训)**:⛔ **「必须停 app」只对文件级操作成立**(`cp`/`mv`/覆盖 WAL 三件套会 salt 错位 ⇒ `file is not a database`);**SQL 变更不需要停 app**(执行者永远跑在 app 里,要求停 = 死锁 —— 09-23 22:0x 已纠偏,见 §0.7)—— 改数据**只走 SQL 连接**、备份走官方 `Connection.backup()`/`VACUUM INTO`、改完必跑 `integrity_check`。
version: 1.7.5
updated_at: 2026-09-28
created_from: 覆盖网络线 19 个会话(2026-09-16 ~ 09-17)的实测复盘 —— 09-17 链条连续 8 棒零断链,规划棒成本降至旧形态的 1/8–1/12
last_change: 【2026-09-28 跨线交付漏排棒】新增 §3.1.3「**投单 ≠ 启动**」—— 实测:给另一条线投了对接单+在对方入口加了指针,但 automation 全表**无一条** `cwds` 指向该工作区 ⇒ 对方**当天零活动**(用户直接问「为什么另一个会话还没开发 app」)。判据 = 投完单必自问「**这张单由谁、在什么时候读**」;没有 automation 就**立刻补一条 `once`**(`cwds` = 对方工作区)。附两条:① 投单前要核对对方「开工依据」是否仍成立(实测其依赖的宿主目录**已被删除** ⇒ 拉起来也会失败,须在棒里写明「局面已变、⛔ 别按旧入口办」);② 跨线排棒若涉及**自己正在用的文件** ⇒ 设成「**只读核对 + 出结论**」防两条线撞车。此前 09-27:【2026-09-27 全库体检】加 §0.6「己·入口声明的工作区与磁盘不符」成因类(→ 六类)+「停维护 ≠ 停用」铁律(旧侧入口必须改 stub)+ 实测事故·四(全库 7 工作区扫描,附复跑脚本 `scripts/audit-mirror.py`)。
agent_created: true
```
---
## 变更历史(**对侧副本** frontmatter · 逐字保留 · 来自 `dsh-auto-handoff-chain` 的 文档库 版)
> ⚠️ 本档正文取自**另一侧**(超集);此处补上对侧副本的版本史,⛔ 以保证不丢任何事实。
```text
name: dsh-auto-handoff-chain
description: 长任务「多棒自动接力」编排法 —— 把一个跨越多个上下文窗口的大任务,拆成「规划棒 ↔ 执行棒」交替的一次性自动化链条,每棒做完自动开新会话接下一棒,**全程零人工点击**。当用户说「自动新建会话接续处理」「接力跑下去」「多步骤任务自动推进」「跑完一棒自动接下一棒」「无人值守推进」,或一个任务预计要跨 ≥3 个会话 / 超过一个上下文窗口时触发。核心 = 六件套 prompt 骨架(状态单点 → 唯一执行依据指针 → 全局锁 → 单一动作 → 成本纪律 → 收尾四件套)+ **登记门禁(★要拍板的,等拍了再登记 —— 用户 2026-09-17 明令,当天已有实测事故 §3.3.1)** + 七条实测防护(断链 / 双开 / once 不转完成态 / 跨过拍板点 / **下一棒定太晚** / **预登记多个接续棒** / **重挂改时间不触发(必须新建)**,排期纪律见 §3.1.1)+ 实测成本基线 + 复跑脚本 `scripts/chain_report.py`。⛔ **排期两条铁律(§3.1.1,用户 2026-09-18 明令)**:首个(唯一)接续棒 = 收口 + **5~8 分钟**;**同一时刻只挂一个接续棒**,下一棒由当棒收官时再排。⛔ 两条关键判据:**prompt 里绝不抄任务细节**(细节只有一个漂移源 = 入口文件的「本轮动作」块);**先判「是不是要拍板」,未命中才轮到「候选排不排得出优劣」—— 顺序颠倒就会自我扩权**。⛔ **工作区归属五律(§0.6,2026-09-21 立①②③④ / 09-22 加⑤)**:入口文件只允许一份(在本工作区)· automation 的 `cwds` = 本工作区 · 参考接续规则用本技能(不是去读别的工作区的文档)· 🔴 **`cwds` 只写「与手动开会话的 cwd 逐字同形」(判据 = 实测 `sessions.cwd`;**当前正解 = `E:/ProgramData/AIProject/aliyun-dsh-server` 正斜杠**)**(`E:/…` ✅/`E:\…` ❌/`e:\…` ❌ ⇒ 同一工作区在 UI 里裂成**两个同名会话分组**,实测 09-22 斜杠方向、09-23 盘符大小写**两次**;⚠️ 宿主去重键 = `path.trim().toLowerCase()` ⇒ **只小写、不统一斜杠**(09-24 代码级实证))。🔴🔴 **活库改动铁律(§0.7,09-23 血教训)**:动 `workbuddy.db` 前**必须先确认 app 已关**,⛔ **禁止文件级 `cp`/`mv`**(WAL 三件套会 salt 错位 ⇒ `file is not a database`),改数据**只走 SQL 连接**、备份与恢复**都三件套一起**、改完必跑 `integrity_check`。
version: 1.7.2
updated_at: 2026-09-23
created_from: 覆盖网络线 19 个会话(2026-09-16 ~ 09-17)的实测复盘 —— 09-17 链条连续 8 棒零断链,规划棒成本降至旧形态的 1/8–1/12
last_change: 【2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v1.5.0);正文与历史中的版本号为当时记录,未改动。
agent_created: true
```