- README.md is now the product/repo front door (architecture, repo map, local dev, test tiers, delivery flow, doc map). Engine positioning, VedAstro/Codex setup and the oracle/benchmark command reference move verbatim to docs/engine/README.md, docs/engine/vedastro-gateway.md and docs/benchmark/README.md. Capability badges realigned with the registry (91/78/8/0); tests/test_readme_badges.py was red on staging. - AGENTS.md: Part A (environment truth, delivery, worktrees, record placement, bug workflow, growth freeze, frontend red lines, privacy, pre-work check, test tiers) and Part B (reading-rigor constraints). GitHub issue-tracker/triage boilerplate removed: GitHub is a read-only mirror. All strings locked by tests/ are preserved. - CLAUDE.md added: roles, three working modes, task-brief sections, acceptance criteria, session discipline; imports AGENTS.md. - 50 tracked TASK-*/PROGRESS-* files and 3 never-committed briefs move to docs/tasks/ with an index; REPO_LAYOUT.md merged into README. Docs-only change (no gated path touched). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
9.2 KiB
任务书 · 一道题选「一时说不好」就交付:不确定度停止规则缺样本下限(2026-09-03)
基线:origin/staging 1d77b5b1。本单独立于 TASK-rectification-adopt-flow-fix-20260903.md(BUG-501/502),但建议由同一实现者串行做:若 codex/rectification-adopt-flow-fix-20260903 尚未合入 staging,就在该分支之上继续,避免 docs/BUG_HISTORY.md 与测试文件冲突。
0. 真实环境复现(用户 09-03 反馈,事实)
用户走完开场、给了几件带日期的事之后,整场只收到一道选择题:
说到这几段短暂的恋情,回想一下你自己在亲密关系里更接近哪一种相处方式? A. 把日子过稳当,也在意关系里的安全感 / B. 爱聊、爱换话题,新鲜感一过就容易飘 / C. 把对方当家人养,情绪来了也藏不住 / D. 一时说不好
用户选 D。系统回复"已记录你的选择,并更新了候选比较。"随后直接交付:
已经从最初的 60 分钟收到 14:02–14:43 这 41 分钟。代表分钟是 14:43。当前最优结果是候选时间段,而不是已经确认的唯一出生分钟。临时代表时间仅用于下一轮验证与比较。
用户原话:"41 分钟跟没做有什么区别"、"全程就反馈给我一个问题,我选择了'一时说不好'就直接给我定了时间"。
1. 根因(静态核对,不是猜)
frontend/src/lib/rectification-agentic/v9/decision-from-dossier.ts uncertaintyHighFromAnswers(~529):
const userAnswers = answers?.filter((item) => item.classified_from === "choice" || item.classified_from === "declined") ?? [];
if (userAnswers.length === 0) return false;
const uncertain = userAnswers.filter((item) => item.answer_class === "unsure" || item.classified_from === "declined").length;
return uncertain >= Math.ceil(userAnswers.length / 2);
没有样本下限:1 道题选 D → 1 >= ceil(1/2) = 1 → userUncertaintyHigh = true → core/rectification-decision.ts classifyStop 返回 { kind: "exhausted", reason: "user_uncertainty_too_high" }(~214)→ decideRectification 在 !separation.sufficient 之前就命中 completeWithRange(…, "exhausted")(~297)→ canAdopt 为真时 sessionOutcome = adopt_representative → answer-choice.ts persistApplied 用 deliveryAdoptNarration 出交付话术,shouldContinueAfterStructuredChoice 因 nextAction 不是 ask_* 而不续跑。第一道题选"说不好"就等于按了"先这样"。
同一方向还有一条会在第二道题触发:apply-probe-outcome.ts 把 unsure 记成 low_information 轮(~55),decisionBudgetFromInference 连续 low_information 计入 plateauRounds,maxPlateauRounds = 2(references/rectification_policy.v1.json)→ budgetExhausted → 同样 completeWithRange。即连着两道选 D 也立刻交付。
这条规则来自 TASK-rectification-convergence-20260830.md 任务 6 引入的上游 Stop Rule "user uncertainty is too high to map support/conflict reliably"(3a4396a4)。上游语义是"样本够多且大半答不上来",本仓实现成了"任意一道答不上来"。
两个旁证:
- D 答案
deltas全 0,却仍走appliedInference: true的旁白"已记录你的选择,并更新了候选比较。"——没更新任何比较。 - 交付话术不说明为什么停(
user-copy.tsnonConvergingRangeNarration/deliveryAdoptNarration不接stopReason),用户只看到"收到 41 分钟",不知道是自己的"说不好"把访谈停掉的。
2. 修法
2.1 P1 · 不确定度停止加样本下限(BUG-503)
references/rectification_policy.v1.json增加"minUncertaintyAnswers": 3(scripts/rectification_policy.py读同一文件,加键不影响它;Python 侧不必读这个键)。uncertaintyHighFromAnswers:userAnswers.length < RECTIFICATION_POLICY.minUncertaintyAnswers→ 返回false;达到下限后沿用现有"≥ 一半"判定。classified_from === "declined"继续计入不确定,但同样受下限约束。- 平台期:
decisionBudgetFromInference的plateauRounds只在userAnswers.length >= minUncertaintyAnswers后才对unsure轮计数——实现方式二选一,实现者定并写进 BUG 条目:(a) 在decisionBudgetFromInference里按answered_probes的用户答题数做同样的下限门;(b)apply-probe-outcome.ts对unsure返回新 kind"unsure",plateauRounds只数low_information(即真正"答了但分不开"的轮),并把ROUND_KINDS/compose-receipt.ts/core/types.ts同步。不得改maxPlateauRounds = 2本身。 answer-choice.tscomposeChoiceNarration:answer_class === "unsure"时旁白改为"已记录。这题先不计分,换一件事问。"——choice-action.ts加unsure分支,appliedInference语义不变(仍持久化 inference 行,只是话术不说"更新了比较")。
2.2 P2 · 交付时说出停止原因
persistApplied 的 adoptionNarration / completedRangeNarration 在 stopReason 非空时,在 progressClause 之前加一句原因(放 user-copy.ts,进 listUserVisibleCopy):
user_uncertainty_too_high:前面几道题你多半选了"说不好",再问下去也分不开,先停在这里。tied_first:几个候选打成平手,问题已经分不开它们。insufficient_dated_events/insufficient_domains走的是collect,不在此列。decisionReceipt已有stopReason,前端不需要新字段;nextAction公开stop_reason若尚未暴露则加上(只读字段,publicNextAction)。
3. 硬红线
- 采用门 / 确认门语义不动:
publicCanAdopt、sessionOutcomeAllowsAdopt、确认门 fail-closed 一律不改;本单只改"什么时候算 exhausted"。 maxPlateauRounds、DEFAULT_MAX_DISCRIMINATION_ROUNDS = 8、EFFECTIVE_ANSWER_SAFETY_CAP = 10三个外层熔断不动。- 用户主动「先这样」(
userStopped)仍立即交付,不受本单影响。 - 选项、answer_class、计分服务端所有;不改 Python 引擎;Skill 10.0.14 不 bump。
./node_modules/.bin/tsc --noEmit通过(不用npx tsc);npm run lint --prefix frontend0 错误;rectification-*/consultation-*/consult-*/chat-*测试 fail=0;改动的既有断言逐条三栏说明(旧 → 新 → 保留语义)。- 无凭据不得声称已真实环境验证。
4. 测试
rectification-convergence-budget.test.ts"dossier and post-inference decisions share the half-uncertain stop rule"(~338):现在的halfUncertain(2 答 1 unsure)与halfDeclined(2 答 1 declined)断言user_uncertainty_too_high→ 改为stopReason ?? null === null(旧:2 答 1 不确定即停 → 新:不足 3 答不停 → 保留语义:达到下限后一半不确定仍停)。补三条:1 答 1 unsure → 不停且 nextAction 仍是ask_candidate_discriminator(给一个可用 probe);3 答 2 unsure →user_uncertainty_too_high;4 答 2 unsure → 停(一半规则不变)。evidenceDoesNotDilute用例改成 3 答 2 unsure + 3 条 evidence 行,语义保留(evidence 行不稀释)。- 平台期:
rectification-inference-machine.test.ts或新用例——连续 2 道 unsure、用户答题数 < 3 →budgetExhausted为假、决策仍出 probe;答题数 ≥ 3 后连续 2 道 unsure → 停。 rectification-answer-choice.test.ts:unsure 的旁白为"已记录。这题先不计分,换一件事问。",appliedInference仍为 true。- 用户可见文案:新增两句进
listUserVisibleCopy,rectification-user-copy相关测试(含"不得/请写成"指令腔过滤)通过。 - 用 §0 场景写一条端到端形状测试(fake accounting):3 条带日期证据、1 个可用 probe、用户答 D → 返回体
nextAction.type === "ask_candidate_discriminator"或至少不是adopt_representative,且持久化了下一道题。
5. 开工前置
git fetch origin --prune
# 若 codex/rectification-adopt-flow-fix-20260903 未合入,则以它为起点:
git worktree add -b codex/rectification-uncertainty-stop-20260903 \
../.worktrees/rectification-uncertainty-stop-20260903 origin/codex/rectification-adopt-flow-fix-20260903
# 否则以 origin/staging 为起点。
读 TASK-rectification-convergence-20260830.md 任务 6(停止规则来源)、references/upstream/evidence_thresholds.md、docs/BUG_HISTORY.md(BUG-463 起的收敛链)。BUG 编号 BUG-503(复发自:无;引入于 3a4396a4);§2.2 可并入 503 或独立成条。行号是线索,按符号名定位。
6. 验收标准
- §0 形状:1 道题选 D → 不交付,继续出下一道题;连续 2 道 D 且答题数 < 3 → 仍不交付。
- 3 道题 2 道 D → 交付,且交付话术第一句说明"多半选了说不好"。
- D 答案旁白不再说"更新了候选比较"。
minUncertaintyAnswers只在references/rectification_policy.v1.json定义一处。- tsc + lint + 四组测试 fail=0;BUG_HISTORY 追加 BUG-503;三栏说明齐。
- 真实环境人工清单(部署后):开场给 3 件带日期的事 → 第一道选择题选 D → 应继续出题而不是交付;连选 3 道 D → 交付并写明原因。