# 多棒自动接力编排法(规划棒 ↔ 执行棒) > **归属**:技能 `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 ``,约 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`(保「同一时刻只挂一个」);⛔ 别用改时间重排。取证方式 = 运行记录形如 `:`,去 `E:/ProgramData/.workbuddy/logs/daemon.log` 里按 `:<期望时间戳前 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/<工作区目录名>/.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 ```