# TASK · 记完经历后 `rectification-set-focus` 连续失败六次(`tool_failed`),原始错误无处可查(2026-09-12) - 基线:`origin/staging` @ `529fcc09`(已部署;含 BUG-656~658) - 分支:`codex/rectification-set-focus-tool-failed-20260912`,基于 `origin/staging` - 执行方:coding agent;验收:Claude - BUG 编号起点:**BUG-659**(开工时核对 `docs/BUG_HISTORY.md` 最大号,应为 658) - 不改引擎、不改决策门、不升 Skill ## 1. 事故实证(2026-09-12 13:06 staging 真机,代码 529fcc09,Skill 10.0.24) 开场 → 用户说两件学业(2016-09 入学、2020-06 毕业)→ 本轮工具回执:`rectification-read-case` 完成、`rectification-record-evidence-batch` 完成(1.7 s)、`rectification-set-focus` **失败**,界面显示「设置对话焦点未完成」六次;回执 `detail = {error:"tool_failed", safe_error_code:"tool_failed"}`,`elapsed_ms: 10`。本轮 `answer_origin = host_fallback`,正文「记下了:2016-09 2016年9月上大学(本科入学)、2020-06 2020年6月毕业。」(日期重复)。GET 快照:`current_question = collect:invite:more`(active,焦点 15ef4f5c…),`next_user_action = ask_method_followup`。用户没有被卡住,但这一轮多等了约 20 秒、六次无效重试、正文是宿主兜底文案。 ## 2. 已定事实(按 529fcc09 源码) 1. **「还有吗」这个焦点不是 set-focus 写的,是 batch 工具顺手写的。** `rectification-v9-tools.ts` L963–972:记完证据后同一工具内调 `persistServerOwnedFocus(followup = 计划的 next_followup)`,把 `collect:invite:more` 落成 active。随后模型再调 `rectification-set-focus`(同一 questionId)就走 RPC 的「已存在同 id 焦点」分支。 2. **`tool_failed` 是兜底码**:`safeToolErrorCode` 的已知列表(`tool-service.ts` L2030 起)之外的任何异常都记成 `tool_failed`;`invalid_case_id / invalid_domain / invalid_event_kind / invalid_focus` 这几个工具自己抛的码也**不在列表里**,同样显示为 `tool_failed`。set-focus 的失败回执(L1183)只写 `safeErrorCode`,没有任何地方记录原始错误文本;`console.warn` 也没有。所以六次失败的真实原因目前无法从库或日志判断。 3. 可疑点按代码排序:(a) RPC `set_agentic_rectification_conversation_focus`(`20260902010000` 迁移 L74–111)对同 id 的 active 焦点做 identity 比较(schema 去 prompt),batch 写入的 schema 与 set-focus 工具写入的 schema(`serverOwnedExpectedAnswerSchema` + `withSpokenPrompt`)若键集合不同即抛 `focus_idempotency_conflict`——但该码在已知列表里,理论上不会显示为 `tool_failed`,需实测排除;(b) 模型把 `next_followup.kind_hint`(`invite_more` / `targeted:*` / `anchor:*`)抄进 `targetKind`,zod `evidenceKindSchema` 拒绝 → Mastra 以 `validationErrors` 返回,`mapStreamChunkToActivity` 应记成 `tool_call_rejected` 而不是 `tool_failed`,与回执不符,也需实测排除;(c) 工具内 `assertCaseRef` / `invalid_domain` / `invalid_event_kind` 抛出(模型传错 caseId 或 domain)→ 正是 `tool_failed`;六次同样失败、每次 10 ms,与「入参错、无 DB 往返」相符。 4. **宿主兜底正文日期重复**:`host-fallback.ts` `recapLine` 把 `display_date_label`(`2016-09`)和模型写的 `event_phrase`(已含「2016年9月」)直接拼接。 ## 3. 决策记录(产品负责人 2026-09-12) 1. 工具失败必须可诊断:失败回执的 `resultFingerprint` 写入 `engineMessageForReceipt(error)`(脱敏、≤120 字),并 `console.warn` 一条 `rectification_tool_failed {tool, case_id, code, message}`;`invalid_case_id / invalid_domain / invalid_event_kind / invalid_focus / tool_call_rejected` 进入已知列表,界面按码显示原因。 2. 同一 questionId 的焦点已由服务端落下时,set-focus 工具**幂等返回**已存在的焦点(不比较 schema 键集合,只更新 spoken_prompt),不得抛错让模型重试。 3. 模型对 set-focus 的 `targetKind` 只允许证据种类或空;`kind_hint` 类提示由服务端忽略而不是拒绝(schema 放宽为 string,服务端 clamp 为 null)。 4. 兜底文案:`event_phrase` 已含年月时不再前缀 `display_date_label`。 ## 4. 硬红线 - 不改 DB 迁移;不改 Skill;不动 BUG-656~658 的 kind 映射与刷新逻辑。 - 记录不含真实用户资料。 ## 5. 任务分解 - **T0 取证**:在 staging 容器日志里找 13:06:28 前后本 Case 的 set-focus 六次调用(若无日志即证明 §2.2);本地用夹具复现 §1 序列的第二轮,分别构造 (a)(b)(c) 三种入参,记录各自的回执码,写进进度记录第一段。 - **T1 失败可诊断(BUG-659)**:按 §3.1 改 `rectification-v9-tools.ts` 的失败回执与 `safeToolErrorCode` 已知列表;`mapStreamChunkToActivity` 对 `tool-error` 也带 `code`。验收:合同测试断言三类工具内抛错的回执码各自可辨,`tool_failed` 只剩真正未知错误。 - **T2 同 id 幂等 + targetKind 放宽(BUG-659)**:按 §3.2 / §3.3;set-focus 在 RPC 前先读活动焦点,同 id 直接返回(写 spoken_prompt 用 `linkFocusAskedTurn` 或更新 schema)。验收:batch 已落 `collect:invite:more` 后调 set-focus 同 id → `status=completed`、`idempotent=true`;传 `targetKind:"invite_more"` → 不拒绝、落库 `target_kind=null`。 - **T3 兜底文案(BUG-660)**:`recapLine` 在 `event_phrase` 含 `\d{4}年` 或以 `display_date_label` 开头时只用 phrase。验收:夹具「2016-09 / 2016年9月上大学」→「记下了:2016年9月上大学」。 - **T4 记录**:BUG-659/660 入历史(关联 BUG-650 host_fallback、BUG-442 工具路径);CHANGELOG;PROGRESS。 ## 6. 让步顺序 T1 + T2 不可省;T3 可并入下一单。 ## 7. 开工前置命令 ```bash git fetch origin --prune git worktree add -b codex/rectification-set-focus-tool-failed-20260912 .worktrees/rectification-set-focus-tool-failed-20260912 origin/staging grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 658 cd frontend && npm test -- tests/rectification-v9-agent.test.ts tests/rectification-server-focus.test.ts tests/rectification-collect-prompt.test.ts ``` ## 8. 验收口径 - 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build` 后 `/` 仍 Static、首屏 gzip ±2%。 - 部署后真机:开场说两件学业,本轮工具回执不得出现 `tool_failed`;正文由模型或兜底生成时日期不重复。