diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 459eef25..06bc4ab8 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -100,7 +100,7 @@ | `TASK-rectification-collect-semantics-20260910.md` | `PROGRESS-rectification-collect-semantics-20260910.md` | 产品决策三改:Skill 去掉「财务/健康只有主动说才问」并清空 Python `VOLUNTEER_ONLY`,升 10.0.22(BUG-641);带年份线索的采集题优先、题干带线索不矛盾,推翻 BUG-539 的整句去前缀(BUG-642);删掉「没有/记不清」原字匹配,分类器分 no/unsure 两类(BUG-643) | 待验收 | `52db714b`(BUG-641~643) | | `TASK-rectification-superseded-focus-duplicate-20260910.md` | `PROGRESS-rectification-superseded-focus-duplicate-20260910.md` | P0:一轮两次 batch 让感情采集题被顶替成 superseded 行;题号唯一约束含已结束行、汇总只聚合 declined/skipped,计划层看不见→撞号 `duplicate_focus` 被当「已问过」→出口误走交付旁白无卡(BUG-644);交付口吻与 `can_adopt=false` 矛盾(BUG-645) | 待验收(环境缺口:`next build` / 真人走查) | `a3a51c32`(BUG-644/645) | | `TASK-rectification-exhausted-gate-exit-20260910.md` | `PROGRESS-rectification-exhausted-gate-exit-20260910.md` | 五领域全拒答 + 三件带月经历:门槛句后无载体、客户端误报「没有拿到下一个问题」并循环「接着问」(BUG-646);第三件被留作 holdout 导致永远没有选择题(BUG-647)。含产品建议:一次性开放追问 + 终态标记 + 穷尽后释放 holdout | 已被取代(并入采集重设计单) | — | -| `TASK-rectification-collection-redesign-20260910.md` | `PROGRESS-rectification-collection-redesign-20260910.md` | 产品口径:用户先说、系统从已说的事锚定追问、不得用生日推年份、不得说「领域」、永远交付结果。S0 开场→S1 自由采集≤2 轮→S2 锚定补全≤4 问→S3 选择题(≥4 件才留 holdout)→S4 交付卡 + 暂定采用。撤回 BUG-642 年份线索与 BUG-646 单终态;Skill 10.0.23(BUG-646~648) | 待执行 | `codex/rectification-collection-redesign-20260910` | +| `TASK-rectification-collection-redesign-20260910.md` | `PROGRESS-rectification-collection-redesign-20260910.md` | 产品口径:用户先说、系统从已说的事锚定追问、不得用生日推年份、不得说「领域」、永远交付结果。无固定题数:收集问题池按信息价值排序问到训练门开;选择题问到收敛门开或增益见底;门关不出结果只出精确缺口句并保持开放;≥4 件才留 holdout;撤回 BUG-642 年份线索、BUG-646 单终态与「暂定采用」;Skill 10.0.23(BUG-646~648) | 待执行 | `codex/rectification-collection-redesign-20260910` | ### 聊天主链路与首页 diff --git a/docs/tasks/TASK-rectification-collection-redesign-20260910.md b/docs/tasks/TASK-rectification-collection-redesign-20260910.md index ff20e7b1..2d5c07c9 100644 --- a/docs/tasks/TASK-rectification-collection-redesign-20260910.md +++ b/docs/tasks/TASK-rectification-collection-redesign-20260910.md @@ -13,6 +13,7 @@ 1. Agent 一上来就按领域一个个追问,年份还是拿生日推出来的;应该**先等用户说出有价值的经历**,再从这些经历往下问。 2. 追问时**不得说「任何领域」「领域不限」**——用户不知道有哪些领域;要给具体例子。 3. 生时校正**必须走到结果**:要么直接成功,要么细节不全就问用户补全、补完拿结果。不允许流程走完告诉用户「做不了」。 +4. (第二轮补充)**材料不够门槛时继续收集,不出结果**;不要固定题数,**按收集到的信息达到阈值来收口**。 ## 1. 现状为什么违反这三条 @@ -24,89 +25,82 @@ | 三件带年月经历里一件被留作 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. 目标流程(状态机) +## 2. 目标流程(状态机,全部用阈值收口,没有固定题数) ``` -S0 开场 ─▶ S1 自由采集(用户主导,≤2 轮邀请)─▶ S2 锚定补全(从已说的事推导,≤4 问)─▶ S3 区分选择题 ─▶ S4 交付结果 - ▲ │ - └────────────── 用户任何时候补一件带年月的事 ◀─────────────────┘ +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 自由采集(用户主导) +### S1 收集:用户先说,系统按价值追问,问到训练门开为止 -- 用户每说一批,batch 写入、复述「记下了:…」,然后**只问一句「还有吗」**,例子只列**还没提过的种类**,最多 4 个,用具体事物不用领域词:「还有吗?比如第一份工作、搬到别的城市、谈恋爱或分手、家里添丁或长辈住院。」 -- 最多两轮邀请。用户说「没有了 / 就这些 / 记不清」→ 进 S2。训练门一开(3 件带月 / 2 类)→ 直接进 S3,不再邀请。 -- 不问带年份线索的领域题;不按领域轮转。 +**收集问题池**由服务端按「信息价值」排序,每轮只问价值最高的一条;价值 = 这条问题能补上训练门缺口的概率 × 缺口权重。没有固定题数,停止条件只有两个:训练门开,或池空。 -### S2 锚定补全(从已说的事推导,不看生日) +1. **自由邀请**(价值最高,只要还在产出就一直排第一):用户每说一批,batch 写入、复述,然后问一句「还有吗」,例子只列**还没提过的种类**、最多 4 个、用具体事物:「还有吗?比如第一份工作、搬到别的城市、谈恋爱或分手、家里添丁或长辈住院。」邀请的价值随产出衰减:上一轮邀请用户给了新事 → 价值不变;上一轮邀请用户答「没有了 / 就这些 / 记不清」→ 价值降到 0,从池里移除。 +2. **锚定追问**(从用户已说的事派生,年份只来自用户):每个已记事件按 `kind` 派生 1–2 条,题干必须带那件事的年份或名字,例如「2020 年毕业后第一份工作大概哪年开始?」「2016 年上大学是搬到别的城市住吗?」「后来是分开了还是结婚了,大概哪年?」「2024 年 10 月那次之后,工作或住处有没有变?」派生规则与模板放服务端(`anchoredFollowups(evidence)`),Agent 只做口语润色,不得改年份、不得加生日推出来的年份。价值 = 派生概率(预设表,例如「毕业→第一份工作」0.8,「上大学→搬城市」0.6)× 缺口权重(补新种类 1.0,补同种类 0.5)。答「没有 / 记不清」→ 该条移除,不重问。 +3. **通用补问**(价值最低,只在前两类都空了才轮到):按种类问、**不带年份**、每种只问一次、例子具体:「工作上还记得哪年入职或换过工作吗?」用户已拒答的种类不再出现。 -每个已记事件按 `kind` 派生 1–2 个**锚定追问**,一次只问一句,答「没有 / 记不清」即跳过,最多 4 问: +**池空且训练门仍关**(用户确实只有一两件):不出结果卡,也不说「做不了」。写一句**精确缺口**:「现在记下的是 2016 年上大学和 2020 年毕业两件,都是上学的事。再来一件不是上学的、记得大概年月的事就能开始筛,比如第一份工作哪年开始、哪年搬到别的城市、家里哪年添丁。」缺口句里的例子必须来自未覆盖的种类;Case 保持开放,输入框占位「再说一件带年月的事」,用户补一件即自动继续。这是唯一允许的「停在收集」状态,文案禁词见 §2 末。 -| 已记事件 | 派生追问(题干里必须带上那件事的年份或名字,年份来自用户,不来自生日) | -| --- | --- | -| `education_start`(上大学 2016) | 「2016 年上大学是搬到别的城市住吗?」 | -| `education_completion`(毕业 2020) | 「2020 年毕业后第一份工作大概哪年哪月开始?」 | -| `career_change` / 第一份工作 | 「那之后有没有换过工作或升过职?大概哪年?」 | -| `relocation` | 「搬过去之后,工作或感情有没有跟着变?哪年?」 | -| `relationship_start` | 「后来是分开了还是结婚了?大概哪年?」 | -| `relationship_end` / `separation` | 「分开前后有没有换工作或搬家?」 | -| `self_health_event`(外伤 2024-10) | 「2024 年 10 月那次之后,工作或住处有没有变?」 | -| `family_event` | 「那件事前后你自己有没有搬家或换工作?」 | -| 只有学业、没有工作 | 「毕业后第一份工作大概哪年开始?」(这是最常见的缺口,优先级最高) | +### S2 区分选择题:直到收敛门开或增益见底 -- 派生规则放服务端(`anchoredFollowups(evidence)`),题干模板固定,Agent 只做口语润色,不得改年份、不得加生日推出来的年份。 -- 4 问之后仍不足 → 进 S4(不是死路)。 +- 现有逻辑(信息增益排序、±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 区分选择题(现有逻辑,改两处) +### S3 交付结果 -- **holdout 只在不影响筛选时保留**:带月事件 ≥4 才留 1 件作 holdout;恰好 3 件时全部参与训练,回执 `holdout.status="not_reserved_min_events"`,报告 Real Case Calibration 行写 `not reserved`。确认门不变(本来就关)。 -- 选择题、旁白、时间线口径不变(BUG-634/606)。 - -### S4 交付结果(永远有) - -- 触发:S3 问完;或 S2 四问用尽;或用户说「先这样」。 -- 内容:现有区间卡(`range_delivery` 三列)+ 一句定性 + **一句具体的「再补什么能收窄」**:从 S2 未答的锚定追问里取第一条,写成「如果还记得毕业后第一份工作是哪年开始的,范围还能再收一截」。绝不写「任何领域」「领域不限」「做不了」「材料不够」。 -- 训练门关时也交付:卡片标「暂定」,`selection_allowed` 对「暂定采用」放开(写入 `accepted_time` 且 `acceptance_kind="provisional"`),排盘可用,报告与咨询头部写「暂定出生时间(N 件经历)」;正式「采用」仍走原门。这是对 `selection_allowed = acceptance_allowed` 的有意放宽,**只放采用,不放确认**。 -- 交付后 Case 不关:用户随时再补一件带年月的事,回到 S3/S4 重算,卡片更新。 +- 触发:收敛门开,或增益见底 / 探针池空(训练门已开是前提)。 +- 内容:现有区间卡(`range_delivery` 三列)+ 一句定性 + **一句具体的「再补什么能收窄」**:从收集池里价值最高的未答条目取,写成「如果还记得毕业后第一份工作是哪年开始的,范围还能再收一截」。绝不写「任何领域」「领域不限」「做不了」「材料不够」。 +- 交付后 Case 不关:用户随时再补一件带年月的事,重算并更新卡片。 +- **不做「暂定采用」**(第二轮口径撤回):训练门没开就不出卡,采用门维持原样。 ### 文案硬规则 -- 用户可见文案禁止:领域、任何领域、领域不限、方法覆盖、分盘、探针、训练门、holdout、做不了、材料不够。 -- 每个「还有吗」和「再补什么」都必须带 ≥2 个具体例子,例子只从未覆盖的种类里选。 +- 用户可见文案禁止:领域、任何领域、领域不限、方法覆盖、分盘、探针、训练门、holdout、做不了、材料不够、还差 N 件。 +- 每个「还有吗」「再补什么」「精确缺口句」都必须带 ≥2 个具体例子,例子只从未覆盖的种类里选。 - 静态词表合同(`agent-voice-copy-contract`)加以上禁词。 ## 3. 决策记录 1. 撤回 BUG-642「采集题带年龄段年份线索」:`COLLECT_QUESTION_YEAR_CUE` 删除;`evidence_collection_probes` 的 age_band 年份只用于内部排序,不进任何题干。 -2. 撤回 BUG-646 单决策 2(终态 = 门槛句 + 先这样按钮):终态改为 S4 的交付卡。撤回决策 3 的「释放 holdout」:改为「≥4 件才留 holdout」的静态规则,不需要「穷尽后释放」的动态分支。 -3. BUG-558 的防复发(不得无限循环「再说一件」)保留:S1 邀请 ≤2 轮、S2 追问 ≤4 问,都有硬上限,超过即交付。 -4. 「暂定采用」是新增能力:只影响采用与排盘标注,不影响确认门、不影响唯一分钟声明。 -5. Skill 10.0.23:把「先走完方法覆盖(感情 → 事业 → 家人 → 职业 → 占问),再对已覆盖领域做精度追问」改为本单 S1→S2→S3→S4;删除「财务与健康…」相关旧句(10.0.22 已删)。 +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` 只允许出现在带用户年份的锚定模板里)。 -- S1/S2 上限必须是代码常量并有测试;不得依赖模型自觉。 -- 「暂定采用」写入必须带 `acceptance_kind`,报告/咨询头部必须能区分暂定与正式。 -- 既有测试总数不降;被本单推翻的断言(BUG-642 年份线索、BUG-558「不落焦点」、BUG-646 单若已开工)逐条写原值 / 新值 / 原因。 +- 收集池「每条只问一次、答否即移除」必须是代码不变量并有测试;停止条件只能引用 §2 表里的门与常量,不得新增题数常量。 +- 训练门关时不得写 `range_delivery`、不得出卡、不得出现禁词。 +- 既有测试总数不降;被本单推翻的断言(BUG-642 年份线索、BUG-558「不落焦点」)逐条写原值 / 新值 / 原因。 - Bug 历史与测试不得含真实用户资料。 ## 5. 任务分解 -- **T1 S1 邀请**(BUG-648):`method-followup.ts` 覆盖链前置一个 `openInviteFollowup(evidence, inviteCount)`,例子从未覆盖种类里选;`invite_count` 落在 conversation summary;≤2。验收:两轮后不再邀请;例子不含已记种类;无「领域」。 -- **T2 S2 锚定追问**(BUG-648):`anchoredFollowups(evidence, askedKeys)` + 模板表;`persistServerOwnedFocus` 落 `collect:anchor::`;≤4。验收:学业两件 → 首问是「毕业后第一份工作」;答「没有」跳下一条;四问后不再问。 -- **T3 删年份线索**(撤回 BUG-642):删 `COLLECT_QUESTION_YEAR_CUE` 与相关分支;`USER_COLLECT_QUESTION` 保留为 S2 之外的兜底但不再主动轮转。验收:`rectification-spoken-collect.test.ts` 相关断言改写。 -- **T4 holdout 规则**(BUG-647):`MIN_EVENTS_TO_RESERVE_HOLDOUT` 语义改为「训练 ≥3 后才留」;回执状态;报告行。验收:3 件全训练、4 件留 1。 -- **T5 S4 交付与暂定采用**(BUG-646):`persistExhaustionCollect` / `ensureNonTerminalTurnExit` 终态改为交付卡 + 「再补什么」句;Python `selection_allowed` 拆成 `provisional_selection_allowed`(候选存在即真)与原 `selection_allowed`;accept RPC 带 `acceptance_kind`;报告头部标注。验收:2 件学业 + 全拒答 → 出交付卡、可暂定采用、无禁词;客户端不出现「没有拿到下一个问题」。 -- **T6 Skill 10.0.23 + 词表合同 + 记录**:SKILL.md、registry、`case-status.ts`、CHANGELOG、BUG-646~648 历史、`docs/testing/` 真人清单(全拒答路径、只有学业路径、正常路径三条)。 +- **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. 让步顺序 -T5 的「暂定采用」半边可延后(先交付卡、采用仍按原门;写 `BLOCKED.md`);T1~T4 与 T5 的交付卡不可省。 +T2 的概率表可先用常量 0.5 统一(写进度记录);T1 + T3 + T4 + T5 不可省。 ## 7. 开工前置命令