ソースを参照

docs(v3): 文档里'预算'改为'允许延伸游走次数'(首次出现标注原词)

用户反馈'预算/budget'起名费解(跟钱无关却满是钱味)。三份 V3 文档(09/09.5/10)
中文'预算'全替换为'允许延伸游走次数/延伸次数',每份首次出现标注'(原来的预算)'建立映射;
'游走预算→游走延伸次数''预算耗尽→延伸次数用完''低预算→减半延伸次数''预算闸→次数闸'等。
代码标识符(budget_tier/edge_budgets/budget_downgrade/budget_exhausted/low_budget/
max_total_actions)是真实代码/JSON/DB 名,照旧引用未动(用户'代码不碰'口径);
一处指金钱的'/bin/zsh.022/条预算'改为'成本'。老 V1/V2 文档(00-07)暂未动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sam Lee 1 日 前
コミット
737b309212

+ 16 - 16
tech_documents/工程落地/09.5_V3阶段开发完成后的修补.md

@@ -1,6 +1,6 @@
 # 09.5 V3 阶段开发完成后的修补(2026-06-12)
 
-> 一句话:V3 第一次真跑出来数据(见《10 报告》)之后,围绕"代码治理 + 真跑暴露的问题"做了一轮密集迭代——把 §15 风险表从 9 条清到 0、把 V2 时代的死代码/僵尸配置/脏数据连根清掉、把游走预算和热度判定按真实数据重设计。本文按主题把这段时间的全部改动与拍板串成一份连贯的迭代史。
+> 一句话:V3 第一次真跑出来数据(见《10 报告》)之后,围绕"代码治理 + 真跑暴露的问题"做了一轮密集迭代——把 §15 风险表从 9 条清到 0、把 V2 时代的死代码/僵尸配置/脏数据连根清掉、把游走的允许延伸游走次数(原来的预算和热度判定按真实数据重设计。本文按主题把这段时间的全部改动与拍板串成一份连贯的迭代史。
 >
 > **时间边界**:起点是真实测试节点 `db98612`(《10_V3跑通与代码治理报告》,2026-06-12 真跑:同一个"中医养生"需求单,V2 0 进池/83 分钟 → V3 6 进池/3 分 19 秒)。终点是 HEAD。其间属于本文范围的后端/配置/文档改动共 **16 个 commit**(另有若干 `web-v3` 前端 commit 交错其间,归 web 会话管,本文不涉及)。
 
@@ -13,7 +13,7 @@ V3 立项要证明的命题是"换 Gemini 直读视频,合格的中老年内容
 真跑第一次给了我们三样东西:**一个能进池的结果**(6 条)、**一批真实判定样本**(19 次,16 成功)、以及**一份诚实的体检单**(《10 报告》第二/三/四节)。有了真实数据,两件事立刻变得可以做、且必须做:
 
 1. **代码治理**——V3 换掉了判定引擎(decode+分类树 → Gemini)、游走架构(老策略文件 → 边表 walk_graph/walk_policy)、规则包(5 包瘦身到 2 包),留下了大量死代码、僵尸配置和 V2 时代的脏数据。真跑前不敢大删(怕动了正在调试的链路);真跑跑通、基线钉死之后,清理时机成熟。
-2. **真跑暴露的问题**——《10 报告》第二节列了 5 个问题,其中"热度锚点成了事实上的进池闸门""游走预算 tag 被用满""预算耗尽静默无痕"都有了真实证据,可以拍板修复。
+2. **真跑暴露的问题**——《10 报告》第二节列了 5 个问题,其中"热度锚点成了事实上的进池闸门""游走延伸次数 tag 被用满""延伸次数用完静默无痕"都有了真实证据,可以拍板修复。
 
 这一轮迭代的纪律和 V3 主线一致:**每批独立 commit、测试全绿、行为中性的批保证快照零重钉、改行为的批先贴 diff 给用户过目**,全程四岗 subagent(代码/配置/DB/验收)交叉核验。下面分主题展开。
 
@@ -33,8 +33,8 @@ V3 施工过程中,§15"风险与未验证假设"累计登记了 9 条风险(R1
 | **R4** | 视频号搜索接口偶发 25011 故障码 | 已解除(真返 12 条对题结果;仅作者边仍 blocked,属边表设计已覆盖的预期退化) | 2026-06-11;`9d420f1` 移出 |
 | **R5** | 并发判定的结果能否和串行一致(M5 最大未知) | 随 M5 落地销项(剔时戳后逐条比对,结果一致) | M5;`9d420f1` 移出 |
 | **R6** | 生产库里还躺着 V2 时代的历史脏数据(旧 decode 证据/老种子/旧理由码) | 21 表 1451 行**归档后删**,decode 7 列 + 画像 1 列 DROP | `6df5fae`+`1eb8916`(B4);`cded8b5` 销项 |
-| **R7** | 游走预算钉在 V1 保守硬限(tag 仅 1、作者 2),召回面窄 | 放宽 tag 1→3、作者 2→3(零代码,只改 JSON) | `6e12f8b`;`6e93e35` 销项 |
-| **R8** | 预算耗尽静默无痕,复盘分不清"质量差被拒"还是"预算没轮到" | 三个预算切断点各补一条"因预算跳过"记录(`budget_exhausted`) | `1a0020c`;`6e93e35` 销项 |
+| **R7** | 游走延伸次数钉在 V1 保守硬限(tag 仅 1、作者 2),召回面窄 | 放宽 tag 1→3、作者 2→3(零代码,只改 JSON) | `6e12f8b`;`6e93e35` 销项 |
+| **R8** | 延伸次数用完静默无痕,复盘分不清"质量差被拒"还是"延伸次数没轮到" | 三个次数切断点各补一条"因延伸次数用完跳过"记录(`budget_exhausted`) | `1a0020c`;`6e93e35` 销项 |
 | **R9** | real_id45 指纹快照不含 wa_id,跨 run 锁定弱一档 | 随 M5 销项(同 run 内并发一致性不受影响) | M5;`9d420f1` 移出 |
 
 > 销项节奏:`9d420f1`(真跑后首个文档 commit)先把已随 smoke/M5 解决的 R1/R2/R4/R5/R9 从风险表删掉,风险表收敛为待办的 R6/R8/R7/R3 四条;随后 B 批清理销 R6(`cded8b5`)、游走批销 R7/R8(`6e93e35`)、热度批销 R3(`1cf0fd8`),至此清空。**详细销项逐条见 09 附录 E.1/E.4/E.5。**
@@ -77,25 +77,25 @@ V3 换了判定/游走/规则之后,V2 时代留下三类"垃圾":**死代码**(
 
 ---
 
-## 三、游走预算两项(R7/R8)
+## 三、游走延伸次数两项(R7/R8)
 
-真跑给了游走预算第一批实证:翻页用了 2、作者顶格 2、**tag 顶格 1**(被用满)。两件事随之拍板。
+真跑给了游走延伸次数第一批实证:翻页用了 2、作者顶格 2、**tag 顶格 1**(被用满)。两件事随之拍板。
 
-### R8:预算耗尽不再静默(`1a0020c`)
+### R8:延伸次数用完不再静默(`1a0020c`)
 
-**问题**:翻页/标签/作者三个预算切断点,合格候选排不上队时,老代码直接 break,**连一条记录都不留**。复盘时分不清一条内容是"质量差被判拒"还是"质量没问题但预算没轮到它"。
+**问题**:翻页/标签/作者三个次数切断点,合格候选排不上队时,老代码直接 break,**连一条记录都不留**。复盘时分不清一条内容是"质量差被判拒"还是"质量没问题但延伸次数没轮到它"。
 
 **修法**:三个切断点各补一条 `walk_status=skipped / reason_code=budget_exhausted / budget_tier=blocked` 的留痕(标签每内容至多一条,不占去重集;已执行集合逐行不变)。
 
-**调研结论(最小化改动)**:DB 零 DDL(`reason_code` 无约束列)、validation 零改(`skipped` 在白名单)、web 零改(透传);仅 dashboard 补"预算耗尽"中文标签。
+**调研结论(最小化改动)**:DB 零 DDL(`reason_code` 无约束列)、validation 零改(`skipped` 在白名单)、web 零改(透传);仅 dashboard 补"延伸次数用完"中文标签。
 
-**真实效果(受控重钉)**:real_id45 指纹从 7→11 行,diff 全可解释——+2 条进池内容的标签排不上 budget=1 的队(`budget_exhausted`)、+2 条复看内容的 deny 留痕浮出(原先被预算 break 连 deny 记录都压住);零行消失。新增翻页/作者溢出守卫测试×2。测试 **324→326 passed**。
+**真实效果(受控重钉)**:real_id45 指纹从 7→11 行,diff 全可解释——+2 条进池内容的标签排不上 budget=1 的队(`budget_exhausted`)、+2 条复看内容的 deny 留痕浮出(原先被延伸次数 break 连 deny 记录都压住);零行消失。新增翻页/作者溢出守卫测试×2。测试 **324→326 passed**。
 
-### R7:放宽游走预算 tag 1→3、作者 2→3(`6e12f8b`)
+### R7:放宽游走延伸次数 tag 1→3、作者 2→3(`6e12f8b`)
 
-**用户拍板原话**:"R7 游走预算太保守,把预算放宽一点:标签 1 放到 3,作者 2 放到 3。这个你直接改配置。"
+**用户拍板原话**:"R7 游走延伸次数太保守,把延伸次数放宽一点:标签 1 放到 3,作者 2 放到 3。这个你直接改配置。"
 
-**改法**:**零代码改动**,只改 `walk_policy.json` 两个数字 + provenance(注明真跑 `v1_run_4e24c1b85637` 两项预算顶格、召回面收窄的实证)。翻页 3 / 每作者作品 3 不变。最终四项预算齐为 **3/3/3/3**。
+**改法**:**零代码改动**,只改 `walk_policy.json` 两个数字 + provenance(注明真跑 `v1_run_4e24c1b85637` 两项延伸次数顶格、召回面收窄的实证)。翻页 3 / 每作者作品 3 不变。最终四项延伸次数齐为 **3/3/3/3**。
 
 **真实效果(受控重钉)**:real_id45 指纹从 11→12 行——+2 条 success 标签搜索(2026养生/中医养生)、−1 条 `budget_exhausted` skip(该内容标签转正执行),其余 10 行原样。测试仍 326 passed。
 
@@ -169,8 +169,8 @@ R3 讨论中暴露:scorecard 里还躺着 5 个 `runtime_status=deprecated` 维
 | 3 | 六张零行表处置 | 全保留 | 交叉验证证实是活功能(进池即写) |
 | 4 | 老游走策略僵尸段 | JSON+Excel 都删 | 真实配置已是 walk_graph+walk_policy |
 | 5 | 生产库画像列 `content_audience_profile` | 归档后删列 | V3 行里只是 `fit_senior_50plus` 的冗余镜像,删了不丢独有数据 |
-| 6 | R7 游走预算放宽 | tag 1→3、作者 2→3,直接改配置 | 真跑顶格实证召回面窄 |
-| 7 | R8 预算耗尽留痕 | 按"补一条因预算跳过的记录" | 复盘要能区分质量差被拒 vs 预算没轮到 |
+| 6 | R7 游走延伸次数放宽 | tag 1→3、作者 2→3,直接改配置 | 真跑顶格实证召回面窄 |
+| 7 | R8 延伸次数用完留痕 | 按"补一条因延伸次数用完跳过的记录" | 复盘要能区分质量差被拒 vs 延伸次数没轮到 |
 | 8 | R3 贴题分定位 | Gemini 真读判定、当门用;偏题踢掉 | 接近满分没问题,偏题的极少数直接拒 |
 | 9 | R3 热度按平台分算 | 每平台按可拉数据单独算热度 | 各平台能拉的互动数据天差地别 |
 | 10 | R3 进池率 | 不追求目标进池率,客观即可 | "一百个里只有一条能进,就进一条" |
@@ -222,7 +222,7 @@ R3 讨论中暴露:scorecard 里还躺着 5 个 `runtime_status=deprecated` 维
 
 ### web 侧待清(归 web 会话管)
 
-- **ConfigPage 两张空卡**:`WalkStrategyTab` 仍渲染已删除的"预算策略/停止策略"两张卡(B2 删了对应 JSON 段后将显示为空)——请在 web 会话删这两张卡。
+- **ConfigPage 两张空卡**:`WalkStrategyTab` 仍渲染已删除的"延伸次数策略/停止策略"两张卡(B2 删了对应 JSON 段后将显示为空)——请在 web 会话删这两张卡。
 - **DEPRECATED 集合 stale**:S1/S2 删维度后,web 侧若有废弃维度的展示集合需同步。
 
 ### 本次不动的候选(后续拍板)

+ 26 - 26
tech_documents/工程落地/09_V3阶段开发计划.md

@@ -14,7 +14,7 @@ V2 把"单平台抖音 + decode 异步解构 + 分类树回扣 + 5 包分派"这
 |---|---|---|
 | **判定太死,真实数据全军覆没** | 生产库 `content_agent_rule_decisions` 17 条决策**无一进池**,拒因全是 `content_pattern_recall_required`×9 + `missing_content_portrait`×8 | 砍 decode+分类树门槛,改 Gemini 直读判定 + 顺手补画像 |
 | **慢且不可控** | decode 是提交+每 5s 轮询的异步等待:成功 run `v1_run_3a3bc9f0d72d` 83 分钟、decode 串行轮询主导;超时档 `CONTENTFIND_PATTERN_RECALL_MAX_WAIT_SECONDS=1200`,其余节点合计 < 70s | Gemini 直读 ~24s/条 + 串行管线改并发批处理 |
-| **单平台、游走三段硬编码** | 只接抖音;`walk_engine.py` 三段写死(`_execute_author_edges`/`_pagination_queries`/`_tag_queries`),每条边各写一份血缘/去重/预算,作者血缘 bug 即出于此 | 数据源扩到抖音+视频号双渠道;游走改边表驱动统一 frontier loop,平台无关 |
+| **单平台、游走三段硬编码** | 只接抖音;`walk_engine.py` 三段写死(`_execute_author_edges`/`_pagination_queries`/`_tag_queries`),每条边各写一份血缘/去重/允许延伸游走次数(原来的预算,作者血缘 bug 即出于此 | 数据源扩到抖音+视频号双渠道;游走改边表驱动统一 frontier loop,平台无关 |
 
 V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)+ 换游走架构(边表驱动单 loop),在抖音+视频号双渠道上真实跑通;判定字段、游走护栏、规则包都配置驱动,新平台接入只填 profile 不改代码。**
 
@@ -43,7 +43,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 | **M3** 规则包 5→2 + 判定配置重写 | 基于 Gemini 字段重写内容包,游走控制并入 walk_policy | M2 | 主线 |
 | **M4** 边表驱动统一 frontier loop | 删三段硬编码,平台无关游走 | M1,M3 | 主线 |
 | **M5** 并发批处理 | M2+M4 串行改并发 | M2,M4 | 提速 |
-| ~~**M6** 复盘闭环反哺~~ | **不做,并入 M7**(增量被 M4 edge_permissions 吸收,反哺预算与 M7 标定耦合;见 §11) | — | — |
+| ~~**M6** 复盘闭环反哺~~ | **不做,并入 M7**(增量被 M4 edge_permissions 吸收,反哺延伸次数与 M7 标定耦合;见 §11) | — | — |
 | **M7** 端到端真实跑测(双渠道)+ 反哺 | **⏸ 暂缓(2026-06-12)**:M5 后系统已可完整跑;先单条真跑看业务,M7 批量验收/标定/反哺暂不启动 | M0–M5 | 验收(暂缓) |
 
 切分依据:M0 地基先行;M1→M2→M3 沿"取数据→判内容→定规则"主线;M4 在 M1(profile)+M3(通行证)就绪后重构游走;M5 在功能正确后retrofit 并发;M7 端到端收口并一并落地反哺(原 M6,因其增量依赖 M7 的真实数据标定)。镜像 V1 的"按管线阶段线性串联 + 地基阻塞全部"。
@@ -322,14 +322,14 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 
 ## 9. M4 边表驱动统一 frontier loop(游走重构)
 
-目标:删掉 `walk_engine.py` 的三段硬编码(`_pagination_queries`/`_tag_queries`/`_execute_author_edges`),改成一个有界 frontier loop:异构队列(query/video/author 同队)出队 → 查 `walk_graph.json` 该节点出边 → 查 `platform_profiles/<platform>.json` 此边是否 supported(unsupported/blocked 静默跳过) → 查 `walk_policy.json` 预算与 `edge_permissions` 通行证 → 执行并把新节点入队。去重、预算、血缘做成 loop 内建,所有边共享,不再逐边手写。
+目标:删掉 `walk_engine.py` 的三段硬编码(`_pagination_queries`/`_tag_queries`/`_execute_author_edges`),改成一个有界 frontier loop:异构队列(query/video/author 同队)出队 → 查 `walk_graph.json` 该节点出边 → 查 `platform_profiles/<platform>.json` 此边是否 supported(unsupported/blocked 静默跳过) → 查 `walk_policy.json` 延伸次数与 `edge_permissions` 通行证 → 执行并把新节点入队。去重、延伸次数、血缘做成 loop 内建,所有边共享,不再逐边手写。
 
 当前真实状态:
 - 已实现:`walk_engine.py` 三段 `run_bounded_walk`(31-136)、`_execute_author_edges`(217-408)、`_pagination_queries`(411-448)、`_tag_queries`(451-524)、`_can_expand_from_decision`(704-712);`walk_strategy.py`(`run` 12-123) 生成 source_path 血缘。
-- 已实现:前置 work 齐备——`walk_graph.json`(v2,8 节点/10 边,edge_class/jump_key/endpoint_role/gate)、`walk_policy.json`(各边预算 + edge_permissions + 5 个 TBD_V3)、`platform_profiles/{douyin,shipinhao}.json`(双渠道边就绪度)。
+- 已实现:前置 work 齐备——`walk_graph.json`(v2,8 节点/10 边,edge_class/jump_key/endpoint_role/gate)、`walk_policy.json`(各边延伸次数 + edge_permissions + 5 个 TBD_V3)、`platform_profiles/{douyin,shipinhao}.json`(双渠道边就绪度)。
 - 已实现:现有 10 条 edge_id 与 `product_documents/抖音游走策略/douyin_walk_strategy.v1.json` 一致(walk_graph 零新造)。
-- 未实现:读 walk_graph/walk_policy/profile 的统一 loop;血缘/去重/预算的 loop 内建化。
-- 当前默认值:三段各自写死预算(翻页≤3 页、作者≤10、tag 每内容≤3/全 run≤10 跳,见 `walk_policy.json` 锚定的 v1 真值)。
+- 未实现:读 walk_graph/walk_policy/profile 的统一 loop;血缘/去重/延伸次数的 loop 内建化。
+- 当前默认值:三段各自写死延伸次数(翻页≤3 页、作者≤10、tag 每内容≤3/全 run≤10 跳,见 `walk_policy.json` 锚定的 v1 真值)。
 
 已拍板:
 - 游走改边表驱动单 loop,复用现有 10 edge_id / 8 节点 / 3 类血缘 source_path_type,零新造。
@@ -341,11 +341,11 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 开工前必须拍板(walk_policy.json 的 4 个 TBD_V3,第 5 个挪 M5):
 - `max_total_actions_per_run`(全局总闸,v1 无)。
 - `max_reseed_rounds`(回灌层数,v1 实际只 1 层)。
-- `low_budget` 量化(建议各边预算减半、至少 1)。
+- `low_budget` 量化(建议各边延伸次数减半、至少 1)。
 - `KEEP_CONTENT_FOR_REVIEW` 是否可回灌 tag(v1 口径不明,默认从严 deny)。
 
 可先按当前默认值推进:
-- 各边预算沿用 walk_policy 锚定的 v1 真值(翻页≤3、作者≤10、tag≤3/10)。
+- 各边延伸次数沿用 walk_policy 锚定的 v1 真值(翻页≤3、作者≤10、tag≤3/10)。
 
 本阶段不处理:
 - 并发(M5,先串行行为等价);复盘反哺(M6)。
@@ -359,7 +359,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 主要函数 / 对象:
 - `FrontierWalk`(统一 loop:出队/查边/查 profile/查 policy/入队)
 - `EdgeExecutor`(按 edge_id + profile 执行一条边)
-- 血缘/去重/预算 的 loop 内建组件
+- 血缘/去重/延伸次数 的 loop 内建组件
 
 开发顺序:
 1. 建 FrontierWalk 骨架(异构队列 + 全局护栏 + 去重集 + 血缘记录)。
@@ -371,7 +371,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 达到效果:
 - 加一条边/接一个平台 = 改配置(walk_graph/profile),不改 loop 代码。
 - 视频号在无作者边下正确退化;抖音走全图。
-- 血缘/去重/预算全边一致,作者血缘缺口类 bug 不再逐边复发。
+- 血缘/去重/延伸次数全边一致,作者血缘缺口类 bug 不再逐边复发。
 
 输出:
 - 统一 frontier loop + 边执行器;行为等价回归测试。
@@ -404,7 +404,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 - 仍受 crawler 15s 限流约束(抓取串行/限速,判定可并发——两者解耦)。
 
 开工前必须拍板:
-- `gemini_calls_per_run_cap`(单 run Gemini 调用上限,按需求规模与预算,$0.022/条)。
+- `gemini_calls_per_run_cap`(单 run Gemini 调用上限,按需求规模与成本,$0.022/条)。
 - 并发度(线程数/批大小)与 DB 连接池上限。
 
 可先按当前默认值推进:
@@ -453,9 +453,9 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 
 - **"无效 query 不再二次扩"已被 M4 实现**:M4 的 `edge_permissions` 把 tag 扩展 gate 到**仅 ADD 内容**(KEEP/REJECT 都 deny);而 ADD 内容的源 query 必然 pooled≥1、有效率 >0。"有效率=0 的 query 不扩 tag"在 M4 即成立,M6 这半截无增量。
 - **"有效优先"在 budget=1 下退化为单 tie-break**:`hashtag_to_query.max_total_actions=1`(M4 行为等价值,放宽留 M7/R7 标定),全 run 只产 1 条 tag query,所谓"优先级排序"仅在 ≥2 个不同有效率 query 同时有 ADD 内容时影响"选哪条 tag",无召回量级变化。
-- **反哺预算分配与 M7 预算标定耦合**:真正有价值的"高产 query 多扩 tag"需突破 budget=1,而这本质是 R7(预算起步值放宽)的事——要先有 M7 的真实跑测数据才能定阈值,在 M6 提前拍属拍脑袋。
+- **反哺延伸次数分配与 M7 延伸次数标定耦合**:真正有价值的"高产 query 多扩 tag"需突破 budget=1,而这本质是 R7(延伸次数起步值放宽)的事——要先有 M7 的真实跑测数据才能定阈值,在 M6 提前拍属拍脑袋。
 
-**因此**:反哺机制(有效率计算 + 回灌边优先级/预算 + 配置化可关闭 + 可解释留痕)与 M7 的真实跑测+预算标定合并做,届时有真实数据支撑。时序事实已采证:有效率须在 `walk_engine` 内从 `rule_decisions` 实时算(`search_clues` 是 record 阶段产物、回灌时拿不到),`recorder.py` 无需改;排序口径偏好已定 = **比率主 + 绝对数次 + qid 复合**(`(pooled_ratio, pooled_count, search_query_id)`,稳定全序)。详见 §12 M7。
+**因此**:反哺机制(有效率计算 + 回灌边优先级/延伸次数 + 配置化可关闭 + 可解释留痕)与 M7 的真实跑测+延伸次数标定合并做,届时有真实数据支撑。时序事实已采证:有效率须在 `walk_engine` 内从 `rule_decisions` 实时算(`search_clues` 是 record 阶段产物、回灌时拿不到),`recorder.py` 无需改;排序口径偏好已定 = **比率主 + 绝对数次 + qid 复合**(`(pooled_ratio, pooled_count, search_query_id)`,稳定全序)。详见 §12 M7。
 
 ---
 
@@ -463,7 +463,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 
 > **⏸ 暂缓(2026-06-12 拍板):M5 完成后系统已可完整跑起来,M7 作为正式里程碑(双渠道批量验收+起步值标定+反哺落地)暂不启动。** 当前转向:先真跑单条需求看业务本身(判定规则/游走策略是否合理),再决定标定与复盘学习。本节内容保留作 M7 重启时的依据。
 
-目标:在抖音 + 视频号双渠道各跑真实 run,端到端验证 V3 全部改造:边表 loop 平台无关、Gemini 判定真能让内容进池(对照 V1 的 17 条全拒)、并发提速、血缘 validation pass、视频号退化路径(无作者边)正确。这是补齐验收,不是首测——各里程碑已交付自己的单测与回放。**并入原 M6:用真实跑测数据标定各起步值(R7 预算/R3 热度锚点/M2 阈值)后,落地"有效率反哺回灌边"(原 M6)——有效 query 的 tag 优先/多扩,只影响回灌边优先级/预算、不改判定口径、配置化可关闭、可解释留痕。**
+目标:在抖音 + 视频号双渠道各跑真实 run,端到端验证 V3 全部改造:边表 loop 平台无关、Gemini 判定真能让内容进池(对照 V1 的 17 条全拒)、并发提速、血缘 validation pass、视频号退化路径(无作者边)正确。这是补齐验收,不是首测——各里程碑已交付自己的单测与回放。**并入原 M6:用真实跑测数据标定各起步值(R7 延伸次数/R3 热度锚点/M2 阈值)后,落地"有效率反哺回灌边"(原 M6)——有效 query 的 tag 优先/多扩,只影响回灌边优先级/延伸次数、不改判定口径、配置化可关闭、可解释留痕。**
 
 当前真实状态:
 - 已实现:V2 真实 run 基线(`v1_run_3a3bc9f0d72d` 成功、83min、17 决策全拒);真实 DB 可写(content_rw)、双写 CompositeRuntimeStore。
@@ -475,7 +475,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 - 双渠道各至少 1 条真实 run;以"内容真能进池 + validation pass + 墙钟 < 10min"为通过线。
 - 视频号 run 不期望作者边(blocked·deferred),只验证搜索→内容→tag 回灌闭环。
 - 真实跑测费用受控:Gemini $0.022/条 × 配额上限;crawler 15s 限流。
-- (并入原 M6)反哺只影响回灌边优先级/预算、不改判定口径;配置化(`walk_policy.reseed_feedback`,enabled 默认关、可回滚)、可解释(walk_action raw_payload 记有效率分,零 schema 改);有效率排序口径=比率主+绝对数次+qid 复合。
+- (并入原 M6)反哺只影响回灌边优先级/延伸次数、不改判定口径;配置化(`walk_policy.reseed_feedback`,enabled 默认关、可回滚)、可解释(walk_action raw_payload 记有效率分,零 schema 改);有效率排序口径=比率主+绝对数次+qid 复合。
 
 开工前必须拍板:
 - V3 验收通过的量化阈值(进池率下限、Gemini 判定成功率下限、单 run 成本上限)。
@@ -490,7 +490,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 涉及文件:
 - `tech_documents/工程落地/10_V3_E2E真实跑测计划.md`(新增,仿 07)
 - `scripts/`(跑测/校验脚本,复用 validate_content_agent_db.py)
-- (并入原 M6)`content_agent/business_modules/walk_engine.py`(新增 `_query_effectiveness` + `_expand_queries` tag 段按有效率排序/预算;recorder.py **不改**——有效率 walk_engine 内实时算)、`数据接口与来源/walk_policy.json`(新增 `reseed_feedback` 块)、`content_agent/integrations/walk_graph_json.py`(`_unwrap_policy` 解包新块)
+- (并入原 M6)`content_agent/business_modules/walk_engine.py`(新增 `_query_effectiveness` + `_expand_queries` tag 段按有效率排序/延伸次数;recorder.py **不改**——有效率 walk_engine 内实时算)、`数据接口与来源/walk_policy.json`(新增 `reseed_feedback` 块)、`content_agent/integrations/walk_graph_json.py`(`_unwrap_policy` 解包新块)
 
 主要函数 / 对象:
 - 端到端 run + harvest 收割 + validate_run
@@ -516,7 +516,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 - 抖音 run:有内容进池(非全拒)、作者/tag 边均走、血缘 pass。
 - 视频号 run:搜索抖动被重试吸收、内容进池、作者边静默跳过、tag 回灌闭环。
 - 并发 run 墙钟显著低于串行基线。
-- (并入原 M6)有效 query 的 tag 优先扩、无效不浪费预算;关闭反哺(enabled=false)退回默认顺序+budget=1(可回滚);反哺生效在 walk_action raw_payload 可见可解释。
+- (并入原 M6)有效 query 的 tag 优先扩、无效不浪费延伸次数;关闭反哺(enabled=false)退回默认顺序+budget=1(可回滚);反哺生效在 walk_action raw_payload 可见可解释。
 
 ---
 
@@ -528,7 +528,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 | 接入层(M1) | 双渠道归一化 + 限流/重试 | 视频号抖动吸收、字段同构 canonical |
 | Gemini 判定(M2) | 直读替换 decode/分类树 | ~24s/条、画像随判定产出、失败降级 |
 | 规则配置(M3) | 5→2、配置驱动 | 改判定只改 JSON、引擎不报错、旧门槛消失 |
-| 游走 loop(M4) | 边表平台无关、行为等价 | 视频号退化、血缘/去重/预算全边一致 |
+| 游走 loop(M4) | 边表平台无关、行为等价 | 视频号退化、血缘/去重/延伸次数全边一致 |
 | 并发(M5) | 结果一致、墙钟下降 | 并发==串行结果、< 10min |
 | 真实跑测(M7) | 双渠道端到端 + 反哺(并入原 M6) | 进池(对照 17 全拒)、validation pass、有效 query 优先扩·可解释·可关闭 |
 
@@ -569,15 +569,15 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 | **DB 连接池不做 / 限流器不加锁**(M5,注销原拍板项) | 原计划要拍"数据库连接池开多大、爬虫限流器要不要加并发保护"两个值 | 实施时核实:并发只发生在"看视频判定"这一段,**落库仍是单线程、爬虫调用不经过并发**——两项保护根本用不上。证明"不需要"比硬拍个值更干净,注销 | 决策(注销),无代码落点 |
 | **一致性比对剔除时间戳**(M5) | 验收"并发结果=串行结果"时,把每行自带的"写入时间"字段剔掉再逐条比对 | 两次运行的时间必然不同,比时间没意义;剔掉后其余字段必须一字不差——这才是真正要保的"结果一致" | `tests/test_concurrency_consistency.py`(测试纪律,递归剔时戳键) |
 | **配额事件改走 append_jsonl(逐行日志通道)**(M5) | 系统记"流水账"有两条通道:**正式生命周期通道**(只进数据库,纯文件模式下是空操作)和**逐行日志通道 append_jsonl**(文件+数据库都写)。"配额用完了"这个事件本想走正式通道,发现文件模式下会消失,改走逐行日志通道 | 绕道能用且双模式可见——但暴露了"两条通道行为不一致"的设计债(见下 C 类) | `run_service.py:153-171`(配额事件改走 append_jsonl) |
-| **砍包随 M4**(M4) | 老架构留了 4 个"将来可能用"的空规则包(作者扩展/标签扩展/路径停止/预算观察),**从未启用过**。M4 重写游走时把这 4 个空壳连根删掉 | 它们的职责已由 walk_policy 配置文件承接;留着=死配置。**删的是空壳,不是功能**。之所以"随 M4"而非 M3 删:它们与游走配置强耦合,M3 单删会崩 115 个测试(实测过) | 已执行(规则包现仅 1 包:`douyin_rule_packs.v1.json`) |
-| **删 plan_walk 节点(终端动作并入游走)**(M4) | 老流水线里"游走探索"和"给每条已判定内容记结论动作(进池/降预算/停止)+血缘"是**两道独立工序**;M4 把后者并进游走工序一次做完,删掉那道独立工序 | 两道工序处理的本是同一批数据,分开=多一次交接、将来加一种边要改两处。并入后加边只改一处 | `walk_engine.py` `_terminal_stage`(终端动作并入 loop) |
+| **砍包随 M4**(M4) | 老架构留了 4 个"将来可能用"的空规则包(作者扩展/标签扩展/路径停止/延伸次数观察),**从未启用过**。M4 重写游走时把这 4 个空壳连根删掉 | 它们的职责已由 walk_policy 配置文件承接;留着=死配置。**删的是空壳,不是功能**。之所以"随 M4"而非 M3 删:它们与游走配置强耦合,M3 单删会崩 115 个测试(实测过) | 已执行(规则包现仅 1 包:`douyin_rule_packs.v1.json`) |
+| **删 plan_walk 节点(终端动作并入游走)**(M4) | 老流水线里"游走探索"和"给每条已判定内容记结论动作(进池/降延伸次数/停止)+血缘"是**两道独立工序**;M4 把后者并进游走工序一次做完,删掉那道独立工序 | 两道工序处理的本是同一批数据,分开=多一次交接、将来加一种边要改两处。并入后加边只改一处 | `walk_engine.py` `_terminal_stage`(终端动作并入 loop) |
 | **max_total_actions_per_run=60(全局总闸)**(M4) | 一个需求单全程**最多做 60 个游走动作**(翻页+看作者+扩标签合计),到顶就收 | 防游走失控的总保险;60 沿用 V1 各边合计上限,真跑后可调 | `walk_policy.json:7`(max_total_actions_per_run) |
 | **max_reseed_rounds=1(回灌只一层)**(M4) | 用标签二次搜索搜回来的内容,**不再用它的标签去三次搜索**——藤只顺一节 | 防滚雪球式发散(搜出来的再搜、再搜…);V1 实际也只回灌一层,等价沿用 | `walk_policy.json:9`(max_reseed_rounds) |
-| **low_budget=减半至少1(低预算档)**(M4) | "待复看"内容触发的扩展(如看它的作者)用**半价预算**:各项额度减半、最少保 1 次 | 给拿不准的内容留一点探索空间,但不让它花全价 | `walk_policy.json:37`(halve_min_1);实现 `walk_graph_json.py:32` |
+| **low_budget=减半至少1(减半延伸次数档)**(M4) | "待复看"内容触发的扩展(如看它的作者)用**减半延伸次数**:各项额度减半、最少保 1 次 | 给拿不准的内容留一点探索空间,但不让它花全价 | `walk_policy.json:37`(halve_min_1);实现 `walk_graph_json.py:32` |
 | **KEEP→tag=deny**(M4) | 判定有三档:进池(确定要)/待复看(拿不准,给人工)/拒。这条=**"拿不准"的视频没资格当种子**——不允许用它的标签发起新搜索 | 只让确定的好内容扩散,防把搜索方向带偏(垃圾进垃圾出)。沿用 V1 口径,从严 | `walk_policy.json:28`(KEEP_CONTENT_FOR_REVIEW → video_to_hashtag=deny) |
-| **游走预算用 V1 实际硬限,不用策略文件理想值**(M4) | 配置文件里写过一套大方的预算(翻 30 页/看 10 作者/扩 10 标签),但**老代码从来没用过它**,实际一直是小预算(3/2/1)。M4 拍板:照老代码的实际行为配,大预算留真跑后标定 | M4 的军规是"重构不改行为"——先原样搬家保证不引入新问题,放宽预算是另一件事(见 B 类/R7) | `walk_policy.json:13-18`(edge_budgets;注:已 R7 放宽到 3/3/3/3,详见 B 表) |
+| **游走延伸次数用 V1 实际硬限,不用策略文件理想值**(M4) | 配置文件里写过一套大方的延伸次数(翻 30 页/看 10 作者/扩 10 标签),但**老代码从来没用过它**,实际一直是小额延伸次数(3/2/1)。M4 拍板:照老代码的实际行为配,大额延伸次数留真跑后标定 | M4 的军规是"重构不改行为"——先原样搬家保证不引入新问题,放宽延伸次数是另一件事(见 B 类/R7) | `walk_policy.json:13-18`(edge_budgets;注:已 R7 放宽到 3/3/3/3,详见 B 表) |
 | **wa_id 双后缀冻结**(M4) | 每个游走动作有个指纹编号(wa_id),有两套生成规则(结论动作用"决策号"、探索动作用"语义词")。重构时**两套都原样冻结** | 编号变了=血缘链全断(每条内容"从哪来的"对不上账)。这是重构的等价护栏 | `walk_strategy.py:84-92`(终端边 decision_id 后缀)/`walk_engine.py:1018-1026`(扩展边 target:suffix) |
-| **M6 注销,反哺并入 M7**(M6) | 原计划有个独立里程碑"复盘反哺"(让系统从复盘里学会优先扩高产搜索词)。采证发现其一半内容 M4 已顺手做掉、另一半要等真实数据才能定阈值——单独做=空转,正式注销并入 M7 | "无效词不再扩"已被 M4 的通行证规则实现;"有效词优先"在标签预算只有 1 个名额时几乎无感。不做无意义功能 | 决策,无代码落点(见 §11/§12) |
+| **M6 注销,反哺并入 M7**(M6) | 原计划有个独立里程碑"复盘反哺"(让系统从复盘里学会优先扩高产搜索词)。采证发现其一半内容 M4 已顺手做掉、另一半要等真实数据才能定阈值——单独做=空转,正式注销并入 M7 | "无效词不再扩"已被 M4 的通行证规则实现;"有效词优先"在标签延伸次数只有 1 个名额时几乎无感。不做无意义功能 | 决策,无代码落点(见 §11/§12) |
 | **反哺排序口径偏好(比率主+绝对数次+词ID)**(M6→M7) | 将来名额紧张时挑"哪条搜索词的标签优先扩":先比进池率,持平比进池绝对条数,再持平用词 ID 定序保证每次结果一样 | 既衡量效率又可复现;已定偏好、待真做时最终确认 | 决策(待 M7),无代码落点 |
 | **recorder(复盘报表模块)不改**(M6→M7) | 复盘报表(每条搜索词的成绩单)在游走**结束后**才生成;游走**中途**要用"哪条词高产"只能现场从判定结果实时算,等不到报表 | 时序决定的:报表模块与反哺无关,按"不改不需要改的"原则不动 | `recorder.py`(未改) |
 | **视频号搜索重试 3 次/间隔翻倍**(M1) | 视频号搜索接口时好时坏(偶发 25011 故障码)。只对**暂时性故障**重试 3 次、间隔 1→2→4 秒翻倍;试满仍败就标"待查"绕行,**不卡整条流水线** | 实测该接口"首次失败、重试就好"是常态;参数错/权限错这种重试也没用的不浪费重试 | `platform_profiles/shipinhao.json:9`(runtime.retry.search) |
@@ -589,7 +589,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 | 临时值 | 现值 | 业务含义与风险(大白话) | 代码/数据位置(证据) |
 |---|---|---|---|
 | 平台热度锚点 | 抖音 赞+评+转+藏 四字段复合 / 视频号 单点赞;各字段一套及格线-满分线 | 把"互动数"换算成 0~1 热度分的**及格线/满分线**(如抖音 1 万赞=0 分起步、100 万赞=满分)。这些锚点是**起步值、待真跑标定**:真实互动分布可能完全不在这区间 → 热度分失真 → 打分跟着失真。(原"写死在 platform_heat.py"已过时:现在锚点搬进各平台 profile,代码里只剩兜底默认) | `platform_profiles/douyin.json:12-19`(抖音四字段复合)/`shipinhao.json:12-16`(视频号单点赞);`platform_heat.py:20` 仅剩兜底默认锚点 |
-| 游走预算 | 翻页 3 / **作者 3 / 作品 3 / tag 3**(2026-06-12 R7 放宽:作者 2→3、tag 1→3) | 一个需求单最多翻 3 页、看 3 个作者各 3 条作品、用 3 个标签二次搜索。原 V1 硬限占位值真跑顶格实证后放宽;理想值(30/10/10)仍留后续标定 | `walk_policy.json:13-18`(edge_budgets,现 3/3/3/3;原 3/2/3/1) |
+| 游走延伸次数 | 翻页 3 / **作者 3 / 作品 3 / tag 3**(2026-06-12 R7 放宽:作者 2→3、tag 1→3) | 一个需求单最多翻 3 页、看 3 个作者各 3 条作品、用 3 个标签二次搜索。原 V1 硬限占位值真跑顶格实证后放宽;理想值(30/10/10)仍留后续标定 | `walk_policy.json:13-18`(edge_budgets,现 3/3/3/3;原 3/2/3/1) |
 | 视频压缩档 | 360p / 1fps / crf33 | 投喂 Gemini 前把视频压到 ~4MB 的清晰度档。(原"只在 mock 跑过"已过时:真跑 16 条压缩全成功转正,见 10 报告) | `video_fetch.py:29`(_FFMPEG_ARGS) |
 | 打分权重/阈值 | 相关性 60 : 热度 40;≥70 进池 / 60-69 复看 / <60 拒;置信 <0.6 拒 | "内容贴不贴题"占 6 成、"火不火"占 4 成,总分 70 及格进池。全是起步值,没有真实判定数据校准过 | `douyin_rule_packs.v1.json:327-347`(scorecard 60:40)/`:436-463`(thresholds 70/60)/`:296`(fit_confidence 0.6) |
 | Gemini 配额与并发度 | cap=200 / max_workers=4 | A 表里拍板有依据,但 provenance 自己标了"M7 真负标定 / 实测调"——200 是按"典型 20-40 条留余量"估的、4 是保守起步,**都没经真实流量验证**(真跑可能发现 200 太松烧钱、或 4 太慢/触发限速) | `walk_policy.json:10-11`(cap / max_workers) |
@@ -599,7 +599,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 
 | 权宜 | 业务上发生了什么(大白话) | 坑 | 代码/数据位置(证据) |
 |---|---|---|---|
-| ~~预算耗尽**静默无痕**(=R8)~~ **已修(2026-06-12,commit 1a0020c)** | 翻页/标签/作者三个预算切断点,排不上队的合格候选现在各留一条"预算耗尽"跳过记录 | 坑已填:复盘能区分"质量差被拒"与"预算没轮到";指纹基线受控重钉,已执行集合逐行不变 | `walk_engine.py:282/374/555`(三处发 budget_exhausted skip) |
+| ~~延伸次数用完**静默无痕**(=R8)~~ **已修(2026-06-12,commit 1a0020c)** | 翻页/标签/作者三个次数切断点,排不上队的合格候选现在各留一条"延伸次数用完"跳过记录 | 坑已填:复盘能区分"质量差被拒"与"延伸次数没轮到";指纹基线受控重钉,已执行集合逐行不变 | `walk_engine.py:282/374/555`(三处发 budget_exhausted skip) |
 | 配额事件绕道(见 A 表末行) | 正式事件通道在纯文件模式下是**空实现**(写了等于没写) | 通道设计不一致,维护者要记住这个例外;仍是债 | `run_service.py:171`(改走 append_jsonl)+ `runtime_files.py`(_append_lifecycle_event 文件模式 no-op) |
 | `pattern_recall_evidence` 表"名废实用" | 文档当初说这张表"废弃"(老 decode 时代的判定证据),**实际 V3 把它复用了**——现在存的是 Gemini 判定证据 | §14 措辞与代码不符(本次已纠正);R6 已清(旧 decode 列已 DROP),表在 V3 复用中 | `database_runtime.py`(该表写入,pattern_recall_evidence.jsonl) |
 | mock 判定员默认全给高分 | 本地/测试环境用的"假 Gemini"**永远返回"适合 50+、高分"**,所以日常测试里内容基本都顺利进池 | 低分/拒绝/低置信这些分支**只有真跑才第一次走到**——测试覆盖天然偏乐观,拒判路径的问题会憋到真实环境才暴露 | `tests/gemini_helpers.py`(FakeGeminiVideoClient,默认 fake_gemini_pool 高分);**A/B/C 里唯一无技术孪生的纯观察** |
@@ -650,7 +650,7 @@ V3 的目标(一句话):**换判定引擎(Gemini)+ 换调度模型(并发)
 ## 18. 附录
 
 ### 附录 A:前置 work 引用(数据接口与来源/)
-- `walk_graph.json`(v2,8 节点/10 边)、`walk_policy.json`(各边预算 + edge_permissions + 5 TBD_V3)、`platform_profiles/{douyin,shipinhao}.json`(双渠道边就绪度)、`跨平台字段映射.json`、`接口台账/{抖音,视频号}.md`、`crawler_endpoints.registry.json`、`captures/`(32 份真实抓包)。
+- `walk_graph.json`(v2,8 节点/10 边)、`walk_policy.json`(各边延伸次数 + edge_permissions + 5 TBD_V3)、`platform_profiles/{douyin,shipinhao}.json`(双渠道边就绪度)、`跨平台字段映射.json`、`接口台账/{抖音,视频号}.md`、`crawler_endpoints.registry.json`、`captures/`(32 份真实抓包)。
 
 ### 附录 B:关键代码索引(改动锚点)
 - 判定链:`pattern_recall/{decode.py,category_match.py,recall_decision.py}`、`interfaces.py:63-75`、`run_service.py:605-614`、`graph.py:37-46/122-138`。

+ 2 - 2
tech_documents/工程落地/10_V3跑通与代码治理报告.md

@@ -19,7 +19,7 @@
 **业务读法:**
 - V3 立项要证明的命题——"换 Gemini 直读视频,合格的中老年内容就能进池"——**验证成立**:同一个需求,V2 一条都进不来,V3 进了 6 条,且判定理由可读(适老性、贴题度都有分数和一句话理由)。
 - 全链路(取需求 → 真爬虫搜索 → 下载视频 → ffmpeg 压缩 → Gemini 判定 → 规则打分 → 游走扩展 → 落库 → 血缘校验)**每一环都是真的**,没有 mock。
-- 游走五类动作全走通:翻页 2 次、标签扩词 1 次、看作者作品 2 次、进池沉淀 6 次、降预算 13 次——预算闸、判定通行证、去重、血缘全部按规则执行。
+- 游走五类动作全走通:翻页 2 次、标签扩词 1 次、看作者作品 2 次、进池沉淀 6 次、降延伸次数 13 次——允许延伸游走次数(原来的预算)闸、判定通行证、去重、血缘全部按规则执行。
 - 判定失败的优雅降级也真实验证了:失败内容自动转"待复看"、run 不崩、留痕可查(两轮共 14 条失败全部如此)。
 
 ## 二、真跑暴露的问题(按严重度)
@@ -56,7 +56,7 @@
 | 视频压缩档(360p/1fps) | **16 条真实视频批量压缩全部成功**,Gemini 都能读 | ✅ 起步值靠谱,可转正 |
 | 并发度 4 | 19 条判定并发跑完,无限速报错 | ✅ 起步值可用(提速空间留观察) |
 | 配额 cap=200 | 实际用 19 次,余量充足 | ✅ 合理 |
-| 游走预算(翻页3/作者2/tag1) | 翻页用 2、作者顶格 2、**tag 顶格 1** | ⚠️ tag 预算被用满——召回面收窄的判断成立,放宽要配合成本一起定 |
+| 游走延伸次数(翻页3/作者2/tag1) | 翻页用 2、作者顶格 2、**tag 顶格 1** | ⚠️ tag 延伸次数被用满——召回面收窄的判断成立,放宽要配合成本一起定 |
 | 打分权重/阈值(60:40、70/60) | relevance 无区分度(全 0.8+),进池实际由热度分决定 | ❌ **起步值与真实数据明显不匹配,是标定第一优先** |
 | 热度锚点(1 万~100 万赞) | 成了事实上的进池闸门 | ❌ 同上,与打分阈值一起标定 |