From 3bb02a119ba5a9c9cdcaa6e830008b8c76a6ee3f Mon Sep 17 00:00:00 2001 From: weichen Date: Tue, 13 Oct 2026 09:21:29 +0000 Subject: [PATCH] Guard final-only continuation in the U3 plan Why: Hiding a tool declaration alone does not prevent a model from calling a tool already in the runtime registry. Evidence: The agent-native loop continues after tool errors; the plan now requires a local stop latch before any later provider call. Impact: Clarify the zero-paid test and implementation boundary without changing the approved design or authorizing model usage. Refs: Agent-System-Documents#95; Agent-System-Dev#512 --- ...6-10-13-fast-slow-u3-quality-implementation-plan.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md b/docs/proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md index 09bbaa8..cada1ec 100644 --- a/docs/proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md +++ b/docs/proposals/2026-10-13-fast-slow-u3-quality-implementation-plan.md @@ -142,7 +142,7 @@ **Interfaces:** `type materialBudgetState struct { Limit, Used, LastPrompt, OutputCap, SavedCheckpoints, Responses int; UsageKnown bool }` 与 `materialFinalDecision(s materialBudgetState) (finalOnly, stop bool)` 只处理 `agent.sub.material`。`Responses=0` 是尚未产生调用的初态;已有响应但 Usage 未知则 fail-closed。增长余量 `margin=max(1024, LastPrompt/4)`;最终预留 `reserve=LastPrompt+OutputCap+margin`,再做一次检查点的估算成本也取同一值。`Limit-Used <= LastPrompt+margin` 时停止;否则 `SavedCheckpoints>=2` 或 `Limit-Used <= 2*reserve` 时进入 final-only。首个响应前不套用该估算。这样保留一次最终请求的空间,且一次成果后发现下一次发布会挤占预留时优先收尾,明确这是部分结论而非第二项。final-only 不再向 provider 声明 `publish_work_checkpoint`,并添加系统拥有的简短最终答复指令;若剩余额度不容纳预计最终 prompt,则不发新请求,保留已保存卡和真实失败状态。它不保证在途请求不越过总目标。 -- [ ] **Step 1: 写 RED。** `material_final_reserve_test.go` 用假 stream 顺序返回用量,断言“初始零响应允许首个请求”“两个合法检查点→下一次仅最终答复”“只有一个检查点且额度临界→真实部分最终答复”“额度不足→0 次额外 provider 请求”“已有响应而 Usage 缺失→停止”“非材料子代理保持原行为”。故意让假 provider 在 final-only 中仍请求隐藏的检查点,断言 `runTool` 拒绝且没有持久发布。测试检查 provider 请求里的工具声明和系统提醒,而非只检查内部布尔值。 +- [ ] **Step 1: 写 RED。** `material_final_reserve_test.go` 用假 stream 顺序返回用量,断言“初始零响应允许首个请求”“两个合法检查点→下一次仅最终答复”“只有一个检查点且额度临界→真实部分最终答复”“额度不足→0 次额外 provider 请求”“已有响应而 Usage 缺失→停止”“非材料子代理保持原行为”。故意让假 provider 在 final-only 中仍请求隐藏的检查点,断言 `runTool` 拒绝、设置本次执行的停止标记,下一轮在 provider 前停止且没有持久发布。测试检查 provider 请求里的工具声明和系统提醒,而非只检查内部布尔值。 ```go func TestMaterialFinalReserveAfterTwoCheckpoints(t *testing.T) { @@ -153,9 +153,15 @@ } ``` - [ ] **Step 2: 运行 RED。** `cd modules/backend && go test ./internal/runtime -run 'TestMaterialFinalReserve|TestRunBudgetStreamStopsReportedTokenExhaustion' -count=1`;期望当前两检查点之后仍继续暴露发布工具或临界余额耗尽。 -- [ ] **Step 3: 实现调度。** `subRun.runTool` 仅在检查点真实成功时增加 `savedCheckpoints`,在 final-only 阶段拒绝模型凭旧记忆仍提交的检查点;`onEvent(message_end)` 用返回 Usage 更新 `Used/LastPrompt/Responses/UsageKnown`。在 `SubLLMRunner.Execute` 的 `wrapped` 调用前计算决策;final-only 只改当前请求的工具声明和系统提示,不写回历史、不提高 `MaxTokens` 或单次输出上限。预算不能证明可用时保留原失败/部分投影,不伪造正文。 +- [ ] **Step 3: 实现调度。** `subRun.runTool` 仅在检查点真实成功时增加 `savedCheckpoints`,在 final-only 阶段拒绝模型凭旧记忆仍提交的检查点并设置停止标记;`wrapped` 下一次调用先检查此标记,直接返回错误而不接触 provider;`onEvent(message_end)` 用返回 Usage 更新 `Used/LastPrompt/Responses/UsageKnown`。在 `SubLLMRunner.Execute` 的 `wrapped` 调用前计算决策;final-only 只改当前请求的工具声明和系统提示,不写回历史、不提高 `MaxTokens` 或单次输出上限。预算不能证明可用时保留原失败/部分投影,不伪造正文。 ```go + if st.finalOnlyRejected { + out := make(chan agentllm.Event, 1) + out <- agentllm.Event{Kind: agentllm.KindError, Err: fmt.Errorf("budget_exceeded: checkpoint requested after final-only transition")} + close(out) + return out + } limit := *rc.Profile().Budget.MaxTokens // 仅在 MaxTokens 非 nil 且 >0 的材料分支调用 outputCap := outputTokenCap(rc.Profile().Budget, rc.Settings(), "models.chat.maxOutputTokens") finalOnly, stop := materialFinalDecision(materialBudgetState{Limit: limit,