docs(tasks): 咨询追问轮不调排盘工具即整轮失败的任务书(BUG-922/923)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
Jesse_Chen
2026-09-17 06:10:16 +00:00
co-authored by Claude Fable 5.1
parent 1b508d5e9a
commit 6d097de95b
2 changed files with 127 additions and 0 deletions
+1
View File
@@ -129,6 +129,7 @@
| `TASK-rectification-tiebreak-card-loss-20260915.md` | `PROGRESS-rectification-tiebreak-card-loss-20260915.md` | **P0**:点卡上「再答两道参考题」交付卡消失(BUG-706);按钮亮但选项建不出变成裸题(BUG-708);旁白写「相对支持度」并与卡上入口打架(BUG-709)。卡上入口删除,出卡前收集,有活题时卡留下、采用置灰 | 待验收 | `codex/rectification-p0-20260915` |
| `TASK-rectification-p0-fix-20260915.md` | `PROGRESS-rectification-p0-fix-20260915.md` | **验收修复单**`f51e494c` 六条缺陷全部实现且方式正确,但 `page.tsx` 从 1951 涨到 1964 行,撞了 `chart-view-route.test.ts``<= 1951` 上限(AGENTS.md §6 增长冻结)。全量 fail 32→33,就这一条。门禁红很可能是 staging 停在 `2d7698ea`、6 个提交未部署的原因。修法是把 BUG-705 的十来行接线搬出 page.tsx,不放宽上限 | 待验收 | `codex/rectification-p0-fix-20260915` |
| `TASK-settings-dialog-size-and-nav-20260915.md` | — | **复发单**:设置弹窗四个分区尺寸仍随内容跳变(BUG-698,复发自 BUG-554——旧防复发只查「有没有写 height」,查不到「写了没生效」);首要嫌疑是 `.settings-modal``dvh` 没有 `vh` 回退,不支持时整条 `height` 作废退化成内容高度,需先复现确认。另按产品要求去掉分区菜单左侧强调条,并拆开与悬停共用的选中态 | 待领取 | `codex/settings-dialog-size-and-nav-20260915` |
| `TASK-consult-followup-tool-contract-20260917.md` | `PROGRESS-consult-followup-tool-contract-20260917.md` | 真机:申报时段会话连发「?」「你在说什么鬼」都 `run.failed runtime_contract_incomplete`,回执无任何 `tool` 步骤。根因是 Agent 系统指令写明「简单追问可复用已有 packet / context、不调工具」,而 `contractReady()` 要求每次请求恰好一次成功排盘调用;「已有 packet」跨请求并不存在(缓存只在单次请求内)。本命与窗口两个 Agent 同构。**产品拍板方案 1**:每轮必调工具(BUG-922 删例外句 + BUG-923 第 0 步 `toolChoice: required`);否决「没调工具就走不扣点纯对话」。第一轮正经问题为何失败留 T4 取证(回执只在 web 容器日志) | 待领取 | — |
### 个人报告
@@ -0,0 +1,126 @@
# 任务书 · 咨询追问轮不调排盘工具即整轮失败:提示词与运行合同对齐(2026-09-17)
## 0. 基线
- 基线 commit`1b508d5e``origin/staging` head)。
- 分支:`codex/consult-followup-tool-contract-20260917``git worktree add -b codex/consult-followup-tool-contract-20260917 .worktrees/consult-followup-tool-contract-20260917 origin/staging`
- 范围:只有前端(`frontend/src/mastra/index.ts``frontend/src/mastra/consultation-tools.ts``frontend/src/app/api/consult/route.ts` 与对应测试)。不动 Python、不动迁移、不动 Skill、不动生时校正 Agent。
- BUG 段:**BUG-922 起**(基线 `docs/BUG_HISTORY.md` 最大号 BUG-921,开工时再核对一次)。
## 1. 事故实证
产品负责人 2026-09-17 在 staging 一个申报出生时段(`declared_birth_window`)会话里连发三轮,三轮都没有得到回答。第三轮的事件流原样如下(已去掉会话与用户标识):
| 事件 | 内容 |
| --- | --- |
| 请求 | `consultationMode: declared_birth_window``theme: general``question: 你在说什么鬼`;服务端历史只有两条用户消息「请帮我看看我家庭关系的整体模式和特点」「?」,**没有任何助手消息** |
| `skill.started` / `skill.completed` | `jyotish-vedic-astrology` |
| `activity` | `phase: loading-method`,「正在补齐方法与计算步骤」 |
| `run.failed` | `code: runtime_contract_incomplete`,「Agent 未完成必要的方法与计算步骤,本次不会扣点。」 |
| 回执 `steps` | 只有两步:`skill``validation: runtime-contract-retry`**没有任何 `tool` 步骤** |
| 回执 `workflow` | `route: declared-birth-window``status: blocked``techniqueTruth: declared-window` |
代码定位(按符号):
- 门禁:`frontend/src/lib/stream-agent-response.ts``contractReady()``requireTool` 为真时要求 `consultationToolCompleted && consultationToolSuccessCount === 1`。第一轮不满足则调用 `options.retry()` 再跑一轮,仍不满足即 `throw new Error("runtime_contract_incomplete")`,模型已写出的文字全部丢弃(`consumeAttempt` 在契约未绿时不释放文本,见 BUG-286 与测试「text written before the contract completes is dropped」)。
- 申报时段路线:`frontend/src/app/api/consult/route.ts``shouldRunDeclaredWindowWorkflow` 分支调用 `streamAgentResponse({ requireTool: true, retry, ... })``retry` 的追问原文是「运行合同不完整:本次尚未取得声明窗口计算结果。请调用 run-jyotish-window-consultation 完成计算,再据此回答」。
- 窗口 Agent 系统指令:`frontend/src/mastra/index.ts``windowJyotishInstructions`,含这一句:**「For questions that need personal chart structure, call run-jyotish-window-consultation before answering. Simple conversational follow-ups may use the existing packet.」**
- 本命 Agent 系统指令:同文件 `jyotishInstructions`(第 17 行起,含 `natalSpokenReportContract` 之后的第 6 行)含同构的一句:**「For questions that require a new chart claim, call run-jyotish-consultation before answering. Simple conversational follow-ups may use the existing context.」**
- 工具选择:两条路线都是 `toolChoice: "auto"`。本命路线 `consultationNatalPrepareStep``frontend/src/mastra/consultation-tools.ts`)在第 0 步只把 `activeTools` 收窄到排盘工具,但没有强制调用;申报时段路线用的是不带 `prepareStep``streamOptions`
- 历史为什么只剩用户消息:用户消息在扣点预留 RPC 里就写库(`route.ts``p_question_message: { role: "user", text: visibleQuestion }`),运行失败不补助手消息。所以前两轮「家庭关系」「?」也都失败了。
## 2. 根因
**系统提示词与运行合同互相矛盾。** 提示词明确允许模型对「简单的对话式追问」不调排盘工具、复用「已有 packet / context」;服务端合同却要求每一次请求都必须恰好一次成功的排盘工具调用,否则整轮作废。像「?」「你在说什么鬼」这类追问,模型按提示词判断为对话式追问、直接用文字回答,门禁判定合同未完成;服务端追问一句后模型仍按同一套系统指令行事,第二轮也不调工具,于是 `runtime_contract_incomplete`
提示词里的「existing packet / existing context」还是**事实错误**:排盘结果只在单次请求内缓存(`createWindowConsultationTools` / `createConsultationTools` 的闭包变量 `calculation`),跨请求根本没有可复用的 packet。模型信了这句话就会跳过工具。
这两句是 2026-08-21 提交 `9958e00a` 写进去的,门禁是 2026-08-17 提交 `7886629b` 定的,两者从未对齐。Bug 历史里 `runtime_contract_incomplete` 的既有记录(BUG-205 缓存拒绝、BUG-214 失败次数计入门禁、BUG-286 契约未绿文本丢弃)都不是这个原因,防复发措施均仍在,本单是新问题,不是复发。
第一轮「请帮我看看我家庭关系的整体模式和特点」是正经解盘问题,它为什么失败**本单不下结论**,见 T4。
## 3. 决策记录
- 产品负责人 2026-09-17 拍板 **方案 1**:删掉「简单追问可以不调工具」的例外,**每一轮回答前都必须调用排盘工具**。理由:工具在单次请求内有缓存、代价小;每轮都有当轮证据,符合「解盘必须有原始数据」红线。
- **否决方案 2**(保留提示词、让没调工具的轮次走不扣点的纯对话回复):追问轮会没有星盘证据,与 AGENTS.md Part B 的证据要求冲突。
- 不推翻任何既有红线:`contractReady()` 的「恰好一次成功调用」口径不放宽;BUG-214 允许失败尝试后重试成功的语义保持;BUG-286 契约未绿即丢弃文本的语义保持。
- 生时校正 Agent`agentic-rectification.ts`)不在本单范围,它有自己的合同。
## 4. 硬红线
1. 不改 `contractReady()`;不改 `retry` / `retryForAnswer` 的触发条件;不给失败轮扣点。
2. `page.tsx``jyotish_api_server.py` 不动(AGENTS.md §6)。
3. 既有测试断言不得静默弱化;改任何断言写「原值 / 新值 / 原因」三栏。
4. 不顺手改 Skill 文件、不 bump Skill 版本(本单只改 Agent 系统指令与 SDK 调用参数)。
5. Bug 历史与进度记录不得写入会话 ID、用户 ID、出生资料。
## 5. 任务分解
### T1 删掉两处「简单追问可跳过工具」例外(BUG-922)
- `windowJyotishInstructions`:把「For questions that need personal chart structure, call run-jyotish-window-consultation before answering. Simple conversational follow-ups may use the existing packet.」改为「Call run-jyotish-window-consultation before answering **every** turn, including short follow-ups, clarifications, and complaints; the packet is request-scoped and is never carried over from an earlier turn.」
- `jyotishInstructions`:同样改「For questions that require a new chart claim… Simple conversational follow-ups may use the existing context.」为「Call run-jyotish-consultation before answering every turn, including short follow-ups; the calculation is request-scoped and is never carried over from an earlier turn.」
- 全文再 grep 一次 `follow-ups may use`,确认 `frontend/src` 无残留。
- 验收:
-`frontend/tests/consultation-birth-time-mode.test.ts`(它已经读窗口 Agent)或 `consultation-agentic-runtime.test.ts` 加契约:两段指令 `doesNotMatch(/follow-ups may use the existing/)``match(/before answering every turn/)`
- 既有「personal Agent exposes the Jyotish Skill and named server tool」等测试原样通过。
### T2 第 0 步强制调用排盘工具(BUG-923)
提示词只是软约束,门禁是硬约束,中间应该有一层确定性的 SDK 参数把二者钉在一起。
- `consultationNatalPrepareStep`:第 0 步在 `activeTools: [CONSULTATION_NATAL_CALC_TOOL_ID]` 之外把 `toolChoice` 改为 `"required"`;后续步骤保持 `"auto"`
- 申报时段路线:新增 `consultationWindowPrepareStep`(同文件、同形状:第 0 步 `activeTools: ["run-jyotish-window-consultation"]` + `toolChoice: "required"`),并在 `route.ts` 的申报时段分支把 `streamWithOverflowRetry(agent)``retry` 改为使用带该 `prepareStep` 的选项对象(照本命分支 `natalStreamOptions` 的写法建 `windowStreamOptions`)。
- `retryForAnswer` / `continueAfterLength` / `composeSection` 不带强制(它们是在合同已绿之后跑的,本命分支的 `composeSection` 明确 `toolChoice: "none"`,不动)。
- 开工先确认本仓 Mastra / AI SDK 版本的 `prepareStep` 接受 `toolChoice: "required"`(查 `node_modules/@mastra/core``ai` 的类型定义,写进进度记录)。
- 验收:
- `consultation-agentic-runtime.test.ts` 既有「natal first step exposes only the chart calculation tool」扩成断言第 0 步 `toolChoice === "required"`、第 1 步 `"auto"`;为窗口 `prepareStep` 加对称的一条。
- 加一条 `route.ts` 源码契约:申报时段分支的 `agent.stream(` 调用与 `retry` 使用带 `prepareStep` 的选项(对齐 `consultation-stream-recovery.test.ts` 那类结构断言的写法)。
- 已有的「a calculation that succeeds only after failed attempts still satisfies the contract」「text written before the contract completes is dropped」原样绿。
### T3 记录
- `docs/BUG_HISTORY.md` 新增 BUG-922、BUG-923:现象、触发条件(申报时段 / 本命模式下发短追问)、根因(提示词例外 vs 合同)、修复、验证、防复发(契约测试 + 第 0 步 required)、关联 BUG-205 / 214 / 286。
- `CHANGELOG.md` 一条:追问、反问、短句也会先重新取回本轮星盘证据再回答,不再整轮失败。
- `docs/tasks/PROGRESS-consult-followup-tool-contract-20260917.md`:测试数字、失败清单与基线逐条比对、gzip 前后、`prepareStep` 类型核对结果。
### T4 第一轮失败原因取证(不占 BUG 号,写进进度记录)
第一轮是正经解盘问题,不能用 T1 的根因解释。回执只写到 web 容器控制台(`frontend/src/lib/agent-observability.ts``console.info("[agent-observability]", …)`),不进数据库。执行方**做不了**(无 VPS 登录态),把下面的取证方法原样写进进度记录,留给产品负责人或部署侧:
```bash
# 在 VPS 上,以 deploy 用户
cd /opt/jyotisha-production
docker compose -f deploy/docker-compose.server.yml logs web --since 2026-09-17T00:00:00 \
| grep agent-observability | grep '<该会话的 sessionId>' | head
```
三条 `run.failed` 各自的 `steps` 若都没有 `tool` 步骤,即同一根因、本单覆盖;若第一轮有 `tool` 步骤且 `status: failed`,另立单,不得并进本单。
## 6. 让步顺序
1. T2 若本仓 SDK 版本的 `prepareStep` 不接受 `toolChoice: "required"`(类型或运行时报错),退而只做 T1,并在进度记录与 `BLOCKED.md` 写明版本与报错原文;不得顺手升级依赖。
2. T2 若 `required` 让本命第 0 步在 `generalDailyContext`(首页今日)路径误触发(那条路径 `requireTool: false`,用的是 `getGeneralJyotishAgent`,理论上不经过 `natalStreamOptions`),先用测试证明,再决定是否只对申报时段路线加 required。
3. T1、T3 不可让步。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/consult-followup-tool-contract-20260917 .worktrees/consult-followup-tool-contract-20260917 origin/staging
cd .worktrees/consult-followup-tool-contract-20260917/frontend
./node_modules/.bin/tsc --noEmit && npm run lint && npm test 2>&1 | tail -20 # 记下基线总数与失败清单
grep -o "BUG-[0-9]\+" ../docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 BUG-921
grep -rn "follow-ups may use" src # 应恰好 2 处
```
验收口径:`tsc` 0 错;lint 0 error`npm test` 失败清单与基线逐条一致(无 Docker 的那组不算新增)且总数不减;`next build``/` 仍 Static;首屏 gzip ±2%(本单不动客户端 bundle,应为 0 变化)。
## 8. 环境缺口(留给产品负责人真机)
部署后(先核对 `/api/health``deployment.gitCommit` 等于本单合入 staging 的 SHA)在申报时段模式的会话里:
1. 先问一个正经问题(例如「看看我事业上的整体特点」),应正常出回答。
2. 紧接着发「?」,再发「你在说什么鬼」。两轮都应先出现「正在比较声明出生窗口内的稳定层」再出回答,**不得**出现「Agent 未完成必要的方法与计算步骤」。
3. 换到本命模式(有出生分钟)的会话重复第 2 步,同样不得整轮失败。
4. 同一会话刷新页面后,历史里应能看到助手回复,不再是只有用户消息。