文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。
IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。
插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。
仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
35 KiB
name, description, version, updated_at, created_from, last_change, agent_created
| name | description | version | updated_at | created_from | last_change | agent_created |
|---|---|---|---|---|---|---|
| dsh-auto-handoff-chain | 长任务「多棒自动接力」编排法 —— 把一个跨越多个上下文窗口的大任务,拆成「规划棒 ↔ 执行棒」交替的一次性自动化链条,每棒做完自动开新会话接下一棒,**全程零人工点击**。当用户说「自动新建会话接续处理」「接力跑下去」「多步骤任务自动推进」「跑完一棒自动接下一棒」「无人值守推进」,或一个任务预计要跨 ≥3 个会话 / 超过一个上下文窗口时触发。核心 = 六件套 prompt 骨架(状态单点 → 唯一执行依据指针 → 全局锁 → 单一动作 → 成本纪律 → 收尾四件套)+ **登记门禁(★要拍板的,等拍了再登记 —— 用户 2026-09-17 明令,当天已有实测事故 §3.3.1)** + 七条实测防护(断链 / 双开 / once 不转完成态 / 跨过拍板点 / **下一棒定太晚** / **预登记多个接续棒** / **重挂改时间不触发(必须新建)**,排期纪律见 §3.1.1)+ 实测成本基线 + 复跑脚本 `07-scripts/chain_report.py`。⛔ **排期两条铁律(§3.1.1,用户 2026-09-18 明令)**:首个(唯一)接续棒 = 收口 + **5~8 分钟**;**同一时刻只挂一个接续棒**,下一棒由当棒收官时再排。⛔ 两条关键判据:**prompt 里绝不抄任务细节**(细节只有一个漂移源 = 入口文件的「本轮动作」块);**先判「是不是要拍板」,未命中才轮到「候选排不排得出优劣」—— 顺序颠倒就会自我扩权**。⛔ **工作区归属五律(§0.6,2026-09-21 立①②③④ / 09-22 加⑤)**:入口文件只允许一份(在本工作区)· automation 的 `cwds` = 本工作区 · 参考接续规则用本技能(不是去读别的工作区的文档)· 🔴 **`cwds` 只写反斜杠+大写盘符的原生长路径**(`E:\…` ✅/`E:/…` ❌ ⇒ 同一工作区在 UI 里裂成**两个同名会话分组**,实测 09-22)。🔴🔴 **活库改动铁律(§0.7,09-23 血教训)**:动 `workbuddy.db` 前**必须先确认 app 已关**,⛔ **禁止文件级 `cp`/`mv`**(WAL 三件套会 salt 错位 ⇒ `file is not a database`),改数据**只走 SQL 连接**、备份与恢复**都三件套一起**、改完必跑 `integrity_check`。 | 1.7.1 | 2026-09-21 | 覆盖网络线 19 个会话(2026-09-16 ~ 09-17)的实测复盘 —— 09-17 链条连续 8 棒零断链,规划棒成本降至旧形态的 1/8–1/12 | 【2026-09-22 按要求统一版本号】frontmatter `version` → `1.0.0`(原 v1.5.0);正文与历史中的版本号为当时记录,未改动。 | true |
dsh-auto-handoff-chain — 多棒自动接力编排法
一句话:不要用「一条 prompt 跑完整条工作线」,而是让每一棒只做一件事,做完自己把下一棒排上。
用户原话(2026-09-17 立项):
「每次执行完毕后 自动根据方法决策 自动新建会话接续处理,在处理多步骤任务的时候非常好」
0. 与其他技能 / 文档的分工
| 谁 | 管什么 |
|---|---|
| 本技能 | 怎么把长任务排成一条自动接力的链条(编排层) |
dsh-change-workflow |
单次改造怎么落地(六阶段) |
dsh-decision-method |
单次取舍怎么定得对(规划棒应加载) |
工作区 会话接续规范_20260916.md |
上下文超限时的接续包 v2 模板 / 开机四步 / 多线并行(本技能是其"多棒编排"的补充,不重复它已写的接续包字段与成本公式) |
工作区 接续入口_<线名>_<日期>.md |
每一条工作线的唯一执行依据(本技能的核心依赖) |
0.6 🔴 工作区归属五律(2026-09-21 立①②③④ / 2026-09-22 加⑤ —— 治「参考别的工作区的规则 ⇒ 会话落错家」+「同一工作区裂成两个同名分组」)
背景实测:某工作区会话「参考接续会话的规则」时照抄了 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 加) |
⛔ 只写反斜杠 + 大写盘符的 Windows 原生长路径:E:\ProgramData\AI技能\aliyun-dsh-server。⛔ 不写正斜杠(E:/…)、⛔ 不写小写盘符 |
E:\… 与 E:/… 被宿主当成两个不同 cwd ⇒ UI 里同一工作区裂成两个同名会话分组(实测 aliyun 151 vs 6、capability 5 vs 4) |
律⑤ 的实测事故(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:/…)⇒ 后棒照抄 ⇒ 一路正斜杠 |
判据:cwds 里出现 / ⇒ 必错;盘符小写 ⇒ 必错。会话已经裂开 ⇒ 只能发现后清理(旧不必自动消失),⛔ 但新建 automation 一律写反斜杠。
自查一句:我这个 cwds 是不是反斜杠 + 大写盘符?(E:\… ✅ / E:/… ❌ / e:\… ❌)
律④ 的实测事故(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:/…(斜杠方向)/大小写盘符 |
目录名看着像同一个,实为两个 cwd | 新建一律反斜杠;旧份发现后清理 |
| 丙·会话标题重名 | 不同线起了同名 automation | 标题完全相同、cwd 也相同 | 改标题加线名/序号(本项目已普遍带线名,风险低) |
| 丁·cwd 落到时戳目录(律④) | prompt 没 cd,宿主按默认目录拉起 |
一堆 E:\ProgramData\WorkBuddy\<时戳> |
见律④ |
跨工作区取脚本的正确姿势(不违反律②)—— 本工作区没有脚本时,用绝对路径调、并指定 --ws:
python "E:/ProgramData/AI技能/aliyun-dsh-server/state.py" --ws "E:/ProgramData/AI技能/<自己的工作区>"
⛔ 把 state.py 复制到每个工作区 = 多一份要维护的代码,--ws 是正解(2026-09-21 加;state.py 同时新增归属校验:头部声明的 工作区 ≠ 本工作区 ⇒ 标「外来线」并排除出 §2 / 口令)。
🔎 收尾自检一句:本棒 cwds 是否 = 我这条线自己的工作区?入口文件是否只在我这个工作区存在一份?本次写入的产物是否都在该工作区路径下(律④)?
0.7 🔴🔴 活库(workbuddy.db)改动铁律(2026-09-23 血的教训 —— ⛔ 违反必炸库)
事故:为清理「同名会话分组」,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. 形态:规划棒 ↔ 执行棒 交替
规划棒①(出交接单)→ 执行棒①(照单落地)→ 规划棒②(出下一单)→ 执行棒② → …
↑ 每棒 = 一个全新会话 + 一条一次性 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 字符。越长越糟 —— 每多抄一个技术细节,就多一个漂移源。
<线名> · <第 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 回报格式。
(规划棒专属)⛔ 开工前先加载技能 `dsh-decision-method`(取舍判据以它为准,不凭记忆)。
约束:⛔ 不 commit / push;⛔ 不做 <明确点名的排除项>;⛔ 不重做 <已收官的序号>;⛔ 不扩大单子范围(单外发现的缺陷先报告、不动手)。
成本纪律:批量活先写脚本再让脚本跑;取证最多 3 条命令;⛔ 不要 Glob/Grep 全库摸底;一轮内工具调用次数尽量压低。
纪律:技术实现项**自决不上抛**;只有「没有客观优劣」的取舍才列候选,且每个候选必须写「优点 / 缺点」、**候选竖排成段**(不横排);判据必须可被第三方复现。
收尾(缺一即算未完成):① 释放锁 `--release-exec`;② **先过登记门禁(见 §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 里的内联摘要在起作用,完整方法论从未进上下文。
修法(分棒区别对待,别一刀切):
- 规划棒必须加:
⛔ 开工前先加载技能 dsh-decision-method(规划棒基数只有 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 收尾陈述句模板(实测原文,照抄)
已登记自动接续:一次性 automation `<id>`,约 5~8 分钟后(08:37)自动开新会话,不用你操作;
接续点 = 序 ④「443/TCP 兜底」的规划棒(出 `05-交接单/交接单_443兜底_20260917.md`,只出单、不改服务器)。
收口:锁 抢 ✓ → 释放 ✓(08:32)|未 commit / 未 push|入口 §0/§2 已刷到「③ 已完成 → 下一棒 ④」|产出物已交付。
⛔ 只登记不告知 = 缺陷:新建会话是用户可感知的状态变更。实测:408636f2 登记了自动化却一字未提,用户两小时后自己发现才追问。
3.1.1 🔴 排期两条铁律:首个(也是唯一的)接续棒 = 收口 + 5~8 分钟;⛔ 同一时刻只挂一个(2026-09-18 用户明令定稿)
用户 2026-09-18 原话:「首个接续任务 5-8分钟」+「最好不要建立多个接续任务,一个会话结束时在排下一个」
- 铁律①(间隔):
scheduledAt= 收口时刻 + 5~8 分钟。⚠️ 这 5~8 分钟是「从本会话收口,到那"唯一一个"接续棒开跑」的间隔 —— ⛔ 不是"棒与棒之间的间隔",后者根本不存在(由铁律②,任何时刻只该有一个待跑接续棒)。 - 铁律②(唯一):同一时刻只挂一个接续棒,下一棒只能由"正在收官的那一棒"自己排。⛔ 禁止预登记队列 / 堆叠("我先把后面两棒都排上" = 违规)。
- 后续项不会丢:把它写进入口文件 §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.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 轮,且要用户自己介入收拾。
三条留档(别再犯)
- 边界外的事,候选再明显更优也不许自己拍 —— 这次就是「A 零成本零等待」看着更优,但选项本身越界。
- 拍板项一旦上抛,本棒就不许再登记下一棒 —— 机器定时与人的拍板节奏没有同步点,不等就必然错位。
- 判「某棒是否在跑」看锁的时间戳(域锁/全局锁皆可),⛔ 不是
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 个待跑棒 ⇒ 抢锁空转;后续项要么白烧要么被撤,用户当场纠正 | 实测 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 本身,零人工介入)。
取数方法(已固化为脚本,一条命令出全套):
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" \
"E:/ProgramData/.workbuddy/08-skills/dsh-auto-handoff-chain/07-scripts/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. 落地清单(新开一条接力链时)
- 建入口文件
接续入口_<线名>_<日期>.md,把状态、定序、§2「🎯 本轮动作」块写进去 - 让
state.py能读到它(状态脚本的[入口]段只展开入口 §2 ⇒ 钉在 §2 顶部才有用) - 定序:把整条线切成「规划棒①→执行棒①→规划棒②→…」,并写进入口 §0
- 起第一棒:
automation_update建一次性 automation(+5~8 分钟,见 §3.1.1 铁律①),prompt 照 §2 骨架填 - 每棒收尾走完 §3 四件套(其中第 ② 件前置 §3.3 登记门禁)—— 推进 §2 + 登记下一棒;🔴 同一时刻只挂一个接续棒:本棒只排"下一棒"一棒,后续项写进入口 §2 的「⏭️ 本线下一项」行,⛔ 不预登记队列
- 收口:链条跑完时,最后一棒不登记下一棒,改为明确告知用户"链条已完结"
- 遇到拍板点:停在「待你拍板」节(候选竖排 + 优缺点 + 我的倾向),并明确告知链条已暂停;等用户拍板后再由当时会话登记下一棒