session-mechanism:主会话只能由用户开(撤掉"排期建主会话"那条路)
用户报障:「技能中没有任何创建主会话的机制,除非是创建接续会话」。
病灶:supervise-persistence.md §〇 早期把「定时任务创建主会话」与「用户手动
创建主会话」并列成两种合法场景,SKILL.md §⑥ 顺着抄了一遍 ⇒ 读到的人会理解成
"这条路是允许的",于是照做。
实测事故(mcn-aigc-ontology 2026-10-14 07:31):init_workspace 装机制后建出
recurring 排期 [主]-super-i卡片采集-主会话(FREQ=DAILY;BYHOUR=09),prompt 里
写着「你是本工作区的主会话……要推进就直接建 [执行] 排期拉起任务会话」——
主会话的口径被套到了一条排期身上。排期开出来的会话恒是任务会话那一档
(is_background_automation=1)⇒ 台账里多一条"主会话",用户那边没多窗口。
改动:
- SKILL.md §⑥:改成「形态只有一种:主会话由用户手动开」,⛔ 排期拉不出主会话
- supervise-persistence.md §〇:表格两列改一列 + 新增事故回放小节
- supervise-persistence.md §八:删掉「B · 定时任务创建主会话」整行
- architecture.md §1:新增《主会话怎么来:只能由用户手动开》段
- session-rules-check.py:新增判据 ⑬ main_sched(扫排期名以 [主] 开头或含
"主会话" ⇒ ACTIVE 报 fail、非 ACTIVE 报 warn);扫描面 recurring 与 once 全含
- SKILL.md 体检清单:十二项 → 十三项,补 ⑬ 变异对照记录
判据验收(非恒绿):喂 mcn 区真数据三种输入 —— 原样 PAUSED ⇒ warn;
伪造成 ACTIVE ⇒ fail;无此类排期的区 ⇒ ok。
同批带上的既有改动:supervise-launch.py 路径相对化 + 三级取值(禁回落 ~)。
This commit is contained in:
1 parent
aa89ee0333
commit
053a6a6723
5 files changed
+178
-24
No files matched your search
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: session-mechanism
|
||||
description: 「**会话机制 + 多会话执行**」的合并总入口(原 `multi-session-collab` + `workbuddy-session-forensics` 已并入本包)。🔴 **2026-10-05 起会话只三类:主会话 + 任务会话 + 检查会话**(唤醒/跟进/队列上报**已整套退役**;**任务会话=旧称「执行会话」,同一个角色**)|🔴 **2026-10-03 起「上报」整套真删**——现行机制=**常驻程序 `collabd.py` 接收(`--report` 写台账)+ 处理(建检查会话排期)**(⛔ 「协作程序」是 10-03 已退役的旧称,见文首术语表),⛔ **队列变化不再自动通知任何人**,要落事得**显式建执行会话**。两段可分别加载:① **会话机制** —— 管「会话怎么活下去、怎么不哑掉、怎么不失忆、跨机器怎么一键装好」:治「会话卡住 / 一直转圈 / 发消息没反应 / 界面不刷新」「会话日志涨到上限把界面顶死」「上下文爆了要换会话 / 接续会话怎么开」「抢锁 / 执行锁 / 并发 / 别的会话在动」「钩子没生效 / 钩子把我也拦住了」「换一台电脑要重装这一堆钩子」「某个历史会话当时到底干了啥」。② **多会话执行** —— 管「一个主会话带多个执行会话把需求做完」:治「多个会话协同但工期被拖长 / 有会话在干等 / 链条断了没人接 / 分不清『真完成』还是『只排了下一棒』/ 问清主会话·执行会话两类怎么分(**接续会话是撞阈值时的「形态」,⛔ 不是第 3 类**)」。🔴 **主触发句(2026-10-03 用户定案)**:用户说「**使用执行会话完成 XXXX 目标**」「**继续 XXXX 目标**」时**直接走本技能**(=要派执行会话去把一个目标做完/接着做)。其他触发:「怎么协同多个会话」「别的会话都在干等」「任务没推进」「链条断了」「你监督这些会话」「会话卡住」「日志要爆了」「换机器怎么配」「升级 / 安装这套机制」。核心=**会话机制三件套(钩子 + 锁 + 日志闸)全局生效** + **执行两条通道各走各的**(派活靠自动化/收结果直读宿主库/机械判定下沉到常驻程序/人只看一个看板)+ **一键配置 `install.py`**(换机器不手抄绝对路径)。
|
||||
description: 「**会话机制 + 多会话执行**」的合并总入口(原 `multi-session-collab` + `workbuddy-session-forensics` 已并入本包)。🔴 **2026-10-05 起会话只三类:主会话 + 任务会话 + 检查会话**(唤醒/跟进/队列上报**已整套退役**;**任务会话=旧称「执行会话」,同一个角色**)|🔴 **2026-10-03 起「上报」整套真删**——现行机制=**常驻程序 `collabd.py` 接收(`--report` 写台账)+ 处理(建检查会话排期)**(⛔ 「协作程序」是 10-03 已退役的旧称,见文首术语表),⛔ **队列变化不再自动通知任何人**,要落事得**显式建执行会话**。两段可分别加载:① **会话机制** —— 管「会话怎么活下去、怎么不哑掉、怎么不失忆、跨机器怎么一键装好」:治「会话卡住 / 一直转圈 / 发消息没反应 / 界面不刷新」「会话日志涨到上限把界面顶死」「上下文爆了要换会话 / 接续会话怎么开」「抢锁 / 执行锁 / 并发 / 别的会话在动」「钩子没生效 / 钩子把我也拦住了」「换一台电脑要重装这一堆钩子」「某个历史会话当时到底干了啥」。② **多会话执行** —— 管「一个主会话带多个执行会话把需求做完」:治「多个会话协同但工期被拖长 / 有会话在干等 / 链条断了没人接 / 分不清『真完成』还是『只排了下一棒』/ 问清主会话·执行会话两类怎么分(**接续会话是撞阈值时的「形态」,⛔ 不是第 3 类**)」。🔴 **主触发句(2026-10-03 用户定案)**:用户说「**使用执行会话完成 XXXX 目标**」「**继续 XXXX 目标**」时**直接走本技能**(=要派执行会话去把一个目标做完/接着做)。🔴🔴 **未装即装(2026-10-14 用户令)**:说「使用项目机制完成目标:XXXX」时,若本区 **没装机制** ⇒ **当场跑 `install.py --apply --workspace <本区根>` 装好,再往下走**(装机制是这条链的第一环,⛔ 不报成阻碍、⛔ 不绕过机制直接干、⛔ 装完不停手)。其他触发:「怎么协同多个会话」「别的会话都在干等」「任务没推进」「链条断了」「你监督这些会话」「会话卡住」「日志要爆了」「换机器怎么配」「升级 / 安装这套机制」。核心=**会话机制三件套(钩子 + 锁 + 日志闸)全局生效** + **执行两条通道各走各的**(派活靠自动化/收结果直读宿主库/机械判定下沉到常驻程序/人只看一个看板)+ **一键配置 `install.py`**(换机器不手抄绝对路径)。
|
||||
version: 1.3.0
|
||||
updated_at: 2026-10-05
|
||||
last_change: 2026-10-05 10:5x · 🔴🔴 **`lifecycle` 判定收敛为「单一事实源」**:抽 `goal_life_of(root)`(剥全角/半角括号后缀 + 认不出时写日志),`goal_life()` 降为薄封装,`peer_supervise_sweep()` 改调它 —— 此前它**自己又抄了一遍**精确匹配(不剥后缀)⇒ **同一份 `goal.json` 两条路两个答案**。⛔ 判「目标状态」一律问代码(`--check-status`/`goal_life_of()`),⛔ 不许肉眼读原文比对。⚠️ 端到端实测坐实**夹具坑**:合成区只复制配置字节、不改 `workspace` 字段 ⇒ 钩子读的是**真区心跳** ⇒ 正确地"在位沉默",而我差点误判成"钩子失效"(P0-53)。 🔴 起不来先报错这条红线已落进 T 表 §1.6(用户原话「必须首先启动好在执行」)+ **`supervise-ensure-hook.py` 两个真缺陷已修并变异验证报红**:① 工作区原先只读 `DSH_WS_ROOT`(宿主 env 三级全空 ⇒ **钩子长期空转**)② `env=dict(os.environ)` 对 `exec_module` 里的 `load_cfg()` **无效** ⇒ 会验到别的区。⛔ 教训=**「零输出」既可能是"正确沉默"也可能是"根本没看见"⇒ 必须造合成区做反向对照**。 🔴 10-04 17:3x · **文档索引已建**:`references/01-文档索引.md`(干什么事→看哪篇 + **文档四条规则**:分类索引/结论在最前/历史倒排·新的在前/单条 ≤6 KB)+ SKILL.md 第一屏挂「改机制前必读三篇」(⛔ 只写进工作区日志的规矩=没立:10-03 立的「⛔ 写会怎样前先取证」10-04 复发)。 🔴 **本行是「现行口径台账」,⛔ 不是变更日记**:只记**现行结论**,实体与来路在 `references/`(下面每条都给指向)。⛔ **不再在这里堆叠「本条为准」式的旧口径**(那会让同一件事在两处各存一份、改一处忘另一处就打架)。
|
||||
@@ -92,6 +92,24 @@ agent_created: true
|
||||
技术选型/实现路径/命名/调参/部署/排查/版本/兼容降级/文档技术内容。
|
||||
**只准提报三类**:① 功能语义分叉(做 A 还是做 B,影响用户看到什么)② 红线门禁 ③ 超出决策边界(花钱、对外承诺、要凭据)。
|
||||
|
||||
### 🔴🔴 第 0 步的连体动作:**本区未装机制 ⇒ 当场装**
|
||||
|
||||
> **判据(一句话)**:**用户说了触发句 ⇒ 第 0 步先加载本技能;加载后查本区有没有装机制,没装就当场装完再动手。⛔ 不许把"未装"报成阻碍、也不许绕过机制直接干。**
|
||||
|
||||
- **为什么在这里**:触发句「使用项目机制完成目标:XXXX」**本身就是授权**(见上一条)—— 它授权的是"按机制把目标做完",**装机制是这条链的第一环**,不是需要另行批准的事。
|
||||
用户原话(2026-10-14):「**我都说 按照项目机制完成目标了,技能没要求自动安装这套嘛**」「**没安装就要自动安装,是不是技能没说清楚**」。
|
||||
- **查法(问程序自己,⛔ 不许 glob/手拼路径)**:本区读数是 `<工作区根>/.workbuddy/collab/collabd.config.json` **在不在**。
|
||||
在 ⇒ 已装,直接往下走;**不在 ⇒ 未装,立刻装**。(`glob('**/x')` 不穿透 `.workbuddy` 这类点目录 ⇒ 零命中 ≠ 不存在,10-04 栽过。)
|
||||
- **装法(一条命令,⛔ 不许手抄路径)**:
|
||||
```bash
|
||||
python <技能包根>/install.py --apply --workspace <本工作区根绝对路径>
|
||||
```
|
||||
它按**声明表**接线全局钩子 + 写本包 `env/roots.env` + 初始化本区 `.workbuddy/collab/`。
|
||||
跑完接一条 `--verify`(每个钩子空载荷 rc=0 + `collabd --where` + selftest)。
|
||||
- **可逆性(这就是它可以自决策的原因)**:`--apply` 每次另写**带微秒的时间戳备份**;`--uninstall` 优先用 `-orig` 备份还原 `settings.json`,还原后做**逐字节**比对,不符即报错。⇒ 装错能退回,不构成不可逆破坏。
|
||||
- **⛔ 装完不停手**:装机 ≠ 完成目标。装完**继续**把触发句那个目标接下去(起常驻 → 查目标 → 派这一轮任务会话)。
|
||||
⛔ 不许在装完或 `--verify` 全绿处停下、回头问"要不要继续干"—— 那是把已授权的事重新要签字。
|
||||
|
||||
## 🚨 十条禁令(2026-10-05 立 · ⛔ 每条都栽过,栽一次就重来一遍)
|
||||
|
||||
> 用户 2026-10-05 原话:「**你不要给我知道了,你给我记下来,每次都是知道了,知道了,过一会儿又忘了**」。
|
||||
@@ -404,6 +422,8 @@ python .workbuddy/collab/collabd.py --set-life 进行中 --by "主会话"
|
||||
|
||||
**① 我要装 / 要换机器** ⇒ 跑 `python install.py --dry-run` 看 diff,再 `python install.py --apply`,最后 `python install.py --verify`。
|
||||
(`--apply` 会:写 `env/roots.env` → 按**声明表**接线全局钩子 → 初始化工作区 → 记 `install.log`。⛔ 不硬编码 python 路径与盘符。)
|
||||
🔴🔴 **未装即装(2026-10-14 用户令)**:用户说「使用项目机制完成目标:XXXX」而本区没装 ⇒ **先 `--apply --workspace <本区根>` 装好再干活**(判据与停止线见上面「第 0 步的连体动作」)。
|
||||
🔴 **`--workspace` 要点名本区绝对路径**:`roots.env` 里 `DSH_WS_ROOT` 可能指向别的工作区(实测本机指向 `ai1net-dsh-server`)⇒ ⛔ 不让它顶替,装到别区 = 静默错归属。
|
||||
🔴 **包内目录怎么分(2026-10-11 用户令:「脚本是脚本 配置是配置」)**:**每台机器各不同**的配置一律放本包 **`env/`**(`env/roots.env` 本机生成、不入库;`env/roots.env.example` 是入库样例);`scripts/` 只留通用代码。仓根 `.gitignore` 只放行 `env/**/*.example`。
|
||||
|
||||
**⓪ 我要动手(建判据/写验收/报"已完成"/任何带"现在·已·还差"的结论)** ⇒ **先读 `references/00-动手前必过.md`**(🔴 **动手层**,2026-10-04 建,只有三条动作:① 查状态**问程序自己**,⛔ 不许 glob/手拼路径 ② 引用任何读数**先看它什么时候写的** ③ 写判据**先让基线全绿**再谈抓得住 —— 每条都对应当天真实翻车,且都已落判据不许退化)。
|
||||
@@ -417,7 +437,9 @@ python .workbuddy/collab/collabd.py --set-life 进行中 --by "主会话"
|
||||
|
||||
**⑤ 踩过坑 / 要避坑** ⇒ `references/pitfalls.md`;**包内文件清单与来源** ⇒ `references/manifest.md`(逐文件 md5 + provenance)。
|
||||
**⑥ 我要让常驻长期在线(⛔ 会话/工具调用起的活活不过当轮)** ⇒ 读 **`references/supervise-persistence.md`**(唯一权威:计划任务 `collabd-keepalive-<区>` → `pythonw.exe` → `supervise-launch.py` · 三条封死路 · 两个秒退坑 · 心跳三判据验收)。
|
||||
🔴 **先分场景再动手**(该文档 §〇 有对照表):**用户手动创建主会话** / **定时任务创建主会话** —— 两者都要**同一个载体**(计划任务 + 永不返回的守护循环);⛔ **排期代替不了载体**(跑完即 `completed`,下一跳之前是空窗);⚠️ `status=ACTIVE` **≠ 在跑**(`once` 排期过期即哑:`next_run_at=None`)。
|
||||
🔴 **形态只有一种:主会话由用户手动开** —— ⛔ **排期拉不出主会话**(排期开出来的会话恒是任务会话那一档 `is_background_automation=1`,
|
||||
顶着 `[主]-` 的名字也不是主会话 ⇒ 台账里多一条、用户那边没多窗口,2026-10-14 实测事故见该档 §〇)。
|
||||
载体恒为**计划任务 + 永不返回的守护循环**;⛔ **排期代替不了载体**(跑完即 `completed`,下一跳之前是空窗);⚠️ `status=ACTIVE` **≠ 在跑**(`once` 排期过期即哑:`next_run_at=None`)。
|
||||
|
||||
**⑦ 我要判断「这件事该自己定,还是该问用户」/ 要给用户一个「待拍板」清单** ⇒ 读 **`references/02-功能优先协作协议.md`**(2026-10-04 由 `dsh-decision` 搬入本包,**内容守恒**)。
|
||||
它答的是**会话里做事时的自决策口径**:**功能卡 4 问**(用户只填「谁用/在哪用/要做什么/怎样算成功」)· **9 类自决策白名单**(技术选型 / 实现路径 / 命名 / 调参 / 部署 / 排查 / 版本 / 兼容降级 / 文档技术内容 ⇒ **永不问**)· **只准提报用户 3 类**(功能语义分叉 / 红线门禁 / 超出决策边界)· **提报用户格式**(⛔ 不许出现包名 / 路径 / commit / 代码标识符)· **拆包提报用户**(红线问题⛔ 不得与技术方案捆着问)· **答复与交付格式**。
|
||||
@@ -788,10 +810,10 @@ python .workbuddy/collab/collabd.py --set-life 进行中 --by "主会话"
|
||||
· ① 原话逐字:「**这个会话和协作会话的技能包 运行的第一件事 ,就应该是检查清楚 所有会话规划是否配置完整且生效,然后标记一个状态**」
|
||||
· ② 原话逐字:「**就应该是检查清楚 所有会话规则机制 是否配置完整且生效, 不是规划 是 规则**」⇒ 对象 = **规则机制**(钩子 / 闸门 / 技能指针 / 常驻 / 编排…),⛔ 不是"排期规划"。🔴 首版按①的字面做成「会话规划体检」、**只查排期那一面** ⇒ 当天实测出的三类失效(钩子注入指向**已退役技能名** / 快照**写进幽灵目录** / **每轮注入的记忆**里指针悬空)**一条都查不到** ⇒ 旧脚本已退役到 `<WS>/归档/技能包-旧件-20261002/`,**同包内只剩一个入口**(两个入口 = 「在册 ≠ 生效」本身)。
|
||||
⇒ **怎么跑**:`"$PY" "<本包>/scripts/session-rules-check.py" [--ws <工作区>]` —— ✅ **已接进工作区 `state.py`**(跑状态快照就自带这一段,⛔ 不必另记一条命令)。
|
||||
⇒ **查三类、十二项**:**A 机制装没装好** ① 关键钩子在册 ② 钩子脚本路径存在 ③ 钩子注入里引用的技能名**是否还存在** ④ 钩子**真在被调用**没(闸门日志新鲜度)|**B 规则载体同没同步** ⑤ **每轮注入的记忆**里引用的技能名存在 ⑥ 常驻规则快照**不比权威旧**|**C 编排在不在跑** ⑦ 唤醒 / 跟进两台**周期钟**(缺 = 没人推 / 没人收)(⚠️ 其中「唤醒」这台是**代偿形态** —— 定案的唤醒时钟=**常驻投递**(⑩ 那一项查的才是它);「唤醒排期在册」⛔ **不等于唤醒时钟已就位**,两件事要分开读)⑧ 排期绑的模型**会不会被服务端拒**(`model_is_thinking=0` + flash 系 ⇒ 每触发必拒,2026-10-02 实测)+ 🔴🔴 **机制建的排期模型是否与「本区主会话」同值**(2026-10-05 加,P0-80 —— 此前 `collabd.py` 把模型**写死**成 `space-bunny`,而主会话是 `deepseek-v4.1-flash` ⇒ 「同一件活在两种模型上跑」;⚠️ 该判据**扫描面必须含 `once`**,机制建的排期全是 `once`,只扫 `recurring` 就恒绿)⑨ `cwds` **归属同形**(错一字面 ⇒ 裂组且自我强化)⑩ **投递(常驻)**心跳 ⑪ 三类会话**当前有没有活的** ⑫ 有没有「**从未运行**就失效」的一次性排期。
|
||||
⇒ **查三类、十三项**:**A 机制装没装好** ① 关键钩子在册 ② 钩子脚本路径存在 ③ 钩子注入里引用的技能名**是否还存在** ④ 钩子**真在被调用**没(闸门日志新鲜度)|**B 规则载体同没同步** ⑤ **每轮注入的记忆**里引用的技能名存在 ⑥ 常驻规则快照**不比权威旧**|**C 编排在不在跑** ⑦ 唤醒 / 跟进两台**周期钟**(缺 = 没人推 / 没人收)(⚠️ 其中「唤醒」这台是**代偿形态** —— 定案的唤醒时钟=**常驻投递**(⑩ 那一项查的才是它);「唤醒排期在册」⛔ **不等于唤醒时钟已就位**,两件事要分开读)⑧ 排期绑的模型**会不会被服务端拒**(`model_is_thinking=0` + flash 系 ⇒ 每触发必拒,2026-10-02 实测)+ 🔴🔴 **机制建的排期模型是否与「本区主会话」同值**(2026-10-05 加,P0-80 —— 此前 `collabd.py` 把模型**写死**成 `space-bunny`,而主会话是 `deepseek-v4.1-flash` ⇒ 「同一件活在两种模型上跑」;⚠️ 该判据**扫描面必须含 `once`**,机制建的排期全是 `once`,只扫 `recurring` 就恒绿)⑨ `cwds` **归属同形**(错一字面 ⇒ 裂组且自我强化)⑩ **投递(常驻)**心跳 ⑪ 三类会话**当前有没有活的** ⑫ 有没有「**从未运行**就失效」的一次性排期 ⑬ 🔴🔴 **有没有「顶着主会话名字」的排期**(2026-10-14 加 —— 用户报障「技能中没有任何创建主会话的机制」;**主会话只能由用户开**,排期开出来的会话恒是任务会话那一档 ⇒ 台账里多一条、用户那边没多窗口,mcn 区 07:31 实测;⚠️ **扫描面 recurring 与 once 都要含**,报 `fail`=还在 ACTIVE、`warn`=已非 ACTIVE 的留痕)。
|
||||
⇒ **标记**:结论写成 `<WS>/.workbuddy/collab/session-rules.json`(`verdict` = `ok`/`warn`/`fail` + 逐项 `detail`)—— 后续会话与看板**读它**,⛔ 不靠人复述。
|
||||
🔴 **为什么必须是第一件事**:2026-10-02 实测——排期**都在册**、模型**都可用**、cwds **都同形**,**却三类会话一条活的都没有**(=配置在、机制没在跑);同一天还查出钩子注入文本指着**已合并退役的技能名**、常驻快照脚本**写到没人读的幽灵目录** ⇒ 全是「**看着有配置、其实没生效**」。这类状态**不问就不会知道**,等它表现成"卡住"时已经晚了。
|
||||
🔴 **判据本身也要能报出问题**:⑨ / ⑫ 这两项(以及 `cwds` 判据的边界)用**合成样本 + 四个变异体**做过红绿对照(夹具 `<WS>/tmp/rules-check-mutate.py`,跑完即弃)—— 变异体=判据恒空 / 判据放宽成"同父目录即报" / 把"从未运行"当"跑完了" / 不排除"还有下次触发"的排期,**逐一按预期报红**。⛔ **别拿"实跑一次没报错"当验收** —— 判据恒空时那次实跑**同样是绿的**。
|
||||
🔴 **判据本身也要能报出问题**:⑨ / ⑫ / ⑬ 这三项(以及 `cwds` 判据的边界)用**合成样本 + 变异体**做过红绿对照(夹具 `<WS>/tmp/rules-check-mutate.py`,跑完即弃)—— 变异体=判据恒空 / 判据放宽成"同父目录即报" / 把"从未运行"当"跑完了" / 不排除"还有下次触发"的排期,**逐一按预期报红**。⛔ **别拿"实跑一次没报错"当验收** —— 判据恒空时那次实跑**同样是绿的**。⑬ 的红绿对照(2026-10-14,mcn 区真数据喂三种输入):原样(PAUSED)⇒ `warn`;伪造成 ACTIVE ⇒ `fail`;无此类排期的区 ⇒ `ok`。
|
||||
⛔ **只标记、不设卡**:体检 `rc≠0` 也照常开工 —— 它的职责是**把状态问清楚**,不是拦人。
|
||||
- 🔴🔴 **开工清单:主会话开工的第 0 步不是"派活",是"把三类会话摆好"**(用户 2026-10-01 明令:「**开始会话完成需求的时候,主会话需要创建 唤醒会话 以及根据分工类别 创建 协作会话 和 跟进会话呢 不然整个机制跑不起来**」)⇒ 建 **唤醒会话** `[唤醒]-<类别>-<具体>`(少建=**没人推**,需求原地静着)+ **每个分工类别一条任务会话** `[执行]-<类别>-<具体>`(少建=**没人干**)+ **跟进会话** `[跟进]-<具体>`(少建=**没人收**)。🔴 **跟进会话是全局唯一席位、⛔ 不按类别各建一条**(2026-10-02 用户订正);它是**收口者**:任务会话干完活把**待核对状态**写进执行队列,上报给**这固定的一个**跟进会话处理。⚠️ **只有自动化能开新会话** ⇒ "建会话"=登记一条自动化,⛔ 不是自己 spawn;标题**第 2 级必须带方括号、值取 `goal.json` 的 `topics`**(⛔ 用 `short` ⇒ 静默漏管)。细则 ⇒ `architecture.md §2.3.0d`。
|
||||
- 🔴🔴 **缺会话 ⇒ 自动拉起**(用户 **2026-10-02** 口径,逐字:「**是用户说 使用协作会话方式 完成目标 或 继续完成目标**」)
|
||||
|
||||
@@ -108,6 +108,14 @@
|
||||
|
||||
**宿主(WorkBuddy 本体)=地基,⛔ 不是第 6 个主体**。它只提供四样:**状态**(三张只读表)/**调度**(自动化排期=**唯一能开新会话的通道**)/**事件**(钩子)/**互斥**(域锁)。
|
||||
|
||||
🔴🔴 **主会话怎么来:只能由用户在 WorkBuddy 里手动开**(2026-10-14 重申,用户原话「**技能中没有任何创建主会话的机制**」)。
|
||||
· **⛔ 排期开不出主会话** —— 排期拉起的会话恒是任务会话那一档(`is_background_automation=1`);
|
||||
它顶着 `[主]-…` 的名字也**不是主会话**:台账里多一条,**用户那边根本没多窗口** ⇒ 让人误以为"机制自己创建了一个主会话"。
|
||||
· **排期只用于派活与检查两条线**:`[执行]-[<类别>]-<具体>`(主会话派活)/`[检查]-[结果检查/目标检查]-…-第N棒`(常驻建)。
|
||||
· **接续会话**是主会话撞阈值时**由它自己建出来的形态**(角色继承被接续那条),⛔ **不是**"机制建主会话"的又一条路。
|
||||
· 判据:`session-rules-check.py` ⑬(`main_sched`)—— 报本区有没有「顶着主会话名字」的排期。
|
||||
· 实测事故 ⇒ `supervise-persistence.md §〇`(mcn-aigc-ontology 2026-10-14 07:31)。
|
||||
|
||||
🔴 **职责硬边界(2026-09-29 用户定案)**:**「投递(通知主会话)」唯一属于投递** —— 目标检查**只维护队列/台账与投影**,⛔ **不做任何投递**。
|
||||
· 理由:两条路径各自判重 ⇒ 同一内容**成对重复投递**(实测:同 hash 秒级两次)⇒ 每次投递都往主会话**会话队列里压一条** ⇒ 压满即"卡死假象"(22:53 那次事故的根因)。
|
||||
· 🔴 **2026-09-30 更新**:常驻程序的投影轮(`--once`)必须传 `mutate=False`,**⛔ 不许消费队列**(否则"消费掉却不投递" ⇒ 通知永远发不出去)。详见 **§4.2**。
|
||||
|
||||
@@ -22,19 +22,41 @@
|
||||
> + 2026-10-05/06 的 P0-72/P0-73/P0-74 实测。
|
||||
|
||||
|
||||
## 〇、🔴🔴 先分场景:两种"长期"不是一回事
|
||||
## 〇、🔴🔴 先分场景:长期在线的会话只有一种
|
||||
|
||||
**两种都要长期在线,但它们要的东西不同** —— ⛔ 别混用,混用就会出现
|
||||
"看起来在跑、其实随时会死"或"该停时还在跑"。
|
||||
**要长期在线的会话只有一种:用户在 WorkBuddy 里手动开的那条主会话。**
|
||||
⛔ **排期按点拉起主会话这条路不存在** —— 它是本档早期版本自己推演出来的形态,**2026-10-14 已判定作废**。
|
||||
(本档 §八 验收表里那行「B · 定时任务创建主会话」同批作废,⛔ 别照做。)
|
||||
|
||||
| | **场景 A · 用户手动创建主会话** | **场景 B · 定时任务创建主会话** |
|
||||
|---|---|---|
|
||||
| 典型形态 | 用户在 WorkBuddy 里自己开一条 `[主]-…` 会话,盯着它推进 | 排期按点拉起 `[主]-…`(`recurring`,如 `FREQ=HOURLY;INTERVAL=1`) |
|
||||
| 会话寿命 | **随用户何时收工**(可能几分钟,也可能几小时) | 跑完即 `completed`,**与下一跳之间有静默窗** |
|
||||
| 🔴 **对"常驻"的硬要求** | ⛔ **不能靠会话活着** —— 一收工就没人拉 ⇒ **必须脱离会话** | ✅ 会话自己周期性回来 ⇒ 但**每跳之间仍有空窗** ⇒ **同样要脱离会话** |
|
||||
| 唯一合格载体 | **计划任务**(第一~三节) | 同左(⛔ **排期代替不了载体** —— 排期只是"谁按点喊一次",喊完会话就结束) |
|
||||
| 怎么验它真活着 | 心跳 `pid` 活 ∧ `ts` 距今 < 90 s + `round` 递增 | 同左,⚠️ **且要静置 ≥ 12 分钟**(⛔ 短观察证明不了事) |
|
||||
| 谁负责"目标完成后关它" | 常驻主循环见 `guard.stop` 即收工(第七节) | 同左 —— ⛔ **排期不会帮你关** |
|
||||
| | **唯一场景 · 用户手动打开主会话** |
|
||||
|---|---|
|
||||
| 典型形态 | 用户在 WorkBuddy 里自己开一条 `[主]-…`(或 `主控 · …`)会话,盯着它推进 |
|
||||
| 会话寿命 | **随用户何时收工**(可能几分钟,也可能几小时) |
|
||||
| 🔴 **对"常驻"的硬要求** | ⛔ **不能靠会话活着** —— 一收工就没人拉 ⇒ **必须脱离会话**(计划任务,见第一~三节) |
|
||||
| 唯一合格载体 | **计划任务**(第一~三节) |
|
||||
| 怎么验它真活着 | 心跳 `pid` 活 ∧ `ts` 距今 < 90 s + `round` 递增 |
|
||||
| 谁负责"目标完成后关它" | 常驻主循环见 `guard.stop` 即收工(第七节) |
|
||||
|
||||
### 🔴🔴 为什么⛔ 不能"用排期拉起主会话"(2026-10-14 实测事故)
|
||||
|
||||
> **一句话**:**主会话只能由用户开**(`SKILL.md`《三类会话》类别表逐字:「主会话 | **用户手动开** | 管目标与方向、派活 | 用户关窗」)。
|
||||
> 排期开出来的会话**恒是任务会话那一档**(`is_background_automation=1`)⇒ 它一出生就**不是主会话**,
|
||||
> 却顶着 `[主]-…` 的名字 ⇒ **台账里多一条"主会话",用户那边根本没多窗口** ⇒ 报"机制自己创建了一个主会话"。
|
||||
|
||||
**事故回放**(mcn-aigc-ontology,2026-10-14 07:31):
|
||||
1. 用户 07:12 手动开 `9e069caf`「加载会话技能」= 该区真主会话。
|
||||
2. 07:30 `init_workspace.py` 装机制、常驻拉起,顺手把 `9e069caf` 登记进 `goal.json`。
|
||||
3. **07:31:11 建出一条 `recurring` 排期** `[主]-super-i卡片采集-主会话`(`FREQ=DAILY;BYHOUR=09;BYMINUTE=0`),
|
||||
prompt 逐字写着「你是本工作区的主会话……要推进就直接建 `[执行]` 排期拉起任务会话」
|
||||
—— **这是"主会话"的口径,被套到了一条排期身上**。
|
||||
4. ⇒ 该排期每天 09:00 会开出一条**顶着主会话名头的任务会话**;长期跑 ⇒ 台账里主会话越积越多。
|
||||
5. **处置**:该排期已置 `PAUSED`(`automation_update`,零删除可逆)。
|
||||
|
||||
**为什么会有人这么干**:本档 §〇 早期表格把「定时任务创建主会话」与「用户手动创建主会话」**并列成两种合法场景**,
|
||||
`SKILL.md` §⑥ 也顺着写了「用户手动创建主会话 / 定时任务创建主会话」。
|
||||
读到这里的人(或 AI)会理解成「排期建主会话 = 允许的」,于是照做。
|
||||
⇒ **2026-10-14 已把这两处一并改掉**(本档 §〇 表格 + `SKILL.md` §⑥ 那句),
|
||||
⛔ 保留本事故回放只为留痕,⛔ 不表示这条路还存在。
|
||||
|
||||
### 🔴 常态口径(2026-10-04 用户定案,当场订正)
|
||||
|
||||
@@ -405,14 +427,18 @@ $p = Get-Process -Id <pid> -ErrorAction SilentlyContinue # $null = 已死
|
||||
(⛔ 别把跨区自愈当载体的替代品:它要求本区常驻先活着)。
|
||||
|
||||
|
||||
## 八、按场景对照(选哪条 / 怎么验 / 常见坑)
|
||||
## 八、对照(怎么验 / 常见坑)
|
||||
|
||||
| 场景 | 主会话怎么来 | 常驻载体 | 目标完成后谁关 | 常见坑 |
|
||||
|---|---|---|---|---|
|
||||
| **A · 用户手动创建主会话** | 用户自己开 `[主]-…`,盯着推进 | 计划任务 → `pythonw` → `supervise-launch.py` | 常驻见 `guard.stop` → 优雅退出 | 🔴 用户一收工就没人拉 ⇒ 若没装载体,常驻会在会话结束时**静默死掉**(看板还显示"在线") |
|
||||
| **B · 定时任务创建主会话** | 排期 `recurring` 到点拉 `[主]-…` | 同上(⛔ **排期代替不了载体**) | 同上 | 🔴 **`once` 排期过期即哑**(`next_run_at=None`、界面仍显示 ACTIVE)⇒ **静默窗内无人**;🔴 排期跑完即 `completed`,**下一跳之前是空窗** |
|
||||
| **唯一 · 用户手动创建主会话** | 用户自己开 `[主]-…`,盯着推进 | 计划任务 → `pythonw` → `supervise-launch.py` | 常驻见 `guard.stop` → 优雅退出 | 🔴 用户一收工就没人拉 ⇒ 若没装载体,常驻会在会话结束时**静默死掉**(看板还显示"在线") |
|
||||
|
||||
**共同判据(两种场景都一样,⛔ 每次改完都验)**:
|
||||
> 🔴🔴 **2026-10-14 撤掉一行**:原表里还有一行「**B · 定时任务创建主会话**」(排期 `recurring` 到点拉 `[主]-…`)。
|
||||
> **该行已作废** —— 主会话**只能由用户开**(见 §〇 事故回放),排期开出来的会话恒是任务会话那一档。
|
||||
> 那行里唯一还有价值的读数留在这里:🔴 **`once` 排期过期即哑**(`next_run_at=None`、界面仍显示 ACTIVE)
|
||||
> ⇒ **静默窗内无人**;🔴 排期跑完即 `completed`,**下一跳之前是空窗**。
|
||||
|
||||
**共同判据(⛔ 每次改完都验)**:
|
||||
1. `State` = **`Running`**(或 5 分钟窗内 `Ready`);
|
||||
2. `LastTaskResult` = **`267009`**(长驻中);⛔ `1` 也是信号(秒退);
|
||||
3. 心跳 `pid` 活 ∧ `ts` 距今 **< 90 s** ∧ `round` 递增;
|
||||
|
||||
@@ -625,6 +625,37 @@ def check_orchestration():
|
||||
else:
|
||||
rec("cwd", "C", "ok", "`cwds` 归属同形", "本工作区的排期都写在同一条路径上")
|
||||
|
||||
# ⑬ 🔴🔴 「顶着主会话名字的排期」—— 2026-10-14 加(用户报障「技能中没有任何创建主会话的机制」)
|
||||
# 口径(`SKILL.md`《三类会话》类别表逐字):
|
||||
# **主会话 | 用户手动开 | 管目标与方向、派活 | 用户关窗**
|
||||
# ⇒ 排期**开不出主会话**:它开的会话恒是任务会话那一档(`is_background_automation=1`)。
|
||||
# 症状(mcn-aigc-ontology 2026-10-14 实测):
|
||||
# 07:31 建出 `[主]-super-i卡片采集-主会话`(`recurring` `FREQ=DAILY;BYHOUR=09`),
|
||||
# prompt 里写着「你是本工作区的主会话……要建 `[执行]` 排期拉起任务会话」
|
||||
# ⇒ 每天 09:00 开出一条**顶着主会话名头的任务会话** ⇒ 台账里主会话越积越多,
|
||||
# 而用户那边**根本没多窗口** ⇒ 用户报「机制自己创建了一个主会话」。
|
||||
# ⚠️ 与「旧前缀 `[主]`」区分:`[主]` 本身没作废(现役主会话可写成 `[主]-…` 或 `主控 · …`),
|
||||
# 本判据抓的是**它出现在排期名里**这件事,⛔ 不抓会话名。
|
||||
# ⚠️ 扫描面**必须含 `recurring` 与 `once` 两种**(当年就是只扫 `recurring` 才看不见机制建的 `once` 排期)。
|
||||
_mainlike = [a for a in _allmine
|
||||
if (a.get("name") or "").startswith("[主]")
|
||||
or "主会话" in (a.get("name") or "")]
|
||||
if _mainlike:
|
||||
_st = ["%s(%s)" % ((a["name"] or "")[:30], a.get("status") or "?") for a in _mainlike]
|
||||
# ACTIVE 才算真隐患;已 PAUSED 的留痕只提示
|
||||
_act = [a for a in _mainlike if (a.get("status") or "") == "ACTIVE"]
|
||||
if _act:
|
||||
rec("main_sched", "C", "fail",
|
||||
"排期顶着「主会话」的名字 ⇒ 主会话只能由用户开(2026-10-14 口径)",
|
||||
"本区 %d 条在 ACTIVE:%s" % (len(_act), "、".join(_st[:3])))
|
||||
else:
|
||||
rec("main_sched", "C", "warn",
|
||||
"排期曾顶着「主会话」的名字(现已非 ACTIVE,留痕非隐患)",
|
||||
"本区 %d 条:%s" % (len(_mainlike), "、".join(_st[:3])))
|
||||
else:
|
||||
rec("main_sched", "C", "ok", "本区没有「顶着主会话名字」的排期",
|
||||
"扫描面 %d 条(recurring + once 全含)" % len(_allmine))
|
||||
|
||||
# ⑩ 投递心跳
|
||||
hbs = [WS + "/.workbuddy/collab/logs/supervise-heartbeat.json",
|
||||
WS + "/.workbuddy/collab/supervise-heartbeat.json"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
🔴🔴 2026-10-05 **为什么必须要有这个启动器**(本轮实测踩到的坑,P0-73):
|
||||
新形态(计划任务 → `pythonw.exe` → `collabd.py --supervise`)**直起**时,
|
||||
任务环境里**没有 `CODEBUDDY_CONFIG_DIR`** —— 旧看守 `start-supervise.ps1` 里那句
|
||||
`$env:CODEBUDDY_CONFIG_DIR = "E:\\ProgramData\\.workbuddy"`
|
||||
`$env:CODEBUDDY_CONFIG_DIR = "E:/ProgramData/.workbuddy"`
|
||||
在我改成两层形态时**被丢掉了** ⇒ 后果:
|
||||
· `_wb_db()` 落到 `~/.workbuddy/workbuddy.db`(**0 字节空库**)⇒
|
||||
· `_all_sessions_idle()` 每轮报 **`no such table: sessions`** ⇒ 闸② 恒判"有会话在跑"
|
||||
@@ -15,19 +15,86 @@
|
||||
🔴 用 `runpy.run_path`(⛔ 不用 `exec`):`runpy` 会把 `__name__`/`__file__`/`sys.path[0]`
|
||||
都按"真在跑那个脚本"设好 ⇒ `collabd.py` 顶部的 `_sm_load_roots()` 靠 `__file__` 找 `roots.env`
|
||||
与命令行分发(它自己读 `sys.argv`)全对。
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔴🔴 2026-10-14 **宿主库路径须「相对/外置」,且必须两处兜底**(用户令:技能里的文件路径用相对路径)
|
||||
本文件**只允许**用 `HERE`(=自身所在目录)拼相对路径,⛔ 不写死任何本机盘符。
|
||||
宿主库路径按**下面三级**取,命中即止:
|
||||
|
||||
① 宿主 env `CODEBUDDY_CONFIG_DIR`(宿主进程给了就用它,⛔ 不覆盖);
|
||||
② 本区 `collabd.config.json` 的 `host_db`(单区兜底,**部署时由 init_workspace 按本机根目录生成**);
|
||||
③ 包内 `env/roots.env` 的 `CODEBUDDY_CONFIG_DIR`(本包全局单例,跨区共享)。
|
||||
|
||||
⛔⛔ **禁止回落 `os.path.expanduser("~/.workbuddy")`** —— 本机它是 **0 字节空库**
|
||||
(实测 `C:/Users/Administrator/.workbuddy/workbuddy.db` = 0 字节、零张表;真库在
|
||||
`E:/ProgramData/.workbuddy/workbuddy.db`,124 MB)⇒ 回落即 `no such table: sessions`
|
||||
⇒ 闸② 恒判"有会话在跑" ⇒ **检查会话再也不建**(2026-10-14 mcn-aigc-ontology 实测复现)。
|
||||
⚠️ 10-11 那版写 `expanduser("~/.workbuddy")` 的**动机是对的**(不写死盘符、跟着用户走),
|
||||
但**兜底选错了目录** —— 「不写死盘符」要靠**外置配置**实现,⛔ 不是靠猜 `~`。
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 🔴 启动器所在目录即本区 `.workbuddy/collab`(由 init_workspace 分发到各区)
|
||||
# 本文件内的所有路径一律以 `HERE` 为基准(相对),⛔ 无任何本机盘符字面量。
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
CB = os.path.join(HERE, "collabd.py")
|
||||
CFG = os.path.join(HERE, "collabd.config.json")
|
||||
|
||||
# 🔴 活动环境目录(宿主库所在)—— 这是**旧看守里有、新形态里被我漏掉**的那一条。
|
||||
# 🔴 2026-10-11:默认值改成 `~/.workbuddy` 形态(⛔ 不写死本机盘符)——
|
||||
# 跟着**当前用户**走;宿主 env 里已有的值优先(setdefault)。
|
||||
os.environ.setdefault("CODEBUDDY_CONFIG_DIR", os.path.expanduser("~/.workbuddy"))
|
||||
|
||||
def _resolve_config_dir() -> str:
|
||||
"""宿主库所在目录(`CODEBUDDY_CONFIG_DIR`)。三级取值,⛔ 不写死盘符、⛔ 不猜 `~`。"""
|
||||
# ① 宿主 env 已给 ⇒ 直接用,⛔ 不动
|
||||
given = (os.environ.get("CODEBUDDY_CONFIG_DIR") or "").strip()
|
||||
if given:
|
||||
return given
|
||||
|
||||
# ② 本区配置的 host_db(取它的父目录)
|
||||
try:
|
||||
with open(CFG, encoding="utf-8") as f:
|
||||
_host_db = (json.load(f).get("host_db") or "").strip()
|
||||
if _host_db:
|
||||
return os.path.dirname(os.path.normpath(_host_db))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ③ 包内 env/roots.env(HERE 往上找包根:collab → .workbuddy → 工作区根;再试技能包位置)
|
||||
for _up in range(1, 8):
|
||||
_pkg = os.path.dirname(HERE)
|
||||
for _ in range(_up - 1):
|
||||
_pkg = os.path.dirname(_pkg)
|
||||
_roots = os.path.join(_pkg, "env", "roots.env")
|
||||
if os.path.isfile(_roots):
|
||||
try:
|
||||
with open(_roots, encoding="utf-8", errors="replace") as f:
|
||||
for ln in f:
|
||||
ln = ln.strip()
|
||||
if ln.startswith("CODEBUDDY_CONFIG_DIR="):
|
||||
val = ln.split("=", 1)[1].strip()
|
||||
if val:
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
_cfgdir = _resolve_config_dir()
|
||||
if _cfgdir:
|
||||
os.environ["CODEBUDDY_CONFIG_DIR"] = _cfgdir
|
||||
else:
|
||||
# ⛔ 取不到 ⇒ 宁可留空让下游自己报错,⛔ 也不回落 `~/.workbuddy`(那是 0 字节空库)。
|
||||
try:
|
||||
sys.stderr.write(
|
||||
"[supervise-launch] ⚠️ 取不到 CODEBUDDY_CONFIG_DIR"
|
||||
"(宿主 env / 本区 host_db / 包内 roots.env 三处都空)"
|
||||
"⇒ 宿主库不可读,检查程序会静默失效\n"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
os.environ["COLLABD_CONFIG"] = CFG
|
||||
os.environ["PYTHONIOENCODING"] = "utf-8"
|
||||
os.environ["PYTHONUNBUFFERED"] = "1"
|
||||
|
||||
Reference in new issue
Block a user