- 按用户指示清空原有 25 技能内容,只提交 session-mechanism(57 文件) - 附 .gitignore(产物 + 本机凭据) - 令牌明文已脱敏(历史 .neodata_token 与 pitfalls 引用均不入库) - 本提交为孤儿提交(父提交为空),历史自此重新开始
96 KiB
⚠️ 本件=原
multi-session-collab技能的SKILL.md(2026-10-01 并入本包)· 🔴🔴 2026-10-03 07:2x 新增状态块(本条最优先,⛔ 排在下面 10-01 那条之上)——「上报/投递」整套退役: ⛔ 队列投递已真删(
supervise()203→71 行、两个_deliver_str()调用点删除、wake_enabletrue→false) ⇒ 本件里凡讲投递/上报/唤醒回路/跟进会话收件的段落,读到即跳过(含 §1.4a、§0.5 里的wakeups.jsonl面板)。 ✅ 现行机制只有两条腿:接收=--report写四态台账;处理=建[检查]-…排期。 🔴 「常驻投递」这个角色名也别再用 —— 角色本来就叫「协作程序」,"常驻"是运行形态、不是角色名(P0-37)。 ⚠️ 本件 §0.5 的看板版面纪律仍然全部有效(2026-10-03 两格合一就是按它做的);⛔ 只有"图上画着投递"那部分作废。 ⇒ 冲突时以architecture.md当前结论节 +SKILL.md文首 10-03 状态块为准。 · 为什么带进来:本包references/{architecture,deploy,pitfalls,taskgraph}.md都写着「主干判据在SKILL.md §x」 —— 指的就是这一份。不带进来,包内就有 8 处悬空引用。 · 🔴 权威关系:references/architecture.md是唯一权威(其头部有「当前结论」节)。 凡本件与它冲突,一律以architecture.md为准;本件只作细则补充 + 溯源。 · 🔴🔴 本件内所有「零常驻/按需唤起/跑完即退/不需要常驻/不用常驻描述主体」的表述,一律已被推翻 (2026-10-01 定案:协作与投递一直运行(常驻);理由「可能不是所有队列都是钩子产生的」+「定时任务的方案已经废弃了」 —— ⚠️ 2026-10-03 订正:"投递"那半边已退役,⛔ 只剩"常驻程序一直运行") ⇒ 读到即跳过,现行口径见architecture.md §4/§5-1。 · 原件 mtime2026-10-01 13:56;原件全文(含 YAML 头)⇒<工作区>/归档/技能-退役-20261001/multi-session-collab/SKILL.md。
🔴 读法索引(先看这张表,⛔ 别从头顺读)
| 章节 | 性质 | 怎么读 |
|---|---|---|
| §0.5 · §0.5.0–§0.5.6(看板) | 本件独有 = 活判据 | ✅ 必读:看板总则、版面纪律、文案纪律、形状语义、改完怎么验 |
§1.3 拉取模型 · §1.4a 唤醒回路判据 · §6 自愈纪律 · §8 派活前自检 · §9 collabd.py 维护要点 |
本件独有 = 活判据 | ✅ 用到时读 |
| §3 任务图 · §4 证据分级 · §5 自动化白名单 · §2 主会话只做两件 · §7 落地三个件 | 细则 | ✅ 用到时读(§3 亦见 taskgraph.md;§5 亦见项目层 CODEBUDDY.md §F) |
| §0 五个主体 · §0.05 目标 · §1 四条通道 · §10 防打转判据 · §11 术语表 | 已被取代 | ⛔ 跳过 ⇒ 读 architecture.md 的 §1/§0.1/§3/§6/§7 |
| §11.1 命名铁律 · §12 文档权威 | 摘要 | ⚠️ 略读即可 |
⚠️ 存档说明:本件正文取自原技能
SKILL.md的主体。 原件的 YAML 头部(name/description/version/updated_at/ 逐轮last_change变更流水 /agent_created)已于 2026-10-01 删除 —— 那是技能加载器的元数据 + 发布流水,放在参考件里对使用零价值、且会随时间腐坏。 原件全文(含该 YAML 头)见<工作区>/归档/技能-退役-20261001/multi-session-collab/SKILL.md。
multi-session-collab — 主会话 × 多子会话执行机制
🔴 一句话:派活用自动化(唯一能开新会话的通道)|收结果直读宿主库(0 token)|机械判定下沉到本地只读脚本|AI 只在需要判断时被叫起|用任务图+关键路径防干等。
🔴 当前结论(先读这里 · 最后更新 2026-09-30 12:30)
⚠️ 本文件通篇是追加式写法 ⇒ 下文可能残留已被取代的内容(已就地标注)。 冲突时以「本节 +
references/architecture.md的「当前结论」节 + 两文件 §迭代记录最新条」为准,并请顺手把冲突处就地标注。
项 当前结论 唯一权威 架构 ⇒ references/architecture.md(⛔ 别处⛔ 不再写第二份)。交付物/里 4 份"多会话协同"平行件 +落地清单-唤醒回路⇒ 2026-09-30 已加退役标注,⛔ 不作用判据。运行形态 🔴 本条已订正(2026-09-30)· 原 09-29 定案「协作程序与监督程序一直运行」已废弃。实测判据(用户原话「你把监督程序停掉关了 整个流程还是照样跑」): guard自 09-29 23:59 停到现在,投递一路正常(当日 8 条http=200)⇒ 停掉常驻,流程照跑。⇒ 现形态 = 两个模式都由宿主钩子按需唤起:--once(协作程序 · 维护队列)/--tick(投递 · 唯一投递方)。⚠️ 「监督程序」这个名字已退役 —— 它从来不是「一个该常驻的进程」(用户:「跟监督程序没有半毛钱关系」)。本机起法 唯一可行 = 宿主后台任务机制 + stdout全重定向到文件。⚠️detached活不过工具调用边界;schtasks/reg/wmic/sc/wsl被内置程序黑名单硬拦。派活模板必带 ① 开工第 0 步:抢域锁(域按显式 domain 切,⛔ 不按 cwd/⛔ 不按工作区)②收尾自判 ⇒ 有缺口就接下一棒;blocked 则⛔ 不接、改喊用户。看板(三层) 🔴 只读旁路观测(用户 2026-09-30:「看板不能影响程序执行,可以异步、可以延迟」)⇒ board.py --serve:后台线程产快照,请求线程只读内存缓存(⛔ 不碰 DB/⛔ 不读文件),开几个标签页都不加宿主负载。🔴 架构图三层:用户/主会话/分工板块(按线,不是历史会话列表)/常驻程序+投递 + 宿主地基带;每格带真状态(会话取宿主库、程序取状态戳 mtime)。详见 §0.5。🔴 看板=单实例(2026-09-30 加护栏) Windows 的 SO_REUSEADDR允许同端口重复绑定且不报错 ⇒ 多个实例静默并存、同一 URL 被随机应答 ⇒ 快照/代码版本打架(实拍:已有看板时第二个照样打印"看板已起")。⇒board.py --serve起前探/healthz签名,已有 ⇒ 拒绝启动;换新代码用--takeover。停机 ⇒stop-collab.py④。详references/pitfalls.mdP0-9。已知待修 ① ✅ goals_open()已补读acceptance_state(2026-09-30)② ⚠️wake_round()已停用未删 ③ 常驻与钩子的投递唯一性未按新方案收口(现靠三重去重兜着)④ ⚠️board.py改完必须重启服务(进程内已 import,不重启就跑旧代码)⑤ ✅--once已认「已停」总闸(2026-10-01 修:goal_paused()/paused_round()+ 自测 6 项)⑥ ⚠️ "全节点 done 但验收未声明" ⇒ 已补验收判据(V1–V5)+ 日志节流 600 s;⚠️ 判据仍由对话说明(goalctl declare),⛔ 别拿本次这五条当项目常量 ⑦ ✅ 多目标 tab(2026-10-01 加:build()出goals[]+ 看板renderTabs()/paintScope();tab 第一行=完整目标名,⛔ 不许退回"只给简称")。数据源=<inbox>/goals/*.json(一个文件一个目标,活跃排第一);⛔ 没有goals/时行为与旧版逐字一致(goals[]只有 1 项)。⚠️ 台账按line ∈ 目标 topics切到各目标;不属任何目标的件归活跃目标且照常显示(⛔ 不静默丢)。改board.py记得走 ④ 的重启🔴 2026-10-01 体检新增三修 ① --once/--tick认总闸(run != active⇒ 只写一行"已停"、清告警投影、⛔ 不投递)② 主会话解析只认活会话(_pick_live)—— 实测断点:解析出一条已completed的「主控」当投递目标 ⇒ 一路main-not-live(程序在跑、通知永远发不出去);修后判读转为target-busy③ 退役线清队列:台账/任务图 18 条全是已退役的手机接入线 ⇒ 队首长期指向退役需求(已归档、按当前类别重建)🔴 唤醒主会话:只画了格,会话没建(2026-10-01 实测) 会话表里零条 [唤醒]前缀会话(带「唤醒」字样的 6 条全是completed的旧棒)⇒ 技能 §1.4a 那条"随需求确定时创建"从未执行 ⇒ 看板上「还没建」虽然按定性是正常态,但那条腿实际是空的。叠加"网关排期=死能力"(CODEBUDDY_DISABLE_CRON=1)⇒ 全员静止时零唤醒。处置:本次未擅自建(属"派活/接续"白名单外 ⇒ 待用户拍板)🔴 ⑤ 已停 ≠ 静默(2026-10-01 实测 · ⛔ 见
tmp/协作机制体检-2026-10-01.md):goal.json.run=paused时,collabd.py全文零处引用run/paused(只有wake_enable管投递) ⇒--once照跑、照写STALL.md/NEXT.md/digest.md/TO-MAIN.md,而digest.md的内容 经钩子注入每个会话的每一轮 ⇒ 一个"人为停掉"的需求被读成"机制坏了"。 正解:--once开头加总闸,run != active⇒ 只写一行"已停",⛔ 不产告警。 🔴 ⑥ 判不出完成 ⇒ 唤醒永不暂停:任务图 18/18 done、队列 0 待办,但acceptance_state只有说明行、无判据 ⇒goal_state判「判不出来」⇒goals_open()=true⇒ §1.4a ②「已完成 ⇒ 唤醒任务暂停」永不成立,TO-MAIN.md每轮照发。正解:补判据,或改成「全 done 且未声明 ⇒ 喊一次就静默」。
🔴🔴 架构正文不在本文件 ⇒
references/architecture.md=「多会话协作」的唯一架构文档(2026-09-29 用户定案)。 🔴🔴 本机制的一切迭代都在本 skill 内进行(用户 2026-09-29 明令:「都要在技能中去迭代」)—— 架构 ⇒references/architecture.md|部署与起法 ⇒references/deploy.md|踩坑 ⇒references/pitfalls.md|操作 ⇒ 本文件|代码与配置 ⇒scripts/。 ⛔ 不在工作区或别处另开平行文档;⛔ 不把机制文档散进业务件(业务件只留"用了哪条机制"的指针)。 🔴🔴 改完必跑回归自测:python scripts/selftest.py—— 全绿(rc=0)才算改完。 它把所有异常情况做成了用例(⚠️ 条数别写死 —— 会随迭代增长,grep -c '^@case(' scripts/selftest.py数一下才准: 看板与收尾确认的归属判据必须同款/投递成对重复/纯投影不消费队列/投不出不消费/ 两级命名/目标闸/台账四态/启动对账/僵尸/不显窗/假绿/会话哑掉(诊断日志撞上限)/常量完整/优雅退出 …), ⛔ 测试不碰生产(用tmp/selftest/独立工作区)。 🔴 写用例的两条纪律(2026-10-01 各踩到一次): ① "前提不成立就 SKIP"要把前提判「全」,⛔ 别靠副作用反推一半 —— 例:僵尸用例只看adopted就断言, 漏了「无 IN_PROGRESS 运行」那半 ⇒ 自动化会话跑自测时必然假红(它自己就是那条运行)。 ② 用例自造的假数据每轮清干净(放_prepare()里清),⛔ 别让上一轮的留在原地 —— 断言"精确集合"的用例会被跨轮残留多算几条 ⇒ 第二次跑才红(最难查的一型)。 📌 用例是从真实事故里长出来的 ⇒ 每踩一个新坑,先把它加成一条用例,再改代码。 · 改架构 ⇒ 只改那一份(五个主体 / 任务队列=上报制 / 唤醒=静默判据 / 四条通道 / 三种节奏 / 防打转判据 / 术语表)。 · 本文件 = 操作入口(怎么派活、怎么收尾、怎么排坑);下文出现的架构内容为就近速查,冲突一律以architecture.md为准。 · ⚠️ 【已作废 · 2026-09-30 就地标注】 此处原写"⛔ 零常驻 —— 凡见『常驻程序』字样均属旧说法"。 该说法已被用户 2026-09-29 定案推翻(见上表「运行形态」:常驻程序与投递一直运行)。 ⛔ 保留此行只为拦住"翻到旧文又照做";判据一律以上表 +architecture.md §5为准。
0 第 0 步:先认清五个主体(本机制的唯一主体清单)
🔴 架构里只有这 4 个主体:用户 · 主会话 · 任务会话 · 常驻程序。 🔴 「主会话」是解析出来的,不是登记出来的(2026-09-30 用户定则:「以 一个工作区 为主会话的工作区」): 登记(
roles[sid]=="main")只在该会话确实活着时才有效;失效则按工作区重新认 (cwd == 本工作区且标题不带[协作]),仍认不出 ⇒ ⛔ 拒绝盲投、报no-main-session并喊用户。 ⛔ 曾有「目标不在活会话里就随手取第一条口」的盲选回落(=零判据投递,会投错窗口),已删除。 判据唯一实体=resolve_main();完整分析见references/architecture.md §7。🔴 命名定则(2026-09-30 用户):主会话 / 接续会话的标题前缀 =
主控,形如主控 · <任务类别> · <具体>—— 例:主控 · 机制线(查唤醒为什么断)。 · ⛔ 中段写「任务类别」,不是目标名(用户原话:「而且后面也不叫 手机接入」); · 🔴 「任务类别」是同一个词的四处(⛔ 别当成四件事):goal.json.topics的一项 ≡ 会话标题里的<任务类别>≡ 台账条目的line≡ 分工板的一行。⛔topics缺省才回落short。 · 🔴 同一工作区可有多个任务类别(2026-09-30 用户:「之前的协作会话是跨工作区的, 要能支持同一个工作区 多会话协作(…通过协作会话名称前缀区分具体执行会话, 所有主会话,协作会话,自动唤醒任务,都在一个工作区)」)⇒ 每个类别各有自己的主会话;投递按件的类别选主会话(resolve_mains()+main_for_topic())。 ⛔ 自动唤醒任务的排期名也必须带类别前缀([协作]-<类别>-<具体>)(⚠️ 仅代偿形态下适用;定案时钟=常驻投递,不开会话、无排期名)—— 实测踩到:[协作]-唤醒A · <工作区>缺第 2 级 ⇒ 解析不出类别。 全文 ⇒references/architecture.md §2.3.0。 · ⛔ 主会话的接续会话不许带[协作]前缀 ——[协作]是协作棒的标记,带上它会被本机制 当成棒排除掉(我犯过:把接续会话建成[协作]-[手机接入]-接续…⇒ 自己排除自己); ·主控前缀是显式标记 ⇒resolve_main()优先认它(比"最近活动"可信:那是用户自己标的,不是猜的); · ⚠️ 实测坑:网关POST /api/v1/sessions/{id}/rename回 204 但不落宿主库(显示名与库标题可能是两套) ⇒ 改名后必须回读sessions.title确认,⛔ 别只看 204。 ⚠️ 2026-09-30 订正:原来的第 5 个主体「投递」已退役 —— 它其实只是同一个collabd.py的--tick模式(投递),⛔ 从来不是一个独立进程。 ⚠️ 宿主(WorkBuddy 本体)是地基,不是第 6 个主体;它只提供四样:状态(只读 3 表)/调度(自动化排期 = 唯一能开新会话的通道)/事件(钩子)/互斥(域锁)。 🔴 ⛔ 不用"角色"这个词(历史文档里"三个角色"的说法已被本表「主体」取代,见 §12);也 ⛔ 不用"常驻"描述任何主体 —— 本机制的设计前提是零常驻。 ⚠️ 但「主体」与「会话类别」是两根不同的轴,⛔ 别混: · 本表(§12)= 主体 —— 谁负责什么(含用户与两个程序:常驻程序/投递)。 · 会话类别 = 四类(2026-10-01 用户口径)—— ① 主会话 ② 协作会话 ③ 唤醒会话 ④ 队列上报的跟进会话, 同工作区、靠标题两级前缀区分 ⇒ 见collab.md §4=architecture.md §2.3.0b(接续=形态,⛔ 不是类别)/§2.3.0c(第④类,未落地)。 ⚠️ 已知缺口:第 ③④ 类尚未列进本表(本表是"主体"视角,唤醒会话/跟进会话都是会话,落点在会话类别那根轴上)。
| # | 主体 | 只做什么 | ⛔ 不做什么 | 与谁接口 |
|---|---|---|---|---|
| 1 | 用户 | 🔴 职责(2026-09-30 用户原话):制定目标 · 调整方向 · 做决策。可做的动作:看板 · 放行授权 · 改目标 · 喊停 | ⛔ 不管过程;⛔ 不必催进度 | → 主会话:需求/判据/授权 |
| 2 | 主会话 | 判断 + 派活:读库+看板判缺口 ⇒ 写一行排期;收尾自判 | ⛔ 不替别线干活;⛔ 不做机械判定;⛔ 不搬砖(除非最靠前那步自己就能做) | → 宿主:一行排期;→ 用户:看板/待授权 |
| 3 | 任务会话 | 一棒一线,一次一件:做本棒;收尾两件=写产出 + 判本线缺口接下一棒 | ⛔ 不跨线;⛔ 不夹带;⛔ 不常驻 | → 宿主:运行结论(自动落库) |
| 4 | 目标检查 | 持有需求台账;收上报/判定/告警/体检/看板覆写/单例(--once 投影轮) |
🔴 ⛔ 不派活(不写排期、不开会话);🔴 ⛔ 不投递、⛔ 不推进队列(mutate=False) |
→ 用户:看板;→ 主会话:机械摘要 |
| 5 | 投递(⚠️ 旧名「监督程序」,2026-09-30 退役。它是同一个 collabd.py 的 --tick 模式,⛔ 不是一个独立主体/进程) |
逐条读队列 ⇒ 投给主会话(单条+握手/三条件唤醒)⇒ 唯一的投递方+唯一的队列推进方 | ⛔ 不开会话;⛔ 不派活;⛔ 口令不落盘/不进日志/不回显 | → 主会话:通知 |
🔴 判定你是哪一个:你是被派出去干一件具体事的那个 ⇒ 任务会话;你是"入口"、负责判缺口与派活 ⇒ 主会话;你是脚本 ⇒ 投递(只读只判)或目标检查(其余全部,但不派活)。
0.05 🔴🔴 目标不是固定配置 —— 它在「调用本技能的那次对话」里说明(2026-10-01 用户订正)
用户原话:「目标是 通过对话在调用 会话协作skill时说明的,不是固定的」
这条规矩治的是本技能 2026-09-30 犯过的一个错:把 goal.json.topics 按工作区里现有的
接续入口/接续包文件名填了 4 类,还当成"待拍板事项"去问用户要不要定死。
⇒ 那是猜,不是说明;而且一旦写进 goal.json,机制就会照它跑(判归属、选主会话)——
猜错了不会报错,只会静默漏管/错投。
| 要说明什么 | 落到哪 | 谁来说 |
|---|---|---|
| 目标(一句话说清要做成什么) | goal.json.title |
用户(对话里) |
| 为什么 / 边界 | goal.json.why |
用户(对话里) |
| 任务类别(同一工作区靠它分前缀) | goal.json.topics |
用户说 / 主会话梳理 |
| 验收判据 | goal.json.acceptance_state |
用户说 / 主会话提 |
🔴 三条硬规矩
- 技能侧与脚本 ⛔ 不许预设目标与类别,⛔ 更不许从目录名/文件名/接续入口名去"推" —— 推出来的东西会静默决定「哪些会话算本项目、投递往哪条主会话去」。
- 登记有唯一落点(="说明"这个动作的唯一出口):
⛔ 默认干跑;
python .workbuddy/collab/goalctl.py declare --title "…" [--why "…"] [--topics "A,B"] [--kpi "V1=pass"] --yes--title必填(脚本不替你编目标);省略某个参数 ⇒ 不动那一项 (⛔ 不拿旧值凑数);--topics ""⇒ 显式清空(回到单类别回落)。 - 可以被覆盖 —— 下次调用技能时在对话里再说一遍 ⇒ 用
declare覆盖即可; ⛔ 别把上一次说明的目标当成项目常量(这正是出错的根源)。
⚠️ 未说明时的行为(⛔ 不许静默):topics 缺省 ⇒ 回落 goal.short(单类别,向后兼容),
但看板必须显式标出「⚠ 未声明 ⇒ 暂回落目标简称」,⛔ 不许让人以为"这就是定下来的类别"。
落点:board.py::_goal_topics_source() → project.topics_source.kind ∈ {declared, fallback, none}
(declared 时一并显示说明时间/说明人)。回归用例:selftest.py::看板:任务类别来源必须说得出口。
0.5 🔴 看板(scripts/board.py + assets/board.html)
0.5.0 🔴🔴 「看板」默认指那个实时动态看板 —— 它是要起着的(2026-10-01 用户点破)
用户原话:「我说的看板是 实时动态看板 现在被关闭了」
⚠️ 别用"一份离线快照页"去顶 —— 快照只是复核手段(见 §0.5.4),用户要看的是活着的那一个。
| 项 | 值 |
|---|---|
| 地址 | http://127.0.0.1:8788/(⛔ 只绑回环) |
| 起法 | 会话后台任务 + stdout 落文件(本机唯一可行的起法) |
| 命令 | COLLABD_CONFIG=<ws>/.workbuddy/collab/collabd.config.json DSH_COLLAB_WS=<ws> "<python>" "<技能>/scripts/board.py" --serve 8788 --takeover > <ws>/tmp/board-serve.out.log 2>&1 |
| 换代码重起 | 加 --takeover(先停旧的再接管)。⛔ 别裸起第二个 —— Windows SO_REUSEADDR 会让两实例静默并存、同一 URL 随机应答("改了、也重启了、却还是旧的"就是这么来的)。单实例护栏:已有看板 ⇒ 拒绝启动 |
| 改完必须重启 | ⚠️ 服务进程已 import 技能里的 board.py ⇒ 改快照内容后不重启就跑旧代码 |
| 停机出口 | .workbuddy/collab/stop-collab.py(dry-run 只报告)|goalctl stop --yes 会顺带停它 |
⚠️ 两条如实登记的边界(⛔ 别当没这回事)
- 它是会话后台任务 ⇒ 关会话/关宿主就停。本机没有真正的常驻手段(detached spawn 活不过
工具调用边界;
schtasks/reg等持久化工具在内置程序黑名单里)⇒ 这不是"忘了常驻",是做不到。 - ⚠️ 该会话只要挂着
pending/running的后台任务,宿主的idle钩子会被静默压制 (2026-09-30 实测被一个僵尸任务压过 6h20m)⇒ 起看板的那个会话,其唤醒/投递回路会哑。 ⇒ 正解=让"起看板"这个动作落在一个不承担派活职责的会话里,⛔ 别让主会话干这件事。
0.5.1 铁律:看板不能影响程序执行(用户 2026-09-30 明令)
用户原话:「看板不能影响程序执行,可以异步 可以延迟」。
三条落地(照这个改,⛔ 别把看板做成"第二个常驻程序"):
| # | 约束 | 做法 |
|---|---|---|
| ① | 解耦 | 常驻程序/守护程序一行都不引用 board.py(可用 grep -rn "board" scripts/*.py 复核);board.py 从不写任何账本(只写 board.json,且仅在 CLI 模式)。 |
| ② | 异步 | --serve 起一个后台线程按 --interval(默认 3 s)产快照 ⇒ 请求线程只吐内存缓存字节(⛔ 不碰 DB、⛔ 不读文件)⇒ 宿主库查询频率恒定 1/interval,与标签页数量无关。页面轮询节奏跟随 board.refresh_interval(⛔ 不自行加频)。 |
| ③ | 降级不静默 | 宿主库只读连接 + busy_timeout=300(撞写锁 0.3 s 就放弃,⛔ 不排队);任一块读不到 ⇒ 记进 board.json.warn 并在界面顶部黄条显示,⛔ 不伪装成"0 个会话"(那是假情报)。刷新失败 ⇒ 保留上一份快照,界面显示「数据延迟 N 秒」。 |
⚠️ 改完 board.py 必须重启看板服务(进程内已 import,不重启跑的还是旧代码)。⚠️ 页面每请求重读 board.html ⇒ 只改 HTML 不用重启。
⚠️ 起服务时把 stdout 重定向到文件(> tmp/board-serve.log 2>&1)—— 会话后台任务的每轮 stdout 会唤醒宿主会话。
0.5.2 架构图三层布局(用户 2026-09-30 指定)
🔴🔴 2026-10-01:第三层语义又变了一次(第三次)⇒ 本节正文里"分工板块(按线)"已作废。 用户原话:「主会话 下面 那一排只放协作会话,横着排 有几个放几个,当前没有对应协作会话 就空着」 +「放一个空的框 说明 暂无协作会话」+「唤醒机制如果是主会话的 事就放到主会话框里去」。 ⇒ 以 §0.5.6 为准(那一排 = 任务会话排;任务类别搬进主会话框)。下文保留作历史。
用户
↓ 发消息 · 看板
主会话
↓ ① 派活 · 自动化排期
分工板块(按线)…每格:最近协作任务 / 承接会话 / 件进度
↓ ② 上报 · --report
目标检查 ──队列──▶ 投递 ──③ 投递 · reply/唤醒──▶ (右侧竖井回主会话)
↑ ④ 收结果 · 直读宿主库
宿主 WorkBuddy(地基)
- 🔴 第三层是「分工板块」,⛔ 不是历史会话列表(用户:「协作会话不是历史记录,是展示分工的板块」)。
分工位 = 线(
goal.json.lines∪ 台账里出现过的线);每格展示该线最近的协作任务(用户追加要求)、 当前承接会话、件汇总。三色:有会话在跑(绿)/⚠ 有件没人在跑 = 缺口(黄,最有用的信号)/件已全完(灰)。 - 🔴 承接会话怎么认:拿"在跑会话标题里的件 id"匹配(长 id 优先,防
N1抢N10)。 ⛔ 不用 cwd 推断 —— §2.3 明令。匹配不上就如实写「当前无会话在跑」,⛔ 不硬凑。 - 🔴 会话归属判据(本项目 vs 别的项目):
board.py与collabd.py各有一份in_project()/_in_project(), 必须逐字同款 —— 一旦漂移就会出现「看板说没人跑、--ready-next却被别的项目拦死」。 已有回归用例selftest.py::归属判据:看板 ≡ 收尾确认逐例比对(⛔ 别删)。 - 🔴 常驻程序节点带「队列计数」(用户 2026-09-30 追加要求):队列 = 需求台账
tasks.json(不是queue.json,architecture.md§迭代记录明载)⇒ 显示队列 N 件 · 已完成 X · 未完 Y(· 受阻 Z),四态pending/running/done/blocked。 ⚠️ 机制里没有「待验收」这个态 —— 用户问「待验收队列数量」时按四态如实给数,⛔ 不臆造一个数字; 要不要真加一道「验收」关口,属机制变更,待用户拍板。 - ⚠️ 【本条已作废 · 2026-09-30】 原文讲「投递显示『已停』是正常的」—— 前提已不存在:该名字已退役,看板不再显示「已停」(见 §0.5.2b/§11.1)。以下原文仅作历史:
- ~~🔴🔴 投递显示「已停」是正常的、有据的止损,⛔ 别当故障去"修"(出处
.workbuddy/memory/2026-09-29.md2424–2435 行 +architecture.md §5-1,代码常量board.py::GUARD_STOP_REASON): 2026-09-29 23:59 主会话guard.py --stop主动停 —— 守护当时是用「会话内后台任务」起的 ⇒ 任务归属发起会话 ⇒ 该会话被判定"一直挂着长跑任务" ⇒ 实测「一启动会话就卡消息输出」。 🔑 根因 =「有网关口令」与「不占会话」不可兼得。正解 = 拆两件:「发现」走会话外常驻(启动文件夹/独立窗口, ⛔ 但拿不到口令)、「投递」走宿主起的通道(钩子--tick/ 低频排期 —— 宿主起的子进程天生有口令)。⇒ 现投递已由钩子🔴 2026-10-02 标注(⛔ 不是口径变更):这段已作废 —— 用户 2026-10-01 已拍板:「协作与投递一直运行(常驻)」+「定时任务的方案已经废弃了」⇒ 常驻必须回来(见 §5-1)。⛔ 别再把上面那句当现状读。--tick事件驱动,守护常驻按设计不再需要;⚠️ 代价 = 没有独立唤醒时钟 (即用户点破的「心跳成摆设」)—— 此点仍待用户拍板是否恢复常驻。
0.5.2b 🔴 看板版面纪律:小字描述只进右上角 ?
用户 2026-09-30:「把各板块的小字描述放到各板块对应右上角 ? 号图标中,鼠标移上去显示
(只保留标题,主体,类别标签这类信息)」,并点名删除三处。
| 版面只留 | 进 ?(.qtip) |
|---|---|
标题(h2)· 主体(表格/chips/架构图)· 类别标签(h2 .hint,如 tasks.json/组件状态) |
该板块的说明性小字(为什么这样、判据是什么、口径提醒) |
- 实现:
.qh(右上角圆点?)+ 内嵌.qtip,CSS:hover / :focus-visible才显示 ⇒ 纯 CSS,零 JS; 用<span class="qh" tabindex="0">⇒ 键盘也能看。 - 🔴 动态的长解释也要进来(如「投递为什么是停的」)—— 做法:给
.qtip一个 id, 每轮渲染先tip.innerHTML = tip.dataset.base(首轮存静态原文)再按条件追加,⛔ 防重复追加。 - ⛔ 别再往版面摊解释性小字;警告/读数这类主体内容不算小字,可以留在版面。
- 回归用例:
selftest.py::看板:版面纪律(?数量与.qtip配对 + 被点名删除的三句不得复活)。 - 🔴 「延迟」与「保留的快照时刻」是例外:必须固定显示在「协作架构」板块右上角**,⛔ 不许收进
?(用户 2026-09-30:「延迟 = 你看到的时间 −<快照时刻>保留时间 放在协作架构板块右上角」)。 表单:.herometa两行 —— 第 1 行只放快照时刻(<b id="ts2">),第 2 行放延迟 <span id="lag">。 ⚠️ 2026-09-30 用户又划掉一次:「延迟 = 你看到的时间 −」这句不要,时间下面直接写「延迟 X 秒」即可 ⇒ ⛔ 不要再加解释性前缀。由paintAge()每秒刷新,超过 15 秒整块转警示色。 ⛔ 别在 header 再放第二份时刻(已去掉)。
0.5.1b 🔴🔴 技能 / 使用方分离(用户 2026-09-30 定则)
用户原话:「技能就是技能 程序就是程序,谁用产生的文件 放在他自己那里」
技能 = 通用能力(SKILL.md / references/ / scripts/ 的代码 / assets/ / 范例配置)。
使用方产生的东西 ⇒ 放使用方自己那里:部署配置、运行日志、编译产物、看板扩展、部署启动器。
| 件 | 归谁 | 落点 |
|---|---|---|
| 部署配置 | 使用方 | <工作区>/.workbuddy/collab/collabd.config.json(技能里⛔ 不放;只有 collabd.config.example.json 范例) |
| 运行日志 | 使用方 | 配置键 log(默认落在工作区内);⛔ LOG = HERE / … 是旧写法,已禁 |
| 看板「前置」块 | 使用方 | 配置键 board_ext → .workbuddy/collab/board_ext.py,契约 build(ws) -> {title,tag,chips,paragraphs,tip} |
| 部署启动器 | 使用方 | 工作区自己的 start-guard.cmd(里面写死本机 python 路径 ⇒ 属部署件) |
🔴 配置查找链(collabd.py):① 环境变量 COLLABD_CONFIG → ② <COLLABD_WORKSPACE 或 cwd>/.workbuddy/collab/collabd.config.json → ③ 都没有 ⇒ 拒跑 rc=2。
⚠️ 为什么 ③ 必须拒跑:不配配置时 workspace 会回落 cwd,而 cwd 常常就是技能目录 ⇒ 技能里长出 tmp/supervise-inbox/ 与 _collabd.log。
⛔ 连"未找到配置"那条告警都不许写文件(它自己就会造成同样的污染)⇒ 只打 stderr。
🔴 接线方(钩子/启动器)必须显式传 COLLABD_CONFIG + COLLABD_WORKSPACE;⛔ 不许做"回落同目录旧副本"的兜底 —— 那是静默换版本的经典来源。
🔴 回归用例:selftest.py::技能侧零项目串(技能目录无结构性产物/日志不挂 HERE/board.py 代码零项目串/看板不回读废弃项目键)。
⚠️ 迁移期尾巴:改完这套后,迁移前起的旧进程仍会用旧代码路径写文件(实测 wb-supervisor-watch.py --interval 10)⇒ 等它结束才彻底干净。
0.5.2c 🔴 文案纪律(去 AI 味)—— 看板 + 注入给会话的正文
用户 2026-09-30:「现在很多文案不是抓不住重点,就是描述太AI味」。 用户 2026-10-01:「会话的反馈信息排版 非常不利于阅读,改为段落排版」+ 给出目标形态: 一行 header + 其后每行「标签:一段话」。
🔴 适用范围(2026-10-01 扩写 · 原来只管看板,是个漏洞):本节纪律同时管两处 ——
① 看板板面;② 🔴 注入给会话的正文:digest.md(机械摘要)+ 三个信号文件
(STALL.md / VACUUM.md / READY.md)。为什么必须一致:后者是每轮钩子注入给每个会话的,
排版差 = 每个会话每轮都要多花注意力去拆句,是全平台共用的阅读成本;
而且用户根本分不清哪句来自看板、哪句来自注入 —— 两处形态不一致就等于纪律没落地。
⚠️ 载体:注入正文的唯一生成处 = scripts/collabd.py::digest_text()(digest.md)与 signals()(三个信号文件)。
改文案=改这两处,⛔ 别去改生成的 .md(下一轮就被覆写)。
规则来源:技能 humanizer-zh(24 类 AI 写作模式)+ agent-operating-rules §2.5。两处文案都按这两份过。
| ⛔ 别写 | ✅ 改成 | 实例 |
|---|---|---|
| 否定式排比「不是 X,而是 Y」 | 只说后半句 | 「⛔ 不是历史会话列表」→「按线分工」 |
| 金句(听着能被引用) | 说具体事实 | 「一眼看得出来」→「看格子颜色就知道」 |
⇒ 满屏(每段好几个) |
逗号 / 句号 | 「两段都在线 ⇒ 端到端未验证」→「两个组件都在线,链路还没通」 |
≠ 符号 |
写成句子 | 「在线 ≠ 通过」→「都在线,链路也可能是断的」 |
| 加粗滥用 | 只留关键词 | 每段加粗 ≤2 处 |
| emoji 装饰 | 只在真警示处留一个 | 图例里的 ⚠ 删掉(色块已表意) |
| 三段式凑数 | 有几项写几项 | 「最近任务 / 承接会话 / 件进度」→「最近做完的件、现在谁在做、还剩几件没完」 |
| 一条信息说两次 | 只留一处 | chip 只给状态(「已停」),后果写在正文段落里 |
| 解释性前后缀 | 直接给值 | 「延迟 = 你看到的时间 − 07:13:12」→「07:13:12 / 延迟 4 秒」 |
🔴 - 列表碎片 + ;/⇒ 串联短句 |
段落排版:一行 header + 每行「标签:一段话」 | 「- 🔴 **中断/脱节** —— 没有会话在跑,且 0 分钟 无成果;⛔ 未来 1 小时内零排期 ⇒ 不等人发消息就是**确定性静默**」→「状态:没有任何会话在跑,而且已经 3 分钟没有出新成果。未来 1 小时内也没有任何排期,只要没人主动开口,这里就会一直静默下去。」 |
🔴 判据(写文案时的唯一一问):这是给人读的,还是给程序读的?
给人读 ⇒ 段落(完整的句子、标签打头、一行一件事);给程序读(注释 / 日志 / 解析用字段)⇒ 随便。
⚠️ 现有实现里有反面先例:verdicts() 里的 V["verdict"] 就是「; + ⇒ 串联」形态 ——
它保留给 realtime.md 与通知用,但摘要⛔ 不许直接复用它(要按段落重讲,见 digest_text()),
⛔ 也⛔ 不许对它做字符串反解(反解会随措辞改动静默失效)⇒ 事实要由 verdicts() 另带原始键(如 zero_sched / probe)。
自检(改完文案后跑一遍):搜 ⇒ ≠ 一眼看得出来 ⛔ 不是 在非注释行里的出现,应为 0。
⚠️ 判据要排除注释(// 与 /* */、Python 的 #)—— 注释是给未来的我看的,可以带标记;给人看的才是受众。
0.5.3 🔴 看板风格系统(右上角切换)
用户 2026-09-30:「画架构图参考 archify 的样式(https://github.com/tt-a1i/archify), 右上角加个风格切换(保留当前风格)」。
| 风格 | data-style |
说明 |
|---|---|---|
| 当前风格(默认·⛔ 不许删) | (无属性) / base |
原风格,跟随系统深浅色 |
| Archify 暗 | archify-dark |
令牌逐字取自 archify assets/template.html 的 dark 主题(MIT) |
| Archify 亮 | archify-light |
同上,light 主题 |
🔴 实现铁律(违反就会"换风格换出花"):
- 切换只改
<html data-style="…">—— 颜色/字体/网格全走 CSS 令牌(--color-*/--font-ui/--canvas-dot/--node-stroke-w) ⇒ 换风格不需要重画 SVG。 - ⛔ 组件与 SVG 里一律不许出现硬编码颜色(
#hex/rgba(...))—— 只许出现在令牌块里。 已有回归用例守着:selftest.py::看板:风格系统(三风格令牌在位 + 组件零硬编码色)。 - 选
base时要把data-style属性摘掉(不是设成"base"),好让它继续吃@media (prefers-color-scheme: dark)。 - 选择存
localStorage['dsh-board-style'],且在<head>里尽早套用(⛔ 别等load,否则先闪一下原风格)。 - archify 的关键令牌备忘:画布
#020617(暗)/#f4f5f7(亮);点阵rgba(148,163,184,.16)/#d9dee5; 强调色#34d399/#059669;语义色 backend#34d399、cloud#fbbf24、security#fb7185、external#94a3b8; 字体 JetBrains Mono(拉丁)+ 系统 CJK 回退 ⇒ 本地用ui-monospace, Consolas, "Microsoft YaHei"近似。
⚠️ 等宽字体的字宽与比例字体不同 ⇒ 改风格后必须重跑"文字越界 + 压框"检查(本轮三风格均 0 溢出 / 0 压框)。
- ⚠️ 图上文案宽度:中日韩字符 ≈ 1 em(13 px 字号 ⇒ 约 13 px/字),ASCII ≈ 7 px。
⛔ 别用"每字符 8.4 px"那种估法(会把标题算长一倍 ⇒ 文字溢出框外,第一版就栽在这)。
用
fitText()逐字符累加宽度来截断。验收:跑一遍"逐个text量getBBox()是否越界"的检查,必须为空数组。
0.5.4 🔴 改完看板怎么验(⛔ 别只跑自测 —— 2026-09-30 收尾棒踩出来的)
问题:本机(Windows 客户端)没有独立的浏览器实例给这个技能用,而本机明令禁止自起
headless chrome /附着用户 Chrome ⇒ 不许擅自开浏览器。但"改了看板没验"=没交付。
⇒ 三步取证的组合拳(都在 tmp/ 现做,不用常驻件):
| 步 | 做什么 | 判据 |
|---|---|---|
| ① 语法 | 抽出 board.html 的主脚本 node --check |
通过 |
| ② 渲染 | 极简 DOM 桩跑真 render():喂一份真实快照,断言渲染出来的 DOM 里该有的都有、该没的都没 |
断言全绿 |
| ③ 几何 | 解析渲染出的 SVG:所有 rect/text 在下界内、行带互不重叠、格间零重叠、右沿对齐 | 全部 0 越界 |
🔴🔴 两条最容易踩的坑:
- 桩必须"当场从
board.html现取"主脚本 —— ⛔ 不许读上一轮导出的副本。 副本可能是带补丁的形态(本轮实测:旧副本是"内联快照"版 ⇒ 换新脚本后 10/12 全 ✗, 一查不是代码坏了、是副本形态不对)⇒ 读副本 = 改动越多、假绿越稳。 - 离线预览页是给人眼复核的,不是替代品:把快照内联进
board.html(并让load()优先吃内联件) ⇒file://双击即看,⛔ 不依赖本地服务、⛔ 不依赖网络。桩验"内容与几何",人眼验"观感",两件都要做。
🔴🔴 改之前必须知道的两件位置事实(2026-10-01 实测,⛔ 不知道就会白改):
board.html从「包内」读(board.py用HERE.parent/assets/board.html)⇒ 改包内即生效。board_ext.py从「使用方工作区」读(配置项board_ext,默认<工作区>/.workbuddy/collab/board_ext.py) ⇒ 只改包内那份等于没改 —— 必须cp同步过去,并核 md5 一致。 ⚠️ 同理还有goalctl.py/stop-collab.py/deliver-gateway-token.py等工作区副本。 ⇒ 改任何一件前先diff两份:若差异只有你这次改动 ⇒ 直接cp;否则先弄清谁是谁。
⚠️ 回归工具当前住在 tmp/(tmp/render-check.mjs + tmp/arch-geom-check.mjs + 快照 tmp/board-verify*.json)
—— 而 tmp/ 按规矩是要清的草稿区 ⇒ 清掉后"改完看板怎么验"这条路就断了(selftest.py 盖不住渲染与几何)。
⇒ 要用之前先确认它们还在;⛔ 也别把它们当长期资产(待迁移进包,登记为结构性问题)。
⚠️ 两个脚本的路径常量是写死的:render-check.mjs 的 BOARD 曾长期指向已退役的
skills/multi-session-collab/(合并后没人改)⇒ 一跑就崩。换机器/换技能名后必查这一行。
⚠️ 同理适用于"台账/验收"这类状态:本轮两个真 bug 都属「不崩溃,只是少说一句话」
(见 references/architecture.md),"跑一遍没报错"永远验不出来 —— 只能用两处独立读数对账。
0.5.5 🔴 架构图的形状语义:这一格是一条会话,不是"调度配置"(2026-10-01 · 二次改形状)
形状改过两次(⛔ 别照旧文档做事):
| 时点 | 画成什么 | 为什么 |
|---|---|---|
| 2026-10-01 上午 | 六边形 + 外圈虚线(<path>) |
用户当时说「给新建的唤醒定时任务换个样式,和协作会话区分开」⇒ 用异形表达"它不是会话" |
| 2026-10-01 晚间(现行) | 圆角矩形 <rect rx=14> + 外圈虚线 |
用户随后定性:「唤醒脉冲会话,本质还是会话」(现名:唤醒会话 —— 2026-10-01 用户再改名,原话「唤醒主会话 改为 唤醒会话」)⇒ 与主会话/执行会话同族 |
🔴 结论:形状不再是区分手段,位置才是 —— 它在主会话下面单独一行(RW 行)的左格,
与右格「跟进会话」等宽并列(用户 2026-10-01:「唤醒会话和 跟进会话 单独放一行」+
「唤醒会话 和 跟进会话 框一样大小」),⛔ 不进下方那一排。全图没有六边形。
⚠️ 位置改过一次:上一版是"贴在主会话左边";拆行后主会话那一行只剩主会话独占
(="主会话只能是用户触发"在图上成立:指进它的线只有上面"用户"那一条)。
🔴 三条硬定性(2026-10-01 用户原话:「唤醒会话 是独立会话,不要在主会话上处理,是随着需求确定时创建的」): ① 独立会话(自己一条,⛔ 不是主会话的职能);② ⛔ 不在主会话上处理(叫醒的执行体是它自己); ③ 随需求确定时创建(⇒ 看板上「还没建」是正常态,⛔ 不是故障)。详见 §1.4a ⑤。
| 形状 | 含义 | 出现在哪 |
|---|---|---|
圆角矩形 <rect rx=14> |
会话/程序/地基(一直在那儿的角色) | 主会话 · 主会话下面那一行左格的唤醒会话 · 同行右格的跟进会话 · 下方那排任务会话 · 目标检查 · 上报 · Hook进程 · WorkBuddy |
外圈虚线 .trig-halo |
叠加在唤醒会话上 ⇒ "它靠脉冲被叫醒,不是一直在跑"(⚠️ 「脉冲」是 2026-10-01 及更早的说法;现行口径里负责叫醒的是常驻投递。⛔ 但图形保留 —— 这一格确实不是一直在跑,虚线仍然如实) | 只有这一格有 |
🆕 大圆角 + 细虚线 + 不填色 .grp |
分组(⛔ 不是节点、也⛔ 不是"新一层") | 上框=主会话+唤醒+跟进;下框=任务会话那一排(见 §0.5.7) |
- 🔴 状态用"颜色 + 徽章"两重表达,⛔ 不能只靠颜色(色盲/黑白打印会丢信息)。
front.triggers[].state三态:running绿 ●运行中 /paused黄 ‖已暂停 /none灰 ○未登记; ⚠️ 缺state时回落up(向后兼容只给up的老使用方)。 - 🔴 徽章用 SVG 图形,⛔ 不用
●‖○这类字符 —— 字符依赖字体,缺 CJK 字体的环境会渲染成方框。 - ⛔ 技能里不写死这一格叫什么 —— 名字由使用方经
front.triggers[].name给。 🔴 用户 2026-10-01 定的名(现行)=「唤醒会话」;同日更早叫过「唤醒主会话」(已作废,用户原话「唤醒主会话 改为 唤醒会话」),再早叫「唤醒脉冲会话」「唤醒定时任务」(均已作废)。 属项目侧,board.html里⛔ 不写死这个名字 —— 由使用方经front.triggers[].name给。 判据:assets/board.html的渲染逻辑里不该出现具体项目名。 - 🔴 2026-10-01 23:1x 图上改「简称」(用户第六条口径):
唤醒会话 →唤醒、队列上报的跟进会话 →跟进 (⛔ 用户明令两者字号一致,n-title-sm13px)、目标检查 →协作(n-title16px)。 ⚠️ 简称 ≠ 改角色 —— 正文/代码里的全称不变;图里?/aria-label必须带简称对照 (否则「协作」会被误读成「协作会话」)。⚠️ 探针别拿唤醒当子串判据(见architecture.md §2.3.0c-2)。 项目侧front.triggers[].name也随之给了唤醒(board_ext.py)⇒ 图格标题取的是简称。 - 🔴 读数来源随定性一起改了:它是一条会话 ⇒ 状态读
sessions表 (title/custom_title前缀[唤醒],且须排掉deleted_at),⛔ 不再读automations。 ⚠️ 旧口径(读automations.status/next_run_at,并限定schedule_type='recurring')已作废 —— 但它作为教训留着:一次性派棒(如「接续 · …(唤醒回路实战)」)名字里也带「唤醒」二字, 混进来会把状态判成running= 假绿(2026-10-01 实测:捞出的 11 条里 8 条是一次性)。 - 🔴 2026-10-02 订正:原文写「下次几点到点」程序读不到(理由=脉冲是会话自己起的一次性后台任务)—— 两半都不对:① 定案的时钟是常驻投递(⛔ 不是「会话自己起后台任务」——那条是明令禁区,见 §4.0 末);② 「下次到点」读得到 —— 代偿形态下它就在排期表
automations.next_run_at。 ⚠️ 但看板那一格目前还没读它(=已知缺口)⇒ 眼下仍只给「上次真的响过是什么时候」;⛔ 不许编一个「下次到点」出来。 - 回归:
selftest.py覆盖不到图形 ⇒ 靠tmp/render-check.mjs(形状/徽章/图外说明/帮助文本,8 条断言) +tmp/arch-geom-check.mjs(唤醒会话必须贴在「唤醒+跟进」那一行(RW) —— 它已不再贴主会话那一行; ⚠️ 这条"贴在哪一行"的判据 2026-10-01 拆行时必须跟着改:不改 ⇒ 拿 R2 顶去比 ⇒ 假红)。 🔴🔴 改形状必须同步改断言:否则过时断言 = 假红; ⛔ 更阴的是恒真断言 = 假绿(本轮实测:形状改回<rect>后,几何自检里那条查<path class="trig-hex">的断言恒不命中 ⇒ 恒真 ⇒ 假绿,改了形状却"看起来还是绿的")。 断言里的数字一律从快照现算,⛔ 不写死;新增断言要做一次反向对照(故意造错,确认它真会红)。
0.5.6 🔴 第三层 = 任务会话排;任务类别搬进主会话框(2026-10-01 用户定 · 这一排第三次换语义)
来历(三次换语义,⛔ 别照旧文档做事):
| 版本 | 那一排画什么 | 为什么换 |
|---|---|---|
| ≤ 2026-09-30 上午 | 按线(goal.lines/台账 line) |
跨工作区时代的维度 |
| 2026-09-30 晚 | 按任务类别(goal.topics) |
为治"同一工作区多类别塌成一行"——⚠️ 当时不是错的;但类别本质是主会话自己的事,占着"任务会话"的位置会误导 |
| 2026-10-01(现行) | 按任务会话(sessions 里 role !== '主会话') |
用户:「那一排只放协作会话,横着排 有几个放几个,当前没有对应协作会话 就空着」 |
触发这次改动的三连问:「唤醒定时任务 和 主会话下方的 唤醒机制是不是 重复了」→ 「你说的唤醒机制 是不是 主会话根据目标创建的 协作会话」→「那你的唤醒机制 是啥意思嘛 跟主会话都一个 ID,难道是主会话?」⇒ 病根 = 那一排把"类别"和"会话"混着画,读者分不清谁是谁。
三条硬规矩
| # | 规矩 | 为什么 |
|---|---|---|
| ① | 那一排只放执行会话,⛔ 不塞类别、⛔ 不塞台账旧线 | 用户原话。混着画就分不清"谁在干活"与"分了几类活" |
| ② | 一条都没有时也要画空框(虚线框 +「暂无协作会话」+一句说明) | 用户追加:「放一个空的框 说明 暂无协作会话」。⛔ 不许因为"没内容"就把这层省掉 —— 读者会以为图缺了一块 |
| ③ | 🔴 2026-10-01 同日已删 —— 原「任务类别搬进主会话框」(一行「负责类别:…」,按 main_by_topic 反查)不复存在。任务类别现在只在两处说:顶部「本项目」卡的任务类别 chips(每个 chip 带「主会话 <id8>」/「⚠️ 无主会话」)+ 图外说明「当前」块第 1 条 |
先有用户:「唤醒机制如果是主会话的 事就放到主会话框里去」⇒ 搬进去;随后用户指着那行问:「这个是什么意思,如果没用就删掉」⇒ 删。两条删的理由(实测,⛔ 不是口味):① 它会说出假话 —— topics_source.kind='declared'(类别在对话里说明过)而 main_by_topic 为空时,它渲染成「未在对话里说明」,把"这条主会话没对应到任何类别"说成了"类别没声明过"(两件事被一个判据混在一起);② 同一事实版面上已说过两遍,且方向更对(类别→谁负责),这行是第三遍、还是反方向。⚠️ 顺带:R2.h 由 112 回落到 84,tmp/arch-geom-check.mjs 的 rows.R2 必须同步 |
🔵 「自动化排期」是主会话的**动作**,⛔ 不是独立角色 —— 用户 2026-10-01 点破:
「自动化排期开新会话:这个不就是主会话创建和派活吗 也是自动任务的方式」⇒ 对。
⇒ 它画在连线标签里(① 派活 · 自动化排期)就够,⛔ 别单开节点。
⚠️ 但"它是开新会话的唯一通道(钩子开不了会话)"这条硬约束光看线看不出来
⇒ 在图外说明里点一句即可,⛔ 不动图结构。
(这条踩过:2026-10-01 我先把它当成"图上缺了一环"报上去,被用户当场纠正 ——
判据:角色才配节点,动作只配标签/说明。)
🔵 空态时 ①② 两个标签都要画 —— 原来只画 ①,② 的标签在 else 分支里
⇒ 一条任务会话都没有时,图上只剩 ①,看不出还有 ② 上报这条回程。
⛔ 空态 ≠ 这两条通道不存在,只是当前没有会话可连(2026-10-01 修)。
🔴 「未归类」也⛔ 不占这一排 —— 台账里不在当前类别清单的旧线,汇总改由图外说明说清 (「N 条线 · 共 M 件 · 已完成 K」)⇒ ⛔ 不许因为"没地方画"就静默丢(本线红线:读到了就要说出口)。
🔴 任务会话格画什么(2026-10-03 定稿):只画三行 —— 会话名 / 状态·id8 / 多久没活动。
⛔ 不画「所属类别」那一行(原本有 类别:xxx,无类别时又加了一串提示)——
这行已被用户指示删除(逐字:「越写越看不懂 还是删除了把」)。
留档(三次踩坑,别再犯):① 原文案「(标题里没读类别前缀)」只说现象、没信息量;
② 我改成「⚠ 无类别 ⇒ 不会被派活」——🔴 那个后果是我编的(取证:派活读 tasks.json,
topic 在派活链路上根本没出现;而那排格子本身就是正在执行的任务会话);
③ ⇒ 结论:这行信息量不足,⛔ 不值得占版面、更不值得编文案。
📌 两条通用红线:⚠️ 写「会怎样」前必须先取证那个后果真的存在(否则=编);
⚠️ 改文案改到第三轮还说不清 ⇒ 该问的是「这行要不要留」,⛔ 不是继续润色。
前一个版本留下的两处真缺陷(代码仍在,⛔ 别照抄回去)
- 🔴
board.py::_labor()的回落分支 ⛔ 不许再排除主会话:旧判据… and str(s.get("role")) != "主会话"本意是"最近在做的=承接会话",但该类只有主会话自己在跑时 会把候选整类判空 ⇒ 同一格里 ③ 说「无可归到它的会话」、④ 说「承接<同一条>」= 自相矛盾 (用户看到的正是这个画面)。⇒ 候选不排除任何角色,working排最前。 - ⚠️
fmtAge()的返回值自带"前"字(4 分钟前)⇒ ⛔ 别再加一个 —— 2026-10-01 实测拼出过 「4 分钟前前有活动」。这是造正例快照时才暴露的(见下)。
回归
tmp/render-check.mjs(数字一律从快照现算): ① 🔴 主会话框⛔ 不许再写「负责类别」(2026-10-01 删了那行 —— 它会说出假话,见 §0.5.6 表 ⓷) + 架构图⛔ 不许再出现「未在对话里说明」(同族假话:类别声明过却说没说过); ② 第三层只出现任务会话的id8(0 条时必须是「暂无协作会话」+虚线框); ③ ⛔ 不许再有「收件主会话」这类旧类别格残留(防回潮); ④ 🆕 「当前」块必须按topics_source.kind说类别(declared⇒ 点类别名 + 对应主会话; 否则 ⇒ 明说「还没在对话里说明过」)—— ⛔ 不许拿回落值当已声明的类别报出去。- 🔴 断言必须"改前能报红"(⛔ 不许写成恒真 —— 那是假绿)。做法:拿改前的备份跑同一套桩,
同一份快照下"改前红、改后绿"才算真判定。2026-10-01 删那行时就是这么验的:
线上真实快照 改前 6 红 → 改后 4 红、合成
kind='fallback'样本 改前 7 红 → 改后 4 红 (残留那 4 条是写死给样本快照的内容型断言 ⇒ 拿线上快照跑就假红,属桩的已知弱点,登记为 P2)。 - 🔴 正例快照必须造:真实工作区经常 0 条任务会话 ⇒ 只跑默认快照,"有几个放几个"那条分支
从没被执行过(=未验证)。做法:把
sessions追加几条role='任务会话'(含一条topic为空的), 再node render-check.mjs <该快照>跑一遍。2026-10-01 就是用这招抓出「前前」那个拼接 bug 的。 ⚠️ 同理:topics_source.kind='fallback'也要合成一份样本 —— 现成两份快照都是declared⇒ 不合成就验不到「没声明过」那条分支(2026-10-01 实测踩着)。 tmp/arch-geom-check.mjs:内置层高表rows必须与board.html同步 (R2 84→112→84 —— 2026-10-01 那天来回过,⛔ 改R2.h必须同时改这里)—— ⛔ 不同步 ⇒ 几何自检是假绿。
1 🔴 四条通道(哪件事走哪条 —— 走错就是今天最大的浪费来源)
| 要做的事 | 走哪条 | 成本 | 为什么必须是它 |
|---|---|---|---|
| 派活 / 唤醒会话的首次创建 | 自动化(宿主排期) | 一个会话 | 🔴 钩子开不了新会话 —— 自动化是唯一通道(宿主硬边界);⚠️ 派活首次创建会话走这条是对的,⚠️ 但「周期性叫醒」⛔ 不该走这条(那是常驻投递的活,见下行) |
| 🔴 收结果 / 检测 | 直读宿主库(automation_runs,只读 SQL) |
0 token | 宿主每次跑完就把它自己的收官结论落库 ⇒ ⛔ 不必轮询、⛔ 不必扫文件、⛔ 不必叫 AI |
collabd.py --tick)🔴 2026-10-02 标注(⛔ 不是口径变更):本行已作废 —— 用户 2026-10-01:「协作与投递一直运行(常驻)」+「定时任务的方案已经废弃了」⇒ 定案=常驻投递(0 token · 0 会话 · 1 常驻),钩子只作补充(§5-1) |
钩子是宿主子进程 ⇒ 自带口令 + 不占会话 + 跑完即退;而"需要投递的时刻"全都伴随会话在动 ⇒ 那一刻钩子必然响(architecture.md §4.1) |
||
| 机械判定(文件在不在 / rc / 哈希 / 锁 / 端口) | 本地脚本(由钩子按需唤起) | 0 token | 这些判断几毫秒可做;塞进"定时叫 AI"是又慢又贵 |
| 人看进度 | 一个看板(每轮覆写) | — | 用户只该看这一处 |
⚠️ 推论(最容易搞错):"发消息给另一个会话"和"读它的结果"不是一回事 —— 前者要网关投递,后者只要读库。 ⚠️ 判据:任何"我需要(或用户需要)持续监控"的诉求 ⇒ 第一反应是「钩子按需唤起本地脚本」,⛔ 不是自动化、⛔ 也不是常驻。
1.1 🔴 与主会话的双向同步(⛔ 不需要"监管棒"这个角色)
| 方向 | 怎么做 | 要点 |
|---|---|---|
| 主会话 → 程序 | 无需同步:改 任务图.json / 看板 / 产出文件即可,程序下一轮自然读到 |
文件即接口 |
| 程序 → 主会话 | ① 钩子注入:UserPromptSubmit 时把程序摘要作为 additionalContext 注入 ⇒ 用户每次发话就顺手带上最新状态(零自动化)② 需要时主会话主动读实时状态 / digest.md |
程序开不了会话,但能把状态送进会话 |
| 派活 | 仍由会话做(白名单内) | 只有会话能创建自动化 |
🔴 结论:不需要独立的「监管棒」角色 —— 判断 + 派活归主会话;程序负责"把状态摆到主会话眼前"。 ⇒ 若发现自己在建"另一个会话来监管",先问三句:① 机械判定能否下沉到程序?② 状态能否用钩子注入?③ 派活能否由主会话顺手做?
注入实现要点(照抄):
- 钩子脚本对
UserPromptSubmit允许写 stdout(stdout 正是钩子协议通道):输出{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"…"}}; 其他事件仍保持零输出(⛔ 别破坏原有纪律)。 - 顺带把信号文件(
VACUUM.md/READY.md/STALL.md)的存在也拼进上下文 ⇒ 主会话一眼看到"有东西待处理"。 - 注册:
settings.json的hooks.UserPromptSubmit只能追加条目(⛔ 禁整段覆盖 —— 会抹掉别人的钩子)。 - ⚠️ 钩子=会话启动时快照 ⇒ 改完必须完全重启宿主才生效(关窗 ≠ 退出);⛔ 别默认它已生效。
1.2 🔴 严格队列(避免打架 · 处理好一个再处理下一个)
用户原话:「严格用队列的方式处理,避免打架,处理好一个,再处理下一个」
队列与域锁分工不同(两者都要):
- 队列 管「谁做下一件」—— 调度串行
- 域锁 管「能不能动这个资源」—— 资源互斥
| 规矩 | 做法 |
|---|---|
| ① 一次一件(调度串行) | 队列只呈现一个"队首";⛔ 不许一次派多件 |
| ② 原子取件 | 取件动作 = mkdir <inbox>/claims/<id> ⇒ 建不成 ⇒ 别人取走了 ⇒ 换队首/等待(这就是"不打架"的硬保证) |
| ③ "谁在做"唯一权威 | 取到后写 claims/<id>/holder(内容 <会话名>@<线>) |
| ④ 出队=删 claim | 做完必须删 claims/<id> ⇒ 下一个才可能成为队首(⛔ 不删 ⇒ 该线一直被占 ⇒ 队列卡住) |
| ⑤ 同线互斥、跨线并行 | 同一条线同时只允许一件;不同线可并行(队列按 line 判)⇒ 既"不打架"又不干等 |
| ⑥ 卡死回退 | claim 超 20 分钟未续 ⇒ 程序自动移到 claims-stale/(可追溯,⛔ 不删)⇒ 队列自动放行 |
| ⑦ 优先级 | "在关键路径上游闭包内" ⇒ 优先(做它能解锁关键路径);⛔ 不能只看"是不是关键路径节点本身" —— 那会漏掉它的前置,跑去干无关的活(实测踩过:队首一度推荐非关键路径的 N11) |
产出:程序写 queue.md(人看:队首/在做/待办+冲突提示)+ queue.json(机读)。
⚠️ 命令里别用 ASCII 双引号写中文文案(会 SyntaxError):一律用 「」(见 P11)。
1.3 🔴 拉取模型(pull):队列是"拉"的接口 · ⛔ 不要后台任务
用户原话:「不是程序直接调用你,只产生待处理队列,你开个后台任务去获取然后处理」 ⇒ 方向对(把"推"改成"拉"),但**"开后台任务"这一步可以去掉** —— 见下。
为什么这里不需要"后台任务":它会产生任务状态通知/失败通知,每次都是一次打断;叠加"改代码→重启"就变成 P12 那种反复折腾。 ⇒ 正解(本机制):不需要任何后台任务,程序也不调用任何人 —— 只写文件。 🔴 ⚠️ 两处口径必须分清(2026-09-30 用户反证后定稿):
- ⛔ "卡消息输出"的真因⛔ 不是"任务挂在会话名下" —— 真因=会话日志撞 ~10 MiB 被
dropped(实物读数见references/pitfalls.md P0-2)。 用户反证:挂着后台任务的会话照样能随便发消息。 - ⛔ "后台任务不能用来投递"也⛔ 不成立 —— 它有口令、能长期活,是合法次选(见
references/architecture.md §4.1.1)。 本机制选"钩子"的理由只是三条次要优势:不需容器会话 · 必须活着的进程数=0 · 重启后自动生效。
| 角色 | 做什么 | ⛔ 不做什么 |
|---|---|---|
| 程序(钩子按需唤起·只读投影) | 只维护队列(queue.md/queue.json)+ 信号文件(VACUUM/READY/STALL)+ 视图 |
⛔ 不派活、⛔ 不起后台任务;⚠️ 投递另归投递(--tick) |
| 会话 | 在三个时机主动拉(见下)⇒ 取队首一件 ⇒ 原子 claim ⇒ 派活或自己做 |
⛔ 不轮询(拉的是"被唤醒的时机",不是定时器) |
三个"拉"的时机(够用,且零通知):
- 用户发话时 —— 钩子把摘要 + 队首 注入
additionalContext(钩子在会话侧发生 ⇒ 本质就是"拉") - 每个棒收尾时 —— 顺手读队首 ⇒ 取一件接着做(收尾自判,零额外会话)
- 需要时 —— 主会话主动读
queue.md
⇒ 闭环:程序只"摆件";会话只"取件";没有任何一方被对方打断。 ⚠️ 代价(如实说):没有会话活跃时,队列里的件不会被自动取走 ⇒ 这时才需要"叫一次"(那属于白名单的接续/派活,⛔ 不要为此建轮询式自动化)。
1.4 🔴 主会话必须"用户不发消息也能自己处理"(2026-09-29 用户定案)
用户原话:「主会话就是要用户不发消息自己能处理,用户发消息是需求上的事」 ⇒ 用户的消息 = 需求输入(新增/变更需求),⛔ 不是推进的动力源。
🔴 硬边界:会话被唤醒只有两条路 —— ① 用户发消息 ② 自动化(宿主排期);钩子开不了会话。 ⇒ 所以"主会话自己会动"的唯一合规形态 = 主会话自己续自己:
主会话跑完一轮(判断 → 取队首 → 派活/自己做 → 更新任务图)
│
└─ 自续期:再排一条一次性自动化,scheduledAt = now + 40 分钟,prompt = 本段原文(逐字复制)
⇒ 形成「自续链」;⛔ 全节点 done(或用户要求停)⇒ 不再自续
🔑 关键判据:"续主会话"属于白名单里的「接续会话」⇒ 免确认 ✅ (这正是"接续会话"这一类的本义 —— 把链条接上,包括把主会话这条链接上。)
纪律:
0. 🔴 间隔必须动态(2026-09-29 用户点破"整套机制都是短的,都要我来说一句你才执行一下"):
队列里有可派件 ⇒ now + 5 分钟(有人在等 ⇒ 快叫醒);无 ⇒ now + 30 分钟。
⛔ 固定长间隔(如 40 分钟)= 出现"可派未派"窗口(实测:42~43 分钟毫无变化,用户以为"只有我说话才动")。
- 频率上限 5 分钟(别更密 —— 更密就是"轮询式自动化",属非白名单)
- 每轮只做一件(严格队列:取队首一件)⇒ 跑完即退,⛔ 不留常驻
- 终止条件必须写进 prompt(全节点 done ⇒ 停),否则会永远续下去
- ⛔ 不为"推进"另建轮询式自动化 —— 自续链就是那个机制
⇒ 与 §1.3 的分工:自续链负责"醒来"(没人发消息也会醒来);队列负责"做什么"(醒来后取队首一件);钩子注入负责"用户发话时顺带汇报"(不是动力源)。
1.4a 🔴 唤醒回路的判据 + 投递路径(2026-10-01 用户两条批注 · 现行口径)
① 判据 = 三条件合取(⛔ 不是计时器):
| # | 条件 | 实现现状 |
|---|---|---|
| (a) | 主会话 和 任务会话都空闲 | ✅ 已按此收窄(_main_state() ∨ _anyone_busy(),排除规则见 ④) |
| (b) | 队列没有待反馈(无 awaiting ∧ 无 pending) |
✅ 已实现(no_fb) |
| (c) | 需求仍未完成 | ✅ 已实现(goals_open()) |
② 需求三态 → 唤醒任务(用户原话:未完成=继续/有阻碍=暂停/已完成=暂停):
| 需求状态 | 唤醒任务 |
|---|---|
| 未完成 | 继续(照三条件判) |
| 🔴 有阻碍 | 暂停 |
| 已完成 | 暂停 |
✅ 已实现(goal_stuck = 只剩 blocked 项、没有 pending/running ⇒ 不叫)。
③ 投递路径(用户第二条批注):
- ✅ 应:提交到目标检查队列 ⇒ 走既有的「上报 → 主会话处理」流程(见 §1.2/§1.3)。
- ⛔ 现状:唤醒绕开队列,直接
_deliver_str(txt, "上报·唤醒", st)投一句给主会话 ⇒ 主会话收到后无处标完成(它不在队列里)⇒ 没有闭环。
⚠️ ③ 会改队列语义(唤醒件算不算一件待办、谁给它标
done、会不会淹没队列) ⇒ ⛔ 未擅自改,先把口径钉在这里。
④ 🔴 自指死结 + 解法(2026-10-01 用户点破 · 本节最要紧)
死结:唤醒的就是这条会话自己 ⇒ 它醒来那一刻,自己正在跑 ⇒ 条件①「都空闲」被它自己否掉 ⇒ 永远不叫 (=醒一次、白判一次、接着睡;用户原话追问「谁把他顶掉了」)。
用户给的解法(原话):
「把自己排除不就行了,通过会话名称前缀区分,还有也不用他自己判断,可以通过代码判断」
⇒ 落地两条硬规矩:
| # | 规矩 | 实现 |
|---|---|---|
| A | 判据在代码里,⛔ 不让会话自查 | 在 supervise() 里算;会话只负责被叫醒后干活 |
| B | 排除自己 —— 两道互补,都要 | ① SELF_SID(CODEBUDDY_SESSION_ID)精确:程序跑在会话里时=自己② 名字前缀 [唤醒]:程序在会话外常驻时拿不到 SELF_SID,只能认名字 |
🔴 新增第三类名字前缀:[唤醒]-[<类别>]-<具体> ⇒ parse_session_name() 返回 role="waker"
(原只有 [主] / [协作] 两类)。
⚠️ 2026-10-01 用户口径已扩到「四类」(新增 ④ 队列上报的跟进会话 ⇒ 第 1 级 [跟进])——
但这一类的解析与路由尚未落地(全库零命中)⇒ ⛔ 本节的 A/B 两条规矩只覆盖已落地的前三类,
第 ④ 类的落地清单一律以 references/architecture.md §2.3.0c 为准。
🔴 第四形态:接续会话(2026-10-01 落 · 详见 references/architecture.md §2.3.0b)
🔴 定性:它是「形态」,⛔ 不是第 5 类会话 —— 用户原话:「接续会话 不是单独的一类会话,是这几类会话到达阈值时 创建的接续会话」
⇒ 角色继承被接续的那条(主会话的接续仍是主会话候选),⛔ 不要把它当成一种并列的类别。
—— 由会话自己建出来的下一棒,标题由建它的那条会话写 ⇒ 常写成
[<类别>] 接续 · … 或 接续棒:…(没有角色方括号)。判据:含"接续"二字 ⇒ 判 worker
(⛔ 不是主会话);另把 主控 · …(中点分隔、无括号)判 main。
⚠️ 不改会怎样:旧判据只排 [协作] ⇒ 接续棒进主会话候选、又因标题含 [<类别>] 被认成
"该类别的主会话" ⇒ 投递把通知投给它自己(2026-10-01 实测)。
⇒ 主会话候选的排除判据=「角色不是 worker/waker」(⛔ 不再写 [协作] not in t);
实现处两处必须同款:collabd.py::parse_session_name()(权威)/board.py::_role_of_title()(镜像),
已有真对账用例在 selftest.py 守着。
🔴 建接续棒时必须按形态命名(否则新会话要么冒充主会话、要么在看板上归不了类别):
[协作]-[<类别>]-<具体> / 主控 · <类别> · <具体> / [唤醒]-[类别]-<具体>。
⚠️ 已知边界:[<类别>] <具体> 不带"接续"二字的仍判角色未知(⛔ 不猜),但会被看板点名。
⚠️ 实测暴露的另一件事(2026-10-01):现役会话名实际写成 主控 · 唤醒机制线 · 棒:…
(中点分隔、无方括号)⇒ parse_session_name() 解析不出来 ⇒ 光靠名字判会漏。
这正是 SELF_SID 那道排除不能省的原因(该会话的 id 与 SELF_SID 一致)。
⑤ 🔴 唤醒会话的三条硬定性(2026-10-01 用户第三次定义 · 覆盖前两次)
用户原话:「唤醒会话 是独立会话,不要在主会话上处理,是随着需求确定时创建的」
| # | 定性 | 意思(⛔ 别读错) |
|---|---|---|
| (a) | 独立会话 | 它自己一条会话,⛔ 不是主会话的一个"职能"/一种模式。⇒ 看板上它是独立一格;判"都空闲"时它也必须被排除(见 ④) |
| (b) | ⛔ 不在主会话上处理 | 叫醒这件事的执行体是它自己 ⇒ ⛔ 不许把"该它干的活"挂到主会话身上(主会话只管判断与派活)。⇒ 那个脉冲任务起在它里面,⛔ 不是起在主会话里 |
| (c) | 随需求确定时创建 | 创建时机 = 需求确定的那一刻(不是常设件;⛔ 不提前建,没需求不建)。⇒ 看板上「还没建」是正常态,⛔ 不是故障;此时链路缺口敞开,如实说 |
🔴 board_ext.py 落地:_waker_session() 读 sessions(前缀 [唤醒]);_triggers() 的三态 + tips
把这三条写进图例("还没建"那一支要说明这是按定性的正常态)。回归断言:render-check.mjs
两条(「独立会话/⛔ 不在主会话上处理」+「随需求确定时才创建」)。
1.4b 🔴 换主会话(交接)的确切触发条件(2026-09-30 实测,⛔ 别再靠猜)
换不换,只看一件事:roles 里登记为 main 的那条,此刻在不在活会话集合里**。**
| 登记那条 | 结果 | 依据 |
|---|---|---|
| 还在活会话里(桌面还开着) | 🔴 钉死在它身上 —— 新建的主控会话完全被无视 | roles:main |
| 不在(已关/已切走) | ✅ 立刻按"显式前缀 → 最近活动"换过去 | prefix:主控 / workspace |
⚠️ 另一条老会话开着不影响(只要它不是登记的那条)—— 别把"旧会话还开着"当成不换的原因。
交接的正确姿势(三步,缺一不可):① 新标题以 主控 开头 ② 先把旧主会话从桌面切走(⛔ 不是删会话、⛔ 不是杀进程)③ 同时把新主会话打开(它必须出现在某个网关口口的 live= 上,否则投递被拒 main-not-live)。
⛔ 两条主会话同时开着 ⇒ 一定被钉回旧的那条。
🔴 两个已知坑(待修,改前先看 §9):
- 前缀判据不认方括号:代码认
startswith("主控"),而实际标题常写成[主控]-…⇒ 判 False ⇒ 那条主会话只能靠"最近活动"兜底,本工作区任何一条会话活动更新就会静默把它顶掉。⇒ 命名一律用主控 · …(⛔ 别加方括号)。 - "登记还活着"压过"显式前缀":短路顺序是「登记」在「前缀」之前 ⇒ 旧窗口还开着时新主会话永远接管不了。正确优先级应是 显式前缀 > 登记 > 最近活动。
⚠️ 活会话集合为空时(枚举失败)会反而信任登记(解析出一条早关掉的会话)—— 不误投(后续仍拒投),但归因会误导。
⚠️ 一条正在跑的会话会自带一个网关口(live=它自己),跑完口就消失 ⇒ 别把"某个口曾经 live 过它"当成"现在投得进去"。
2 🔴 主会话只做两件(其余下沉)
- 判断:按
任务图.json的判据判"节点是否真的完成"(⛔ 不看自述,看可核对产物) - 派活:只派依赖已满足的节点,优先关键路径
⛔ 主会话不该做的(做了就是走错通道):持续监控(⇒ 常驻程序)/轮询别的会话干完没(⇒ 读库)/机械核对(⇒ 常驻程序)。
3 🧭 防干等 = 显式任务图 + 关键路径(总工期问题的唯一正解)
任务图.json:每节点写 id / title / line / deps / status / evidence|blocker,并给 critical_path。四条派活规则:
- 只派「依赖已满足」的节点 ⇒ 能并行的立刻并行
- 优先关键路径(它决定总工期;非关键路径押后不拖工期)
- 一条线同时只挂一个,多条线可同时挂
- 收口即派(+3~4 分钟),⛔ 不要 +5
8 分钟空窗(🔴 2026-10-01 用户口径:基准 = 收口 + **34 分钟**)
🔴 两个必检的浪费信号:
- 可派未派:可派集合非空 ∧ 没有任何棒在跑 ⇒ 有活没人干 ⇒ 立即派
- 关键路径单线化:关键路径上只有一个执行主体 ⇒ 把它拆成更小的可验步骤,能交给别的线的部分并行出去
📄 规范 + 模板 ⇒ references/taskgraph.md(含节点写法与"可派/等待"算法)
4 🔴 证据分级:真成果 ≠ 接续任务(不区分就会被"空转链条"骗)
| 类 | 判据 | 算不算进展 |
|---|---|---|
| ✅ 真成果 | 运行记录为完成且有结论,最好有可核对产物/读数(文件 / rc / 端口 / 数据对照) | ✅ 算 |
| 🟡 接续任务 | 只是新增了一条排期(执行方自己排的下一棒) | ⛔ 不算 —— 只证明「有下一步」,不证明「这步干成了」 |
| 🏃 刚开始跑 | 有运行记录但尚无结论 | ⛔ 还不算 |
| ⛔ 哑火 | 到点却没产生运行记录 | 🔴 当异常(链条断了) |
⚠️ "在跑的棒 N 个" 是排期数,⛔ 不是成果数。
5 🔴 新建自动化 = 白名单 + 确认制(否则会退化到"啥都用自动化")
只有两类可不经确认直接建:
- 接续会话 —— 把某条链/某一线的下一棒接上
- 给其他会话安排任务 —— 派活
⇒ 其余一切用途(监管轮/巡检/检查点/体检/观测/清理/日报…)必须先取得用户确认。
执行:建前自问这两问;答不上 ⇒ 报给用户等确认(⛔ 不许先建后报)。✅ 删冗余不属新建 ⇒ 可直接做但须报告。
🔴 配额:一个需求线的常驻自动化 ≤ 2(1 唤醒 + 必要时 1 截止类)。
⚠️ 🔴 2026-10-02 标注(⛔ 不是口径变更):这里那个「1 唤醒」只在代偿形态下成立(常驻停机 ⇒ 用排期顶替时钟);定案的唤醒时钟是常驻投递(⛔ 不占排期名额)。
⚠️ 但"连通知主会话都要靠自动化"是错的 —— 投递走宿主钩子(--tick),⛔ 不加排期(pitfalls.md P0-3)。
6 ⚠️ 自愈/守护的硬纪律(都是踩出来的)
- 自愈必须带去抖:连续 N 次(≥3)失败才动手 + 冷却。否则"自愈"会反复杀掉正在服务的好实例 —— 比故障本身更伤(实测)。
- ⛔ 不用会话后台任务跑长跑:它每轮输出会唤醒宿主会话 ⇒ 会话永不空闲 ⇒ 用户看到「卡死」。
✅ 可行形态:由用户在自己的独立窗口起(
cmd start/wmic/Start-Process常被安全策略拦,⛔ 别在这上面耗时间)+ 完全静默(只写自己的日志)。 🔴 实测补充(2026-09-29):从会话里起的常驻活不长 —— 它会随其宿主会话结束被回收(实测:13:09起的 pid 在会话收尾后消失,13:12后再无一轮,单例端口变Connection refused)。 ⇒ 两条一起用:常驻只当"锦上添花";机制必须有一条「钩子事件驱动」的腿(每个会话收尾跑一轮),否则常驻一死,监控/队列/唤醒全部静默停摆(而没有人会发现)。 - 单例:常驻程序独占一个本地端口 ⇒ 防重复实例双写。
- fail-open 会掩盖字段错误:异常全吞 ⇒ 查询写错也"rc=0" ⇒ 必须核对输出非空,⛔ 不能只看退出码。⚠️ 视图文件要定期 diff 一眼。
- 域锁要切细:按节点涉及的文件/子目录声明;⛔ 禁止整工作区域粗域 —— 那是自造串行瓶颈(实测造成多次「抢锁失败 ⇒ 整轮白开」的纯浪费)。
- 🔴 别"改一行重启一次"常驻程序:用会话后台任务起常驻本身已被禁(见第 2 条);而每次 kill/launch 都会留下
failed通知 ⇒ 反复打断主会话,把自己弄卡(实测一个阶段重启 7~8 次)。⇒ 攒批重启(≥3 处改动一次)+ 规则/配置做成热加载(每轮读文件)+ 长任务交棒换会话。详见references/pitfalls.mdP12。
📄 完整踩坑清单(含每条的现象/根因/修法)⇒ references/pitfalls.md
7 落地:三个件(部署顺序)
| # | 件 | 作用 |
|---|---|---|
| 1 | 常驻程序 scripts/collabd.py --once(投影轮) |
判定(证据分级/推进/真空/哑火)+ 任务图「可派未派」+ 体检 + 视图 + 需求台账。⛔ 不派活、⛔ 不投递、⛔ 不推进队列 |
| 2 | 投递 同文件 --tick(投递轮) |
逐条读队列 ⇒ 投给主会话(单条+握手/三条件唤醒)⇒ 唯一投递方+唯一队列推进方 |
| 3 | 任务图 任务图.json(模板见 references/taskgraph.md) |
显式依赖 + 关键路径 ⇒ 防干等 |
| 4 | 🔴 钩子接线:宿主在 UserPromptSubmit + PreToolUse(matcher ^Bash$) 上调用本地脚本 ⇒ 脚本内部再跑 --once 与 --tick |
这是"投递"的全部 —— ⛔ 不需要排期、⛔ 不需要常驻。钩子是宿主子进程 ⇒ 自带网关口令(能投递)、⛔ 不占会话、零 token。⚠️ 接线必修:工作区若另存旧副本,钩子指向它 ⇒ 回路等于没接;⚠️ 新加的钩子事件需宿主重启后才生效(UserPromptSubmit 那条立即生效) |
配置:scripts/collabd.config.example.json(workspace / 行名 / 目标件 / 任务图 / 看板 …)⇒ 复制成 collabd.config.json 改路径即可复用。
用法:--once(目标检查·投影轮)|--tick(投递·投递轮)|--report(任务会话上报)|--reconcile(启动对账)|--where(路径自证)。
🔴 投递真绿的判据:⛔ 不是 rc=0,而是 wakeups.jsonl 新增了一行 ok:true。
8 自检(每次派活前问这四句)
- 这件事非得开新会话吗?(机械 ⇒ 常驻程序;判断 ⇒ 已有唤醒)
- 我要派/建的东西在白名单里吗?不在 ⇒ 先问用户。
- 依赖满足了吗?它在关键路径上吗?不在 ⇒ 它能不能押后?
- 我是靠可核对产物判"完成",还是靠自述/排期?(后者 ⇒ 那是"接续任务",⛔ 不算成果)
9 🔧 维护 scripts/collabd.py 的硬要点(2026-09-29 实测,⛔ 别再踩)
两条实测事实(推翻了原设计的隐含假设)
automation_runs.status实测全是ACCEPTED,从来没有IN_PROGRESS⇒ ⛔ 不能当「有人在跑」信号。- 一次性棒跑完
next_run_at归零(实测全0/None)⇒ ⛔ 也不能当信号。 - ⇒ 唯一可得的「有人在跑」=
sessions.status='working'(取值域:archived/completed/error/working)。 ⚠️ 用它判「有没有人在推进」时必须排除观察者自己(否则主会话自己跑着 ⇒ 永远报"有人在跑",用户问的"是不是又发呆了"就永远答不出来)。
改本程序的标准手法(⛔ 别手改单点)
- 先备份到工作区
tmp/bak-collabd-<日期>/(⛔ 不在技能目录里留副本)。 - 用断言式补丁脚本:
old/new成对替换,任一锚点count != 1⇒ 整份不写盘;写完ast.parse门禁 ⇒ 杜绝"半改状态"。 - ⚠️ 锚点必须先用
repr()核过 —— Read 工具的行号切分与本文件真实字节不一致(长行会被折),照抄 Read 的显示会静默失配。 - ⚠️
Path.write_text()在 Windows 静默把\n转成\r\n⇒ 写完必比对md5sum与内存 md5;不一致就按newline="\n"归一化回 LF。 - 🔴 它不是常驻进程(由
UserPromptSubmit钩子按需--once调起)⇒ 改完下一轮钩子即生效,⛔ 无需重启。 - 验证:连跑
--once看rc+queue.json/digest.md/信号文件(NEXT/STALL/VACUUM/READY)是否与预期一致。
队列的四个部件(各有唯一权威)
claims/<id>/holder=<会话名>@<线>@<完整会话id>—— 「谁在做」的唯一权威;第 3 段别省(有它才能判持有者是否已终结 ⇒ 立即出队;没有就只能等 20 分钟超时,期间队首会僵尸化并反复复活)。blocked.json={"<节点id>": "<原因·谁在等>"}—— 受阻件 ⛔ 不当队首(否则每次唤醒都白跑一遍同一个受阻件)。解除后记得摘掉。gate-done.stamp+claims缺失即gate=free:闸门靠钩子事件驱动,⛔ 不靠轮询。- 🔴 「同线互斥」取
holder的第 2 段(线名)** —— 取第 1 段(会话名)会恒不相等 ⇒ 该机制静默失效(实测:同一条线可被同时派多件)。 - 🔴🔴 「算不算做完」以需求台账(
tmp/supervise-inbox/tasks.json)为准,⛔ 不是以任务图节点为准(2026-10-01 加)。 病根:任务图是纯手维护件 —— 全仓无任何代码回写它(实测TG在collabd.py里只于 3 处被读,无一处写), 而会话「上报完成」只写台账 ⇒ 两处必然漂,且症状极隐蔽:claims/是空的、没人占线, 但队首出不来 ⇒ 它被反复当"可派未派"喊,READY.md/STALL.md每小时喊一遍同一件,控制台判「目标未完成」。 实测(M5):台账done、上报单done、产物已核对(wakeups.jsonl第 82 行http:200 ok:true), 任务图仍留running⇒ 整条链被一个早已做完的件卡死,goalctl只会说「目标状态 = 未完成(任务图 M5)」,⛔ 不告诉你是"早就做完了"。 ⇒ 现行口径:图 done ∪ 台账 done都算 done(deps解析同理,前置满足即算满足)。 ⚠️ 实现落在两处、必须同口径:collabd.taskgraph()与goalctl.goals_open()(⛔ 只改一处 ⇒ 控制台与看板两边打架)。 ⚠️ 不改任务图本身 —— 它仍是「分解件」,人可手改;只让"算不算做完"以台账为准。 - 🔴 目标「三路全过」⇒ 终态:⛔ 不产任何告警(2026-10-01 加)。原实现只认
goal.json.run != active, 目标是全过了、run却仍是active⇒ 照产STALL/VACUUM/READY⇒ 把「做完了」读成「坏了」 (实测:M5 解卡后立刻喊「卡住:有会话在跑但 52 分钟无成果」)。 ⇒ 现口径:--once/--tick在goal_paused() or not goals_open()时走paused_round()—— 只留一行终态说明、清掉四个信号投影、⛔ 不投递;看板也一并收口(否则它会永久停在最后一张"还在跑"的快照)。
🔴 它到底什么时候才会跑(最容易误解的一条 · 2026-09-30 更新)
--once(投影)挂在UserPromptSubmit(节流 180 s)+PreToolUse ^Bash$上 ⇒ 有人发话、或任何会话跑一次 Bash 就会被调起。--tick(投递)挂同样两个事件(节流 120 s)⇒ 投递与"有事发生"同刻。- ⇒ 不再需要靠宿主排期"让机制活着"(旧结论「没有宿主排期 = 确定性静默」已作废)。
⚠️ 剩下的唯一真相:"全员静止"期间(没人发话、任何会话都不跑 Bash)不会有通知飞出去 —— 那是为摆脱排期而明确接受的代价;
停滞类信号由会话外的守护落
STALL.md/NEED-USER.md,下一次任意宿主事件时补投。
idle("多久没成果")只认「真成果」:服务探针的状态变化属服务层 —— 既不重置 idle、也不进 🟢。
(实测教训:4 小时静默里程序那唯一一轮恰好赶上探针「不通→通」⇒ 摘要写「1 分钟无成果」而不是「4.5 小时」⇒ 连一声告警都没有。)
受阻 ⇒ ⛔ 不能静默:选不出队首但 blocked.json 非空时仍写 NEXT.md(受阻通报)—— 否则就是「有活 ∧ 谁都动不了 ∧ 程序一声不响」。⚠️ 写 NEXT.md 时内容不得含时间戳,否则唤醒回路的内容哈希去重失效 ⇒ 每轮都投一遍(刷屏)。
唤醒回路的两个触发面:① NEXT.md 存在且 gate=free;② NEXT.md 不存在但停滞 / 可派未派(哈希须用粗粒度键:种类 + 空闲按 30 分钟取整,⛔ 不能用带时间戳的正文)。
🔴 链条不能只靠「上一棒派下一棒」:棒中途死掉(实测发生过:探针回环打到自己会话)⇒ 链条静默断掉,只能等下一次有人发消息。
⇒ ⛔ 不要靠发明新件来兜底(我发明过"+30 分钟看门狗",已废弃)。正解在架构原文里:收尾自判(在已存在的会话里判本线缺口并写下一行 automations)+ 每小时兜底唤醒(定稿 §3②,专防链条断掉)。改前先过 §10。
10 🔴 改本机制前必须过的「防打转判据」(架构原文 · 顶层设计 §7)
原文原话:"以后凡是给这套系统加东西,先过这张表 —— 只要让『必须存在的东西』或『需要记得清理的东西』变多,就要停下来重新想,而不是继续补。"
| 判据 | 目标 |
|---|---|
| 自造件数 | ≤3(机械判定脚本 / 看板 / 规则文档) |
| 🔴 自造协议数("需要记得清理"的) | 0 —— ⛔ 不自造 mkdir / flag 去抖:域锁就是单例 |
| 必须活着的进程数 | 0(⛔ 不用会话后台任务) |
| 必须存在的会话数 | 0 |
| 额外会话 / 棒 | 0(收尾自判在已存在的会话里完成) |
四条地基(顶层设计 §1/§2):① 状态只读宿主库(mode=ro)② 调度只靠 automations 一行(⛔ 不自建调度器)③ 互斥只用既有域锁(⛔ 不自造第二把锁/第二套去抖)④ 执行体无状态(会话死了状态不能跟着死)。
正确形态 = 2 个声明式操作 + 1 份规则:派活 = 往 automations 写一行(不"叫会话");收结果 = 读 automation_runs(⛔ 不轮询、不催、不问)。⇒ 「监管者」这个角色不存在,只有两个动作:收尾自判(在已跑的会话里)+ 兜底唤醒(⚠️ 原文写「宿主排期」—— 🔴 2026-10-02 标注(⛔ 不是口径变更):定案的兜底唤醒走常驻投递,排期只是代偿,见 §5-1)。
🔴 权威清单(改任何判定前先看它 —— 权威只在这四处)
| 要判什么 | 权威(唯一) | ⛔ 不许 |
|---|---|---|
| 谁在跑 / 哪条线被占 | sessions.status='working' + 其 cwd(末段=线名) |
自己写 holder 文本再解析 |
| 多久没成果 | automation_runs.updated_at(毫秒 epoch) |
自己维护 last_progress_at 时钟 |
| 有没有排期(会不会有人自动跑) | automations.next_run_at 未来 1 小时内 |
看"全库有没有排期" |
| 一棒做完没有 / 结论是什么 | automation_runs(thread_title;⚠️ thread_id 形如 <aid>:1,不是会话 id) |
轮询别的会话 |
⇒ 自己写的文件只有两种合法身份:投影(可删可重建:看板 / NEXT.md / queue.json)或人工输入(如 blocked.json)。⚠️ 凡"需要记得清理"的一律不许(claims/TTL/僵尸目录都属此类)。
11 🔴 术语表(说话/写文档一律用「正式名」这一列)
2026-09-29 用户指出"宿主是什么/机械程序又是什么,之前只提过投递和常驻程序" —— 属实:同一个程序在文件里有 4 个名字,图当然读不懂。以本表为准。
| 正式名 | 实体(在哪) | 干什么 | ⛔ 别再叫它 |
|---|---|---|---|
| 宿主 | WorkBuddy 本体 | 提供五样:状态(只读 3 表)/调度(自动化排期)/事件(钩子)/执行体(会话)/互斥(域锁) | 「平台」「服务端」 |
| 投递 | advance-watch.py(.workbuddy/tools/) |
零 token 机械判定:直读 automation_runs、判靶点/锁,写 advance.md + needs-ai.json。⛔ 不派活/不开会话/不联网/不读口令 |
「机械层程序」「机械程序」「看门狗」 |
| 常驻程序 | collabd.py(自述"协作守护程序") |
队列闸门(NEXT.md)+ 唤醒投递 + 证据分级/真空/断链告警/体检/看板覆写。⛔ 不做派活 |
「判定层」「常驻程序」「机械层」 |
| 会话 | sessions 表 |
执行体:「读—判—写」;一次性、可替换 | —— |
| 棒 | 一次派活 = 一个会话 | 同一件事的执行单位(「一棒一线」) | —— |
| 看板 | scripts/board.py --serve → assets/board.html |
用户唯一入口(只读旁路 · 异步快照) | ⛔ 别把 digest.md(机械摘要)当用户入口;⛔ 也别再提已退役的 交付物/手机接入-实时状态.md |
| 设备接入 · 本地反代 | dsh-client · 真身 @dsh-client/device-shim(127.0.0.1:20090) |
只绑回环的 HTTP+WS 反代,在进程内注入本机 DSH 的会话凭据,供覆盖网络中继从设备入口访问桌面 DSH。⚠️ 该包已标「已退役」,计划并入 @dsh-local/ai1net 的第 6 块模块M6「设备接入」,但尚未落地(仓库里还没有 ai1net 包) |
⛔ 别再叫它「垫片」「shim」「薄垫片」 |
| 覆盖网络节点 · 中继客户端 | dsh-client · <Repo>/lib/net/relay/main.js --client,由 overlay-node-daemon.ps1 监护 |
本机作为节点接入覆盖网络:向中继注册(registered host=… accepted=[20090])、维护长连、转发流 |
⛔ 别再叫它「设备侧 worker」「worker」「节点进程」 |
11.1 🔴 命名铁律(用户 2026-09-30 明令)
用户原话:「垫片 20090 在线,设备侧 worker 在线:禁止用这么抽象的词, 用 系统-模块-功能名(系统-功能名)」
- 🔴 凡涉及外部系统/组件,一律写成「
系统·模块·功能名」 —— 系统=它属于哪个系统/仓库(如dsh-client、ai1net);模块=在这个系统里算哪一块(如 设备接入、覆盖网络节点); 功能=它干的活(如 本地反代、中继客户端)。简写时至少保留系统 · 功能名。 - ⛔ 不许自造简称("垫片"/"worker"/"两条腿"/"有腿掉了" 这种),⛔ 也别只报端口或只报英文包名当名字。
- ⚠️ 报"在线"必须说清是哪一段的什么状态,且 ⛔ 不许由"两段都在线"推出"链路通过" ——
实测教训:中继客户端
state=up而streams=0(从未有请求真正被转发)⇒ 那时写"这条链是通的"=过度断言。 ⇒ 判「端到端通过」的唯一判据是中继真转发过流(streams>0);否则一律写「未验证」。 - 📌 落地处:
board.py顶部有本铁律的注释块 +front字段全部改用真名;看板「链路前置」卡按真名渲染。
⚠️ 以下为 2026-09-29 的史实快照(当时的"现状打架"),⛔ 不是现状;现状以
architecture.md头部「当前结论」为准。
——advance-watch.py死活之争协同监管棒-SOP.md §276说它「已吸收退役」,而顶层设计/定稿/实施方案/CODEBUDDY.md仍把它当活件。已随退役件一并作废。- 同一程序多个名字:任务图叫「协作程序」、它自述「协作守护程序」、原技能叫「机械层/常驻程序」、实施方案叫「机械层脚本」⇒ 统一口径见
architecture.md §7 术语。 同一程序 2 个路径—— 旧副本~/.workbuddy/skills/multi-session-collab/已于 2026-10-01 归档退役,现只剩一份:本包scripts/collabd.py。
12 文档权威(治"架构都是乱的" —— 原技能时代的问题)
🔴 本节原声明「本技能 = 协作机制的唯一权威」—— 已作废。并入本包后,唯一权威是
references/architecture.md(见本件头部)。
当时的病:曾有 5 份"架构"并存且互不认 —— 交付物/多会话协同机制-定稿/…顶层设计/…实施方案/协同监管棒-SOP/原技能本身;
实测出 5 处互相矛盾(一棒做完怎么办 / 「监管棒」算不算角色 / 投递的死活 / 钩子生效了没 / 钩子调的是哪一份程序)。
⇒ 现已收口:架构只认 architecture.md;那 4 份业务件 2026-09-30 已加退役标注,可作背景,⛔ 不作用判据。