Files
mcn-short-video/.workbuddy/memory/2026-10-08.md
T
maogeigei 3a940270b5 docs: 会话任务链路待优化项立档(跨会话可见)
新增 mcn-workshop/docs/会话任务链路-待优化项.md:4 项待优化(任务模型浮动/面板跳转入口/任务权限/旧会话归位)+红线+已修历史。
同步:项目 MEMORY.md 权威源表加该文档指针,工作台红线补 workspace_scope 一条。
理由:只写进当天日志的规矩换会话读不到,必须落到工作台文档与长期记忆。
2026-10-08 18:00:20 +08:00

39 KiB
Raw Blame History

2026-10-08

会话技能加载 + 项目记忆去重

  • 按用户指令加载 session-mechanism:四类必读已读(00-动手前必过.md / rules.md / manifest.md / 01-文档索引.md)+ SKILL.md 第一屏(触发句、第 0 步加载门槛、十条禁令、自决策白名单、现状地图)。
  • 内化判据:边界内自决策(技术选型/实现路径/命名/调参/部署/排查/版本/兼容降级/文档技术内容);只提报三类(功能语义分叉/红线门禁/超边界:花钱·对外承诺·要凭据)。
  • 项目 MEMORY.md 去重重组为 v4(17,841 → 17,315 B):新增 §0 两条指针(工作台 AI 通路 docs/AI链路-本地CLI接入.md、技能侧 WeKnora 调用);§2.1 补 09-29 AI 通路四条硬红线+两段流程模型来源;§4.2/§4.3 降级为「存根+硬红线」;清历史流水。

未决

  • ⚠️ MEMORY.md 体积仍会被注入截断(阈值约 10–12 KB)。解法二选一:① 维持单文件(细节自动可见,但注入截断)② 拆专题文件(不截断,但细节失去自动注入)。无客观优劣 ⇒ 待用户定。

工作台「更新榜单数据」按钮排查(功能正常,是可见性问题)

  • 现象:用户点击后「没有会话执行」。
  • 取证(四处读数):宿主库 automations 有该 once 任务(ACTIVE、next_run_at 已过期、model=hy4-preview、skills=["mcn-data-insight"]);automation_runs.status=IN_PROGRESS + automation_runtime_state.running=1(⚠️ running_conversation_id=null 是常态,不代表没起会话);sessions 已建后台会话 8a085794…(bg=1,10:07:23);会话 jsonl 283 KB 且 mtime 持续更新(10:14),工具调用 30 Bash/14 Read/4 Write ⇒ 任务真的在跑。/api/dsh/ranking-update 返回 files=104、lastUpdateAt 10:13:51 ⇒ 数据已在落盘。
  • 真因两条(都与抓取逻辑无关):① 会话 cwd = …\mcn-work-shop ⇒ 落在侧边栏 mcn-work-shop 空间分组,不在用户当前空间,需切组才可见(这是设计,非 bug);② 工作台服务作为会话后台任务被回收 ⇒ 前端轮询 /api/run/status 持续抛错。
  • 修复:public/data-pages.js updateRankingData() 轮询加失败计数 —— 连续 3 次查询失败即提示「工作台服务已断开,任务仍在后台执行」并结束轮询(原逻辑静默 continue 到 15 分钟超时,用户零感知)。
  • ⚠️ 排查方法论沉淀:判「后台任务有没有在执行」的正确取證链 = automations → automation_runs → automation_runtime_state → sessions → 会话 jsonl 的 mtime/大小。⛔ 别只看 running_conversation_id(常为 null,会误判成没执行)。

工作台目录统一改名 mcn-work-shop → mcn-workshop

  • 动因(用户):「分组已改为 mcn-workshop,需要全部替换;执行会话任务时需要按分组名称显示在会话列表」。 🔑 机制:WorkBuddy 侧边栏空间分组名 = 会话 cwd 的 basename ⇒ 只改字符串无效,必须真改名目录。
  • 执行五步:① 停服务释放句柄(否则 rename 必 WinError 5)→ ② mv mcn-work-shop mcn-workshop(成功)→ ③ 临时脚本全仓替换 mcn-work-shop→mcn-workshop:37 文件 / 121 处;⛔ 按沿革句规矩跳过 .workbuddy/memory/YYYY-MM-DD.md(16 个历史日志保留当时目录名史实)→ ④ 修 2 处被替换改坏语义的注释(server.js L1265 / L1269,原「旧 mcn-workshop/新建 mcn-workshop」被换成同词,已补回三代路径)→ ⑤ 重启验证:app.js/data-pages.js 200,data-pages.js 含 pollFail 修复。
  • ⚠️ 事实澄清(易踩):D:\AI技能\mcn-workshop 不是工作台,是 09-01 路径拼接 bug 留下的垃圾目录(3 个 0 字节文件 + outputs)。工作台本体一直在仓库内 …\V1.0\mcn-workshop。sessions 表取证:该垃圾目录下零会话;未删除会话中仅 1 条 mcn-work 相关,cwd 仍是旧 …\mcn-work-shop。
  • 净效果:新建后台会话 cwd = …\V1.0\mcn-workshop ⇒ 侧边栏分组显示 mcn-workshop ✅
  • 残留:project/…/subskills/mcn-data-insight/scripts/__pycache__/*.pyc(二进制缓存含旧串,会自行重生成,无需处理)。

追加澄清(用户真实意图,⛔ 别再理解成"目录改名")

  • 用户要的不是改目录名,而是:工作台提交 WorkBuddy 定时任务后,产生的 AI 会话应归属哪个「空间分组」。
  • 机制:分组名 = 会话 cwd 的目录名;工作台侧由 server.js 的 SESSION_CWD(当前 = 工作台自身目录)决定 ⇒ 改目录名只是让分组名变成 mcn-workshop 的手段,不是目的本身。
  • ⚠️ 存量不掉头:10:07 那条榜单更新任务 cwd 仍是旧 …\mcn-work-shop ⇒ 显示在旧分组;改名后新建的任务才落到 mcn-workshop。清理用的 SESSION_CWD_PATTERN='%mcn-work%' 覆盖新旧三代,不受影响。
  • 待用户定(功能语义分叉):① 维持工作台专属分组 ② 归入用户当前主项目分组 ③ 自定义独立分组(需目录真实存在且在 workspaces 登记,08-27 即此法)。可选项:把 SESSION_CWD 提升为 config.json 可配字段,换分组只改配置不动代码。

后台任务进度可见(用户:「我需要看到 才知道执行是否正常」)

  • 真需求:不是分组放哪,而是在工作台内直接看到执行状态,判断在跑还是卡住、做了什么。
  • 后端 server.js /api/run/status 增强:automation → 关联它拉起的后台会话(sessions.is_background_automation=1 且创建时间落在任务创建 −15s~+300s)→ 读该会话 jsonl 尾部 256 KB → 新增字段 elapsedMs / sessionId / sessionTitle / lastAction(最近一段文本,截断 160 字)/ toolCalls(按工具名计数)/ updatedAgoMs / stalled(>90 s 无新输出)/ logBytes。
    • ⭐ 会话日志路径换算(可复用):cwd → projects 目录名 = 盘符小写 + 分隔符全转 -(escapeCwdForProjects());基目录 <workbuddy>/projects/<转义名>/<sessionId>.jsonl。
    • ⚠️ stalled 只在 state=running 时有意义(已完成任务日志当然不再写入,会恒 true)。
  • 前端新增 window.TaskProgress(实现在 public/app.js,样式 style.css 的 .tp-*):右下角常驻面板 = 状态/已运行时长/工具调用/最近动作/停滞警告。四处轮询点全接入:app.js executeTask、data-pages.js 的榜单更新/批量更新账号/视频解析。终态保留 8 秒自动收起,运行超时则保留供继续观察。
  • 验证:三文件 node --check 全过;服务起来后 app.js 含 6 处、data-pages.js 含 9 处 TaskProgress,style.css 含 .tp-panel;状态接口返回完整进度字段。

✅ 分组归属定案:就用 mcn-workshop 文件夹对应的分组(已实测)

  • 用户拍板:任务会话归到 mcn-workshop 文件夹对应的那个分组。
  • 实测(提交轻量自检任务取证):新会话 cwd = …\V1.0\mcn-workshop ✅;对照 10:07 那条旧榜单任务仍是 …\mcn-work-shop(两者在侧边栏分属不同分组,属正常存量,不用管)。
  • 结论:不需要再改代码 —— SESSION_CWD = ROOT,目录改名后自动生效。自检任务已删排期。
  • ⚠️ 认知要点:侧边栏分组按 完整 cwd 聚合,不是按文件夹名合并 ⇒ 同名不同路径会是两个分组。

⚠️ 更正(12:2x):分组靠 cwd 命中已打开的工作区,光改目录名不够

  • 上一条只对了一半:目录改名后会话 cwd 是仓库内 …\V1.0\mcn-workshop,不是用户的工作区 ⇒ 仍显示在「未分组任务」区域(用户实测反馈)。
  • 用户纠正:D:\AI技能\mcn-workshop 才是那个工作区。我此前仅凭目录里只有 3 个 0 字节垃圾文件就判它"是垃圾目录"——没查登记表就下结论,违反「判状态先问程序自己」。
  • 🔑 真机制:侧边栏分组 = 会话 cwd 命中已打开过的工作区才显示为命名分组;没命中 ⇒ 落「未分组任务」。
    • 取证:workspaces 表仅 7 行且全是 C:\Users\maidou\WorkBuddy\<时间戳>,不含任何 D:\AI技能\* ⇒ 分组依据不是这张表,而是按会话 cwd 对已打开工作区的聚合。
  • 修法(配置化,⛔ 不写死):load-config.js 增 sessionCwd(默认 '' = 用 ROOT)→ server.js SESSION_CWD = String(CFG.sessionCwd||'').trim() || ROOT → config.json 设 "sessionCwd": "D:\\AI技能\\mcn-workshop"。换分组只改配置,不动代码。
  • 实测:新会话 cwd = D:/AI技能/mcn-workshop ✅;任务在该工作区执行正常(done,结果"成功",73 秒)。对照 12:23 那条仍在仓库内路径 ⇒ 两组并存,旧的会随过期清理消失。
  • ✅ 13:06 二次模拟请求复验一致(用户指示:不测真实功能,只发模拟请求看分组):写入 cwds=["D:\\AI技能\\mcn-workshop"],会话 cwd 同样命中 ⇒ 配置化改法稳定。两次自检排期均已删,会话留在目标工作区供用户肉眼确认。

提交(13:09)

  • commit f77735b:目录改名 + 121 处替换 + 任务进度面板 + 会话归属配置化 + 前端轮询缺陷修复。提交前已查:仓库内无 API key(grep -rl "ck_fzpjin5c0iyo" 空)、新目录未被 gitignore 误伤(git check-ignore 无输出)。工作区干净(0 未提交)。
  • ⚠️ push 失败(非权限问题):ssh work.alotbuy.com:222 连接超时 —— 公司内网 git 服务器,当前网络不通。走 10800 代理也失败:Git Bash 没有 connect 工具(ProxyCommand 用不了)。
  • 待办:回到公司网络/VPN 后跑 git push origin main(本地提交已就位,领先 origin 1 个)。

🔴 新远程地址实测:能连通,但两条历史无共同祖先(已停手,未推送)

  • 用户给新地址 [email protected]:admin/mcn-short-video.git(旧 work.alotbuy.com:222 超时)。实测 能连通:ls-remote 成功,远程 main = e738dd1(2026-10-07 10:48)。
  • 🔴 git merge-base main FETCH_HEAD 为空 ⇒ 无共同祖先;两边各 253 个提交,rev-list --left-right --count = 253 / 253。
    • 本地最早 2026-08-06「Full upload: MCNVideo AI project」,远程内容同源但 hash 全不同 ⇒ 历史被重写过,是两条独立线。
    • 远程有本地没有的提交(如 10-07 那条 gitignore 补充),说明远程不等于"落后于本地"。
  • ⛔ 不能直接 push:普通 push 必被拒(非 fast-forward);强推会抹掉远程 253 个提交,不可逆。
  • 待用户定:① 推到远程新分支(安全,推荐)② 合并两条历史(--allow-unrelated-histories,冲突量极大)③ 强推覆盖(⛔ 需明确授权)④ 不推,等原地址恢复。
  • ⚠️ 排查教训:换远程地址前必须先看 merge-base,⛔ 不能因为"地址能连通"就推。

合并两条历史并完成同步(15:4x)

  • 合并 admin/main(两条独立历史、无共同祖先):冲突 20 文件 47 块。
    • 40 块自动:规则①一边为空 → 取非空(纯新增,取并集);规则②ours 含 mcn-workshop 且 theirs 含 mcn-work-shop → 取 ours(目录改名,本地最新)。
    • 7 块人工(全在 MEMORY.md):本地 v4 精简版覆盖远程 v3,远程的详细条目在 RUNBOOK/技能文档均有指针 ⇒ 取本地。
    • ⚠️ 脚本坑:冲突标记正则必须写 \r?\n(工作区是 CRLF),第一版漏了导致 0 匹配。
  • ⭐⭐ 合并的隐藏副作用(换线合并必查):远程那条线的工作台目录仍是旧名 ⇒ 合并后旧目录 mcn-work-shop 被整个带回,两套代码并存。
    • 取证三步:① diff -rq 显示旧目录零独有文件;② server.js 报 2672 行差异,实为行尾差异(旧 CRLF / 新 LF),diff --strip-trailing-cr 后仅 206 行;③ 其中旧目录独有的 40 行全是本地已升级掉的旧代码(旧版凭据解析 / 旧状态接口 / 写死的会话目录)⇒ 远程无本地缺失的实质改动。
    • 结论:安全删除,已 git rm -r -f(合并暂存态需 -f)。
  • 推送:git push admin main = 快进 e738dd1..67317c4(合并后远程变成本地祖先,不是强推)。本地与 admin 已完全一致(rev-list --left-right --count = 0 / 0)。
  • ⚠️ 旧 origin(work.alotbuy.com:222)仍 ahead 256,网络不通未推。

旧远程清理(16:1x,用户:「旧的远程没用了」)

  • 处理三步:① git remote set-url origin 指向 [email protected]:admin/mcn-short-video.git——保留默认远程名 origin,脚本与命令习惯不用改;② 删除冗余别名 admin(与 origin 同址);③ 删除随旧远程留下的本地分支 admin-main(= e738dd1,是 main 的祖先,零独有内容)。
  • 验证:只剩 origin 一个远程;本地与 origin 完全一致(0 / 0)。
  • 📌 远程口径(本项目现行):[email protected]:admin/mcn-short-video.git(旧 work.alotbuy.com:222 已废弃)。

🔴🔴 再修正:后台任务会话不进「空间」分组(这才是真机制,前两轮都猜错了)

  • 现象:cwd 已改成 D:\AI技能\mcn-workshop,会话仍落在「未分组任务」。
  • 取证(sessions 表):group_id / group_title 全为 null;project_id 也全 null ⇒ 分组不靠这三个字段。
  • ⭐ 真因:侧边栏是「空间区」与「任务区」两套——
    • 手动会话(is_background_automation 为 0/null)→ 按 cwd 聚合成空间分组(如 mcn-short-video、vibe-video-analysis);
    • 后台任务会话(is_background_automation=1)→ 一律进任务区,与 cwd 无关。
    • 工作台 /api/run 提交的任务会话全是 bg=1 ⇒ 改 cwd 永远改不了它进哪个区。
  • 想让「mcn-workshop 空间」出现在侧边栏:在 D:\AI技能\mcn-workshop 手动开一个会话(手动会话按 cwd 聚合才会生成该空间);但后台任务仍会在任务区,属产品设计。
  • ✅ 正解(已落地):看后台任务执行状态用工作台右下角进度面板,不必依赖侧边栏分区。
  • ⚠️ app.asar 里 grep「未分组」= 0 次 ⇒ 该文案在别处生成(未继续逆向)。

✅✅ 真根因并修复:automations.workspace_scope 漏填(不是 cwd、不是工作区登记表)

  • 逆向线索:is_playground 赋值处 = typeof e.space?.autoGenerated=='boolean' ? +!!e.space.autoGenerated : null ⇒ 「空间是自动生成的」才落未分组。
  • 交叉表取证(决定性):
    • 走正规 automation 工具创建的任务:workspace_scope='workspace'(36 条) ⇒ 会话 is_playground=0 ⇒ 正常进工作区分组;
    • 工作台直接写库的任务(createOnceAutomation + /api/run 两处 INSERT):workspace_scope 为 null(当天 5 条) ⇒ 会话 is_playground=1 ⇒ 落「未分组任务」。
  • ❌ 排除的假因:改 cwd(无效)、把目录登记进 workspaces 表(无效,该表不是决定因素;登记行已保留,无害)。
  • 🔧 修复:server.js 两处 INSERT INTO automations 增加 workspace_scope 列并填 'workspace'(createOnceAutomation 约 L407、/api/run 约 L879)。
  • 实测:新任务 scope=workspace、新会话 is_playground=0 ✅(对照组仍是 1)。
  • 📌 红线:工作台凡直接写宿主 automations 表,必须带 workspace_scope='workspace',否则会话一律落「未分组任务」。

会话任务链路审计(用户:「看看哪些需要调整」)

已修:4 个轮询点口径统一(此前只有榜单更新一处有容错)。

  • 轮询点 = app.js executeTask(通用 AI 任务)/data-pages 的榜单更新、批量更新账号、视频解析。
  • 原先 3 处是 catch(e){continue},服务被回收时静默空转 15~35 分钟才超时 ⇒ 用户只看到按钮转圈。现均为「连续 3 次查询失败 → 提示服务断开 → 结束轮询」。

已确认健康(无需改):

  • 写宿主 automations 表仅 2 处(createOnceAutomation 约 L409、/api/run 约 L880),均已带 workspace_scope。
  • submit-review-tasks.cjs、check-run-status.cjs 走 HTTP 接口/只读,不直接写库 ⇒ 自动被修复覆盖。
  • 技能挂载(skills_json)、浏览器锁约束、会话清理(SESSION_CWD_PATTERN='%mcn-work%' 仍覆盖新 cwd)均正常。

待用户定(各有权衡): 1、任务模型浮动:model_id 取 sessions 最近活跃手动会话的 model,与 config.ai.cli.model(deepseek-v4.1-flash)无关 ⇒ 同一任务换次数可能换模型,产出不稳定。建议加 taskModel 配置(留空=跟随现状,填了=固定)。 2、进度面板缺「打开会话」入口:能看状态但跳不进会话。 3、任务权限恒为 fullAccess(可写文件/执行命令):是否收紧取决于任务类型。 4、修复前创建的旧会话仍 playground=1:改库可归位,但需重启 WorkBuddy 才生效。

素材库补采:批 65 收尾 + 批 66/67 完成(30 卡)

  • 承 09-29 进度:批 65(细节专业 15 卡)已 apply 完成,细节专业 40→55。
  • 批 66(细节专业 15 卡):取材 31748 姜乘澜底妆 / 17511 笑笑易 / 19014 俊希做菜 / 24821 烟道逃生 / 25090 姜乘澜画眼线。✅ 本批 5 条原生即教程/实操题材,与细节专业天然契合(不像批 65 从非专业题材硬提)。apply:接受 15/退回 0,问法改写 15 张平均 +32 字。细节专业 55→70。
  • 批 67(自嘲反差 15 卡):取材 26484 路之坑爹感恩宴 / 31816 直男五合一洗护 / 31970 李普不离谱 / 21500 桃气小周买饭 / 26411 老弟受难日记。含 1 张元层面自黑卡(31970 全家崩溃时推销泡面)。apply:接受 15/退回 0,问法改写 15 张平均 +12 字。自嘲反差 56→71。
  • 两批均走完整链路:写 md → _tmp_bNN_spec.py 生成 spec → meta.json → build-pending → cat PROMPT_polish_v3.md + build-pending 组装 prompt → 派 doubao-seed-2-1-pro → _wNN.py 校验落盘 → apply-pending。

本轮新踩坑

  • ⚠️ MCP myai-mcp-production 登录态会过期:批 66 拉第 4 条时报「认证失败」。解法:先 auth_status 确认 → feishu_login 重授权(拉起浏览器)→ 重试即通。连续拉多条详情时中途要留意。
  • ⚠️ spec 脚本里别夹英文单词:批 66 的 neg 句误写「等摊主 processing」,批量产出会污染语料。写完 spec 脚本前先自查。

当前缺口(修正后口径,2026-10-08 10:50)

标签 现有 缺口 候选
食材极致 8 92 ⛔ 0
预见式服务 21 79 ⛔ 2
感官沉浸 59 41 14
细节专业 70 30 22
自嘲反差 71 29 34
视觉冲击 76 24 10

达标:品质对比 110 / 氛围沉浸 106 / 反差 217 / 反常识 128 / 价值观冲击 108。

  • ⛔ 卡点未解:食材极致(缺口 92)与 预见式服务(缺口 79)候选池近乎空,占剩余缺口一半以上。需换关键词或换捞法才能推进。
  • 暂存批累计 24 批(43–52、54–67,缺 53)。

补采续跑:批 68(感官沉浸 15 卡)/批 69(细节专业 9 卡)

批 68 · 感官沉浸(33289 子杭自驾318 ×6 / 31773 萝卜乔乔英国留学回国 ×6 / 30090 姐弟双向送礼 ×3)

  • apply 结果:接受 15/退回 0,问法改写 15 张,平均 +19 字。感官沉浸 59 → 74。
  • ⭐ 两条来源 0 卡,已在 md 写明判据:28012(电焊工试戏)男主嘴配的「滋滋」是谐音梗的音效而非真实声音质感,事件主角是"身在曹营心在焊" → 归 反常识/自嘲反差;28415(男朋友的算计)踩碎眼镜/摔筷子/拍桌的动静是发疯甩锅的附产品 → 归 反差/价值观冲击。
  • 感官通道分布齐全(冷/缺氧失眠/冰雹砸车/撕羊腿/热水澡/久坐腰酸 · 热瓶/苦到脸垮/中药味/红米肠/赶机喘/长途疲惫 · 中暑虚脱/猛灌水/红糖蛋滋啦)。

批 69 · 细节专业(28328 海鲜蒸汽 ×4 / 37316 巧克力棒手作 ×5)= 9 卡

  • apply 结果:接受 9/退回 0,问法改写 9 张,平均 +20 字。细节专业 70 → 79。
  • ⭐ 本批 3 条来源 0 卡,坚持不凑数(沿用批 56 口径):
    • 20461(机票骗局)—— 骗子的"专业"是话术与骗术设计(伪装客服、真退票取信、连环索要卡号),不落在"这一下换普通人来做就做不到位"的操作细节上 → 反差/反常识。这是细节专业最容易误判的一类:话术专业 ≠ 操作专业。
    • 24820(烟道脱困)—— 主角是作死翻车与体力消耗,片尾"专业人士、专业场地、专业操作"是反讽 → 自嘲反差/难度极限;且同账号同题材 24821 已在批 66 取过两卡,本条脱困手法同构,不重复入库。
    • 22163(轮椅游渔岛)—— 倒拖轮椅过软沙滩属体力付出 → 难度极限;其余为情侣互坑 → 反差/自嘲反差。
  • ⭐ 取批命中率下降的信号:细节专业候选池看着有 22 条,但主队列取 5 条只有 2 条可用(命中率 40%)。后续该标签每批卡数会低于 15,属正常,不要为了凑 15 张硬塞。

新增踩坑与复用做法

  • ✅ _wNN.py 用 sed 复用(sed -e 's/_raw68/_raw69/g' -e 's/pending_0068/pending_0069/g' -e 's/^N = 15/N = 9/' _w68.py > _w69.py)—— 卡数变化时记得同步改 N。本轮连续 3 批(67/68/69)一次通过,bad: []。
  • ✅ 超长详情自动落盘:33289 返回 88,150 字符超限 → 工具落盘到 D:\.workbuddy\projects\d-AI技能-mcn-short-video\<sessionId>\tool-results\*.txt,用 Read 直接读(该文件只有 117 行,一次读完)。
  • ⚠️ 派模型改用「让 Agent 自己 Read prompt 文件 → 自己 Write 落盘 _rawNN.txt」——比让模型把 JSON 回吐到对话再手工落盘更省上下文,连续 3 批稳定。

当前缺口(批 69 后):自嘲反差 29(候选 34)|感官沉浸 26(候选 14)|视觉冲击 24(候选 10)|细节专业 21(候选 22)。食材极致 92/预见式服务 79 仍挂起(用户 2026-10-08 决策:先不处理)。

批 70(自嘲反差 15 卡)· 本轮第 5 批 → 复核点

结果:接受 15/退回 0,问法改写 15 张,平均 +27 字(前几批 +19/+20,本批问法增厚更充分)。自嘲反差 71→86(缺口 14)。 来源:35376 唐轩乌龙×6 | 35301 丁浩普信男×6 | 28146 妈妈硬核推理×3。5 条视频里 2 条 0 卡(33593、21923)。

⭐ 自嘲反差口径新增形态「姿态过载」(本批确立):

  • 定义:主角自己把一件小事升格到远超其体量的规格,且全程一本正经(妈妈把女儿回家晚做成刑侦审讯:北风4级/100米/11分37秒 vs 6点12分39秒)。
  • 判据仍落回一句话:笑点落在主角自己身上。妈妈是主角、推理的荒谬感由她自己一本正经地制造 → 收。
  • ⛔ 反向凡尔赛揭晓(语数英 100/98/100 + 兑现平板)→ 笑点在预期被颠覆 → 反常识;儿子 O 型嘴石化 → 笑点落在旁观者受击 → 反差(世界参差)。均不收。

两条 0 卡判据(写入 meta):

  • 33593(张开父子的下场):家族护短复仇爽剧,爽点落在反派父子被制裁上,主角阵营全程降维打击,无一处笑点落在自己身上 → 反差/价值观冲击。其「悲痛段落硬塞洗面奶口播」虽属元层面自黑,但批 67 已收同构卡(C1 全家崩溃时推销泡面)→ 不重复入库。
  • 21923(消防设施瘫痪的代价):真实事件改编的沉重社会悲剧(烟花引火→消防栓被锁→接头拧不上→母亲丧生→儿子一夜白头),全片无笑点 → 价值观冲击。
  • ⭐ 通用判据沉淀:沉重社会悲剧类一律不进自嘲反差;元层面自黑要查是否已收同构卡(批 67 已占一类)。

其他弃卡判据:35376「你那算盘珠子都崩我脸上了」是吐槽他人 → 反差;「我没吃过我妈包的包子」与「我妈不会做饭」是同一场乌龙的两端 → 分作「立 flag」与「揭盅」两卡,不合并(同批 67 B2/B3/B4 细分做法)。35301 防晒植入段属商业植入;丽丽怒骂并入 B4 不单列。

本轮 5 批(66–70)合计 69 卡:66=15 | 67=15(自嘲反差)| 68=15(感官沉浸)| 69=9(细节专业,不凑数)| 70=15(自嘲反差)。 当前缺口(批 70 后):感官沉浸 26(候选 14)→ 视觉冲击 24(10)→ 细节专业 21(22)→ 自嘲反差 14(34)。食材极致 92/预见式服务 79 挂起。 体检:5_tag_coverage.py 卡片总数 1017(源文件 69 个),12 维无自造标签;⛔ 零覆盖仅 难度极限(用户决策不处理),⚠️ 偏少仅 食材极致(8,挂起)。

复核抽查:批 70 卡 6(哭着嚼坚果上供)润色生效——台词前置 + 破折号拍点(「我以后再也不养小仓鼠了,我对不起你」——唐轩把相框平放在桌上…),锁定 4 字段零改动,无编造。spec 回写 sim=3/neg=2、tag 单一、src 3 个,全部符合。

⭐ 入库状态核查(用户问「素材都同步到 wekonra 了吗」→ 答案:没有,两层原因)

  • 流程层:按暂存模式,补采只落 batches/pending_import/,统一入库必须跑 13_import_pending.py --run。截至批 70,27 批 397 卡全部堆在暂存区,一条都没进库(13_import_pending.py --help 盘点:27 批/397 卡/异常 0)。batches/*.md 41 批(早期跑过 2_import_batch.py 的)是唯一进过库的部分。
  • 环境层:WeKnora-app 容器 Exited (127),最后一条日志停在 2026-09-29 23:53(health 200)→ 31607 端口不通,连"库里现有多少条"都查不了。同项目其余容器(frontend 24719 / docreader / postgres / redis)都在跑。整个 10-08 的补采从未连过库。
  • 部署信息:镜像 wechatopenai/weknora-app:latest,restart unless-stopped,compose 工作目录标签 /home/maidou/weknora(WSL 路径,本机 docker ps -a 可见)。拉起命令 docker start WeKnora-app;若仍 127 需查 compose 的 command/entrypoint。
  • 回炉链路同样未完成:「删旧条目 + 重导 + 检索终验」都还没做;回炉批 31–33 也未跑完。
  • ⚠️ 排查口诀补充:问「同步了吗」时先查两处——① 13_import_pending.py --help 看暂存盘点 ② docker ps -a 看 WeKnora-app 是否 Up。两者都要看,只查一处会误判。

⭐ 规则核对:入库前必须补全 question(用户 2026-10-08 追问「是否遵循」→ 核查 27 批结论)

  • 规则依据(能查到的最强出处):RUNBOOK §步骤 6「写 spec.json(检索问法)」=流水线必经步骤;真正的动机在 RUNBOOK 红线:「追加相似问不重建索引(POST /faq/entries/{id}/similar-questions 返回 200 但向量分数按位不变)→ 要改问法只能删 + 重导」。所以「入库前补全」不是形式要求,而是事后补的代价=整批删+重导。
  • ⚠️ 未找到该规则的原始对话出处:conversation_search 两次 0 命中,本地 memory / RUNBOOK 也没有「入库前要补全 question」这句原话。已按「规范 + 红线」等价认定,未擅自当作既有明文规则引用。
  • 核查结论(27 批全量):全部有 spec.json、std 全部非空 → 形式上 100% 遵循。但有 2 批偏离现行规范:
    批 偏离 范围
    43 neg=3 条(现行应为 2),且内容是关键词短语(「白酒带货 / 餐厅推荐 / 商务礼仪培训」)不是问句 15/15 卡
    48 sim=2 条(现行应为 3) 15/15 卡
    其余 25 批:sim=3(批 47 起)或 sim=6(批 44/45/46,合旧规范)/neg=2,全部合规。
  • ⚠️ 文档与执行脱节(待用户裁定):RUNBOOK §步骤 6 明文写 sim「口语问法 4–6 条」,但批 47 起实际统一为 3 条(_wNN.py 校验也写死 != 3)。要么改文档、要么把 sim 补回 ≥4 条。
  • ⭐ 核查脚本口诀:glob('batches/pending_import/batch_*.spec.json') 逐批 Counter(len(x['sim'])) + Counter(len(x['neg'])) 打分布表,一眼看出条数偏离;别用 md 的 ```text 计数 //2 算卡数(会算成一半,以 spec 长度为准)。

⭐ Docker WeKnora 启动失败排查(2026-10-08 · 已修复)

  • 根因(不是应用崩溃,是容器 init 前的 bind mount 失败):docker inspect WeKnora-app 的 .State.Error 给出原文—— OCI runtime create failed: runc create failed: ... error mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu-24.04/4f4eee8c…" to rootfs at "/app/config/config.yaml": not a directory: Are you trying to mount a directory onto a file (or vice-versa)?
  • 链路:compose 写 ./config/config.yaml:/app/config/config.yaml(文件→文件,写法正确,源文件 /home/maidou/weknora/config/config.yaml 确实存在且是 6545 B 的文件)→ 但 Docker Desktop 的 bind-mount 代理层(WSL 9P 中转,路径被哈希成 4f4eee8c…)把源识别成了目录 → 挂载失败 → 容器连 init 都没进 → Exit 127(entrypoint ./scripts/docker-entrypoint.sh 根本没执行)。
  • 时间线:StartedAt 09-28 01:45,FinishedAt 10-08 02:51 —— 正常跑了 10 天,今天凌晨有一次重启尝试(Docker Desktop/WSL 层变动)时代理层状态不对而失败。restart unless-stopped 不会救「启动失败」,所以一直停在 Exited。
  • ✅ 修复:docker start WeKnora-app 一次即恢复(Up (healthy),31607 通)→ 属代理层临时状态问题,重试可解。复发时的升级路径:重启 Docker Desktop → docker compose up -d 重建容器(会换新的代理哈希)。治本方向:单文件 bind mount 跨 WSL 本就脆弱,可改 docker cp 或 volume。
  • ⭐ 排查口诀:容器 Exited(127) 先看 docker inspect --format '{{.State.Error}}',不要只看 docker logs(日志只到上次正常运行,看不出启动失败原因)。本次日志最后一条停在 09-29 23:53 health 200,完全看不出问题,是 inspect 的 State.Error 直接给出根因。

库内实测(服务恢复后)

  • FAQ 总数 631(分页抖动,去重拉取 624)。问法 100% 齐全:无 std 0 / 无 sim 0 / 无 neg 0。
  • sim 条数分布 {5:312, 6:222, 3:46, 2:30, 4:14}、neg {2:542, 3:82} —— 历史口径混着(旧 5/6 条、现 3 条、少量 2 条)。
  • 库内 tag_name 分布:反差 172/反常识 117/价值观冲击 93/视觉冲击 67/自嘲反差 44/细节专业 32/感官沉浸 26/氛围沉浸 24/品质对比 19/预见式服务 19/食材极致 9/难度极限 2 → 与本地统计一致,即已入库 = 早期 41 批;暂存 27 批 397 卡确实未进。
  • ⚠️ 接口结构坑:FAQ 列表返回是 data.data({data:{total,page,page_size,data:[...]}})两层嵌套,只取一层会得到空列表;条目标签字段是 tag_name(不是 tag/tags);page_size=total 一次拉全会返回空 → 必须分页 100 逐页 + 按 id 去重。

⭐⭐ 27 批 397 卡统一入库完成(2026-10-08 · 13_import_pending.py --run)

  • 结果:成功 27 批 [43–70 除 53] / 失败 0。库内 631 → 1028,增量 397 == 暂存卡数,完全对上(判据用库内增量,不用 SUMMARY 的 success 数)。问法完整性:无 std 0/无 sim 0/无 neg 0。
  • 导入后 tag:反差 224 / 反常识 132 / 品质对比 109 / 价值观冲击 108 / 氛围沉浸 105 / 自嘲反差 85 / 细节专业 79 / 视觉冲击 76 / 感官沉浸 70 / 预见式服务 22 / 食材极致 9 / 难度极限 2。
  • ⚠️ 注意:库内 tag 数与本地 5_tag_coverage.py 统计不同(例:感官沉浸本地 74 vs 库内 70)—— 库内按 spec 的单值 tag(每卡主标签),本地按卡片事件标签字段(含辅标签计数)。两者口径不同,不要互相校验。

🔴 新踩坑(重要,会静默导致整批导入失败):spec.json 的 card 字段必须是「标题行」,不是卡片全文

  • 现象:首次 --run 成功 13 批 [43–47,50–58],失败 14 批 [48,49,59–70],报错 ✗ spec 与 md 对不上:[整张卡片全文]。
  • 根因:2_import_batch.py L40–48 的比对是 cards={} → t = b.splitlines()[0].replace("### ","").strip() → cards[t] = b.strip() → miss = [s["card"] for s in spec if s["card"] not in cards] ⚠️ cards 是 dict,in 判的是 key(=标题行),不是 value → 所以 spec 的 card 必须等于「去 ### 前缀后的标题行」,放全文必然 miss。
  • 我犯的错:_tmp_bNN_spec.py 用 CARD_RE 提整张卡塞进 card(且带 ### )→ 批 66–70(及此前 59–65、48、49)全部中招。对比成功的批 58:card = "20829-1|凌晨三点半看完球…"(仅标题)。
  • 修复:对 14 批执行 sp['card'] = blocks[i].splitlines()[0].replace('### ','').strip() 后重跑 → 27/27 成功。
  • ⭐ 更新铁律表述(旧的「card 禁带 ### 前缀」不够精确,易误解成"全文去前缀"):card = 标题行本身(去 ### 、strip),不含正文字段。
  • ⭐ 排查口诀:报「spec 与 md 对不上」时,不要读那一大坨报错文本(它把整卡打印出来,看不出差异),直接复现 L40–48 的两行逻辑比对 spec[0]['card'] 与 cards 的 key。
  • ⚠️ apply-pending 不会修 card:10_polish_batch.py L412–413 只回写 sp["std"]/["sim"]/["neg"],不动 card(标题润色前后不变,所以不修是对的)。card 的正确性全在生成 spec 那一刻,写错只能事后修。

导入后的遗留项

  • 批 48 的 sim=2(少 1 条)已随导入进库 → 按红线「改问法只能删+重导」,若要补须删该批 15 条重导。未处理,待用户定夺。
  • 批 43 的 neg=3(且是关键词短语)—— neg 不入索引(RUNBOOK §步骤6),对召回无影响,可不处理。
  • 回炉链路仍未完成:删旧条目(按 polish_plan.json 冻结的 526 id)+ 重导 + 检索终验。

⭐ 方向性判断:「现在要不要测精准率/召回率」(2026-10-08 用户问,结论待执行)

  • 结论:分拆看,不要笼统测。
    指标 该不该测 理由
    召回率(反查法 R@1/R@3) ⚠️ 只做回归验证,不做优化基线 有 ground truth(卡自身)、能隔离「问法」变量、成本 2 分钟。但历史基线已是 R@1 34/35,问法改写实测差异在噪声内 → 测了也难再提升,价值只剩「验证 397 卡导入后检索没坏」。
    精准率 ❌ 暂不必测 需人工标注「top-N 每条是否相关」,1028 条库成本极高;且分数间隔极窄(top1~top10 仅差 0.047)→ 机器判不了优劣,只能人工。已知不是瓶颈。
    覆盖率(真实需求节点 → 能否找到素材) ✅ 该测,且是当前唯一真瓶颈 3/5 真实节点零命中 = 素材覆盖不足。刚导入 397 卡(631→1028),覆盖率已变,正是重新测的时机。
  • ⭐⭐ 根本前提(决定一切检索指标的传导性):素材召回端到端触发率 ≈ 0,且用户已裁定这是「流程不主动召回、知识库按需被取」的设计意图,hybrid_search 0 次 ≠ bug。 → 推论:检索指标再好,也传导不到最终产出(优化一个几乎不被调用的函数)。所以「测检索指标」的价值只在回归验证,不在效果优化。
  • ⭐ 刚导入 397 卡后必做的不是指标测试,而是「入库正确性验证」:确认新卡真能被检索到(不是精准率/召回率,是「导入成功了吗」)。

⭐ 入库验证 + R@N 基线(2026-10-08 · 反查法,81 条样本)

  • 结论:R@1 = 81/81 = 100%,27 批全部命中 → 397 卡导入成功,检索链路正常。明细 recall_cache/verify_import_1028.json,脚本 _tmp_verify_import.py(可复用,支持 --dry)。
  • 抽样:27 个暂存批 × 每批首/中/尾 = 81 条,覆盖 9 个标签。查询源 = 卡片正文「极致内容」(不在索引里,索引只有 std/sim;neg 不入索引)→ 与 std 不同源,比 18b 的「从 standard_question 提取事件」更远一步。
  • ⚠️ 这个 100% 不能当「召回质量好」的证据:std 本就是由卡片正文改写而来,正文查自己属同源查询,存在天花板效应。它只证明「入库成功 + 检索没坏」,不证明「问法有效」。 与历史基线(18b 反查法 R@1 34/35 ≈ 97%)量级一致,属正常。真要判召回质量仍需异源测试(真实编导需求节点)。

🔴 新踩坑(极具迷惑性,差点得出「召回率 0%」的错误结论)

  • 检索接口路径与参数名:正确是 POST /knowledge-bases/{KB}/faq/search,body 用 query_text(不是 query),返回 r.get("data") or r.get("results")(直接是列表,不是嵌套 data.data)。
  • 我写成了 /faq/search + query → HTTP 404。
  • ⚠️ 坑的杀伤力:404 被 try/except 静默吞掉 → 每条都记成「未命中」→ 输出 R@1 = 0/81 = 0%,看起来像「导入失败/检索全崩」。
  • ⭐ 识别口诀:跑完先看耗时。81 条检索正常约 14s;若「0 命中且耗时 1s」→ 一定是异常被吞了,不是真实结果。批量检索脚本必须让异常显式暴露(打印 err),不要静默记为未命中。

新增踩坑

  • ⚠️ _tmp_bNN_spec.py 里卡片名提取必须 .replace("###",""):head.split("|")[0].strip() 会带 ### 前缀 → assert name in Q 报 AssertionError: ### 吃包子吃出丧母感。批 70 踩到一次,加 replace 后通过。
  • ✅ prompt 组装字节数核对:模板 7,683 + 载荷 34,647 = 42,330(wc -c 可验),比批 68 记录的 15,772 大是因当时统计口径不同,以 wc -c 为准。