- 变更规模:新增 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/ 知识文件,按口径入库)
9.7 KiB
9.7 KiB
S9 复核单 · 投递「目标忙」判据(活会话恒 working ⇒ 投递恒被推迟)
- 任务类别:机制排查与修复
- 复核时间:2026-10-02 09:34–09:45
- 复核会话:
[协作]-[机制排查与修复]-S9 投递目标忙判据复核与放行验证(sid6a501f20-8517-422f-bd4c-6e0e9703a3f6) - 结论:旧「恒真」死结已解开(有端到端实证);当前卡点已前移到「无活跟进会话」这一层。
一、判据层 1:「目标忙」是否仍恒真 —— 已不恒真
1.1 源码形态(~/.workbuddy/skills/session-mechanism/scripts/collabd.py)
_session_status()(:1603)只判「活着 / 已终结」,注释已明写「⛔ 别再用本函数判忙」。_session_busy()(:1656)读宿主状态机日志[SessionRunStateMachine] … sessionId=<36位> … busy=true|false,取最后一条;busy=true且距今 ≤SRSM_FRESH=900 秒才回'busy',否则回'idle',读不到回空串。判据正则用命名组(:1650),从形状上消掉「数括号错位」那类错(曾致busy恒读成False)。_target_busy()(:1698)两道判:① 库status ∈ SID_DEAD{completed, error, archived}⇒ 直接判「没在跑」;② 库working⇒ 才去看日志细分;读不到 ⇒ 按「没在跑」处理并留日志(⛔ 不按「忙」,否则等于把恒真死结换个形状留着)。- 调用点:
:1194(_pick_live前)、:1431(粗判)、:1564(拿锁后精判)。
1.2 时间序列实证(_collabd.log 全文 09-30 07:41:56 → 10-02 09:38:22)
no-follow-session469 条;target-deaf81 条;target-busy69 条;main-not-live3 条;locked1 条。target-busy最后一次出现 = 2026-10-02 00:35:24 —— 原文「延后投递:跟进会话 b11c099d 正在执行 ⇒ 等它空闲」+「投递未成(target-busy)⇒ 保留 M7=running 在队首」。被拦的b11c099d是一条真·跟进会话(标题[跟进]-会话协作自检-队列上报,当时status=working)⇒ 这一条就是旧判据的恒真现场。- 新判据首次在生产出现 = 2026-10-02 01:48:10 —— 原文「忙判据读不到(cbf76e13):状态机日志里没有它的记录 ⇒ 按没在跑处理」。
- 00:35:24 → 09:38:22(≈ 9 小时 03 分)区间内
target-busy= 0 条 ⇒ 换形态后再没恒真过。
1.3 端到端放行实证(不是合成样本,是真投递)
新判据生效后,tmp/supervise-inbox/wakeups.jsonl 记录 3 次真投递成功(kind=上报·单条,http=200,ok=true):
- 2026-10-02 01:48:22 →
cbf76e13([跟进]-会话协作自检-队列上报)@ 端口 60346 - 2026-10-02 02:56:25 →
ba7b24e8([跟进]-会话协作自检-队列上报)@ 端口 57555 - 2026-10-02 04:06:52 →
19e6e204([跟进]-会话协作自检-队列上报)@ 端口 49765
⇒ 投递确实到达过「活的跟进会话」,「活 ⇒ 忙 ⇒ 恒不投」这条死结在真实链路上已解开。
1.4 合成对照与自测(可复跑)
现场函数调用(只读,2026-10-02 09:37):
_target_busy('00000000-0000-0000-0000-000000000000')⇒False。_target_busy()对 3 条completed / archived / error会话 ⇒ 全False。_target_busy()对跟进名册 6 条(5 条completed+ 1 条error)⇒ 全False。_target_busy(6a501f20)(本棒,库working→ 日志busy且新鲜)⇒True(真在跑 ⇒ 判对)。
回归自测(selftest.py <WS> -k <名>,测试工作区 tmp/selftest,不碰生产):
- 「投递忙判据:读宿主状态机日志的 busy=(⛔ 不再用
status=='working'那档恒真的判据)」⇒ PASS 1 / 6 项。六项覆盖:末条busy=true且新鲜 ⇒ 判忙;末条busy=false⇒ 判闲;末条busy=true但已 2 小时前 ⇒ 判闲;日志无记录 ⇒ 回空串;库已终结 ⇒ 不判忙;正则用命名组、零位置组引用。 - 「投递:目标会话(跟进会话)在跑 ⇒ 必须延后(target-busy)」⇒ PASS 1 / 2 项。其中关键一项:伪造
_session_busy → 'idle'⇒ 必须不再延后、往下走成follow-not-live。
复跑命令:
export COLLABD_CONFIG="E:/ProgramData/AIProject/ai1net-dsh-server/.workbuddy/collab/collabd.config.json"
cd "E:/ProgramData/.workbuddy/skills/session-mechanism/scripts"
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" selftest.py "E:/ProgramData/AIProject/ai1net-dsh-server" -k "投递忙判据"
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" selftest.py "E:/ProgramData/AIProject/ai1net-dsh-server" -k "目标会话"
二、判据层 2:当下卡点究竟在哪一层 —— 「无活跟进会话」
09:37 现场读数(只读宿主库 E:/ProgramData/.workbuddy/workbuddy.db):
sessions.status取值域:completed 148、working 2、error 2、archived 2—— 仍无「空闲」这一档。_live_sids()=['3f43ce71', '6a501f20']—— 活会话 2 条,标题分别是接续 · 会话机制合并包 · 任务4b-4d-6与[协作]-[机制排查与修复]-S9 …⇒ 两条都不是[跟进]-角色。_scan_follows()名册 2 条:机制排查与修复 → 6ab1463e(completed)、会话协作自检 → 57f58ecf(completed)。follow_for_topic()三问(机制排查与修复/会话协作自检/ 空)⇒ 全部sid=""、why=follow-not-live。_deliver_str()里_want == ""⇒skipped = "no-follow-session"(collabd.py:1558)。- 队列现场:待上报队首 =
S8=done;日志 09:38:22 仍在刷「投递未成(no-follow-session)⇒ 保留 S8=done 在队首,下一轮重试」。
层级判定:件有(S8=done 待上报)、目标类别登记有(每类别各一条),但名册里的跟进会话全是终结态 ⇒ 投递卡在「无活跟进会话」,不是 target-busy(该档近 9 小时零命中)。⇒「多起一条跟进会话」不能治;治本要求:跟进会话得是长期活的、或收完一件能被续起。
⚠️ 读数会随时间漂(会话状态在动):09:37 读作
completed 148 / working 2 / error 2 / archived 2;09:40 复读作completed 149 / working 1 / error 2 / archived 2—— 是3f43ce71由working转completed,不改变「活会话里没有[跟进]-」这一结论(两次_live_sids()均为['3f43ce71', '6a501f20'])。
三、本节点判据状态
- 层 1(判据不再恒真):成立。证据 = 源码形态 + 9 小时零
target-busy+ 3 次真投递成功 + 自测 2 用例 PASS。 - 层 2(卡点定位):成立。证据 = 现场读数
follow-not-live+ 名册 / 活会话清单。 - 未过项(如实报):「端到端放行」此刻无法现场复现 —— 现场不存在活跟进会话 ⇒ 前置条件不成立。已有的 3 次投成是 01:48–04:06 窗口的实证;要再验一次,须先有一条活着的
[跟进]-…会话。该前置属 S6(建齐三类会话)的持续态,⛔ 不属 S9 缺陷。
四、复现命令(现场读数)
# 判读分布
grep -o "投递未成([a-z-]*)" .workbuddy/collab/logs/_collabd.log | sort | uniq -c
# 判据换形态的分界点
grep -n "target-busy" .workbuddy/collab/logs/_collabd.log | tail -3
grep -n "忙判据读不到" .workbuddy/collab/logs/_collabd.log | head -3
# 投成记录(目标是不是跟进会话)
tail -5 tmp/supervise-inbox/wakeups.jsonl
现场判据读数(只读,自包含;⛔ 不写任何生产文件):
cd "E:/ProgramData/AIProject/ai1net-dsh-server"
export COLLABD_CONFIG="E:/ProgramData/AIProject/ai1net-dsh-server/.workbuddy/collab/collabd.config.json"
"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" - <<'PY'
import os, sys, sqlite3, time
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
os.environ["COLLABD_CONFIG"] = r"E:/ProgramData/AIProject/ai1net-dsh-server/.workbuddy/collab/collabd.config.json"
sys.path.insert(0, r"E:/ProgramData/.workbuddy/skills/session-mechanism/scripts")
import collabd as C
print("now", time.strftime("%Y-%m-%d %H:%M:%S"), "| SID_DEAD", sorted(C.SID_DEAD), "| SRSM_FRESH", C.SRSM_FRESH)
con = sqlite3.connect("file:E:/ProgramData/.workbuddy/workbuddy.db?mode=ro", uri=True)
for st, n in con.execute("select status,count(*) from sessions group by status order by 2 desc"):
print(" 取值域 %-10s %d" % (st, n))
sc = C._scan_follows()
print("跟进名册 sids :", [str(s)[:8] for s in (sc.get("sids") or [])])
print("跟进名册 by_topic:", {k: str(v)[:8] for k, v in (sc.get("by_topic") or {}).items()})
print("活会话 _live_sids:", sorted(str(s)[:8] for s in C._live_sids()))
for tp in ["机制排查与修复", "会话协作自检", ""]:
r = C.follow_for_topic({}, tp, live_sids=[g.get("sessionId") for g in (C.discover_gateways() or [])])
print("follow_for_topic(%-14s) sid=%-8s why=%s" % (tp or "(空)", (r.get("sid") or "-")[:8], r.get("why") or "-"))
for sid, stt in con.execute("select id,status from sessions where status='working'"):
print("working 会话 %s -> _session_busy=%r _target_busy=%s(旧判据 status=='working'=%s)"
% (sid[:8], C._session_busy(sid), C._target_busy(sid), C._session_status(sid) == "working"))
print("假 sid -> _target_busy =", C._target_busy("00000000-0000-0000-0000-000000000000"))
for r in con.execute("select id from sessions where status in ('completed','archived','error') limit 3"):
print("终结会话 %s -> _target_busy = %s" % (r[0][:8], C._target_busy(r[0])))
con.close()
PY