Skip to content

fix(core): stop Goal retries after evidence catalog exhaustion - #8430

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
qqqys:agent/fix-goal-evidence-catalog-loop
Aug 3, 2026
Merged

fix(core): stop Goal retries after evidence catalog exhaustion#8430
wenshao merged 1 commit into
QwenLM:mainfrom
qqqys:agent/fix-goal-evidence-catalog-loop

Conversation

@qqqys

@qqqys qqqys commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR stops Goal mode from scheduling endless synthetic continuation turns after a completion attempt exceeds the bounded evidence catalog. The completion proposal now reaches the verification boundary, where catalog exhaustion transitions the Goal to usage_limited; direct resume is rejected until the user edits the Goal revision or replaces the Goal.

Why it's needed

In long-running Goal sessions, the evidence catalog can exceed its entry or byte limit. The previous worker-side check rejected every completion proposal while leaving the Goal active, and each automatic continuation added more transcript evidence, making recovery impossible and creating an unbounded retry loop.

Reviewer Test Plan

How to verify

  • Create an active Goal with more than 100 eligible evidence records, submit a completion proposal using a currently visible evidence reference, and finish the turn. Confirm the Goal becomes idle with status usage_limited, the verifier is not called, and no second synthetic continuation starts.
  • Attempt to resume the same revision and confirm it is rejected with guidance to edit or replace the Goal.
  • Edit the objective, confirm the revision and evidence cursor change, then resume and confirm exactly one fresh Goal turn starts.
  • Confirm other invalid evidence-reference failures continue to use the existing verifier-rejection path.

Evidence (Before & After)

Before: completion returned proposalRecorded: false because the catalog was truncated, the Goal remained active, and automatic continuation retried indefinitely.

After: the proposal terminates the current turn once, catalog exhaustion records usage_limited, and the host does not start another continuation. Focused verification passed with 159 tests across the evidence, tool, runtime, and reducer suites.

Tested on

OS Status
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

Environment (optional)

Node.js 24.14.1; focused Vitest suites, Core typecheck/build, ESLint, Prettier, and git diff --check passed locally.

Risk & Scope

  • Main risk or tradeoff: a completion proposal with a truncated catalog now stops autonomously instead of retrying; recovery intentionally requires a new evidence revision through edit or replacement.
  • Not validated / out of scope: end-to-end TUI rendering on Windows and Linux; unrelated existing CLI workspace typecheck failures.
  • Breaking changes / migration notes: none.

Linked Issues

N/A

中文说明

本 PR 做了什么

本 PR 修复 Goal 模式在完成提案超过有界证据目录后不断创建 synthetic continuation turn 的问题。现在完成提案会进入验证边界,目录耗尽会把 Goal 转为 usage_limited;同一 revision 不能直接恢复,用户需要编辑 Goal revision 或替换 Goal。

为什么需要

长时间运行的 Goal 会话可能超过证据目录的条数或字节限制。此前 worker 侧会拒绝每一次完成提案,但仍保持 Goal active;每次自动续跑又会增加 transcript 证据,使会话无法恢复并形成无界重试循环。

Reviewer 测试计划

如何验证

  • 创建包含超过 100 条有效证据记录的 active Goal,使用当前可见的证据引用提交完成提案并结束 turn。确认 Goal 进入 idle / usage_limited,verifier 未被调用,且不会启动第二个 synthetic continuation。
  • 尝试直接恢复同一 revision,确认系统拒绝并提示编辑或替换 Goal。
  • 编辑目标,确认 revision 和 evidence cursor 更新;随后恢复并确认只启动一个新的 Goal turn。
  • 确认其他无效 evidence reference 仍沿用原有 verifier rejection 路径。

Before / After 证据

修复前:目录截断时完成提案返回 proposalRecorded: false,Goal 保持 active,自动续跑会无限重试。

修复后:提案只结束当前 turn 一次,目录耗尽被记录为 usage_limited,host 不再启动后续 continuation。证据、工具、runtime 和 reducer 的 159 个聚焦测试全部通过。

测试平台

OS 状态
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

环境

Node.js 24.14.1;聚焦 Vitest、Core typecheck/build、ESLint、Prettier 和 git diff --check 均在本地通过。

风险与范围

  • 主要风险或取舍:目录已截断的完成提案现在会停止自治重试;恢复必须通过编辑或替换创建新的证据 revision。
  • 未验证 / 范围外:Windows 和 Linux 的端到端 TUI 展示;CLI workspace 中与本次修改无关的既有 typecheck 错误。
  • 破坏性变更 / 迁移说明:无。

关联 Issue

N/A

@qqqys
qqqys marked this pull request as ready for review August 3, 2026 06:10
@qqqys

qqqys commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 3, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: real and demonstrable in the current code — no linked issue, but I verified the mechanism in the base tree. When a Goal's evidence catalog is truncated (>100 entries or 24 KB of records after the pinned cursor), update_goal rejects the completion proposal with proposalRecorded: false, the Goal stays active, and finishTurn schedules another synthetic continuation. Each continuation appends more transcript evidence and the cursor never moves, so the catalog can never shrink — the only exit is the 50-turn budget. The old guidance ("continue in a new Goal turn with a smaller evidence set") is not something the worker can actually do.

Direction: aligned. The fix reuses the existing usage_limited machinery (the same terminal state used for the turn-budget and evidence-source-unavailable cases) and matches the recovery model the state already implies — edit/replace resets the evidence cursor. For a direction signal: Claude Code's CHANGELOG shows /goal-style autonomy loops are an active area with recurring fixes for exactly this class of failure mode (e.g. "Fixed /goal silently hanging … now shows a clear message instead of an indicator that never resolves"), so hardening the loop's dead-ends is on-mission.

Size: core paths touched (packages/core/src/goals/**) — 55 production lines (goal-evidence 11, goal-protocol 2, goal-reducer 10, goal-runtime 11, goal-tools 21) + 96 test lines. Well below every threshold; no maintainer escalation needed.

Approach: scope feels right. The decision moves from the worker (which cannot change lifecycle) to the verification boundary (which can), direct resume is gated until the revision changes, and the diff carries nothing beyond that minimal set — no drive-by changes, tests updated with the behavior.

Risk: no elevated risk signals — no high-risk paths matched.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题: 真实存在,且可以在当前代码中直接验证——没有关联 issue,但我在基线代码中核实了机制。当 Goal 的证据目录被截断(固定 cursor 之后超过 100 条记录或 24 KB)时,update_goal 会以 proposalRecorded: false 拒绝完成提案,Goal 保持 activefinishTurn 又调度一个新的 synthetic continuation。每次续跑都追加更多 transcript 证据而 cursor 不动,目录永远无法缩小——唯一出口是 50 turn 预算。旧提示("用更小的证据集开新 Goal turn 继续")实际上是 worker 做不到的。

方向: 对齐。修复复用了现有 usage_limited 机制(与 turn 预算耗尽、证据源不可用同一终态),也符合该状态本身隐含的恢复模型——edit/replace 会重置证据 cursor。方向信号:Claude Code 的 CHANGELOG 显示 /goal 这类自主循环是活跃方向,且反复修复过同类失效模式(如 "Fixed /goal silently hanging … now shows a clear message instead of an indicator that never resolves"),加固循环的死路属于核心使命。

规模: 触及核心路径(packages/core/src/goals/**)——55 行生产代码(goal-evidence 11、goal-protocol 2、goal-reducer 10、goal-runtime 11、goal-tools 21)+ 96 行测试。远低于所有阈值,无需维护者介入。

方案: 范围合理。判定从 worker(无法变更生命周期)移到验证边界(可以),直接 resume 被拦截到 revision 变更为止,diff 中没有超出最小改动集的内容——无顺手改动,测试与行为同步更新。

风险: 无升级风险信号——未命中高风险路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at f98c382571af3d6a1a998d9fa6bb55bf0280390b · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Code review

I traced the loop in the base tree first, then compared against the diff. The fix matches the approach I would have proposed independently: remove the rejection the worker cannot enforce, and let the verification boundary — the only place that can change lifecycle — classify the exhaustion. The PR goes one step further and closes the direct-resume path, which is correct: re-authorizing turns on an exhausted revision would just re-run the same dead loop.

What I verified:

  • No verifier call on exhaustion. validateGoalEvidenceReferences throws catalog_truncated before verifier(...) is reached inside runVerification, and the new branch maps that error code to the usage_limited outcome. The usage-limited path in recordVerificationOutcome clears continuationQueued/pendingProposal, so no continuation follows — the new runtime test asserts host.started stays at 1.
  • The worker-side removal leaves no hole. Only the truncated-complete rejection moved. The stale-reference check (invalidEvidenceRefs) still runs in the tool, and other invalid-reference failures still take the existing verifier-rejection path, as the PR description claims.
  • Reducer gate placement is safe. The new check sits after create/clear/replace/edit/pause are handled, so only resume can reach it. Turn-budget usage_limited Goals remain resumable (existing reducer tests pin that behavior), so the gate is scoped to exactly the unrecoverable case. edit bumps the revision, clears lastReason, and resets the cursor — the test walks the full recovery path end to end.
  • Tests pin both hunks. Reverting the runtime classification fails the new runtime test (verifier would be called, a continuation would start, status would stay active); restoring the worker-side rejection fails the reworked goal-tools test. Neither hunk is dead weight.
  • One non-blocking note: the resume gate keys on exact lastReason equality with a shared constant rather than a dedicated field. Writer and reader both import the constant from goal-protocol.ts, so they cannot drift at compile time, and a hypothetical mismatch fails open (resume allowed → bounded 50-turn behavior, not corrupted state). A dedicated field would require protocol/persistence schema changes — not worth it for this fix.
sequenceDiagram
    participant P1 as update_goal tool
    participant P2 as Goal runtime
    participant P3 as evidence validation
    participant P4 as goal reducer
    P1->>P2: record completion proposal (truncated catalog)
    P2->>P2: finishTurn, begin verification
    P2->>P3: validate cited evidence
    P3-->>P2: catalog_truncated
    P2->>P2: usage_limited, no continuation queued
    P1->>P2: resume same revision
    P2->>P4: reduceGoalControl
    P4-->>P2: rejected until edit or replace
Loading

Test evidence

Quoted from the checks API for the reviewed commit — triage never builds or runs PR code:

Final CI results for f98c382 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
route ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

The green Ubuntu unit suite includes the new stops continuations when completion evidence exceeds the catalog runtime test and the reworked goal-tools test, so the behavioral change is pinned by CI, not just claimed. The macOS/Windows matrix and CLI integration jobs were skipped in this PR's CI; the change is platform-independent state-machine logic with nothing OS-specific, so I don't consider that a gap here. One precheck-pr / precheck entry shows cancelled alongside two success entries — consistent with the autofix takeover re-pushing mid-run; the final precheck is green. The CLI surface is untouched by this PR (it already renders usage_limited generically), so there is no new user-visible surface to drive.

中文说明

代码审查

我先在基线代码中追踪了这个循环,再对照 diff。修复与我的独立方案一致:移除 worker 无法强制执行的拒绝逻辑,交给唯一能变更生命周期的验证边界来判定耗尽。PR 还更进一步关闭了直接 resume 的路径——这是对的:在已耗尽的 revision 上重新授权续跑只会重跑同一个死循环。

已验证:

  • 耗尽时不调用 verifier。 validateGoalEvidenceReferencesrunVerification 内先于 verifier(...) 抛出 catalog_truncated,新分支把该错误码映射为 usage_limited 结果。recordVerificationOutcome 的 usage_limited 路径会清除 continuationQueued/pendingProposal,因此不会再有续跑——新 runtime 测试断言 host.started 保持为 1。
  • worker 侧的移除没有留下漏洞。 只移走了 truncated-complete 拒绝;过期引用检查(invalidEvidenceRefs)仍在工具中执行,其他无效引用失败仍走原有 verifier-rejection 路径,与 PR 描述一致。
  • reducer 拦截位置安全。 新检查位于 create/clear/replace/edit/pause 处理之后,只有 resume 能到达。turn 预算耗尽的 usage_limited Goal 依然可以 resume(既有 reducer 测试已固定该行为),拦截只针对不可恢复的情形。edit 会升 revision、清除 lastReason 并重置 cursor——测试完整走通了恢复路径。
  • 测试固定了两块改动。 还原 runtime 分类会让新 runtime 测试失败(verifier 会被调用、会启动续跑、状态停留在 active);恢复 worker 侧拒绝会让改写后的 goal-tools 测试失败。两块改动都不是多余的。
  • 一个非阻塞提醒:resume 拦截依赖 lastReason 与共享常量的精确相等,而非独立字段。写入方和读取方都从 goal-protocol.ts 导入该常量,编译期即不可能漂移;假设失配也是 fail-open(允许 resume → 回到有界的 50 turn 行为,不会损坏状态)。独立字段需要协议/持久化 schema 变更,对这个修复不值得。

(时序图见上方英文版:提案 → 结束 turn → 证据校验抛出 catalog_truncated → usage_limited 且不再续跑;同 revision resume 被 reducer 拒绝,edit/replace 后恢复。)

测试证据

以上表格引用的是受审 commit 的 checks API 结果——triage 从不构建或运行 PR 代码。Ubuntu 单元测试为绿色,且包含新增的 runtime 测试和改写后的 goal-tools 测试,行为变更由 CI 固定而非仅凭声称。macOS/Windows 矩阵与 CLI 集成测试在本 PR 的 CI 中被跳过;改动是与平台无关的状态机逻辑,不涉及 OS 特定代码,因此这里不视为缺口。有一条 precheck 显示 cancelled,与 autofix 接管期间重新推送一致,最终 precheck 为绿色。本 PR 未触碰 CLI 表面(它已能通用渲染 usage_limited),没有新的用户可见界面需要驱动。

Qwen Code · qwen3.8-max

Reviewed at f98c382571af3d6a1a998d9fa6bb55bf0280390b · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal fix for a loop I verified in the base code; the only nit is the string-keyed resume gate, which is acceptable at this scale.

Going in, my independent read of the bug was that the worker-side rejection is unenforceable — the worker cannot shrink the catalog, so the decision belongs at the verification boundary. The PR does exactly that, and correctly adds the resume gate: re-authorizing turns on an exhausted revision would re-run the same dead loop, and the recovery path (edit or replace → fresh evidence cursor → resume) matches how revisions already work. The diff is 55 production lines in one module with no drive-by changes, tests move with the behavior, and CI is green at the reviewed commit including the new load-bearing tests. I found no correctness, safety, or scope concern — the one design nit is documented in the review above and doesn't block.

One standing, non-blocking observation for the future: a blocked proposal on a byte-truncated catalog could still churn through verifier rejections when the oldest of the three required blocker turns gets pushed out of the catalog window. That path is unchanged by this PR and much narrower than the completion path fixed here, but it's the same family of problem if anyone wants a follow-up.

Approving, pinned to the reviewed commit. ✅

Qwen Code · qwen3.8-max

Reviewed at f98c382571af3d6a1a998d9fa6bb55bf0280390b · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 1 finishedview run. See this round's report below.

中文说明

AutoFix 第 1 轮已完成 —— 查看运行。本轮报告见下方。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no action taken (PR #8430)

Triage result: there is no actionable feedback in this round.

  • Reviews: none newer than the last evaluation (2026-08-03T06:08:22Z).
  • Inline comments: none.
  • Issue-level comments: none.
  • Still-red checks: none.

The single listed item — precheck-pr / precheck: CANCELLED — is a workflow-level cancellation, not a code defect.

Evidence from the workflow definitions:

  • The PR review workflow scopes all PR lifecycle runs to one concurrency group per PR and enables cancel-in-progress for the synchronize and closed actions (.github/workflows/qwen-code-pr-review.yml). A new push to the PR therefore cancels any precheck that is still in flight, and the run triggered by that same push re-runs the precheck against the new head SHA.
  • The precheck job itself (.github/workflows/qwen-pr-safety-precheck.yml) is a short PR-safety assessment whose own concurrency group uses cancel-in-progress: false, so it cannot cancel itself; the cancellation came from the parent workflow's concurrency policy (or an equivalent run-level cancel), not from any step failing.

A cancelled check needs no code change, and this bot holds no GitHub credentials to re-run CI — the next workflow run for this PR is the authoritative gate. The empty "Still-red checks" section confirms no check is persistently failing.

No changes were made this round. The PR head remains the previously pushed commit f98c38257 (fix(core): stop goal retries on evidence exhaustion), with a clean working tree and no merge required (--conflict false).

中文说明

Autofix 审查轮次 — 未采取任何操作(PR #8430

分诊结果: 本轮没有可处理的反馈。

  • 审查(Reviews):自上次评估(2026-08-03T06:08:22Z)以来没有新的审查。
  • 行内评论(Inline comments):无。
  • Issue 级评论(Issue-level comments):无。
  • 持续失败的检查(Still-red checks):无。

唯一列出的条目 —— precheck-pr / precheck: CANCELLED —— 是工作流层面的取消,而不是代码缺陷。

来自工作流定义的证据:

  • PR 审查工作流将所有 PR 生命周期运行限定在按 PR 划分的同一并发组中,并对 synchronizeclosed 事件启用了 cancel-in-progress(见 .github/workflows/qwen-code-pr-review.yml)。因此,向 PR 推送新的提交会取消仍在运行中的 precheck,而触发该取消的同一次新运行会针对新的 head SHA 重新执行 precheck。
  • precheck 作业本身(.github/workflows/qwen-pr-safety-precheck.yml)是一个短时的 PR 安全评估,其自身的并发组使用 cancel-in-progress: false,不会自我取消;本次取消来自父工作流的并发策略(或同等的运行级取消操作),而不是任何步骤执行失败。

被取消的检查不需要任何代码修改,本机器人也没有 GitHub 凭据去重跑 CI —— 该 PR 的下一次工作流运行才是权威的门禁。"Still-red checks" 部分为空,进一步证实没有任何检查处于持续失败状态。

本轮没有做任何代码改动。PR 的 head 仍然是之前已推送的提交 f98c38257fix(core): stop goal retries on evidence exhaustion),工作区干净,也无需合并(--conflict false)。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@wenshao
wenshao added this pull request to the merge queue Aug 3, 2026
Merged via the queue into QwenLM:main with commit 6060a1f Aug 3, 2026
74 of 75 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants