# 唤醒方案评估 · 会话自建后台任务(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 `` message in your next turn**.」 ⇒ **任务完成 ⇒ 宿主往本会话投一条 `` ⇒ 本会话开新的一轮**。这就是用户记忆里「后台任务监控程序安装,装好后会话继续处理」的那条机制,逐字对上。 --- ## §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. **唤醒载荷是 ``,不带自定义指令** —— 要带信息只能让任务 `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 行未再增长。