docs(tasks): rectification collect-direction task brief — OOS before training gate, undirected collect question
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
| `TASK-rectification-adopt-narration-20260904.md` | `PROGRESS-rectification-adopt-narration-20260904.md` | 探针池耗尽时采用卡旁白改 Agent 生成 + 已丢弃探针绕过 BUG-472 早退 + 区分题答否关线 | 已验收(4.1.4 点选入口未通过,见修复单) | `0aaa0d70`(BUG-519/520) |
|
||||
| `TASK-rectification-adopt-narration-fix-20260904.md` | `PROGRESS-rectification-adopt-narration-fix-20260904.md` | 采用旁白 Agent 在点选入口从不运行(早退分支重算过期决策)+ 校验器/可观测/超时 | 已验收(5.1/5.2 实现通过;超时测试挂死事件循环,见修复单 2) | `13dded9f`(BUG-521/522) |
|
||||
| `TASK-rectification-adopt-narration-fix2-20260904.md` | `PROGRESS-rectification-adopt-narration-fix2-20260904.md` | 采用旁白超时用了 unref 的 `AbortSignal.timeout`,测试挂死取消同文件后三条用例,门禁 `npm test` 退出码 1 | 已验收(门禁通过,staging 已部署) | `45bdb63e`(BUG-523) |
|
||||
| `TASK-rectification-collect-direction-20260904.md` | `PROGRESS-rectification-collect-direction-20260904.md` | 可评分事件 2 条时盘外核对抢跑到刚拒答的家人领域,Agent 只能改写成不指向任何领域的泛问;缺第三件带年份的事却先问职业 | 待执行 | — |
|
||||
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505~509) |
|
||||
|
||||
### 聊天主链路与首页
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# 任务书 · 采集阶段第三件事没有方向:盘外核对抢跑到已拒答领域,问题不指向任何领域(2026-09-04)
|
||||
|
||||
基线:`origin/staging` 代码 `45bdb63e`(文档头 `45e00f46`)。本单只改 `frontend/src/lib/rectification-agentic/v9/method-followup.ts`、`spoken-prompt.ts`、`rectification-v9-tools.ts`(读盘投影 + set-focus 校验)与 `agentic-rectification.ts` 提示词,以及对应测试。不改 Python 引擎、不改 `decideRectification` 的采用/确认门、不动 Skill 版本。
|
||||
|
||||
与 `TASK-report-sensitivity-float-crash-20260904.md` 无文件交集,可并行。
|
||||
|
||||
## 0. 用户可感知的现象
|
||||
|
||||
staging(`45bdb63e`)真实会话,出生窗 30 分钟,账本里 2 条带日期事件(事业 1、感情 1)加 1 条只有年份没有日期的感情事:
|
||||
|
||||
| 轮 | 用户 | 助手 | 问题 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 家人某年采集题答「没有」 | 「你平时主要做什么工作?」 | 可评分事件 2 < 3,缺的是**带年份的第三件事**;职业备注没有日期,答完也不推进 |
|
||||
| 2 | 说了职业 | 「当前范围还在继续收窄中,接下来我们继续。」+「除了工作这条线,还有哪件事你能记起大概的年份?」 | 第一句是空话(没有任何数值收窄);第二句**没有指向任何领域**,用户不知道该往哪想 |
|
||||
|
||||
第 2 轮服务端落下的焦点是 `oos_blind:holdout`、`intent=out_of_sample_check`、`domain=family`——**用户上一轮刚拒答的领域**。Agent 拿到的写作提示是「校时还没用过家人这条线……」,它知道家人刚被否,于是把问题改写成不指向任何领域的泛问。
|
||||
|
||||
## 1. 事故实证
|
||||
|
||||
用该 Case 的 receipt 形状(9 个候选、2 条可评分事件、`event_quality.passed=false, minimum=3, scoreable_event_count=2`、`oos_blind_prompts=[family, education, finance]`、7 条 varga 对照探针全部 `year=0` 被丢成 `yearless_ungrounded_contrast`)本地重放,隐私数据不入库:
|
||||
|
||||
**1a. 工具路径(Agent 轮)选中已拒答领域的盘外核对。** `buildMethodFollowupPlan` 传入 `holdoutValidation: "not_started"`(与 `rectification-v9-tools.ts` `followupPlanForParsed` / 读盘投影一致):
|
||||
|
||||
```
|
||||
declined=[family] → next = { method: oos_blind, intent: out_of_sample_check, domain: family, frame: false }
|
||||
```
|
||||
|
||||
无论账本里有没有职业备注、家人有没有拒答,三种形状结果相同。
|
||||
|
||||
**1b. 决定路径(答「没有」那轮)问的是不带日期的职业。** `persistNextInterviewAfterChoice` 不传 `holdoutValidation`,计划走方法覆盖轮转 relationship → career → family(拒答视作 covered)→ **occupation**。学业、财务、搬家这些带年份的领域根本不在这条轮转里。
|
||||
|
||||
**1c. 决定路径再走一轮也是泛问。** 职业答完后若仍走决定路径,计划落到 `!meetsAcceptanceEventQuality` 分支:`domain: null` → `spokenFollowupForUser` 给 `GENERIC_COLLECT_QUESTION`(「从你最容易想起来的一件事开始就好……」)。也就是说,两条路径都不会把用户引到一个具体领域。
|
||||
|
||||
代码定位(`method-followup.ts` @45bdb63e,按符号):
|
||||
|
||||
- `holdoutValidationStatus` / `canAskHoldout`(`decision-from-dossier.ts`):只要引擎给了 `oos_blind_prompts` 就判 `not_started`,不看 `event_quality`。
|
||||
- `buildMethodFollowupPlan` 内 `if (!next && input.holdoutValidation === "not_started")` 分支:直接取 `input.oosBlindPrompts?.[0]`,不过 `declined`,不看 `meetsAcceptanceEventQuality`。`sessionOutcome === "validate_holdout"` 早退分支同样不过 `declined`。
|
||||
- 方法覆盖轮转(`!relationshipCovered` → `!careerCovered` → `!familyCovered` → `!occupationCovered`)之后才是 `!meetsAcceptanceEventQuality` 的泛问分支,且该分支 `domain: null`。
|
||||
- `exhaustionSpokenCollectFollowup` 已经有正确的顺序(family → education → finance → occupation → health/relocation/career/relationship → other)和按领域的 `datedCollectFollowup`,但只在拒答/穷尽路径被调用,计划主流程不用它。
|
||||
- `validateSpokenPrompt`:对采集题只查长度、选项字面、内部 token、`domain_mismatch`(仅当 Agent 显式传了不同 `targetDomain`),**不要求题干提到领域**。Agent 把家人题改写成泛问,服务端照收。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
1. **盘外核对没有前置门。** BUG-396 已钉死「2 条继续收集,训练门只计 training 事件」,但计划层的 OOS 分支只看 `holdoutValidation === "not_started"`,而这个状态只要引擎输出了提示就成立。于是训练门都没开就开始「盘外核对」,并且用的是被 `declinedDomains`(BUG-520)明确排除的领域。
|
||||
2. **计划里没有「补第三件带年份的事」这一步。** 方法覆盖轮转的四个领域里有三个已覆盖或拒答,剩下的职业不带日期;带年份的学业/财务/搬家/健康只存在于穷尽路径。
|
||||
3. **Agent 的写作提示与用户上下文矛盾,校验器又不要求方向。** 提示说问家人,用户刚否掉家人,Agent 只能写泛问;服务端没有任何一条规则要求采集题写出领域。
|
||||
|
||||
## 3. 决策记录(产品负责人 2026-09-04)
|
||||
|
||||
1. **可评分事件不足 3 条时,不得进入盘外核对(OOS / holdout 提问)。** 计划的两处 OOS 分支都加 `meetsAcceptanceEventQuality` 前置;不满足就当作没有 OOS 提示。这不推翻 BUG-463(holdout 不绑采用门)——本单只管「什么时候问」,不碰 `canAdopt` / `canConfirmExactMinute`。
|
||||
2. **OOS 提示必须跳过已拒答领域**(`declinedDomains`,沿用 BUG-520 的 intent 口径)。全部被拒则退到带年份的 holdout 事件,再没有就 `null`。
|
||||
3. **缺第三件事时,先补带年份的领域,再问职业。** 顺序:家人 → 学业 → 财务 → 搬家 → 健康/压力 → 事业 → 感情,跳过已拒答与已有确认证据的领域;全部走完才轮到职业,职业也关了才用泛问。计划主流程与 `exhaustionSpokenCollectFollowup` 共用同一份顺序,不得再复制一份。这条**只在 `!meetsAcceptanceEventQuality` 时生效**,不改 BUG-442/472 关于职业覆盖不挡采用的结论。
|
||||
4. **采集题必须指向领域,方向由服务端定、话由 Agent 说。** 这是 Agent 产品,题干继续由 Agent 用自己的话写,但服务端校验题干必须包含目标领域的至少一个关键词;不合格返回 `invalid_spoken_prompt: domain_missing`,两次不合格后服务端用 `USER_COLLECT_QUESTION[domain]` 落焦点(现有 `spokenPromptFailures` 机制)。领域为 `other` 的泛问只允许在 §3.3 全部走完之后出现。
|
||||
5. **进度句必须有数字,没数字就不说。** 「当前范围还在继续收窄中」这种没有数据支撑的句子不得出现。服务端在读盘投影里给出 `collection_progress = { scoreable, minimum, missing }`(来自 receipt `event_quality`),提示词要求:下一问是采集题时,先用一句话说明还差几件带时间的事,数字只来自这个字段;没有这个字段就不说进度。
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
1. 不改 `holdoutValidationStatus` 的返回语义、不改 `decideRectification` / `deliveryCapability` 的采用与确认门(BUG-463 防复发条款)。修法在计划层的分支条件,不在决策层。
|
||||
2. 不改 `declinedDomains` 的 intent 口径(BUG-520)。
|
||||
3. 顺序只能有一处定义;`exhaustionSpokenCollectFollowup` 与计划主流程必须调用同一个函数。
|
||||
4. `validateSpokenPrompt` 的领域关键词表放在 `user-copy.ts` 或 `agent-voice-lexicon.ts` 旁边,每个领域至少 3 个日常词(例:education → 上学/大学/毕业/考试/学业;finance → 收入/买房/贷款/欠债/钱;relocation → 搬家/住/外地;health_pressure → 生病/受伤/住院/压力;family → 家里/父母/家人/添丁;career → 工作/入职/换工作;relationship → 交往/分手/结婚/感情)。关键词命中是 `includes`,不做分词。不得把这个校验套到区分题(`choice_frame` 非空)或 OOS 题上——它们已有年份校验。
|
||||
5. 既有断言若要改,写「原值 / 新值 / 原因」三栏。预计要动的:`rectification-server-focus` 中「exhaustion `oos_blind` 仍走 `USER_COLLECT_QUESTION.other`」相关用例(若受顺序合并影响);`rectification-yearless-ungrounded` 的 `validate_holdout` 用例需要补足 4 条带日期证据才能维持 `oos_blind` 结论(它现在的 evidence 是 4 条 year 精度 + 1 条职业,`meetsAcceptanceEventQuality` 是否满足由执行方实测后写明)。
|
||||
6. 任务书、进度记录、Bug 历史、测试 fixture 不得出现该 Case 的 ID、日期、职业描述;fixture 用虚构年份。
|
||||
7. 进度记录必须贴 `npm test` 摘要六行(tests/pass/fail/cancelled/skipped/todo)与退出码,以及无 Docker 基线失败清单比对(BUG-523 教训)。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
### 5.1 P0 · 盘外核对加前置门并跳过拒答领域(BUG-524)
|
||||
|
||||
1. 新增 `holdoutFollowupFor(input, declined)`:`meetsAcceptanceEventQuality(input.evidence)` 不满足 → `null`;否则取 `oosBlindPrompts` 中第一个 `!declined.has(domain)` 的提示 → `holdoutAskFields`;没有则退到带年份的 holdout 事件;再没有 → `null`。
|
||||
2. `sessionOutcome === "validate_holdout"` 早退与 `holdoutValidation === "not_started"` 分支都改用它。
|
||||
|
||||
验收(新文件 `frontend/tests/rectification-collect-direction-20260904.test.ts`,fixture 用 §1 的形状但年份虚构):
|
||||
- 2 条可评分事件 + `oosBlindPrompts=[family, education, finance]` + family 拒答 + `holdoutValidation: "not_started"` → `next_followup.intent !== "out_of_sample_check"`。
|
||||
- 4 条可评分事件(3 个领域)+ family 拒答 + `holdoutValidation: "not_started"` 且无可渲染区分探针 → `next_followup` 为 `oos_blind` 且 `domain === "education"`。
|
||||
- 同上但三个领域全部拒答、无带年份 holdout 事件 → 不出 OOS;`sessionOutcome: "validate_holdout"` 时 `next_followup === null`。
|
||||
|
||||
### 5.2 P0 · 补第三件带年份的事进入计划主流程(BUG-525)
|
||||
|
||||
1. 把 `exhaustionSpokenCollectFollowup` 里的领域顺序抽成 `datedCollectOrder`(§3.3 顺序,含跳过规则),穷尽路径与计划主流程共用。
|
||||
2. `buildMethodFollowupPlan`:在方法覆盖轮转之前、OOS 分支之后加一步:`!next && !meetsAcceptanceEventQuality(input.evidence)` → 走 `datedCollectOrder`,取到带领域的 `datedCollectFollowup`;没有再进原轮转(职业)与原泛问分支。
|
||||
3. `spokenFollowupForUser` 对这些 followup 必须返回 `USER_COLLECT_QUESTION[domain]`(不是 `GENERIC_COLLECT_QUESTION`),这是决定路径的题干。
|
||||
|
||||
验收:
|
||||
- §1 形状、family 拒答、`holdoutValidation: "not_started"` → `next_followup = { intent: collect_method_evidence, domain: education }`,`spokenFollowupForUser` 含「上学」或「升学」。
|
||||
- 再把 education 也拒答 → `domain: finance`;family/education/finance/relocation/health_pressure 全拒答且 career/relationship 已有确认证据 → `domain: occupation`;职业也关闭 → 才出 `domain: other`。
|
||||
- `persistNextInterviewAfterChoice`(答「没有」路径,形状同 `rectification-adopt-narration-20260904.test.ts` 的 denial 用例)在 2 条可评分事件下落下的焦点 `target_domain` 为 education 且 prompt 为 `USER_COLLECT_QUESTION.education`;零次 `decideFromDossier`(沿用 BUG-521 源码断言)。
|
||||
- 4 条可评分事件时本分支不触发(`rectification-collect-stall`、`rectification-eight-method` 全绿;BUG-442/472 的采用出口用例不变)。
|
||||
|
||||
### 5.3 P1 · 采集题干必须指向领域(BUG-526)
|
||||
|
||||
1. `validateSpokenPrompt`:当 `followup.intent === "collect_method_evidence"` 且 `followup.domain` 在关键词表内且 `!followup.choice_frame` 时,题干必须命中该领域至少一个关键词,否则 `{ ok: false, reason: "domain_missing" }`。
|
||||
2. `rectification-set-focus` 描述加一句:「采集题必须写出服务端给你的领域(学业/家里/钱/搬家……),不要写成『随便哪件事』」。
|
||||
3. 第二次仍不合格时的服务端兜底沿用现有 `spokenPromptFailures`;确认兜底题干是 `USER_COLLECT_QUESTION[domain]` 而不是泛问。
|
||||
|
||||
验收:`rectification-spoken-prompt`(或新文件):education 题写「除了工作,还有哪件事记得年份?」→ `domain_missing`;写「上学那会儿,哪年升学或大考还记得吗?」→ ok;区分题与 OOS 题不受影响;`rectification-set-focus` 集成用例两次泛问后焦点 prompt 为 `USER_COLLECT_QUESTION.education`。
|
||||
|
||||
### 5.4 P2 · 进度句有数字才说(BUG-527)
|
||||
|
||||
1. 读盘投影(`rectification-read-case` 与 route 的 interview 投影)加 `collection_progress: { scoreable, minimum, missing }`,来自 `decisionReceipt.gates.event_quality`;缺字段时为 `null`。
|
||||
2. `agentic-rectification.ts` 提示词:「下一问是采集题时,先用一句说明还差几件带时间的事,数字只用 `collection_progress`;没有该字段不说进度。不得写『范围在收窄』『继续收窄』这类没有数字的进度句。」把「继续收窄」「范围还在」加进 `MACHINE_VOICE_LEXICON` 只对旁白校验生效(若旁白无校验,则只改提示词并在进度记录写明)。
|
||||
|
||||
验收:投影测试锁 `collection_progress` 取值(2/3/1)与缺 gate 时 `null`;提示词测试断言含「collection_progress」与「不得」句。
|
||||
|
||||
## 6. 让步顺序
|
||||
|
||||
1. 5.1 与 5.2 必做,缺一条用户仍然拿到没方向的问题。
|
||||
2. 5.3 可以退到只加提示词 + 兜底题干为领域题,但校验器改动是防复发的钉子,不做要在进度记录写明理由。
|
||||
3. 5.4 可推迟。
|
||||
|
||||
## 7. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-collect-direction-20260904 .worktrees/rectification-collect-direction-20260904 origin/staging
|
||||
cd .worktrees/rectification-collect-direction-20260904/frontend
|
||||
./node_modules/.bin/tsc --noEmit
|
||||
npx tsx --test tests/rectification-eight-method.test.ts tests/rectification-yearless-ungrounded.test.ts tests/rectification-server-focus.test.ts tests/rectification-collect-stall.test.ts tests/rectification-adopt-narration-20260904.test.ts tests/rectification-answer-choice.test.ts
|
||||
```
|
||||
|
||||
记下开工时这批套件的 tests/pass/fail 数与全量 `npm test` 的失败清单(无 Docker 基线 25 条),交付时逐条比对。
|
||||
|
||||
## 8. BUG 编号起点
|
||||
|
||||
截至本单:BUG-523。本单从 **BUG-524** 起(5.1 → 524,5.2 → 525,5.3 → 526,5.4 → 527);开工时再核对 `docs/BUG_HISTORY.md` 最大号。BUG-524 关联 BUG-396(训练门未开不得进入 holdout)与 BUG-520(拒答口径);BUG-525 关联 BUG-442/472(职业覆盖)与 BUG-426(计划回落到带年份的采集题)。
|
||||
Reference in New Issue
Block a user