Files
dsh_ai1net_server/交付物/唤醒-会话自建后台任务方案评估-20260930.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 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/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

213 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 唤醒方案评估 · 会话自建后台任务(2026-09-30)
> **状态**:✅ 已实测闭合(4 组对照实验)|**日期**:2026-09-30 13:0x |**会话**:`6ecf6d98`
> **一句话结论**:**会话自己起的「一次性后台任务」是一个可用的唤醒原语** —— `run_in_background` 任务**完成即唤醒本会话**,**与 stdout 输出无关**,**静默也唤醒**,**300 秒时长不被回收**;⛔ 但「常驻循环 + 周期 echo」**不行**(不完成 ⇒ 永不唤醒)。
> **本文件只写实测与方案,⛔ 不写法规、⛔ 不写合规。**
---
## §0 本棒范围
| 项 | 内容 |
|---|---|
| **本次要评的** | **会话自建后台任务**能否当"唤醒本会话"的通道;粒度能不能到 5 分钟 |
| **明确不评** | gateway 通道(用户 13:0x 指令:「**不考虑gateway** ,专门做 会话创建后台任务的方案评估测试」) |
| **前置事实(已完成,另案)** | 本机 gateway 直连那条线**已测通**:`.workbuddy/collab/wake-session.py` → 现查网关(三判据 + 按 `live.sessionId` 精确匹配)→ `POST /reply` ⇒ 对本会话 **HTTP 200 `{"data":{"delivered":true}}`**。结论与判据另见 §8 |
---
## §1 机制:为什么它能唤醒
**依据不是推断,是工具契约原文**(`run_in_background` 的说明):
> 「Once backgrounded you will receive a `task_id`; you **do NOT need to poll** — **when the command finishes you will be automatically notified via a `<task-notification>` message in your next turn**.」
⇒ **任务完成 ⇒ 宿主往本会话投一条 `<task-notification>` ⇒ 本会话开新的一轮**。这就是用户记忆里「后台任务监控程序安装,装好后会话继续处理」的那条机制,逐字对上。
---
## §2 对照实验(4 组 · 两组变量)
**变量**:① 有没有 stdout 输出 ② 时长。全部是**一次性**任务(⛔ 不是常驻)。
| 组 | task_id | 命令 | stdout | 时长 | 完成时刻(任务侧写盘) |
|---|---|---|---|---|---|
| **A** | `d5R89w` | `sleep 18; 写状态; echo "BGTEST-A …"` | **有** | 18 s | `A-done 12:57:13` |
| **B** | `KdwR4k` | `sleep 36; 写状态` | **0 字节** | 36 s | `B-done 12:57:31` |
| **C** | `8chtue` | `sleep 20;echo TICK-C1; sleep 20;echo TICK-C2; sleep 20;写状态` | **中途有 2 行** | 60 s | `C-done 12:58:58` |
| **D** | `hixCxx` | `sleep 300; 写状态` | **0 字节** | **300 s** | `D-done 13:02:55` |
**取证方法(两条线独立,⛔ 不靠"我觉得被叫醒了")**:
- **任务侧**:每条任务自己往 `tmp/_bgt_state.txt` 追加带时间戳的 `*-done` ⇒ 证明**任务真的跑完了**(可以把"任务被杀"与"没被唤醒"分开判);
- **会话侧**:`sessions.last_activity_at / updated_at` + **我是否在零用户输入下开了新的一轮**。
### 2.1 实测时间线
| 时刻 | 任务侧事件 | 会话侧 |
|---|---|---|
| 12:57:08 | (A 完成触发的回合开启) | ✅ `last_activity_at`=12:57:08 |
| 12:57:13 | `A-done`(**有声**) | — |
| 12:57:31 | `B-done`(**全静默**) | ✅ **独立唤醒(零输入)** |
| 12:58:05 | — | (我上一轮结束) |
| **12:58:16** | **`C-t1`(中途输出)** | ❌ **`last_activity_at` 未动** |
| **12:58:37** | **`C-t2`(中途输出)** | ❌ **`last_activity_at` 未动** |
| 12:58:58 | `C-done`(静默完成) | ✅ 唤醒 |
| **13:02:55** | **`D-done`(300 s 静默)** | ✅ 唤醒(`updated_at` **13:02:59** > 13:02:55) |
| — | 残留检查 | ✅ `ps` 无 `sleep` 残留进程 |
---
## §3 结论(三条,均为实测)
| # | 结论 | 证据 |
|---|---|---|
| **1** | 🔴 **唤醒的触发 = 「任务完成」这一个事件,与 stdout 输出无关** | A(有声)与 B(**0 字节**)**都被唤醒**;C 的两次中途输出**都没有**唤醒,只有完成才唤醒 |
| **2** | ✅ **静默任务完全安全** | B / D 两条全静默任务各只产生 **1 条**通知,无输出洪泛 |
| **3** | ✅ **长时长不会被沙箱回收** | D 从 12:57:55 跑到 13:02:55,**整 300 秒跑满**,照常唤醒 |
### 3.1 ✅ 同时纠正一条旧记忆的因果(重要)
旧记法:「后台任务**每轮输出都会唤醒**宿主会话 ⇒ 会话永不空闲 ⇒ 看着像卡」。
**实测不成立**(C 组:中途输出不唤醒)。**真因是另一件**:
```
持续输出 ⇒ 转录体积涨 ⇒ 推过 10 MiB ⇒ 宿主 diagnostic-log dropped ⇒ 界面不再显示 ⇒ "看着像卡"
```
⇒ 所以**毒药是「输出的量」,不是「被唤醒」**;**一次性 + 静默**两者都干净。10 MiB 轮转已由既有实物确认(同一目录下 `fe146dd9-*.log / .log.1 / .log.2` 三代各 ≈10 MiB)。
---
## §4 方案设计(P-1 … P-4)
| 方案 | 做法 | 粒度 | 代价 | 判定 |
|---|---|---|---|---|
| **P-1 单发脉冲** | 起 1 条 `sleep N`(静默)一次性任务 | 任意(N 自定,实测到 300 s) | **0 新会话**;跑完即退、**无残留进程** | ✅ **推荐的基础原语** |
| **P-2 批量预排** | 一轮里同时起 K 条错峰静默任务(`sleep N`、`sleep 2N`…) | 覆盖 K 个周期 | 0 新会话;**K 个 sleeping 进程在跑** | ✅ 适合"这一小时每 5 分钟叫一次"这种**有限窗口** |
| **P-3 链式续棒** | 每次被唤醒后,会话再起下一条 | 任意 | 0 新会话;**每周期消耗一轮 AI**;链路依赖会话/宿主活着 | ✅ 省进程,但要接受"每棒一轮 AI" |
| **P-4 常驻循环 + 周期 echo** | `while true; do sleep 300; echo tick; done` | — | 常驻进程 | ⛔ **已实测不可行**:永不"完成" ⇒ **永不唤醒**(结论 1 的直接推论) |
**共同代价(P-1/P-2/P-3 都有)**:生命周期**绑在这个会话上** —— 会话关闭 / 宿主重启 ⇒ 任务与链一起没(对比:自动化是宿主侧持久排期,不受会话影响)。
---
## §5 与既有约束的对账
| 约束 | 本方案 | 判定 |
|---|---|---|
| **T1**(⛔ 垫片/桥不得放进会话后台任务;长跑走一次性自动化或独立进程) | 本方案**不是**把垫片放进去;它是**用一次性任务自身当脉冲**,**无长跑** | ✅ 不触 T1;⚠️ 但 **T1 的文字需要补一条脚注**(禁的是"常驻 + 持续输出",不是"会话起后台任务"本身) |
| **T3**(不占资源、不改它) | ⛔ 不绑端口、⛔ 不改配置/插件/令牌、⛔ 不 kill 宿主 | ✅ 全部满足 |
| **T7**(可静默、可回滚、无残留) | 跑完即退;实测 `ps` 无残留 | ✅ 满足 |
| **09-30 铁律**(手机接入类 ⛔ 不得影响 WorkBuddy 本身运行) | 纯本机、单向依赖:任务被 kill ⇒ **只是"不唤醒"**,宿主无感 | ✅ 降级方向正确(关掉自己) |
| **09-29 坑**(常驻后台任务 ⇒ 卡死) | 本方案**不常驻**;即使要覆盖多周期也走 P-2 预排 | ✅ 规避 |
| **rrule 硬白名单**(自动化最小 1 小时) | 本方案**完全不碰 rrule** | ✅ **绕开了"5 分钟做不到"那堵墙** |
---
## §6 边界与未测项(⛔ 不要当成已经验过)
1. **只能唤醒"自己这个会话"** —— 跨会话投递仍需 gateway `reply`(另案)。
2. **唤醒载荷是 `<task-notification>`,不带自定义指令** —— 要带信息只能让任务 `echo`(那就进转录、会涨体积)。
3. **未测**:更长的时长(>300 s)、更高频/更大量的中途输出是否最终会唤醒、会话关闭后任务的存活边界、宿主重启后残留如何。
4. ⚠️ **本会话的转录日志自 12:26 起已冻结**(`10,485,723 B`,mtime 恒 `12:26:19`)—— **实验前就已如此**。 ⇒ 讨论"规模/体积"时**不能再用这个文件当度量**,要换度量。
5. ⛔ **本棒没有留下任何在跑的脉冲链**(实验任务全部已退出;⛔ 未创建自动化)——要不要真的把 5 分钟脉冲挂上去,属"会影响你桌面体验"的事,**由用户拍板**。
---
## §7 图
### 7.1 泳道图:一次脉冲跨哪几层
```
会话(6ecf6d98) 工具运行器 宿主事件循环 UI
──────────────── ────────────── ───────────────── ──────────
① 起任务 ───────────► spawn `sleep N`
(run_in_background) │
│ N 秒静默运行(不动会话 ↔ 不涨转录)
▼
任务 exit ──────────► ② 生成 task-notification
(★ 全部唤醒都发生在这一格)
│
④ 开新一轮 ◄───────────────────────────────────────┘
(这就是"被唤醒") │
│ ▼
├─► P-1/P-3:再起下一条脉冲 ③ 写入 sessions.updated_at
└─► 无人接棒 ⇒ 链自然停(天然 fail-safe)
★ 判据:能唤醒 = ②那一格发生 ⇒ 只认「完成」;①→③ 之间任何 stdout 都【不】触发它
```
### 7.2 事故链图:09-29「看着像卡」的真实成因(本次已纠正)
```
① 会话里起【常驻】后台任务
│
▼
② 它【持续】往 stdout 吐内容 ← 🔴 毒药在这里(量),不在"唤醒"
│
▼
③ 每段输出都进转录 ⇒ 转录体积持续上涨
│
▼
④ 越过 10 MiB ⇒ 宿主 diagnostic-log dropped(droppedLines 上万)
│
▼
⑤ 界面不再显示新内容 ⇒ 用户看到"卡死"
│
✗ 旧记法把 ② 写成"每轮输出都会【唤醒】宿主会话" ⇒ ❌ 本次 C 组实测推翻
(中途输出【没有】产生任何唤醒;唤醒只认"完成")
本该在场而缺席的防线:
· 缺「输出量闸门」:常驻任务必须完全静默(stdout 全重定向)—— 这条当时没落地
· 缺「体积看门狗」:转录接近 10 MiB 就该告警/轮转 —— 本会话日志 12:26 冻结,至今无人报
```
---
## §8 索引与出处
| 是什么 | 在哪 |
|---|---|
| 本报告 | `交付物/唤醒-会话自建后台任务方案评估-20260930.md` |
| gateway 直连唤醒器(另案,已测通) | `.workbuddy/collab/wake-session.py` |
| gateway 侧方案分析(含 §11「5 分钟自动任务做不到」) | `交付物/唤醒的解决方式-官方文档核对与方案分析-20260930.md` |
| 「唤醒为什么断」的原始定案 | `交付物/查唤醒为什么断-结论-20260930.md` |
| 架构定稿 v3(T1–T7 + 验收判据 V7) | `交付物/手机App经覆盖网络操作WorkBuddy-架构定稿v3-20260929.md` §0.5 |
| 实验原始痕迹(任务侧时间线) | `tmp/_bgt_state.txt`(⛔ tmp 不入库) |
| 本次读数 | `sessions` 行 ・ `logs/2026-09-30/sdk/conversations/6ecf6d98-*.log`(已冻结) |
---
## §9 追问(13:0x):能不能"持续"?—— 能,但要把两件事分开
**用户两问**:①「后台任务不能持续运行吗」②「一次性任务完成后继续创建,这样就能持续?」
### 9.1 🔴 核心区分:「持续运行」≠「持续唤醒」
| 维度 | 能不能 | 依据 |
|---|---|---|
| **持续运行**(进程一直活着) | ✅ **能** | **E1 实测**:上一轮起的常驻静默心跳循环,**跨过本轮的结束与下一次唤醒仍在跳**(`hb 13:08:31 / 13:08:52 / 13:09:12`,20 s 一次)⇒ 后台任务进程**不随本轮结束被杀** |
| **持续唤醒**(每次都被叫起) | ⛔ **常驻做不到** | **同一实验**:13:08:33→13:09:12 之间 E1 吐过 `hb 13:08:52`,而 `sessions.updated_at` **没动** ⇒ 常驻**静默也不唤醒**(与 §3 结论 1 一致) |
| **自举续命**(任务自己续自己) | ⛔ **做不到** | ①任务内 `nohup … &` ⇒ **detached spawn 活不过工具调用边界**(既有实测:日志 0 字节即死)②任务内循环 ⇒ 不"完成" ⇒ 不唤醒 |
| **持久化兜底**(计划任务/服务) | ⛔ **封死** | 沙箱回收子进程 + 内置程序黑名单(`wsl/wslconfig/wmic/sc/reg/schtasks`)⇒ 本机**唯一可行的常驻起法 = 会话后台任务 + stdout 全重定向** |
⇒ **想让"常驻"也能唤醒,只有一条路:它在循环里主动投递**(调 gateway `reply`,见另案 `.workbuddy/collab/wake-session.py`)。**常驻负责"活着",`reply` 负责"叫醒"** —— 两条线在这里汇合。
### 9.2 ✅ 链式续棒(P-3)实测成立
| 棒 | 谁起的 | fires | 是否唤醒 |
|---|---|---|---|
| **第 1 棒** `SAAMf7` | 我(13:08 轮) | `pulse 60s fired 13:09:12` | ✅ `updated_at` **13:09:15** |
| **第 2 棒** `BV7ZIm` | 🔴 **我在"被第 1 棒唤醒的那一轮"里起的** | `pulse 60s fired 13:10:31` | ✅ `updated_at` **13:10:34** |
⇒ **「完成 → 被唤醒 → 再创建下一棒 → 下一棒照样唤醒」闭环成立**(第 2 棒的"起棒人"就是被第 1 棒叫醒的那个会话)。
⚠️ 代价要认下来:**每棒消耗一轮 AI**(5 分钟节奏 = **288 轮/天**);想省这个成本 ⇒ 改 **P-2 一轮预排 K 条错峰**(换成 K 个 sleeping 进程)。**链断即停**既是缺点也是安全特性(没人接就自然停)。
### 9.3 新增可复用工具
`.workbuddy/collab/wake-pulse.sh <秒数>`(默认 300)—— 一次性静默脉冲;三条硬约束写死在脚本头:⛔ 不写 stdout/⛔ 不常驻/⛔ 任务内不自我续棒。实测与内联写法等效(13:09:12 / 13:10:31 两次均由它触发)。
### 9.4 收尾(T7)
⛔ **演示到第 2 棒为止即停,没有让链继续跑**;E1 已 `TaskStop`(runtime 1m18s);`ps` 复查**全部实验进程已退出、无残留**;心跳文件停在 3 行未再增长。