- 变更规模:新增 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/ 知识文件,按口径入库)
14 KiB
唤醒方案评估 · 会话自建后台任务(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 边界与未测项(⛔ 不要当成已经验过)
- 只能唤醒"自己这个会话" —— 跨会话投递仍需 gateway
reply(另案)。 - 唤醒载荷是
<task-notification>,不带自定义指令 —— 要带信息只能让任务echo(那就进转录、会涨体积)。 - 未测:更长的时长(>300 s)、更高频/更大量的中途输出是否最终会唤醒、会话关闭后任务的存活边界、宿主重启后残留如何。
- ⚠️ 本会话的转录日志自 12:26 起已冻结(
10,485,723 B,mtime 恒12:26:19)—— 实验前就已如此。 ⇒ 讨论"规模/体积"时不能再用这个文件当度量,要换度量。 - ⛔ 本棒没有留下任何在跑的脉冲链(实验任务全部已退出;⛔ 未创建自动化)——要不要真的把 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 行未再增长。