feat(core): add configurable auto-compact threshold and Stop hook context usage (#4025) - #5868
Conversation
…text usage (QwenLM#4025) Add two features requested in issue QwenLM#4025: 1. Configurable auto-compact threshold via settings.json - Add context.autoCompactThreshold setting (0-1, default 0.7) - Extend computeThresholds(window, pct?) to accept optional pct parameter - Wire all 4 call sites (chatCompressionService, geminiChat, contextCommand, useContextualTips) - Large windows (>110K) dominated by absolute branch, custom threshold mainly affects small windows 2. Stop hook stdin payload includes context usage data - Add ContextUsageData interface and buildContextUsage helper - Extend StopInput with context_usage, context_limit, input_tokens fields - Wire 3 callers (Session.ts, client.ts, config.ts) - Enables hook scripts to observe context usage and suggest compact strategies
|
Thanks for the PR, @ZijianZhang989! Template looks good ✓ On direction: both features are well-aligned with the project's scope. Configurable auto-compact threshold directly addresses a real pain point for small-window model users (#4025), and exposing context usage data in Stop hooks is a natural extension of the hook observation model. No direct CHANGELOG reference in comparable tools, but the area (context management, hook extensibility) is squarely within qwen-code's core mission. On approach: the scope feels right — two complementary features that share the context-management domain, with a focused diff (+492/-32 across 24 files). The Moving on to code review and testing. 🔍 中文说明感谢贡献,@ZijianZhang989! 模板完整 ✓ 方向:两个功能都与项目范围高度对齐。可配置的 auto-compact 阈值直接解决了小窗口模型用户的真实痛点(#4025),Stop hook 中暴露 context 使用率是 hook 观察模型的自然延伸。没有直接的 CHANGELOG 参考,但方向(上下文管理、hook 扩展性)属于 qwen-code 核心使命。 方案:范围合理——两个互补的功能共享上下文管理领域,diff 聚焦(+492/-32,24 个文件)。 进入代码审查和测试 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal (before reading the diff): To add a configurable auto-compact threshold, I would: add Comparison: The PR's implementation matches this approach exactly. Specific findings:
No critical bugs, no security concerns, no AGENTS.md violations. The diff is focused — every edit serves the stated goals. No drive-by refactors or scope creep. Reuse Check
Unit Test ResultsAll PR-touched test files pass:
Build: 0 errors, 0 new warnings. Typecheck: 0 errors. Real-Scenario Testing (tmux)Tested the Stop hook context usage feature via Tmux session output (PR code, headless
|
|
Stepping back: this is a clean, well-thought-out PR that ships exactly what it promises. The author clearly understands the codebase — the PR description anticipates edge cases (large-window no-op, schema nesting sensitivity, hook observation-only scope), the code is minimal and focused, and the test coverage is thorough. The implementation matches my independent proposal. The real-scenario tmux test confirmed the Stop hook context usage feature works end-to-end: The author has been exceptionally responsive to review feedback — 8 fix commits addressing CI failures, NaN guards, schema constraints, DRY improvements, documentation updates, pct clamping, and TypeScript type narrowing. Each commit is focused and well-described. The review history shows a collaborative process, not a contentious one. 771 unit tests pass across 7 test files, build passes with 0 errors, typecheck passes with 0 errors. The diff is focused — no drive-by refactors, no scope creep, no unnecessary abstractions. If I had to maintain this in six months, I'd thank the author — the validation logic, the spread pattern for optional fields, the clear boundary between "configurable threshold" and "redesigning the three-tier system", and the thorough test coverage make the intent obvious. Looks good, shipping it. ✅ 中文说明退一步看:这是一个干净、考虑周全的 PR,准确交付了承诺的功能。作者对代码库的理解清晰——PR 描述预判了边界情况(大窗口无变化、schema 嵌套敏感性、hook 只观察不触发),代码最小化且聚焦,测试覆盖充分。 实现与我的独立方案一致。 真实场景 tmux 测试确认 Stop hook context 使用率功能端到端工作正常:简单对话轮次后, 作者对 review 反馈积极响应——8 个修复提交涵盖 CI 修复、NaN 守卫、schema 约束、DRY 改进、文档更新、pct clamping 和 TypeScript 类型收窄。每个提交聚焦且描述清晰。 771 个单测通过(7 个测试文件),build 0 errors,typecheck 0 errors。diff 聚焦——无顺手重构、无范围蔓延、无不必要的抽象。 如果六个月后需要维护这个代码,我会感谢作者。看起来不错,可以合并。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Overall: feature implementation is clean, build + 313 unit tests pass locally in the worktree, deterministic checks (tsc, eslint) report 0 findings. Two minor robustness suggestions and one documentation gap inline.
Also worth noting: docs/users/features/hooks.md (lines 562-571) documents the Stop hook's input schema with only stop_hook_active and last_assistant_message, but this PR adds three new optional fields (context_usage, context_limit, input_tokens) to the Stop hook stdin payload. Hook authors reading the docs will not discover these fields without reading the source.
— qwen3.7-max via Qwen Code /review
…d NaN guard to buildContextUsage
Review fixesPushed commit
|
…ontextUsageData>, buildContextUsage validation
Review fixesPushed commit Fixed:
Deferred:
Rejected:
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
[Suggestion] Stop hook documentation not updated
The Stop hook section in docs/users/features/hooks.md (lines 560-579) documents only stop_hook_active and last_assistant_message as event-specific input fields. The three new optional fields added by this PR (context_usage, context_limit, input_tokens) are not mentioned. Hook authors have no way to discover these fields without reading source code.
Consider adding the new fields to the docs with their optionality semantics — they are absent on the first turn and after context resets (when lastPromptTokenCount is 0), so hook scripts should handle missing values gracefully.
— qwen3.7-max via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
Documentation gaps (not in the diff, but load-bearing for this feature)
The code changes look clean (build + 506 unit tests pass in the worktree; tsc/eslint 0 findings). The two findings below are documentation pages that need updating for this feature to be discoverable and usable by end users.
[Suggestion] docs/users/configuration/settings.md:152 — the page still documents model.chatCompression.contextPercentageThreshold as REMOVED with "no longer user-configurable" and no replacement mentioned. This PR reintroduces the capability as context.autoCompactThreshold, but no new row is added to the context.* table. Users reading the settings reference will believe the feature is permanently gone and will never discover the new setting. Add a context.autoCompactThreshold row (type number, range (0, 1], default undefined (0.7)) near the other context.* entries, and update line 152's REMOVED note to point to the new setting name.
[Suggestion] docs/users/features/hooks.md:566 — the Stop hook "Event-specific fields" JSON block documents only stop_hook_active and last_assistant_message, but this PR adds three optional fields (context_usage, context_limit, input_tokens) to the stdin payload via Partial<ContextUsageData>. Hook authors — the primary audience of this feature — won't know the data is available. Add the three optional fields to the JSON block with type annotations, e.g. "context_usage": "number (ratio 0-1+, optional)", "context_limit": "number (context window tokens, optional)", "input_tokens": "number (prompt token count, optional)".
— qwen3.7-max via Qwen Code /review (second opinion pass; inline findings from the prior automated review rounds are unchanged and tracked in the 10 existing inline comments)
…elds documentation
Review fixesPushed commit
|
… threshold propagation test
Review fixesPushed commit
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Overall the implementation is clean — build passes (315 tests), tsc/eslint report 0 findings. One minor Suggestion below.
Review fixFixed documentation error in test comment (contextCommand.test.ts:256): The comment incorrectly stated Commit: 7234694 |
doudouOUC
left a comment
There was a problem hiding this comment.
[Suggestion] Test coverage gaps for context usage propagation at call sites
The new context usage fields (context_usage, context_limit, input_tokens) added to the Stop hook stdin are not fully verified at two call sites:
-
packages/cli/src/acp-integration/session/Session.test.ts— The existing Stop hook test usesobjectContainingbut does not assert the new context usage fields. The test would pass identically if the...contextUsagespread were removed fromSession.ts. -
packages/core/src/config/config.ts:1751-1765— The messageBus Stop handler reconstructsContextUsageDatafrom raw input fields viabuildContextUsage, but has no test verifying thatcontext_limitandinput_tokensare correctly reconstructed and forwarded tofireStopEvent.
Suggested fix: Add explicit assertions for context usage fields in the Session.ts Stop hook test (with a controlled lastPromptTokenCount). Add a test for the config.ts messageBus Stop handler verifying context usage reconstruction.
— qwen3.7-max via Qwen Code /review
Review fixFixed misleading test in chatCompressionService.test.ts:2190: Test name: Changed from "auto=window but hard capped below window" to "auto and hard both equal window" Actual computation:
So hard = window = 200000, not "capped below". Added assertion for hard and corrected the test name. Commit: c047c9a |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Implementation is clean — build passes (315 tests), tsc/eslint report 0 findings. Both features (configurable auto-compact threshold and Stop hook context usage) are well-wired across all call sites with proper validation layers.
— qwen3.7-max via Qwen Code /review
|
修复了一个新的 review 意见: 问题: 当 设置得很低时(schema 允许 minimum: 0.01), 可能产生负数的 阈值。例如 会产生 ,导致:
修复: 在 计算外层添加 确保不会产生负数阈值 Commit: |
…ling Add tests for out-of-range pct values (-0.5, 1.5, NaN) to verify computeThresholds clamping behavior. Fix implementation to use Number.isFinite() check so NaN falls back to DEFAULT_PCT instead of propagating through Math.max(0, NaN) which yields NaN.
Add tests for buildContextUsage runtime validation in MCP Stop dispatch path: - Valid numeric inputs produce correct ContextUsageData - Missing/undefined fields return undefined - String values rejected by Number.isFinite validation - Negative values return undefined Also add Number.isFinite check for contextWindowSize in buildContextUsage to properly validate MCP input types at runtime.
…rameter Use explicit undefined check before Number.isFinite to properly narrow the number | undefined type in the ternary expression.
|
Issue: The recent Fix: Changed from Commit: |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Overall the implementation is clean — build passes (579 tests), tsc/eslint report 0 PR-related findings. Both features (configurable auto-compact threshold and Stop hook context usage) are well-wired across all call sites with proper validation layers. One minor suggestion below.
— qwen3.7-max via Qwen Code /review
| // 0 * 32000 = 0, absolute branch is negative → auto = 0 | ||
| expect(t.auto).toBe(0); | ||
| // warn = max((0 - 0.1) * 32000, absWarn) = -3200 | ||
| expect(t.warn).toBeLessThanOrEqual(t.auto); |
There was a problem hiding this comment.
[Suggestion] The Math.max(0, ...) wrapper was specifically added to prevent negative warn values, but this test only asserts t.warn <= t.auto — which passes even if warn were -3200 (since -3200 <= 0). The non-negative invariant the wrapper establishes is not pinned.
| expect(t.warn).toBeLessThanOrEqual(t.auto); | |
| expect(t.warn).toBe(0); |
— qwen3.7-max via Qwen Code /review
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
doudouOUC
left a comment
There was a problem hiding this comment.
LGTM ✅ — both features (configurable auto-compact threshold and Stop hook context usage) are clean, well-wired across all call sites, and thoroughly tested. The implementation handles edge cases (NaN, negative, >1 pct values, zero tokens) correctly through layered validation.
— qwen3.7-max via Qwen Code /review
What this PR does
Implements two features requested in #4025 (comment by @kaisersong):
1. Configurable auto-compact threshold
Adds a new
context.autoCompactThresholdsetting insettings.jsonthat lets users customize when auto-compaction triggers, instead of the hardcoded 70%.{ "context": { "autoCompactThreshold": 0.5 } }The setting is a number between 0 and 1 (exclusive of 0, inclusive of 1). Invalid values silently fall back to the default 0.7. The threshold is passed to
computeThresholds(window, pct?)which now accepts an optionalpctparameter. All 4 production call sites are wired:chatCompressionService.ts(auto-compaction gate),geminiChat.ts(pre-send hard-cap rescue),contextCommand.ts(/contextdisplay), anduseContextualTips.ts(tip thresholds).Important design note: The three-tier threshold system uses
max(proportional, absolute)where the absolute branch iseffectiveWindow - 13K. For large context windows (>110K tokens), the absolute branch always dominates, so a custom threshold below ~0.7 has no visible effect on large-window models. Custom thresholds primarily affect small-window models (≤128K) where the proportional branch dominates.2. Stop hook stdin includes context usage data
Adds three new optional fields to the Stop hook stdin payload:
context_usage(0-1 ratio),context_limit(context window size in tokens), andinput_tokens(current prompt token count). This enables hook scripts to observe context usage and implement custom compact strategies — for example, a script that prints a reminder to run/compactwhen usage exceeds a custom threshold.{ "session_id": "...", "stop_hook_active": true, "last_assistant_message": "...", "context_usage": 0.72, "context_limit": 200000, "input_tokens": 144000 }A new
buildContextUsage(windowSize, inputTokens)helper inpackages/core/src/hooks/context-usage.tsconstructs theContextUsageDataobject (or returnsundefinedwhen inputs are invalid). Three callers are wired:Session.ts,client.ts, and the messageBus Stop handler inconfig.ts.What this PR does NOT do
additionalContext, but cannot directly trigger/compact. A future PR could add atriggerCompact: booleanfield toStopOutputfor this.max(proportional, absolute)formula is unchanged; this PR only makes the proportional component configurable.cxtdisplay was investigated and found to be working correctly —lastPromptTokenCountis only updated on API responses, which is a fundamental API constraint, not a bug.Why it's needed
Users with small-window models (32K-128K) want to control when auto-compaction triggers — earlier to preserve more headroom, or later to retain more conversation context. The hardcoded 70% is a reasonable default but doesn't fit all workflows.
Hook script authors need visibility into context usage to implement custom strategies. Without
context_usagein the Stop hook payload, there's no way for external scripts to know how full the context window is.Reviewer Test Plan
How to verify
Evidence (Before & After)
Before:
/contextalways shows Auto threshold at ~70% of window (or absolute branch for large windows). Stop hook stdin has onlystop_hook_activeandlast_assistant_message.After:
/contextAuto threshold reflectssettings.jsonvalue when it exceeds the absolute branch. Stop hook stdin includescontext_usage,context_limit,input_tokens.Tested on
Environment
Node v26.2.0, macOS arm64, dev build from worktree.
Risk & Scope
autoCompactThresholdmust be atcontext.propertieslevel insettingsSchema.ts— placing it inside a nested sub-object (likefileFiltering.properties) causes a silent TypeScript error that only appears on clean build (incrementaltsc --buildcache hides it).Linked Issues
Closes #4025
中文说明
这个 PR 做了什么
实现了 #4025 中 @kaisersong 评论里提出的两个功能:
1. 可配置的 auto-compact 阈值
在
settings.json中新增context.autoCompactThreshold配置项,允许用户自定义 auto-compact 触发比例(默认 0.7)。配置值通过computeThresholds(window, pct?)传递给所有 4 个调用点。注意:三级阈值系统使用max(比例, 绝对值)公式,大窗口(>110K)时绝对分支始终主导,自定义阈值主要影响小窗口模型。2. Stop hook stdin 包含 context 使用率
Stop hook 的 stdin payload 新增
context_usage、context_limit、input_tokens三个字段,让用户可以通过 hook 脚本观察上下文使用情况并自定义 compact 策略。这个 PR 没有做什么
/compact)为什么需要
小窗口模型用户(32K-128K)需要控制 auto-compact 的触发时机。Hook 脚本作者需要感知上下文使用率来实现自定义策略。
风险与范围
autoCompactThreshold必须在context.properties层级关联 Issues
Closes #4025