Skip to content

feat(goal): make get_goal's default view a summary instead of the whole catalog - #9973

Merged
wenshao merged 2 commits into
QwenLM:mainfrom
qqqys:goal/e1-slim-get-goal
Aug 25, 2026
Merged

feat(goal): make get_goal's default view a summary instead of the whole catalog#9973
wenshao merged 2 commits into
QwenLM:mainfrom
qqqys:goal/e1-slim-get-goal

Conversation

@qqqys

@qqqys qqqys commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Gives get_goal a view parameter — 'summary' (default) or 'full' — and makes the default read a summary instead of the whole evidence catalog. The summary collapses the Goal snapshot's checkpoint claims to a count (every claim is already a goal_checkpoint catalog entry with the same uuid and its own preview), keeps full 240-byte previews for checkpoint entries and for this turn's entries — the compacted proof of everything earlier and the records a proposal cites next — and shortens previews from earlier turns to 80 bytes, cut on a code point so multi-byte text is never split. The payload names its view and reports how many previews were shortened. view: 'full' returns the payload exactly as before. The tool description tells the model when to ask for the full view.

Every entry uuid is present in both views and remains valid for update_goal: that tool validates evidenceRefs against a fresh runtime catalog, never against what the model was shown, which is what makes a slim read safe. Nothing about what the catalog stores changes — only what a read returns.

Why it's needed

Every get_goal read returned up to 100 catalog entries with 240-byte previews plus the Goal snapshot, which carries the evidence checkpoint verbatim — up to 32 claims of up to 2,000 characters each, all duplicated as catalog previews. A Goal that had been running for a while paid all of that on every read. In the 27-round session analyzed for this series the prompt grew from 44k to 277k tokens, dominated by these reads; in the second session the model called get_goal 32 times in 34 minutes (8.6M tokens in total). On a steady-state fixture — 32 maximal claims, a catalog at its 100-entry cap, a 16-turn lineage — one read drops from 105,317 bytes to 27,225 bytes (3.87×). The catalog is the right design; re-sending all of it on every read was the cost problem.

Reviewer Test Plan

How to verify

  • cd packages/core && npx vitest run src/goals/ — 400 tests, 16 files. New in goal-tools.test.ts: the view schema pin; collapses checkpoint claims and shortens earlier previews in the summary view (claim text absent, checkpoint collapsed to {checkpointId, createdAt, claimCount}, uuid list identical to the full catalog, 240-byte previews kept only for checkpoint and current-turn entries, a 240-byte CJK preview cut to exactly 26 characters / 78 bytes, shortenedPreviews: 60); returns the whole checkpoint and catalog in the full view (byte-for-byte today's payload); keeps a steady-state summary read under a fixed byte ceiling (full > 100,000 bytes, summary ≤ 36,000, ratio ≥ 3).
  • Mutation probes run during development, each restored afterwards: defaulting to full fails 3 tests; removing the current-turn exemption fails the summary test; removing the checkpoint exemption fails the summary test; breaking the full switch fails 2 tests; leaving the claims uncollapsed fails 2 tests. Restored suite 39/39 in the file.
  • npx tsc --noEmit in packages/core: 0 errors. prettier and eslint clean on both changed files.
  • The existing worker-view test (returns only the bounded worker view for the captured permit) changes only by the new view: 'summary' key — its single entry is current-turn, so its preview is untouched.

Evidence (Before & After)

N/A (tool payload only; no UI). Payload size on the steady-state fixture: before 105,317 bytes, after 27,225 bytes.

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows ⚠️
🐧 Linux

Environment (optional)

N/A (unit tests only).

Risk & Scope

  • Main risk or tradeoff: a model deciding what to cite from an 80-byte preview of an earlier-turn record may need a second read with view: 'full'; that is one extra call in the rare case, versus the full catalog on every call today. The 80-byte cap and the two exemptions are the whole policy and live in one function.
  • Not validated / out of scope: no change to continuation prompts (they already say "use get_goal for the authoritative objective and evidence state" and stay correct); no change to the catalog budgets themselves; packages/cli tests that quote the continuation prompt were not re-run (they do not read this payload). Overlaps textually with fix(goal): cite this turn's delivered output instead of refusing over it #9880 (auto-cite) and feat(goal): stop autonomous continuation at a token budget the user re-arms #9891 (tokenBudget in the unpermitted summary) in goal-tools.ts; both edits are in different functions, so the merge is mechanical.
  • Breaking changes / migration notes: none for callers — get_goal with no arguments still succeeds; the payload gains a view key and, in summary, evidenceCatalog.shortenedPreviews and a collapsed snapshot.goal.evidenceCheckpoint.

Linked Issues

Series context: #9834 (B2, merged prompt convergence), #9835 (C2, catalog byte budget), #9880 (G1, current-turn auto-cite).

中文说明

这个 PR 做了什么

get_goal 增加 view 参数——'summary'(默认)或 'full'——并让默认读取返回摘要而不是整本证据目录。摘要把 Goal 快照里的 checkpoint claims 折叠为数量(每条 claim 本来就是一条 goal_checkpoint 目录条目,uuid 相同、自带 preview),对 checkpoint 条目和本轮条目保留完整的 240 字节 preview——它们分别是此前一切的压缩证明和提案接下来要引用的记录——对更早轮次的条目把 preview 缩短到 80 字节,并按码点切割,绝不拆开多字节字符。载荷标明自己的视图并报告缩短了多少条 preview。view: 'full' 原样返回此前的载荷。工具描述告诉模型何时该请求完整视图。

两种视图中每个条目的 uuid 都存在,且对 update_goal 仍然有效:该工具是对 runtime 新取的目录校验 evidenceRefs,而不是对模型看到的内容校验——这正是精简读取安全的原因。目录存储的内容完全不变——只改变一次读取返回什么。

为什么需要

每次 get_goal 读取都会返回多达 100 条带 240 字节 preview 的目录条目,外加 Goal 快照——它原样携带证据 checkpoint:最多 32 条、每条最长 2,000 字符的 claim,而这些全部已在目录里以 preview 形式重复。运行了一段时间的 Goal 每次读取都要付这全部代价。本系列分析的 27 轮 session 里 prompt 从 44k 涨到 277k token,主要就是这些读取;第二个 session 里模型 34 分钟内调用了 32 次 get_goal(总计 860 万 token)。在一个稳态 fixture 上——32 条最长 claim、100 条满载目录、16 轮 lineage——一次读取从 105,317 字节降到 27,225 字节(3.87 倍)。目录本身的设计是对的;每次读取都重发全部内容才是成本问题。

评审验证计划

如何验证

  • cd packages/core && npx vitest run src/goals/——400 个测试,16 个文件。goal-tools.test.ts 新增:view schema 固定;collapses checkpoint claims and shortens earlier previews in the summary view(claim 文本不出现、checkpoint 折叠为 {checkpointId, createdAt, claimCount}、uuid 列表与完整目录一致、只有 checkpoint 与本轮条目保留 240 字节 preview、一条 240 字节的中文 preview 被精确切到 26 个字符 / 78 字节、shortenedPreviews: 60);returns the whole checkpoint and catalog in the full view(与此前载荷逐字节一致);keeps a steady-state summary read under a fixed byte ceiling(完整 > 100,000 字节,摘要 ≤ 36,000,比值 ≥ 3)。
  • 开发期间的变异检验,每次都已还原:默认改为 full 挂 3 个测试;去掉本轮豁免挂摘要测试;去掉 checkpoint 豁免挂摘要测试;破坏 full 开关挂 2 个;不折叠 claims 挂 2 个。还原后该文件 39/39。
  • packages/corenpx tsc --noEmit:0 错误。两个改动文件 prettier 与 eslint 干净。
  • 既有的 worker-view 测试(returns only the bounded worker view for the captured permit)只多了新的 view: 'summary' 键——它唯一的条目属于本轮,preview 不变。

证据(前后对比)

N/A(仅工具载荷,无 UI)。稳态 fixture 上的载荷大小:改前 105,317 字节,改后 27,225 字节。

已测试平台

Linux ✅;macOS / Windows ⚠️(由 CI 覆盖)。

风险与范围

  • 主要风险或权衡:模型凭更早轮次记录的 80 字节 preview 决定引用什么时,可能需要用 view: 'full' 再读一次;那是罕见情况下多一次调用,而今天是每次调用都发整本目录。80 字节上限和两项豁免就是全部策略,集中在一个函数里。
  • 未验证/范围外:不改续跑提示词(它们已写明「用 get_goal 获取权威的 objective 与证据状态」,仍然正确);不改目录预算本身;packages/cli 中引用续跑提示词的测试未重跑(它们不读取该载荷)。与 fix(goal): cite this turn's delivered output instead of refusing over it #9880(自动引用)和 feat(goal): stop autonomous continuation at a token budget the user re-arms #9891(未许可摘要里的 tokenBudget)在 goal-tools.ts 上有文本重叠;改动位于不同函数,合并是机械性的。
  • 破坏性变更/迁移说明:对调用方无——不带参数的 get_goal 仍然成功;载荷新增 view 键,摘要视图下新增 evidenceCatalog.shortenedPreviews 与折叠后的 snapshot.goal.evidenceCheckpoint

关联 Issue

系列上下文:#9834(B2,已合入的提示词收敛)、#9835(C2,目录字节预算)、#9880(G1,本轮自动引用)。

…le catalog

Every get_goal read returned the entire bounded evidence catalog -- up to
100 entries with 240-byte previews -- plus the Goal snapshot, which
carries the evidence checkpoint verbatim: up to 32 claims of up to 2,000
characters each, every one of which is already in the catalog as a
`goal_checkpoint` entry with the same uuid and its own preview. A Goal
that had been running for a while paid all of that on every read. In one
session the prompt grew from 44k to 277k tokens over 27 rounds; in
another the model read the Goal 32 times in 34 minutes.

get_goal now takes `view: 'summary' | 'full'`, default summary. The
summary collapses the checkpoint's claims to a count (the catalog entries
carry their previews), keeps full previews for checkpoint entries and for
this turn's entries -- the compacted proof and the records a proposal
cites next -- and caps previews from earlier turns at 80 bytes, cut on a
code point. Every uuid is present in both views and remains valid for
update_goal, which validates references against the runtime's own
catalog, never against what the model was shown. `full` returns the
payload exactly as before.

On a steady-state fixture (32 maximal claims, a 100-entry catalog, a
16-turn lineage) the read drops from 105,317 bytes to 27,225 bytes; the
test pins a 36,000-byte ceiling and a >=3x ratio on that fixture.

Mutation probes: defaulting to `full` fails 3 tests; removing either
preview exemption fails the summary test; breaking the `full` switch
fails 2; leaving the claims uncollapsed fails 2; restored suite 39/39.
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on 7d6d6be and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— 7d6d6be 的 CI 全绿,延迟审批已提交。查看 finalize 运行

@qqqys

qqqys commented Aug 25, 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 25, 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

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed, not theoretical. The PR cites measured sessions (a 27-round run where the prompt grew from 44k to 277k tokens dominated by get_goal reads; a second session with 32 get_goal calls in 34 minutes totaling 8.6M tokens) and a reproducible steady-state fixture measurement (105,317 → 27,225 bytes per read, 3.87×). It also continues a series already accepted into the codebase — #9834 and #9835 are both merged.

Direction: aligned. Read-cost of the Goal evidence catalog is squarely within the ongoing Goal efficiency work, and the reference ecosystem is investing in the same area (long-goal check-in backoff, redundant-normalization removal for long conversations). No direct CHANGELOG reference to a slim read, but the area is clearly relevant.

Size: core path touched (packages/core/src/goals/goal-tools.ts). Production: 113 lines (+106/−7); tests: 165 lines (goal-tools.test.ts); generated/schema: 0. Well under any threshold — no maintainer escalation needed.

Approach: the scope feels right. The projection lives exactly where the read payload is shaped (projectWorkerView), storage is untouched, and collapsing checkpoint claims is a true dedup — every claim already exists as a goal_checkpoint catalog entry with the same uuid. Keeping full previews for checkpoint and current-turn entries while capping earlier turns at 80 bytes, with a view: 'full' escape hatch, is the minimal version of this. One thought for later (non-blocking): the new capPreviewBytes duplicates the algorithm of the private helper of the same name in goal-evidence.ts (fixed at 240 bytes there); if a third use site ever appears, worth parameterizing and sharing.

Risk: no elevated risk signals — no high-risk paths matched, and the payload shape has no parsing consumers outside the model surface.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测到,不是理论问题。PR 给出了实测 session(27 轮 session 的 prompt 从 44k 涨到 277k token,主要是 get_goal 读取;另一个 session 34 分钟内调用 32 次 get_goal,共 860 万 token),以及可复现的稳态 fixture 测量(每次读取从 105,317 字节降到 27,225 字节,3.87 倍)。这也是已被接受系列的延续——#9834#9835 均已合入。

方向:对齐。Goal 证据目录的读取成本完全属于正在进行的 Goal 效率工作,参考生态也在同一方向投入(长 goal 签退避、长对话去除冗余归一化)。CHANGELOG 无直接对应"精简读取"的条目,但该领域明显相关。

规模:触及核心路径(packages/core/src/goals/goal-tools.ts)。生产代码 113 行(+106/−7);测试 165 行(goal-tools.test.ts);生成/schema 文件 0 行。远低于任何阈值——无需升级维护者。

方案:范围合理。投影恰好放在读取载荷成形的位置(projectWorkerView),存储完全不动;折叠 checkpoint claims 是真正的去重——每条 claim 本来就是一条同 uuid 的 goal_checkpoint 目录条目。checkpoint 与本轮条目保留完整 preview、更早轮次压到 80 字节、再加 view: 'full' 逃生口,这是该方案的最小形态。一个后续可考虑的点(非阻塞):新增的 capPreviewBytesgoal-evidence.ts 中同名私有 helper(固定 240 字节)算法重复;若将来出现第三个使用点,值得参数化后共享。

风险:无升级风险信号——未命中高风险路径,且该载荷形状在模型可见面之外没有任何解析消费者。

进入代码审查 🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

Read the diff against the base at 7d6d6bef. Before looking at it, my independent proposal for "every get_goal read re-sends the whole catalog" was: dedup the checkpoint claims in the read projection only (they already exist as catalog entries), cap stale-turn previews on a code-point boundary, keep full previews for what a proposal cites next, and leave an escape hatch to the old payload — exactly because update_goal validates refs against a fresh runtime catalog, not the model-visible read. This PR is that design, so the comparison was about correctness, not direction.

What I verified in the code:

  • Citation safety holds. UpdateGoalInvocation validates evidenceRefs against workerViewForPermit(...) — a fresh runtime catalog — never against what the model was last shown. The summary changes only what a read displays; every uuid survives in both views (pinned by the new uuid identity assertion).
  • The collapse is a true dedup. checkpointCatalogEntries in goal-evidence.ts emits one goal_checkpoint entry per claim with uuid: claim.id, and claims is required on GoalEvidenceCheckpoint — so {checkpointId, createdAt, claimCount} loses no citable text, and the claims.length destructure cannot throw.
  • capPreviewBytes is multi-byte safe. It iterates code points and slices in UTF-16 units; the test pins a 240-byte CJK preview cutting to exactly 26 characters / 78 bytes.
  • view: 'full' preserves today's contract. Snapshot and catalog come through unchanged (the payload only gains the view key, plus shortenedPreviews when > 0). No mutation of runtime state anywhere — clones or shallow copies throughout.
  • No consumer parses the payload shape. Continuation prompts stay correct as written; coreToolScheduler/useGeminiStream branch on tool names only; no snapshot test pins the old schema. The one existing payload-equality test was updated for the new view key.

Non-blocking, for later:

  • The new capPreviewBytes repeats the algorithm of the private same-named helper in goal-evidence.ts (fixed at 240 bytes there vs parameterized here). Sharing it would mean widening the diff into the storage module, so the trade is reasonable — but if a third use site appears, parameterize one and reuse.
  • summarizeCatalog deep-clones the catalog and then replaces entries wholesale, so the cloned entries are discarded. Harmless; a small redundancy only.

Everything else in the diff is necessary for the stated goal — no drive-by changes.

Test evidence

This is an unattended triage run, so no PR code was built or executed here; the evidence below is the PR's own CI on the reviewed commit, fetched via API. At fetch time the Linux unit suite — the gate that runs the new goal-tools.test.ts assertions (schema pin, checkpoint collapse, uuid identity, byte caps, steady-state ceiling) — is still running; the finalize workflow will update the table below when CI settles. The macOS/Windows/integration skips are by repository design (those jobs run only in the merge queue and report under their exact names to satisfy the required contexts), not failures.

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

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
route ✅ success
Secret scan (TruffleHog) ✅ 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 PR's central claim — the default read shrinks ~3.87× while every uuid stays citable and view: 'full' stays byte-identical — is exactly what the new unit tests assert (byte-ceiling test with ratio ≥ 3, uuid identity across views, claim text absent from the summary, full-view deep equality), so a green Linux suite substantiates it; there is no behavioural residue a sandboxed lane would settle. Not verified here: the author-reported mutation probes and the 400-test src/goals/ run are the author's claims; CI is what independently exercises them. The payload has no UI surface, so real-scenario TUI testing is N/A.

中文说明

代码审查:先独立推演再看 diff——我的方案(只在读取投影里去重 checkpoint claims、按码点截断旧轮次 preview、给引用路径保留完整 preview、留一个回到旧载荷的逃生口)与该 PR 完全一致,因此审查重点是正确性。已核实:update_goal 是对运行时新取的目录校验引用,精简读取不影响引用安全;每条 claim 本来就是同 uuid 的 goal_checkpoint 条目,折叠是真去重;截断函数对多字节安全(测试钉住 240 字节中文 preview 精确切到 26 字/78 字节);view: 'full' 保持现有契约(仅新增 viewshortenedPreviews 键);载荷形状在模型可见面之外无任何解析消费者,续跑提示词无需改动。非阻塞建议:新的 capPreviewBytesgoal-evidence.ts 私有同名 helper 算法重复,将来出现第三个使用点时值得共享;summarizeCatalog 深拷贝后又整体替换 entries,属无害的小冗余。

测试证据:本次为无人值守 triage,未构建或执行任何 PR 代码,以上为通过 API 获取的 PR 自身 CI。抓取时 Linux 单测(运行新增断言的关键门禁)仍在执行,finalize 工作流会在 CI 落定后更新表格。macOS/Windows/集成测试的跳过是仓库设计使然(仅在合并队列运行),不是失败。该 PR 的核心主张(默认读取缩小约 3.87 倍、所有 uuid 仍可引用、full 视图保持逐字节一致)正是新增单测断言的内容,绿色 CI 即可证实;作者自述的变异检验与 400 测试本地运行属作者声明,由 CI 独立执行。载荷无 UI 界面,真实场景 TUI 测试不适用。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal, and well-pinned by tests; the only nit is the duplicated byte-capping helper, which is non-blocking.

Stepping back: this is the third PR in a series I can see being absorbed well (#9834 and #9835 already merged), and it earns its place the same way — a measured cost (reads of 100 KB+ on a mature Goal) removed with a read-side projection that touches nothing stored. The approach matches what I would have proposed before reading the diff, and the safety question this design lives or dies on — can a slim read break a later update_goal citation? — has a structural answer, not just a hopeful one: refs are validated against a fresh runtime catalog, and the tests pin that every uuid survives both views. The escape hatch keeps the old payload reachable byte-for-byte, which is the right way to change a default the model depends on.

The diff carries nothing extra: two files, one projection function, one parameter, tests that assert the actual payload rather than the plumbing. In six months this reads as obvious code. The one thing I'd genuinely flag for the future is the two copies of the code-point byte-capping loop — fine today, worth unifying if a third appears.

Approval is deferred until CI lands green on 7d6d6bef53cd523f4a3e271f98cc04eba33c67e5: the Linux unit suite (the gate that runs the new assertions) is still in progress, so no approval is posted in this run — the finalize workflow will post the commit-pinned approval if everything settles green, or flag the status comment if anything lands red.

中文说明

总结:这是同系列中第三个 PR(#9834#9835 已合入),同样站得住——用只读侧投影消除了成熟 Goal 上每次 100 KB+ 的读取成本,且不动任何存储。方案与我读 diff 前的独立设想一致;该设计的生死问题(精简读取会不会破坏后续 update_goal 引用)有结构性答案而非侥幸:引用针对运行时新取的目录校验,测试钉住了两种视图下所有 uuid 都在。逃生口让旧载荷可逐字节取回——这是修改模型所依赖的默认值的正确方式。diff 无任何多余内容:两个文件、一个投影函数、一个参数,测试断言的是真实载荷而非管道代码。唯一值得留意的是码点字节截断循环现有两份拷贝——今天无碍,出现第三处时值得统一。审批暂缓,等待 CI 在被审提交上落绿:运行新断言的 Linux 单测仍在进行,本轮不提交审批;若全部落绿,finalize 工作流将提交绑定该提交的审批,若有失败则会在状态评论中标出。

Qwen Code · qwen3.8-max

Reviewed at 7d6d6bef53cd523f4a3e271f98cc04eba33c67e5 · 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 — CI landed green after the review. ✅

@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.

Reviewed — no blockers. Suggestions are inline.

中文说明

已审查——无阻断问题。 建议见行内评论。

— qwen3.8-max via Qwen Code /review (v0.22.0)

Comment thread packages/core/src/goals/goal-tools.ts Outdated
Comment on lines +562 to +563
/** Cut `value` to at most `limit` UTF-8 bytes without splitting a code point. */
function capPreviewBytes(value: string, limit: number): string {

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.

[Suggestion] The code-point-safe UTF-8 byte-cap is now implemented twice: this new capPreviewBytes(value, limit) is a verbatim copy of the existing private capPreviewBytes in goal-evidence.ts:1073 (fixed at CATALOG_PREVIEW_BYTE_LIMIT = 240), with only the limit parameterised. The byte-bound guarantee is load-bearing in this module — per the comments in goal-evidence.ts it is what actually holds the preview bound after a real CJK bug — and goal-tools.ts already imports from ./goal-evidence.js, so reuse costs nothing.

The cost is future divergence: any change to truncation semantics — appending a truncation marker as capCheckpointContent in the same file already does, or fixing a boundary edge — must then be applied in two files; updating only one silently diverges catalog-build previews (write path, 240-byte cap) from summary-view previews (read path, 80-byte cap) for the same data, while both copies pass their own tests.

// goal-evidence.ts — generalise the existing helper:
export function capPreviewBytes(value: string, limit: number): string { ... }
// its existing call sites pass CATALOG_PREVIEW_BYTE_LIMIT

// goal-tools.ts — delete this copy and call the shared one
// in summarizeCatalog: capPreviewBytes(entry.preview, SUMMARY_PREVIEW_BYTE_LIMIT)
中文说明

码点安全的 UTF-8 字节截断现在有了两份实现:这个新的 capPreviewBytes(value, limit)goal-evidence.ts:1073 中已有的私有 capPreviewBytes(固定为 CATALOG_PREVIEW_BYTE_LIMIT = 240)逐字相同,只是把上限参数化了。该字节边界保证在本模块中是承重的——按 goal-evidence.ts 的注释,在一次真实的 CJK bug 之后,它是真正守住 preview 边界的实现——而 goal-tools.ts 已经从 ./goal-evidence.js 导入符号,复用零成本。

代价是未来的分叉:任何对截断语义的修改——比如追加截断标记(同文件的 capCheckpointContent 已经这么做)、或修复某个边界情况——都必须同时改两处;只改其中一份,目录构建期(写路径,240 字节上限)与摘要视图(读路径,80 字节上限)对同一份数据的 preview 就会悄悄不一致,而两份副本各自的测试都还能通过。

修复方式见上方英文代码块:把 goal-evidence.ts 的助手函数参数化并导出,删除此处的副本,在 summarizeCatalog 中调用共享版本。

— qwen3.8-max via Qwen Code /review (v0.22.0)

Comment thread packages/core/src/goals/goal-tools.ts Outdated
GetGoalTool.Name,
ToolDisplayNames.GET_GOAL,
'Read the current Goal identity, objective, evidence cursor, and bounded evidence-reference catalog for this permitted Goal turn. Outside a permitted Goal turn it reports "active": false together with "lastGoal", a scalar summary (goalId, revision, status, turnCount, activeTimeMs, tokensUsed, and lastReason when one was recorded) of the session\'s most recent Goal, so a Goal that has already stopped can still be inspected. It never returns uncited transcript history or changes Goal state. Use the result silently; do not narrate or acknowledge the retrieval to the user.',
'Read the current Goal identity, objective, evidence cursor, and bounded evidence-reference catalog for this permitted Goal turn. The default "summary" view keeps every read small: checkpoint claims are reported as a count (each claim is already an evidenceCatalog entry with its own preview), entries from this turn and checkpoint entries keep full previews, and entries from earlier turns carry previews shortened to 80 bytes. Every entry uuid is present in both views and is valid for update_goal; request view "full" only when a shortened preview is not enough to decide what to cite. Outside a permitted Goal turn it reports "active": false together with "lastGoal", a scalar summary (goalId, revision, status, turnCount, activeTimeMs, tokensUsed, and lastReason when one was recorded) of the session\'s most recent Goal, so a Goal that has already stopped can still be inspected. It never returns uncited transcript history or changes Goal state. Use the result silently; do not narrate or acknowledge the retrieval to the user.',

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.

[Suggestion] The 80-byte summary cap is hardcoded as the literal "80 bytes" here and again as "80-byte previews" in the view parameter description (around line 148), instead of interpolating SUMMARY_PREVIEW_BYTE_LIMIT (defined in this file around line 52). Sibling code in this directory interpolates limits into LLM-facing prose — goal-checkpoint-verifier.ts puts ${GOAL_CHECKPOINT_CLAIM_MAX_BYTES} straight into its system prompt.

If SUMMARY_PREVIEW_BYTE_LIMIT is later tuned — its own comment frames it as a heuristic — the code honours the new value but both descriptions keep claiming 80 bytes. The model uses that number to decide whether a shortened preview suffices or whether to spend a view: "full" read, so it would reason about the payload with a wrong contract: requesting expensive full reads it was told to avoid, or trusting previews as longer than they are. The schema test only asserts stringContaining('summary (default)') and cannot catch the drift.

`…previews shortened to ${SUMMARY_PREVIEW_BYTE_LIMIT} bytes…`
`${SUMMARY_PREVIEW_BYTE_LIMIT}-byte previews for earlier turns`
中文说明

80 字节的摘要上限在这里被硬编码为字面量 "80 bytes",并且在 view 参数描述中(约第 148 行)再次写作 "80-byte previews",而没有插值本文件约第 52 行定义的 SUMMARY_PREVIEW_BYTE_LIMIT。同目录的兄弟代码会把上限插值进面向模型的文案——goal-checkpoint-verifier.ts 就把 ${GOAL_CHECKPOINT_CLAIM_MAX_BYTES} 直接写进了系统提示词。

如果以后调整 SUMMARY_PREVIEW_BYTE_LIMIT(它自己的注释就把它描述为一个经验值),代码会采用新值,但两处描述仍声称 80 字节。模型用这个数字来判断缩短后的 preview 是否够用、是否需要花一次 view: "full" 读取,于是它会带着错误的契约来推理载荷:要么去请求被告知应避免的昂贵完整读取,要么把 preview 当作比实际更长来信任。schema 测试只断言 stringContaining('summary (default)'),无法发现这种漂移。

修复方式见上方英文代码块:把两处字符串改为模板字面量,插值 ${SUMMARY_PREVIEW_BYTE_LIMIT}

— qwen3.8-max via Qwen Code /review (v0.22.0)

Comment on lines +555 to +557
return {
...structuredClone(catalog),
entries,

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.

[Suggestion] The summary path deep-clones exactly the data it then throws away: this structuredClone(catalog) is spread and then entries is replaced wholesale with the mapped shallow copies above — only lineageTurnIds/truncated survive the clone — and summarizeSnapshot (around line 524) likewise deep-clones the snapshot including all checkpoint claims before collapsing them to claimCount. A measurement probe at the documented caps (32 claims × 2,000 chars, 100 catalog entries) shows both views clone byte-for-byte the same volume: summary {clonedBytes: 105083, outputBytes: 27285} vs full {clonedBytes: 105083, outputBytes: 105093}.

So every default get_goal against a mid-running Goal allocates and immediately garbage-collects ~105 KB of transient object graphs — strictly more CPU/GC work per call than view: 'full', on precisely the data this PR exists to stop paying for. Bounded per call, so this is an efficiency issue rather than a correctness one.

Strip the dropped data before cloning instead of after, e.g.:

// summarizeCatalog:
const { entries: _entries, ...catalogRest } = catalog;
return {
  ...structuredClone(catalogRest),
  entries,
  ...(shortenedPreviews > 0 ? { shortenedPreviews } : {}),
};
// summarizeSnapshot: build the collapsed { ..., evidenceCheckpoint: { ...rest, claimCount } }
// shape from the originals first, then clone that smaller shape.
中文说明

摘要路径深克隆了它随后丢弃的数据:这里先展开 structuredClone(catalog),然后又用上面映射出的浅拷贝整体替换 entries——克隆体里只有 lineageTurnIds/truncated 存活;summarizeSnapshot(约第 524 行)同样先深克隆包含全部 checkpoint claims 的快照,再把它们折叠成 claimCount。在文档所述上限(32 条 claim × 2,000 字符、100 条目录条目)下的实测探针显示,两种视图克隆的字节量完全相同:摘要 {clonedBytes: 105083, outputBytes: 27285} 对比完整 {clonedBytes: 105083, outputBytes: 105093}

于是运行中的 Goal 每被默认读取一次 get_goal,就会分配并立即回收约 105 KB 的临时对象图——每次调用比 view: 'full' 做更多的 CPU/GC 工作,而这些数据正是本 PR 要停止重复付费的部分。单次开销有上限,所以这是效率问题而非正确性问题。

修复方式是在克隆之前先剥离将被丢弃的数据,示例见上方英文代码块。

— qwen3.8-max via Qwen Code /review (v0.22.0)

expect(entries.find((entry) => entry.uuid === 'earlier-1')?.preview).toBe(
'证'.repeat(26),
);
expect(payload.evidenceCatalog.shortenedPreviews).toBe(60);

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.

[Suggestion] The summary fixture gives every earlier-turn entry a 240-byte preview, so the branch where an earlier-turn preview is already within the 80-byte cap — kept unchanged and not counted in shortenedPreviews — is never exercised. A mutation check confirmed the gap: changing if (preview !== entry.preview) shortenedPreviews += 1; to an unconditional shortenedPreviews += 1; leaves the whole suite green (39/39), and the byte-identical pass-through guarantee for short earlier-turn previews is pinned by no assertion. A regression inflating shortenedPreviews would tell the model that a view: "full" read reveals more than it actually would — undermining the read-full decision the field exists to inform.

Add one earlier-turn entry whose preview is already under the cap, keep the count at 60, and pin the pass-through:

// in the checkpointedCatalog fixture:
{ uuid: 'earlier-short', provenance: 'tool_result', turnId: 'earlier-turn-0',
  preview: '12 tests passed', proofKind: 'external_fact' },

// in the summary test — count unchanged:
expect(payload.evidenceCatalog.shortenedPreviews).toBe(60);
// plus an assertion that the short preview is returned byte-identical

The probe verified the new assertion earns its place: the same mutant fails with AssertionError: expected 61 to be 60 once that entry exists, and the un-mutated code stays green with it.

中文说明

摘要视图的 fixture 给每一条更早轮次的条目都设置了 240 字节的 preview,因此「更早轮次的 preview 已经在 80 字节上限之内——保持原样且不记入 shortenedPreviews」这个分支从未被执行。变异检验确认了这个缺口:把 if (preview !== entry.preview) shortenedPreviews += 1; 改成无条件的 shortenedPreviews += 1;,整个测试套件仍然全绿(39/39);短 preview 逐字节原样返回的保证也没有任何断言锁定。一个夸大 shortenedPreviews 的回归会告诉模型 view: "full" 读取能揭示比实际更多的内容——破坏了这个字段本要支撑的「是否值得完整读取」的决策。

修复方式:在 checkpointedCatalog fixture 中增加一条 preview 已低于上限的更早轮次条目(示例见上方英文代码块),计数保持 60,并断言该短 preview 原样返回。探针已验证该断言有效:加入该条目后,同一变异体以 AssertionError: expected 61 to be 60 失败,未变异代码保持全绿。

— qwen3.8-max via Qwen Code /review (v0.22.0)

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 25, 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

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Round summary — PR #9973 (review round 2)

All four suggestions from the automated review are accepted and implemented in
one commit. No findings were declined, deferred, or escalated.

Findings and dispositions

  • [rc:3849465323] Duplicated capPreviewBytes — Implemented. The
    code-point-safe UTF-8 byte-cap existed twice: the new parameterised copy in
    goal-tools.ts was a verbatim duplicate of the private helper in
    goal-evidence.ts. The existing helper is now parameterised on the limit,
    exported, and its four internal call sites pass CATALOG_PREVIEW_BYTE_LIMIT
    explicitly. goal-tools.ts imports the shared helper and the local copy is
    deleted, so the write-path (240-byte catalog previews) and read-path
    (80-byte summary previews) truncation semantics can no longer diverge.
  • [rc:3849465328] Hardcoded "80 bytes" in LLM-facing prose — Implemented.
    Both the get_goal tool description and the view parameter description
    now interpolate ${SUMMARY_PREVIEW_BYTE_LIMIT} instead of hardcoding 80,
    matching the sibling precedent in goal-checkpoint-verifier.ts. The number
    the model reads can no longer drift from the enforced cap.
  • [rc:3849465332] Summary views deep-cloned data they discard —
    Implemented.
    summarizeSnapshot now collapses the checkpoint claims to
    claimCount on the original snapshot and clones only the smaller shape,
    instead of cloning the full snapshot (up to 32 × 2,000-char claims) and then
    dropping the claims. summarizeCatalog strips entries before cloning
    (const { entries: _entries, ...catalogRest } = catalog) so only the
    surviving metadata (lineageTurnIds, truncated) is deep-cloned; the
    entries were already rebuilt from the originals. Output shapes are
    byte-identical to before — the existing summary/full-view equality tests
    pass unchanged.
  • [rc:3849465335] Unwitnessed pass-through branch — Implemented. The
    checkpointedCatalog fixture gains one earlier-turn entry whose 15-byte
    preview is already within the 80-byte cap; the summary test now asserts the
    preview passes through byte-identical and shortenedPreviews stays 60.

Mutation probe

The new assertion's witness was verified by mutation: changing
if (preview !== entry.preview) shortenedPreviews += 1; to an unconditional
shortenedPreviews += 1; makes the summary test fail with
AssertionError: expected 61 to be 60; restoring the guard returns the suite
to green.

Conflict notes

None — no merge was performed (--conflict false).

Verification

  • vitest src/goals/goal-tools.test.ts src/goals/goal-evidence.test.ts
    (packages/core, touched) — 69 passed (39 + 30), before and after the batch
  • Mutation probe: guard negated → 1 failed (expected 61 to be 60), guard
    restored → 69 passed
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed

No settings source changed, so no schema regeneration. The touched behavior is
covered by core unit tests, not by the bundled CLI or integration harness, so
no integration run was needed.

中文说明

轮次总结 — PR #9973(审查第 2 轮)

自动审查提出的四条建议全部接受,并在一个提交中实现。没有拒绝、推迟或升级任何发现。

发现与处理

  • [rc:3849465323] 重复的 capPreviewBytes — 已实现。 码点安全的 UTF-8 字节截断此前存在两份:goal-tools.ts 中新的参数化副本与 goal-evidence.ts 中私有助手逐字相同。现将已有助手参数化(上限作为参数)并导出,其四个内部调用点显式传入 CATALOG_PREVIEW_BYTE_LIMIT;goal-tools.ts 改为导入共享助手并删除本地副本,使写路径(240 字节目录 preview)与读路径(80 字节摘要 preview)的截断语义不再可能分叉。
  • [rc:3849465328] 面向模型的文案中硬编码 "80 bytes" — 已实现。 get_goal 工具描述与 view 参数描述均改为插值 ${SUMMARY_PREVIEW_BYTE_LIMIT},不再硬编码 80,与同目录 goal-checkpoint-verifier.ts 的先例一致。模型读到的数字不会再与实际强制执行的上限脱节。
  • [rc:3849465332] 摘要视图深克隆了随后丢弃的数据 — 已实现。 summarizeSnapshot 现在在原始快照上先把 checkpoint claims 折叠为 claimCount,再只克隆这个更小的形状,而不是先克隆整个快照(最多 32 条 × 2,000 字符的 claim)再丢弃 claims。summarizeCatalog 在克隆前先剥离 entries(const { entries: _entries, ...catalogRest } = catalog),因此只有存活的元数据(lineageTurnIdstruncated)被深克隆;entries 本就从原始对象重建。输出形状与之前逐字节一致——既有的 summary/full 视图相等性测试原样通过。
  • [rc:3849465335] 无见证的直通分支 — 已实现。 checkpointedCatalog fixture 新增一条更早轮次条目,其 15 字节 preview 已在 80 字节上限之内;摘要测试现在断言该 preview 逐字节原样返回,且 shortenedPreviews 保持 60。

变异探针

新断言的见证能力已通过变异验证:将 if (preview !== entry.preview) shortenedPreviews += 1; 改为无条件的 shortenedPreviews += 1; 后,摘要测试以 AssertionError: expected 61 to be 60 失败;恢复该守卫后测试套件回到全绿。

冲突说明

无——未执行合并(--conflict false)。

验证

  • vitest src/goals/goal-tools.test.ts src/goals/goal-evidence.test.ts(packages/core,受影响包)— 69 通过(39 + 30),修改前后均运行
  • 变异探针:否定守卫 → 1 失败(expected 61 to be 60),恢复守卫 → 69 通过
  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过

未改动任何设置源,因此无需重新生成 schema。受影响行为由 core 单元测试覆盖,而非捆绑 CLI 或集成测试框架,因此无需运行集成测试。

🦷 Gate advisory — this round's changed tests all pass on the pre-round tree (machine-measured, not agent-authored). Expected for a refactor or coverage addition; if this round was meant to FIX a defect, that defect did not reproduce. · 本轮改动的测试在轮前树上全部通过(门自动测量,非 agent 文本)。对重构或补充覆盖属正常;若本轮意在修复缺陷,则该缺陷未能复现。

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

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


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

@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.

No blocking issues. LGTM! ✅

Not explored to full depth (tool budget reached): "agent 1a": none — no check was cut short..

Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:

  • packages/core/src/goals/goal-tools.test.ts:525 — [probe] summary-view tests never pin truncated or lineageTurnIds contents; a mutation dropping truncated from summarizeCatalog survives 403/403 tests
中文说明

无阻断问题。LGTM!✅

未探索到全部深度(达到工具调用预算):"agent 1a"none — no check was cut short.

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.22.0)

@wenshao
wenshao added this pull request to the merge queue Aug 25, 2026
Merged via the queue into QwenLM:main with commit 73a418c Aug 25, 2026
58 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.22.2.

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