# TASK · 生时校正采集流程重设计:用户先说、系统再从已说的事往下追、永远给结果(2026-09-10) - 基线:`origin/staging` @ `3cd7a95d`(代码 `52db714b`,已部署;含 BUG-635~645) - 分支:`codex/rectification-collection-redesign-20260910`,基于 `origin/staging` - **本单取代** `TASK-rectification-exhausted-gate-exit-20260910.md`(BUG-646/647)的决策 1、2、3;那份不再单独领取,其 BUG 编号并入本单。BUG-641/642 里「采集题带年龄段年份线索」的口径被本单推翻。 - 执行方:coding agent;验收:Claude - BUG 编号起点:**BUG-648**(646/647 已占用,含在本单) - Skill:本单改方法覆盖顺序与采集口径,**升 10.0.23**(部署后先验旧版本历史可打开,BUG-621)。 - 不改引擎计分、不改确认门(唯一分钟仍关)。 ## 0. 产品口径(产品负责人 2026-09-10,原话要点) 1. Agent 一上来就按领域一个个追问,年份还是拿生日推出来的;应该**先等用户说出有价值的经历**,再从这些经历往下问。 2. 追问时**不得说「任何领域」「领域不限」**——用户不知道有哪些领域;要给具体例子。 3. 生时校正**必须走到结果**:要么直接成功,要么细节不全就问用户补全、补完拿结果。不允许流程走完告诉用户「做不了」。 4. (第二轮补充)**材料不够门槛时继续收集,不出结果**;不要固定题数,**按收集到的信息达到阈值来收口**。 ## 1. 现状为什么违反这三条 | 现状 | 代码位置 | 违反 | | --- | --- | --- | | 开场问完第一件,立刻按 `DATED_COLLECT_ORDER`(家人→学业→财务→迁居→健康→事业→感情)逐领域轮转 | `method-followup.ts` `nextDatedCollectFollowup`(L1405 在覆盖链之前) | 口径 1:没等用户说完就开始盘问 | | 轮转题干带「2020 年前后 / 2023 年前后」——这些年份来自 `_age_band_year`:出生年 + 该领域典型年龄段中点 | `event_probes.py` `DOMAIN_CATALOG.age_lo/age_hi`、BUG-642 的 `COLLECT_QUESTION_YEAR_CUE` | 口径 1:「干凭生日来推」。这是我今天上午写进 BUG-642 的口径,错了,本单撤回 | | 六个领域全「没有」后,出口只写「还差 1 件带月份的经历,领域不限」,不落焦点、无按钮 | `persistExhaustionCollect`、BUG-558/627 决策 | 口径 2、3:说了「领域不限」,且流程到此为止 | | 三件带年月经历里一件被留作 holdout,训练门要 3 件,于是要 4 件才出第一道选择题;没有一句文案说明 | `split-holdout.ts` `MIN_EVENTS_TO_RESERVE_HOLDOUT=2`、`MIN_ACCEPTANCE_EVENTS=3` | 口径 3:用户按要求给了 3 件,得到的是「还差 1 件」 | | 门关时区间不可采用(`selection_allowed = acceptance_allowed`) | `decision_policy.py` L681 | 口径 3:门关 = 没有结果 | ## 2. 目标流程(状态机,全部用阈值收口,没有固定题数) ``` S0 开场 ─▶ S1 收集(用户先说 → 系统按「信息价值」追问,直到 训练门开)─▶ S2 区分选择题(直到 收敛门开 或 增益见底)─▶ S3 交付 ▲ │ └────────── 用户任何时候再补一件带年月的事,重算并回到 S2/S3 ◀────────────────────────────┘ ``` 两道门都是**信息阈值**,不是题数: | 门 | 标准(现有常量,本单不改数值) | 达标前做什么 | 达标后做什么 | | --- | --- | --- | --- | | 训练门(能不能开始筛) | 带月经历 ≥ `MIN_ACCEPTANCE_EVENTS=3` 且种类 ≥ `MIN_ACCEPTANCE_DOMAINS=2`(`trainingScoreableGate`) | 继续收集(S1) | 进 S2 | | 收敛门(能不能收口) | `convergence-evaluator`:领先 ≥ `CONVERGENCE_LEAD=0.2`、头部占比 ≥ `CONVERGENCE_TOP_SHARE=0.7`、稳定 `STABLE_WINNER_ROUNDS=2`;或剩余探针最高信息增益 < `HIGH_INFORMATION_GAIN=0.08`(连续 `low_information` 轮,即平台期) | 继续出选择题(S2) | 进 S3 交付 | ### S0 开场(文案基本不变) 一句说明窗口与做法,一句「最后给区间和代表分钟」,一句邀请:「先说你最容易想起的一两件,比如上大学、第一份工作、搬到别的城市、谈恋爱或结婚、家里添丁或长辈住院、生病受伤,年月大概就行。」不写年份。 ### S1 收集:用户先说,系统按价值追问,问到训练门开为止 **收集问题池**由服务端按「信息价值」排序,每轮只问价值最高的一条;价值 = 这条问题能补上训练门缺口的概率 × 缺口权重。没有固定题数,停止条件只有两个:训练门开,或池空。 1. **自由邀请**(价值最高,只要还在产出就一直排第一):用户每说一批,batch 写入、复述,然后问一句「还有吗」,例子只列**还没提过的种类**、最多 4 个、用具体事物:「还有吗?比如第一份工作、搬到别的城市、谈恋爱或分手、家里添丁或长辈住院。」邀请的价值随产出衰减:上一轮邀请用户给了新事 → 价值不变;上一轮邀请用户答「没有了 / 就这些 / 记不清」→ 价值降到 0,从池里移除。 2. **锚定追问**(从用户已说的事派生,年份只来自用户):每个已记事件按 `kind` 派生 1–2 条,题干必须带那件事的年份或名字,例如「2020 年毕业后第一份工作大概哪年开始?」「2016 年上大学是搬到别的城市住吗?」「后来是分开了还是结婚了,大概哪年?」「2024 年 10 月那次之后,工作或住处有没有变?」派生规则与模板放服务端(`anchoredFollowups(evidence)`),Agent 只做口语润色,不得改年份、不得加生日推出来的年份。价值 = 派生概率(预设表,例如「毕业→第一份工作」0.8,「上大学→搬城市」0.6)× 缺口权重(补新种类 1.0,补同种类 0.5)。答「没有 / 记不清」→ 该条移除,不重问。 3. **通用补问**(价值最低,只在前两类都空了才轮到):按种类问、**不带年份**、每种只问一次、例子具体:「工作上还记得哪年入职或换过工作吗?」用户已拒答的种类不再出现。 **池空且训练门仍关**(用户确实只有一两件):不出结果卡,也不说「做不了」。写一句**精确缺口**:「现在记下的是 2016 年上大学和 2020 年毕业两件,都是上学的事。再来一件不是上学的、记得大概年月的事就能开始筛,比如第一份工作哪年开始、哪年搬到别的城市、家里哪年添丁。」缺口句里的例子必须来自未覆盖的种类;Case 保持开放,输入框占位「再说一件带年月的事」,用户补一件即自动继续。这是唯一允许的「停在收集」状态,文案禁词见 §2 末。 ### S2 区分选择题:直到收敛门开或增益见底 - 现有逻辑(信息增益排序、±2/±1 计分、淘汰、平台期)不动,只把**停止条件**明确成:`converged` 为真,或剩余可问探针最高增益 < `HIGH_INFORMATION_GAIN` 且连续 `low_information` 轮 ≥ `STABLE_WINNER_ROUNDS`,或探针池空。`DEFAULT_MAX_DISCRIMINATION_ROUNDS=8` 保留为兜底上限(防模型循环),不是设计目标。 - **holdout 只在不影响筛选时保留**:带月事件 ≥4 才留 1 件作 holdout;恰好 3 件时全部参与训练,回执 `holdout.status="not_reserved_min_events"`,报告 Real Case Calibration 行写 `not reserved`。确认门不变。 - S2 中途若用户补了新事 → 重算,回到 S2 顶部。 ### S3 交付结果 - 触发:收敛门开,或增益见底 / 探针池空(训练门已开是前提)。 - 内容:现有区间卡(`range_delivery` 三列)+ 一句定性 + **一句具体的「再补什么能收窄」**:从收集池里价值最高的未答条目取,写成「如果还记得毕业后第一份工作是哪年开始的,范围还能再收一截」。绝不写「任何领域」「领域不限」「做不了」「材料不够」。 - 交付后 Case 不关:用户随时再补一件带年月的事,重算并更新卡片。 - **不做「暂定采用」**(第二轮口径撤回):训练门没开就不出卡,采用门维持原样。 ### 文案硬规则 - 用户可见文案禁止:领域、任何领域、领域不限、方法覆盖、分盘、探针、训练门、holdout、做不了、材料不够、还差 N 件。 - 每个「还有吗」「再补什么」「精确缺口句」都必须带 ≥2 个具体例子,例子只从未覆盖的种类里选。 - 静态词表合同(`agent-voice-copy-contract`)加以上禁词。 ## 3. 决策记录 1. 撤回 BUG-642「采集题带年龄段年份线索」:`COLLECT_QUESTION_YEAR_CUE` 删除;`evidence_collection_probes` 的 age_band 年份只用于内部排序,不进任何题干。 2. 撤回 BUG-646 单决策 2(终态 = 门槛句 + 先这样按钮)与决策 3(穷尽后释放 holdout):终态改为 S3 交付卡;holdout 改为「≥4 件才留」的静态规则。 3. 撤回本单第一版的「≤2 轮邀请 / ≤4 问」固定题数与「暂定采用」:收口只看训练门与收敛门两个阈值;`DEFAULT_MAX_DISCRIMINATION_ROUNDS` 只作防循环兜底。BUG-558 的防复发(不得无限重复同一句)由「池内每条只问一次、答否即移除」保证,不再靠题数。 4. 训练门没开不出结果卡,也不出「做不了」:只出精确缺口句并保持开放。 5. Skill 10.0.23:把「先走完方法覆盖(感情 → 事业 → 家人 → 职业 → 占问),再对已覆盖领域做精度追问」改为本单 S1→S2→S3。 ## 4. 硬红线 - 不得用出生年推年份写进题干(`grep -n "年前后" user-copy.ts` 只允许出现在带用户年份的锚定模板里)。 - 收集池「每条只问一次、答否即移除」必须是代码不变量并有测试;停止条件只能引用 §2 表里的门与常量,不得新增题数常量。 - 训练门关时不得写 `range_delivery`、不得出卡、不得出现禁词。 - 既有测试总数不降;被本单推翻的断言(BUG-642 年份线索、BUG-558「不落焦点」)逐条写原值 / 新值 / 原因。 - Bug 历史与测试不得含真实用户资料。 ## 5. 任务分解 - **T1 收集问题池与价值排序**(BUG-648):`method-followup.ts` 新增 `collectionQuestionPool(evidence, declinedTopics, inviteHistory)` → 按价值排序的 `{kind: "invite" | "anchor" | "generic", value, prompt, key}`;`next_followup` 取首条;`invite_history` 与已答键落 conversation summary。验收:用户连续两轮给新事 → 邀请仍在首位;一轮答「没有了」→ 邀请移除、首条变锚定;锚定答「没有」→ 不重问;池空且门关 → 无焦点、精确缺口句含 ≥2 未覆盖种类例子、无禁词。 - **T2 锚定追问模板与派生表**(BUG-648):`anchoredFollowups(evidence)` + 概率表;焦点 id `collect:anchor::`。验收:学业两件 → 首条「2020 年毕业后第一份工作」;题干含用户年份、不含 age_band 年份。 - **T3 删年份线索,通用补问去年份**(撤回 BUG-642):删 `COLLECT_QUESTION_YEAR_CUE`;`USER_COLLECT_QUESTION` 只在 generic 层使用。验收:`rectification-spoken-collect.test.ts` 相关断言改写并注明。 - **T4 holdout 规则**(BUG-647):训练 ≥3 后才留;回执状态;报告行。验收:3 件全训练、4 件留 1。 - **T5 S2 停止条件与 S3 交付**(BUG-646):`decideRectification` 的交付触发改为收敛门 / 增益见底 / 探针池空;`persistExhaustionCollect` 在训练门关时只写精确缺口句(`preciseGapNarration(evidence, pool)`),门开时走交付卡 + 「再补什么」句;客户端对「停在收集」状态不显示「没有拿到下一个问题」,显示缺口句与占位「再说一件带年月的事」。验收:2 件学业 + 全拒答 → 缺口句、无卡、无禁词、无缺口提示;3 件 2 类 + 探针增益见底 → 交付卡 + 再补句。 - **T6 Skill 10.0.23 + 词表合同 + 记录**:SKILL.md、registry、`case-status.ts`、CHANGELOG、BUG-646~648 历史、`docs/testing/` 真人清单三条(只有学业路径、正常路径、中途补事路径)。 ## 6. 让步顺序 T2 的概率表可先用常量 0.5 统一(写进度记录);T1 + T3 + T4 + T5 不可省。 ## 7. 开工前置命令 ```bash git fetch origin --prune git worktree add -b codex/rectification-collection-redesign-20260910 .worktrees/rectification-collection-redesign-20260910 origin/staging grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | tail -1 cd frontend && npm test -- tests/rectification-spoken-collect.test.ts tests/rectification-exhaustion-exit-20260906.test.ts tests/rectification-surface-state.test.ts tests/skill-registry.test.ts ``` ## 8. 验收口径 - 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build` 后 `/` 仍 Static、首屏 gzip ±2%。 - Python 定向 + `run_quality_gate.py --profile quick`。 - 部署后先打开一个绑定 10.0.22 的历史校正,再做三条真人清单。