docs(tasks): collect-semantics brief (BUG-641~643); drop regex fallback from BUG-635 brief

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
Jesse_Chen
2026-09-10 06:26:47 +00:00
co-authored by Claude Fable 5.1
parent 84572c079d
commit 301827ad59
3 changed files with 76 additions and 2 deletions
+1
View File
@@ -97,6 +97,7 @@
| `TASK-rectification-evidence-turn-empty-answer-20260910.md` | `PROGRESS-rectification-evidence-turn-empty-answer-20260910.md` | 证据轮模型无正文被判整轮失败:证据、评分、下一问都已落库却只剩『没有拿到下一个问题』(BUG-633);答题旁白只说『范围没变』、时间线写死『还在收窄』(BUG-634) | 待验收 |
| `TASK-rectification-unwritten-evidence-claim-20260910.md` | `PROGRESS-rectification-unwritten-evidence-claim-20260910.md` | 证据轮模型只说『记下了』却没调 batch、没设下一问,财务一件静默丢失、流程停在原题且照常扣点(BUG-635);校正流不识别 Mastra schema 拒绝信封,会把被拒的 batch 报成 completedBUG-636 | 待执行 | `codex/rectification-unwritten-evidence-claim-20260910` |
| `TASK-rectification-latent-audit-20260910.md` | `PROGRESS-rectification-latent-audit-20260910.md` | 顺带审计:账本派生同年键误杀 `known_event_quality`BUG-637BUG-389 复发);双轨一致性按 31 分钟逐分判冲突并降置信度、与报告两套口径(BUG-638);不可分宽度按簇代表分钟少算(BUG-639);引擎与推断层两套代表分钟、回执宫位表不是卡片那一分钟(BUG-640);另 8 条观察项 | 待执行 | `codex/rectification-latent-audit-20260910` |
| `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 | 待执行 | `codex/rectification-collect-semantics-20260910` |
### 聊天主链路与首页
@@ -0,0 +1,73 @@
# TASK · 采集语义三改:Skill 去掉「财务只有主动说才问」、带年份线索的采集题优先并改口径、跳过/没有一律由分类器判(2026-09-10)
- 基线:`origin/staging` @ `84572c07`(代码同 `d96b24c2`staging 已部署)
- 分支:`codex/rectification-collect-semantics-20260910`,基于 `origin/staging`
- 串行:改 `method-followup.ts``route.ts`,与 `TASK-rectification-unwritten-evidence-claim-20260910.md`BUG-635/636,改 `route.ts` collect 分支与 `agent-run.ts`)**有文件交集**:先做那份,本单在其分支之上开工;若那份尚未领取,本单先做,那份的 T1 改为在本单的 `collectFocusCloseStatus` 之后接入。与 `TASK-rectification-latent-audit-20260910.md`BUG-637640)无交集。
- 执行方:coding agent;验收:Claude
- BUG 编号起点:**BUG-641**635/636、637640 已由前两份任务书占用;开工时核对 `docs/BUG_HISTORY.md` 最大号)
- **本单升 Skill10.0.21 → 10.0.22**(改 SKILL.md 正文必须升版本,包目录哈希钉在 `skills/skill-package-registry.json`)。
## 0. 产品决策(2026-09-10,产品负责人口头授权)
1. 去掉「财务与健康只有用户主动说才问」。财务、健康与其他领域同权:服务器可以主动问,用户说了就记、就计分。
2. 带年份的题优先。区分候选的带年月点选题永远先问(10.0.21 已定);采集题里,带年份线索的先于没有线索的,同领域的无年份性格卡不得抢在采集题前面。
3. 「没有」「记不清」这类业务判断不得靠正则或原字匹配,一律由意图分类器判;分类器要能分出「这方面没有」和「记不清、以后再说」两种。
## 1. 现状实证(同一份 Case JSONSkill 10.0.21
| 现象 | 代码事实 |
| --- | --- |
| Skill 说「财务与健康只有用户主动说才问,仍可计分」(`skills/jyotish-birth-time-rectification/SKILL.md` L84),服务器却主动问了财务 | TS 采集轮转 `DATED_COLLECT_ORDER``method-followup.ts` L1249)含 `finance`,自 BUG-462 起主动问;Python `event_probes.py` L145 `VOLUNTEER_ONLY = {finance, health_pressure}` 让这两域**不进** `evidence_collection_probes`、不进 `missing_collection_domains`。三处口径各自为政,模型同时读 Skill 和回执。 |
| 家人采集题 `question_id=probe:family.2021`,题干却没有年份 | 2021 来自 `evidence_collection_probes``source=age_band``_age_band_year` L290:出生年 + 该领域典型年龄段中点)。**它不是引擎按候选算出的区分年份,只是帮回忆的线索。** BUG-539 因「某年前后…记得大概哪年就行」自相矛盾而把前缀整句删掉,`probe_year` 只留给计分与去重。用户答「不记得了」后家人线关闭,丢掉的是一个回忆线索,不是区分题——此前审计单观察项 2 把它说成「带年探针被吞」,措辞过重,以本单为准。 |
| 家人分支 `sameDomainYearlessCard("family") ?? makeFollowup(采集)`L2578) | 同领域**无年份**性格卡优先级高于带年份线索的采集题,与决策 2 相反。 |
| 「没有」「记不清」走原字匹配 | `route.ts` L524`isCollectDeclineUtterance` / `isCollectSkipUtterance``turn-intent-classifier.ts` L37–43)只认去掉句末标点后**完全等于**「没有」/「记不清」的句子。「不记得了」「记不得」「忘了」「这方面没什么」全部落到 LLM 分类器;而采集题分类器提示词只教了 `no``shouldDeclineCollectFocus` 只认 `no`,没有「跳过」一类。本例「不记得了」靠 Agent 自己调 `rectification-resolve-focus` 兜住。 |
## 2. 任务分解
### T1 · BUG-641(P2):财务 / 健康同权采集,三处口径合一,Skill 升 10.0.22
- SKILL.md L84 删去「财务与健康只有用户主动说才问,仍可计分。」,改为「财务、健康与其他领域同权:服务器按 `method_followup_plan.next_followup` 主动问,用户说了就记、就计分。」其余句子不动。
- Python`VOLUNTEER_ONLY` 清空(或删除常量与 L353 分支),使 finance / health_pressure 进 `missing_collection_domains``evidence_collection_probes`(带 age_band 年份线索)。`DOMAIN_CATALOG` 若缺这两域的 `age_lo/age_hi`,按事业线同段补。`oos_blind_prompts` 语义不变。
- Skill 升版:复制 `versions/10.0.21``versions/10.0.22`,根 `SKILL.md` 与版本目录逐字节相等;`skill-package-registry.json` 新条目(sha256 / sourceCommit / packagePath / status=active);`case-status.ts` `RECTIFICATION_SKILL_VERSION = "10.0.22"`;钉版本的测试(`skill-registry.test.ts``agent-voice-copy-contract``rectification-collect-stall` 等)按「原值 / 新值 / 原因」更新;`CHANGELOG.md` 写明 Skill bump。
- 验收:`grep -n "主动说才问" skills/` 为空;Python 定向测试:`evidence_collection_probes` 对只有学业/感情事件的账本返回含 finance 与 health_pressure 的行;**BUG-621 回归**:绑定 10.0.21 的历史 Case 仍能打开(`open-request.ts` 不要求绑定版本等于当前版本),写进真人清单。
### T2 · BUG-642(P2):带年份线索的采集题优先,题干带线索但不矛盾
- 口径(推翻 BUG-539 的「整句去前缀」,但保留它指出的矛盾不得复现):有 `probe_year` 的采集题题干写成「家里在 2021 年前后有没有结婚、添丁或住院?有就说个大概年月,别的年份也行。」——年份是线索,不是限定;不再同时出现「记得大概哪年就行」。`USER_COLLECT_QUESTION.<domain>` 拆成 `withYearCue(year)` / `withoutCue` 两个模板,文案对照 `frontend/docs/VOICE.md`
- 优先级:方法覆盖链里,每个领域先出**带年份线索的采集题**,`sameDomainYearlessCard` 只在该领域已覆盖(账本有事件 / `answeredYes`)或已拒答后才可出;`nextDatedCollectFollowup``DATED_COLLECT_ORDER` 内先排有 `evidence_collection_probes` 年份线索的领域,再排没有的。带年月的区分点选题(`rankedCatalog` dated)仍在一切采集题之前,不动 10.0.21 规则。
- 验收:`rectification-spoken-collect.test.ts` / `rectification-method-followup*.test.ts`:家人域有 age_band 2021 且有 D12 无年份对照卡时,`next_followup` 是采集题且 `user_prompt` 含「2021 年前后」、不含「记得大概哪年就行」;家人已拒答后才轮到 D12 卡;无 `probe_year` 的领域用无线索模板;BUG-539 的「不得既指定年份又让报年份」断言改写为新模板断言并注明原值/新值/原因。
### T3 · BUG-643(P2):跳过 / 没有一律由分类器判,删掉原字匹配
- 删除 `isCollectDeclineUtterance` / `isCollectSkipUtterance` 及其在 `route.ts` L524 的短路;不得用任何正则或词表替代。
- 采集题分类器提示词(`turn-intent-classifier.ts` 的 collect 分支)加两条明确口径:「没有、没发生过、这方面没什么」→ `answer_current_focus` + `answer_class=no`(该方面没有事,本次不再问);「记不清、不记得、忘了、想不起来、以后再说」→ `answer_current_focus` + `answer_class=unsure`(先放着,以后可补)。带年月的新经历仍按现有规则进 `provide_new_evidence` / `has_new_dated_event``weak_yes` / `yes` 对采集题无意义,返回即视为 `unclear` 走 Agent。
- 路由:`shouldDeclineCollectFocus` 改为 `collectFocusCloseStatus(classified): "declined" | "skipped" | null``no`→declined、`unsure`→skipped,两者都走 `applyCollectFocusDenial`ack 文案沿用 `collectDeclinedAck` / `collectSkippedAck`);`has_new_dated_event` 为 true 时仍 `deferFollowup` 并进 Agent。分类器抛错或返回 null:**不做关键词兜底**,按现状进 Agent(Agent 可调 `resolve-focus`),并在 `RectificationRunDiagnostic``collectIntent=unclassified`
- 多说一句:分类器对每条采集回答都已在跑,本改动不增加模型调用次数,只是删掉两条捷径。
- 验收:`rectification-turn-intent-classifier.test.ts`:「没有」→no、「不记得了」「记不得」「忘了」→unsure、「2021 年搬过家」→provide_new_evidence、「没有,不过 2019 年换过工作」→no + has_new_dated_event`rectification-spoken-collect.test.ts` L442「没有 and 记不清 short-circuit without a model run」改为「由分类器判定 no / unsure 后走 denial」,原值/新值/原因写进进度记录;BUG-626skipped 健康不进 holdout)回归仍过;源码合同测试断言 `route.ts``turn-intent-classifier.ts` 不再导出/引用这两个原字匹配函数。
### T4 · 记录
- `docs/BUG_HISTORY.md` BUG-641643BUG-539 补「口径被 BUG-642 改为带线索模板」;审计单观察项 2 的措辞在本单 §1 已更正。
- `CHANGELOG.md`:财务/健康同权采集;采集题带年份线索;「没有 / 记不清」改由分类器判;Skill 10.0.22。
- `docs/tasks/PROGRESS-rectification-collect-semantics-20260910.md`
- `docs/testing/`:真人清单三条——(a)新 Case 走到财务题,回「没有」与回「不记得了」各一次,分别看到「这方面先跳过」与「这题先放着」;(b)家人题题干带年份线索且能用别的年份回答;(c)打开一个绑定 10.0.21 的历史校正。
## 3. 让步顺序
T2 的「优先级」半边可延后(写 `BLOCKED.md`),T2 的「题干带线索」半边与 T1、T3 不可省。
## 4. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-collect-semantics-20260910 .worktrees/rectification-collect-semantics-20260910 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | tail -1
.venv/bin/python -m pytest tests -k "event_probes or collection" -q
cd frontend && npm test -- tests/rectification-spoken-collect.test.ts tests/rectification-turn-intent-classifier.test.ts tests/skill-registry.test.ts
```
## 5. 验收口径
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build``/` 仍 Static,首屏 gzip ±2%。
- Python 定向测试与 `run_quality_gate.py --profile quick` 通过。
- 推 staging 后 `/api/health``deployment.gitCommit` 等于最新含代码改动的提交;**部署后先打开一个旧版本历史校正**(BUG-621 教训),再做其余真人清单。
@@ -53,7 +53,7 @@ BUG-278 已实测:`createTool` 的 `inputSchema` 校验失败时**不抛**
1. **「记下了」只能由写入事实支撑。** 任何正文含「记下了」且本 attempt 没有公开写工具 completed、也没走 `applyHostFallback` 的,该正文不得到达用户;流式已发出的部分用既有 `retractSpoken()` 收回。这是对系统提示第 4 条格式要求的宿主侧兜底,不是改提示词。
2. **允许重试一次,条件与 BUG-633 完全一致**:仅当本 attempt 没有任何公开写工具 completed(无证据重放风险,不触犯 BUG-186)。新增错误码 `evidence_not_written`,与 `empty_stream` 同法——不进 `RETRYABLE_ERROR_CODES` 常量,由收尾分支按 `publicWriteToolCompleted` 决定 `retryable` / `failed`。重试 attempt 的 bootstrap 追加一行:「【重试约束】上一 attempt 没有调用 rectification-record-evidence-batch 就写了『记下了』。本轮必须先把用户原话里的带日期事件提交 batch,再用 rectification-set-focus 写下一问,最后才写正文。」
3. **两次都没写 → fail closed 但不断流。** 主持人正文用新增文案 `RECTIFICATION_USER_COPY.evidenceNotRecorded` = 「这件我还没记上。请再说一次大概年月和发生的事。」(对照 `frontend/docs/VOICE.md`,不写内部错误、不写「系统」「模型」)。turn `status=completed``answer_origin=host_fallback`phases 含 `answer.host_fallback`;**不结算计费**(用户这一轮什么都没得到)。若现有 `finalizeTurn("completed")` 与结算不可分离,允许改为 `failed` + 主持人正文,但客户端必须按 BUG-633 的失败路径 `loadCaseSnapshot` 并渲染问题行;两种实现选哪种写进进度记录。
4. **守卫的触发信号**:路由在 collect 焦点分支已拿到分类结果,通过 `runV9AgentTurn` 新 option `expectedWrite: "evidence" | null` 传入(`provide_new_evidence`、或 `answer_current_focus``has_new_dated_event``"evidence"`)。分类器为 null / 出错、或消息不在 collect 焦点分支时,退回对用户原话的年份判定 `/(?:19|20)\d{2}\s*年/u`。不得只用正则替代分类器,也不得因分类器失败而放弃守卫`action``opening` / `read_only` 时不触发。
4. **守卫的触发信号**2026-09-10 产品口径修订:业务判断不得靠正则):路由在 collect 焦点分支已拿到分类结果,通过 `runV9AgentTurn` 新 option `expectedWrite: "evidence" | "none" | "unknown"` 传入(`provide_new_evidence`、或 `answer_current_focus``has_new_dated_event``"evidence"`)。消息不在 collect 焦点分支时,对用户原话跑一次同一分类器(无焦点时 `current_question` 传空),拿 `has_new_dated_event` / `provide_new_evidence` 判定。分类器为 null / 出错:重试一次,仍失败则 `"unknown"`,守卫 fail-open 并在 `RectificationRunDiagnostic``expectedWrite=unknown`**不得用年份正则或关键词兜底**`action``opening` / `read_only` 时不触发。
5. **客户端**:完成轮的 assistant 行没有 `question`,而快照 `current_question.question_source === "focus"` 且该焦点不是本轮新设(`askedTurnId` ≠ 本轮 / 本轮回执无 `rectification-set-focus`)→ 在主持人正文下**再渲染一次**问题行(复用 BUG-633 加的主持人问题行,`focus_id` 只能来自 `current_question`)。`liveQuestionOnMessages` 改为只认**最后一条** settled assistant 消息;旧消息上的同 `focus_id` 不再算「仍在显示」。
6. **BUG-636**`tool-result` 分支按结构识别信封(`error === true``validationErrors` 为对象),发 `tool.activity` `status=failed``code=tool_call_rejected``toolTerminalStatus` 记 failed`batchResultFromToolChunk` 对信封返回 null。不匹配上游英文文案。公开回执不泄漏 `validationErrors` 内容。
7. 不做的事:不改 `turn-intent-classifier.ts` 提示词;不改 Skill 10.0.21;不改引擎计分与淘汰阈值(「范围没变」另议);不给财务/健康域加特殊规则。
@@ -73,7 +73,7 @@ BUG-278 已实测:`createTool` 的 `inputSchema` 校验失败时**不抛**
- `route.ts` collect 焦点分支:分类结果落到 Agent 的两种情形计算 `expectedWrite`;其他路径传 `null`,由运行器按年份正则兜底。
- `V9AgentRunOptions` 新增 `expectedWrite?: "evidence" | null`
- 验收:单测覆盖 `provide_new_evidence``"evidence"``answer_current_focus` + `has_new_dated_event``"evidence"``answer_current_focus` + `no` 不进 Agent(既有);分类器抛错 → `null`;运行器对 `null` + 带年份原话判定为需要写入,对 `null` + 无年份原话不触发
- 验收:单测覆盖 `provide_new_evidence``"evidence"``answer_current_focus` + `has_new_dated_event``"evidence"``answer_current_focus` + `no` 不进 Agent(既有);分类器两次都抛错 → `"unknown"` 且守卫不触发、诊断里有记录;源码合同断言 `agent-run.ts` / `route.ts` 不含年份正则
### T2 · 收尾守卫:无写入的「记下了」不得交付,可重试一次(BUG-635)