Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
11 KiB
11 KiB
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,原话要点)
- Agent 一上来就按领域一个个追问,年份还是拿生日推出来的;应该先等用户说出有价值的经历,再从这些经历往下问。
- 追问时不得说「任何领域」「领域不限」——用户不知道有哪些领域;要给具体例子。
- 生时校正必须走到结果:要么直接成功,要么细节不全就问用户补全、补完拿结果。不允许流程走完告诉用户「做不了」。
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 自由采集(用户主导,≤2 轮邀请)─▶ S2 锚定补全(从已说的事推导,≤4 问)─▶ S3 区分选择题 ─▶ S4 交付结果
▲ │
└────────────── 用户任何时候补一件带年月的事 ◀─────────────────┘
S0 开场(文案基本不变)
一句说明窗口与做法,一句「最后给区间和代表分钟」,一句邀请:「先说你最容易想起的一两件,比如上大学、第一份工作、搬到别的城市、谈恋爱或结婚、家里添丁或长辈住院、生病受伤,年月大概就行。」不写年份。
S1 自由采集(用户主导)
- 用户每说一批,batch 写入、复述「记下了:…」,然后只问一句「还有吗」,例子只列还没提过的种类,最多 4 个,用具体事物不用领域词:「还有吗?比如第一份工作、搬到别的城市、谈恋爱或分手、家里添丁或长辈住院。」
- 最多两轮邀请。用户说「没有了 / 就这些 / 记不清」→ 进 S2。训练门一开(3 件带月 / 2 类)→ 直接进 S3,不再邀请。
- 不问带年份线索的领域题;不按领域轮转。
S2 锚定补全(从已说的事推导,不看生日)
每个已记事件按 kind 派生 1–2 个锚定追问,一次只问一句,答「没有 / 记不清」即跳过,最多 4 问:
| 已记事件 | 派生追问(题干里必须带上那件事的年份或名字,年份来自用户,不来自生日) |
|---|---|
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 |
「那件事前后你自己有没有搬家或换工作?」 |
| 只有学业、没有工作 | 「毕业后第一份工作大概哪年开始?」(这是最常见的缺口,优先级最高) |
- 派生规则放服务端(
anchoredFollowups(evidence)),题干模板固定,Agent 只做口语润色,不得改年份、不得加生日推出来的年份。 - 4 问之后仍不足 → 进 S4(不是死路)。
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 重算,卡片更新。
文案硬规则
- 用户可见文案禁止:领域、任何领域、领域不限、方法覆盖、分盘、探针、训练门、holdout、做不了、材料不够。
- 每个「还有吗」和「再补什么」都必须带 ≥2 个具体例子,例子只从未覆盖的种类里选。
- 静态词表合同(
agent-voice-copy-contract)加以上禁词。
3. 决策记录
- 撤回 BUG-642「采集题带年龄段年份线索」:
COLLECT_QUESTION_YEAR_CUE删除;evidence_collection_probes的 age_band 年份只用于内部排序,不进任何题干。 - 撤回 BUG-646 单决策 2(终态 = 门槛句 + 先这样按钮):终态改为 S4 的交付卡。撤回决策 3 的「释放 holdout」:改为「≥4 件才留 holdout」的静态规则,不需要「穷尽后释放」的动态分支。
- BUG-558 的防复发(不得无限循环「再说一件」)保留:S1 邀请 ≤2 轮、S2 追问 ≤4 问,都有硬上限,超过即交付。
- 「暂定采用」是新增能力:只影响采用与排盘标注,不影响确认门、不影响唯一分钟声明。
- Skill 10.0.23:把「先走完方法覆盖(感情 → 事业 → 家人 → 职业 → 占问),再对已覆盖领域做精度追问」改为本单 S1→S2→S3→S4;删除「财务与健康…」相关旧句(10.0.22 已删)。
4. 硬红线
- 不得用出生年推年份写进题干(
grep -n "年前后" user-copy.ts只允许出现在带用户年份的锚定模板里)。 - S1/S2 上限必须是代码常量并有测试;不得依赖模型自觉。
- 「暂定采用」写入必须带
acceptance_kind,报告/咨询头部必须能区分暂定与正式。 - 既有测试总数不降;被本单推翻的断言(BUG-642 年份线索、BUG-558「不落焦点」、BUG-646 单若已开工)逐条写原值 / 新值 / 原因。
- 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:<kind>:<year>;≤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终态改为交付卡 + 「再补什么」句;Pythonselection_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/真人清单(全拒答路径、只有学业路径、正常路径三条)。
6. 让步顺序
T5 的「暂定采用」半边可延后(先交付卡、采用仍按原门;写 BLOCKED.md);T1~T4 与 T5 的交付卡不可省。
7. 开工前置命令
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 --noEmit0 错、npm run lint0 error、相关套件 fail=0、测试总数 ≥ 基线;next build后/仍 Static、首屏 gzip ±2%。 - Python 定向 +
run_quality_gate.py --profile quick。 - 部署后先打开一个绑定 10.0.22 的历史校正,再做三条真人清单。