- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次) - .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…), 目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪 - .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/) - .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*) - .gitignore 补:备份件(*.bak-*) - 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
18 KiB
协作机制 · 同一工作区 · 多任务类别(2026-09-30)
一句话:把归属锚点从「跨工作区(cwd)」改成「同一个工作区 + 名称前缀里的任务类别」—— 同一工作区里可并存多个任务类别,每类各有自己的主会话,投递按件的类别选主会话。 验证:协作机制自测 PASS 28 / FAIL 0;真实工作区冒烟:分工板已按类别归位、投递目标解析正确。 图 ⇒
协作-同工作区多任务类别-20260930-图.html(① 泳道图:类别前缀在哪些环节起作用 ② 缺口→修复链图)
一、用户要求(原话)
「之前的协作会话是跨工作区的,要能支持同一个工作区 多会话协作 (主会话根据任务自动梳理任务类别:通过协作会话名称前缀的方式区分具体任务会话, 所有主会话,协作会话,自动唤醒任务,都在一个工作区)」
拆成三条可判据的要求:
- 都在一个工作区 —— 归属判据 ⛔ 不许挂在
cwd上; - 任务类别靠名称前缀区分 —— 会话标题里的类别名就是类别键;
- 主会话/协作会话/自动唤醒任务一视同仁 —— 三类都走同一套前缀规范。
二、旧版哪里不够(代码级取证,⛔ 不是推测)
| # | 位置 | 旧实现 | 后果 |
|---|---|---|---|
| A | collabd._in_project() / board.in_project() |
归属判据 ②「标题含 [<goal.short>]」——只有一个 short |
同一工作区跑两个类别时,除那一个之外的类别全被判"不属于本项目" ⇒ 看板不列、--ready-next 不算 ⇒ 静默漏管(不报错,是"什么也不说") |
| B | collabd.resolve_main() / board._resolve_main() |
返回一条主会话("本工作区里那一条就是它") | 多类别 ⇒ 只认一条 ⇒ 投递会把 A 类别的件投给 B 类别的主会话=投错窗口 |
| C | collabd._deliver_str() |
目标是唯一主会话,无"按类别选路" | 同上 |
| D | board._labor() |
分工板的"线"取自 goal.lines,判据是 cwd_tail == 线 |
同工作区多类别 ⇒ cwd 全相同 ⇒ 多类全塌成一行 |
| E | 实际排期名(证据) | [协作]-唤醒轮A · ai1net-dsh-server —— 缺第 2 级(类别) |
解析不出类别;活动一多还会被当成主会话候选 |
实测读到的现状(本工作区,宿主 sessions 表,读时 11 条)
a202550c | working | 主控 · 唤醒机制线 · 棒:worker 退出后能否被外部叫起(无人值守决定性一问)
6ecf6d98 | completed | [主控]-[机制线]-接续:查「唤醒」为什么断
fe146dd9 | completed | 接续 · 机制线(钩子锚点真实投递取证) ← ⚠️ 登记里的 main,**已死**
⇒ 本工作区本来就有 ≥2 条"主控"会话,且它们的类别名已经写在标题里(唤醒机制线 / 机制线)——
说明"用名称前缀分任务类别"是已经在用的习惯,只是程序侧没认。这一棒就是把它接上。
三、落地清单(改了哪几处)
3.1 技能侧代码(~/.workbuddy/skills/multi-session-collab/scripts/)
| 文件 | 改动 |
|---|---|
collabd.py |
① 新增 _goal_topics()(类别清单,缺省回落 short)② 新增 _as_list()(单值/集合归一)③ _in_project() 第 4 参泛化为类别集合(⛔ 传 str 时与旧版逐字一致)④ 新增 _topic_in_title()(最长优先,防短名抢走)⑤ 新增 _scan_mains()(一遍扫描、resolve_main 与 resolve_mains 共用 ⇒ ⛔ 不各扫各的=防两边漂移)⑥ resolve_main() 改为薄封装(行为不变)⑦ 新增 resolve_mains() / main_for_topic() / _title_of() ⑧ _deliver_str(..., topic=) 按类别选主会话,类别已登记但无主会话 ⇒ 降级报告(⛔ 不投给别类别)⑨ supervise() 上报投递带上件所属类别(topic=台账条目的 line)⑩ ready_next() 排除全部主会话(⛔ 否则别类别主会话被算成"在跑"⇒ 永远拦住)⑪ goal_line() 改显示类别清单 |
board.py |
① 同款 _goal_topics() / _as_list() / _topic_in_title() ② 新增 _scan_ws_mains()(与 collabd._scan_mains() 同款)③ _resolve_main() 改用它并返回 all_sids ④ project_scope() 带出 topics / main_sids ⑤ in_project() 认任一类别(⛔ 无 topics 回落 short)⑥ _sessions() 的 role 判定改成"是任一主会话",并给每条会话带出 topic ⑦ _labor():分工板的行 = 任务类别(goal.topics 优先),承接判据 topic == 线 ∨ cwd_tail == 线 ∨ 标题命中该线的台账件(双判据并列=新旧数据都能显示) |
guard.py |
帮助文本补 topics |
selftest.py |
主会话用例加强(22 项,原 12 项):新增 9 条"两处同款/按类别选路/严格不回落"断言;归属判据用例从 7 例扩到 15 例(新增 8 例多类别),并加一条结构断言(两处 by_topic 值必须是 dict) |
3.2 本工作区配置
| 文件 | 改动 |
|---|---|
tmp/supervise-inbox/goal.json |
新增 topics(唤醒机制 / 文档库治理 / 规则载体 / IM,来源=工作区根现有的接续入口/接续包,⛔ 不凭空编)+ 说明段;lines 与 topics 统一为同一个概念(topics 权威,lines 兼容旧名)——旧值是工作区名,正是"跨工作区"时代的残留 |
排期 caca9a89… / 16bec5ce… |
名字补类别前缀:[协作]-唤醒轮A/B · … → [协作]-唤醒机制-唤醒轮A/B · ai1net-dsh-server(两条均 PAUSED,⛔ 未改 rrule、⛔ 未启停) |
3.3 技能文档
references/architecture.md:§2.3 第 2 级从「主题」正名 「任务类别」;新增 §2.3.0「同一工作区 · 多任务类别」(为什么旧版不够/三条规则表/命名/已知边界)。SKILL.md:命名定则补"任务类别"四处同义 + 多类别要求 + 自动唤醒任务的排期名也必须带类别前缀;last_change更新。
四、验证
4.1 自测(技能内置回归)
协作机制 · 回归自测 合计:PASS 28 / FAIL 0
(含"两处判据同款"15 例比对、"投递方唯一"、"零投递"、"不消费队列"等既有硬项。)
4.2 真实工作区冒烟(本棒实测读数,非示例)
topics = ['唤醒机制','文档库治理','规则载体','IM']
resolve_mains(带活网关)
default = a202550c (source=prefix:主控, switched_from=fe146dd9) ← 登记的旧主会话已死 ⇒ 正确跟随
mains = {唤醒机制: a202550c(prefix:主控), 文档库治理: (空), 规则载体: (空), IM: (空)}
main_for("") → a202550c # 空 ⇒ default(心跳这类全局内容走它)
main_for("唤醒机制") → a202550c # 已登记类别 ⇒ 严格取该类别的
main_for("文档库治理") → (空) # 该类无主会话 ⇒ 报"找不到",⛔ 不投给别类别
main_for("ai1net-dsh-desktop")→ a202550c # 未登记(旧数据的工作区名)⇒ 回落 default,向后兼容
分工板(board.build())
唤醒机制 state=busy run=[a202550c] ← ✅ 按任务类别归位生效
文档库治理/规则载体/IM idle
ai1net-dsh-desktop / ai1net-dsh-anywhere ← 旧台账里的工作区名(历史,⛔ 不迁移)
--tick → tick: item=- awaiting=- phase=- deliver=- (rc=0, 静默)
--ready-next → ✅ 可以排下一棒 (rc=0)
4.3 冒烟抓到的一个真 bug(顺带修掉)
by_topic 的值在 collabd._scan_mains() 里是 dict、在 board._scan_ws_mains() 里曾是 裸 sid
⇒ board._resolve_main() 会把整个 dict 当 sid 塞进 all_sids(静默错值)。
已统一为 dict 并加了自测结构断言(⛔ 防回潮)。⇒ 这是"写完必须冒烟"的一个正面例子。
五、边界与已知坑(诚实标注,⛔ 不粉饰)
- 🔴 类别名是子串匹配(主会话是口语式命名如
主控 · 机制线(…),没有方括号)⇒ 类别名别取太通用的词(如"机制"),否则会误吞别人的标题。源码已做"最长优先",但根本解法是类别名取得足够具体。 - ⚠️ 台账里的历史条目
line仍是工作区名(跨工作区时代产物)⇒ 见 §八: 已改为折叠成一格「未归类」照实画出来(初版曾把它们当"分工位"再被图上截断 ⇒ 整块不见,已修)。 ⛔ 不迁移历史数据(迁移=改写记录);新条目上报时用类别即可。 - ⚠️
_in_project()的标题判据要求方括号([类别]),而主会话标题是口语式(无方括号) ⇒ 非默认类别的主会话靠"sid ∈ 解析出来的主会话集"这条判据进来(不是靠标题)。这是有意的:归属与"谁是主会话"是两件事。 - ⚠️
main_sids取解析出来的(默认 + 各类别),⛔ 不是"本工作区所有会话" —— 否则"本工作区随便开个无关会话"会被当成本项目(判据被无声放宽)。 - ⚠️ 只有默认类别(
topics[0])在某类未解析出主会话时会回落default(⛔ 只为兼容单类别部署); 其它类别严格——没解析出来就降级报告,这是设计意图(用户要求"每类各有自己的主会话")。
六、改动落点 / 回滚
- 备份:
tmp/bak-同工作区多任务-20260930-205722/(collabd.py/board.py/goal.json.bak) - 回滚:把备份里的三个文件覆盖回去即可(⛔ 技能目录不留副本,一律绝对路径调用)
- 涉及文件(绝对路径):
E:/ProgramData/.workbuddy/skills/multi-session-collab/scripts/{collabd.py,board.py,guard.py,selftest.py}E:/ProgramData/.workbuddy/skills/multi-session-collab/{SKILL.md,references/architecture.md}E:/ProgramData/AIProject/ai1net-dsh-server/tmp/supervise-inbox/goal.json
七、待用户拍板(⛔ 只有一条,其余我自决)
要不要现在就把 goal.topics 定成"长期类别表"?
- 现状:我按工作区已有的接续入口/接续包填了 4 类(唤醒机制/文档库治理/规则载体/IM),
并把
唤醒机制放第一位(=默认类别,当前唯一有活主会话的)。 - 说明:这决定"哪些会话算本项目、投递往哪条主会话去"。填错不会坏数据,但会静默漏管/多管。
- 倾向:先按现状用(4 类,够用且都取自真实文件);等你哪天说"这几类不对/还要加一类",
改
goal.json.topics一行即可(下一轮钩子即时生效,⛔ 不必重启)。
八、看板改动 + 整体验证抓到的两个同族红线(2026-09-30 收尾棒)
用户原话:「改完记得整体验证,确定一切正常 就修改看板 看看流程对不对」⇒ 本节就是这三步的账。
8.1 看板改了什么(assets/board.html + board.py)
| 位置 | 改成 |
|---|---|
| 项目卡 | 新增**「任务类别(同一个工作区 · 按名称前缀区分)」**一行:每类一枚 chip,带该类主会话;无主会话 ⇒ ⚠️ 无主会话 警示色 |
| 架构图第三层 | 从"按线"改成按任务类别:一行 = 一类;格内五行 = 类别名/该类主会话/最近在做什么/当前谁承接/件汇总。R3 行高 110→134,R4 366→516 顺移 |
| 会话表 | 新增**「任务类别」**列(值=解析出来的类别,判不出显 —) |
| 台账表 | 列名「分工(线)」→「任务类别」 |
| 验收为空 | 不再静默 ⇒ ⚠️ 未声明验收判据(⛔ 不因此判完成) |
8.2 🔴 红线 ①:acceptance_state 只有说明行时被当成"全过"
- 形状(同族红线:不崩溃,只是少说一句话):
goalctl.goals_open()写的是if not acc—— 判的是字典空不空;而实际有_说明/_更新两个说明行 ⇒ 永不触发 ⇒ 控制台显示「三路全过」。collabd._acc_short()同族(兜底打印"全部 pass —— 目标已达成")。 - 修法:判据改成「有没有有效项」(键不以
_开头)⇒ 新增goal_state()三态:open(有未过项)/pass(有判据且全过)/undeclared(一条有效判据都没有 ⇒ ⛔ 不算过)。board.py的命令行摘要同族(… or "无"读起来就是"全过")⇒ 一并收敛到_acc_summary()。 - 复验:控制台现显
⚠️ 未声明验收判据(只有说明行)⇒ **判不出来**,⛔ 不因此判完成。
8.3 🔴 红线 ②:台账里的旧线(工作区名)整块不见
- 实况:类别清单已迁到任务类别(唤醒机制/文档库治理/规则载体/IM,各 0 件),
而台账里还躺着 2 条跨工作区时代的旧线(
ai1net-dsh-anywhere/ai1net-dsh-desktop,各 2 件且都已完成)。 旧版把它们也当"分工位"塞进labor,架构图又只画前 4 格 ⇒ 4 件已完成的活一格都看不见, 读者只看到 4 个"件 0 · 完成 0"。形状同族:不崩溃,只是少说一句话。 - 修法:①
_labor()每行加kind(topic= 命中goal.topics;legacy= 只在台账/lines里出现过) ②build()加orphan汇总(条数 + 件数求和)③ 看板折叠成一格「未归类」照样画出来 (格内:⚠ 不属任何当前任务类别+ 线名 + 件数)④ 图外提示点名"哪几条线、共几件" ⑤ 台账表给这些值跟一句弱化说明(旧值 · 不在当前任务类别清单)—— ⛔ 只弱化、不隐藏。 - 判据:图上不再有"看不见的活";也不让工作区名冒充类别。
8.4 验证读数(全部可复跑)
| 验证 | 结果 |
|---|---|
技能自测 selftest.py |
PASS 31 / FAIL 0(新增 2 个看板用例:验收三态 4 项、未归类可见 4 项) |
看板渲染断言(Node DOM 桩,现取 board.html 主脚本喂真实快照) |
18 / 18 |
| 架构图几何自检(越界/压行/格间重叠) | 3 / 3(R3 现 5 格:x=60,296.8,533.6,770.4,1007.2,宽各 212.8,零重叠、右沿正好 1220) |
| 真实工作区快照 | 4 类 + 1 个「未归类」格;orphan = {n:2, total:4, done:4};台账表 4 条均标「旧值」 |
⚠️ 为什么用 Node DOM 桩而不是真浏览器:本机 9223 上没有独立浏览器实例,而本机明令禁止自起 headless
chrome /附着用户 Chrome ⇒ 不擅自开浏览器。桩只验渲染出的 DOM 内容与几何,验不了像素级观感;
另出一份离线预览页(快照内联、file:// 直开)供人眼复核。
九、用户两条订正(2026-10-01)—— 都是"我把机制当成了固定配置"
9.1 🔴 「目标是 通过对话在调用 会话协作skill时说明的,不是固定的」
我上一轮做错了什么:把 goal.json.topics 按工作区里现有的接续入口/接续包文件名填了 4 类
(唤醒机制/文档库治理/规则载体/IM),还当成"待你拍板定死"的事项去问。
⇒ 那是猜,不是说明。危害不是"填错会报错",而是它静默决定了
「哪些会话算本项目、投递往哪条主会话去」—— 且没有任何一处会说"这批类别是猜的"。
落地(三件)
| # | 落点 | 内容 |
|---|---|---|
| 1 | 技能 SKILL.md §0.05 + architecture.md §0.1 |
写明:目标/为什么/任务类别/验收判据四样都由"调用技能时的对话"产生;⛔ 技能侧与脚本不许预设、⛔ 不许从目录名/文件名推 |
| 2 | 使用方 goalctl.py declare(新命令) |
"说明"的唯一落点:declare --title … [--why …] [--topics …] [--kpi …] --yes。⛔ 默认干跑;--title 必填(脚本不替你编目标);省略某项 ⇒ 不动该项;--topics "" ⇒ 显式清空 |
| 3 | 看板 project.topics_source |
kind ∈ {declared, fallback, none} ⇒ 没说明过时明写「⚠ 未声明 ⇒ 暂回落目标简称」(⛔ 不许让人以为"这就是定下来的类别");declared 时显示说明时间/说明人 |
数据订正:goal.json 里我猜的那 3 类已移出 topics(进 _topics候选,注明"未经对话确认、
依据是文件名"),topics 只保留有依据的那一条(唤醒机制 —— 来自本线会话标题
主控 · 唤醒机制线 · …)。lines 同步。
9.2 🔴 「我说的看板是 实时动态看板 现在被关闭了」
事实:goalctl stop --yes(2026-09-30 20:27)把 board.py --serve 一起停了 ⇒ 21:00 之后你看到的
只是我做的离线快照页(复核手段),不是你要的活着的那一个。
已恢复:http://127.0.0.1:8788/ 已重新起着(会话后台任务 + stdout 落文件)。
验收读数:GET / → 200(59,076 B);GET /board.json → 200(9,551 B);
连拉两次 epoch 1790785810.9 → 1790785814.1 ⇒ 真·实时;netstat 确认只绑 127.0.0.1:8788(PID 34184)。
快照内容:topics=['唤醒机制'] · source=declared;labor = 1 个类别 + 2 条 legacy;
orphan={n:2,total:4,done:4};warn=[]。
落点同步:SKILL.md §0.5.0 新增「看板默认指那个实时动态看板」(地址/起法/--takeover 换代码重起/
停机出口),并如实登记两条边界:
- 它是会话后台任务 ⇒ 关会话/关宿主就停 —— 本机没有真正常驻的手段(detached 活不过工具调用边界;
schtasks/reg等持久化工具在内置黑名单里)⇒ 这不是"忘了常驻",是做不到。 - ⚠️ 该会话挂着
pending/running后台任务时,宿主的idle钩子会被静默压制(09-30 实测压过 6h20m) ⇒ 正解=让"起看板"落在一个不承担派活职责的会话里,⛔ 别让主会话干这件事。 - 另:
goalctl start的 ③ 服务层原本只说"不自启常驻"、没给起看板的命令(="开"这条路漏了看板) ⇒ 已补上可直接复制的整行命令。