feat: manual context compression - #9795
Conversation
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/builtin_stars/builtin_commands/commands/conversation.py" line_range="311" />
<code_context>
+ "❌ Context compression requires admin permission in a shared "
+ "group conversation."
+ )
+ return
+
+ if not provider_settings.get("enable", True):
</code_context>
<issue_to_address>
**issue (bug_risk):** When the compact event is stopped before or after the provider call, the command returns immediately after sending the transient progress message and never sets a terminal result. WebChat therefore retains or clears the progress state without receiving the documented cancellation message, leaving the user without an explicit outcome.
**Triggers:** When `/stop` marks the compact event itself as stopped.
**Suggested fix:** Call `reply(cancelled)` before returning from both `message.is_stopped()` branches, unless the surrounding transport explicitly guarantees a terminal response for stopped events.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and /compact replaces persisted conversation history with an LLM-generated summary, permanently discarding older exact messages; reverting the code would not restore histories already compressed. The impact is limited to opted-in conversations and requires an explicit command, but an incorrect summary or authorization check could still cause unrecoverable context loss.
Blocking findings: astrbot/builtin_stars/builtin_commands/commands/conversation.py:311
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
Hi @Soulter, when you have time, could you please take a quick look at whether the feature direction of this PR fits AstrBot’s current roadmap? I’m mainly looking for direction-level feedback before further iteration; no rush on the detailed review. Thanks! |
|
I'll resolve the existing merge conflicts and adapt this PR to the latest Agent Runner configuration changes in #9821, then mark it ready for review again. |
78915fc to
77cdaa1
Compare
There was a problem hiding this comment.
Hey - I've reviewed your changes and they look great!
Sourcery assessment
Needs a human reviewer. The command permanently overwrites a conversation's persisted history with an LLM summary, so omitted details cannot be recovered by reverting the code and may require manual reconstruction. The impact is bounded to conversations where an administrator or user explicitly enables and invokes the feature, rather than affecting all conversations by default.
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
TL;DR
/compactcommand for the local Agent Runner so users can explicitly request LLM-based context compression before the automatic threshold is reached.local + llm_compressconfigurations.Background
Fixes #9281
Related to #8348 and #9252, but intentionally does not implement configurable automatic compression thresholds.
AstrBot already supports automatic context management through maximum-turn enforcement and a token-threshold safeguard. When a request approaches the model context-window limit,
ContextManagercan summarize the history with an LLM or truncate it by conversation turns. However, token-triggered compression does not run until its threshold is reached. With models offering 512K, 1M, or larger context windows, users may encounter practical degradation far below the current 82% threshold:This PR adds the manual
/compactworkflow available in other agent harnesses. It lets users request compression at an appropriate point without changing AstrBot's automatic threshold or existing automatic behavior.The first version uses a command instead of a dedicated ChatUI button. AstrBot's command system already provides registration, autocomplete, enable/disable controls, renaming, and permission management. Reusing it keeps the patch reviewable while supporting WebChat and other messaging platforms without a new API or UI component.
Because an LLM-generated summary may omit role state, narrative facts, or task details, this capability is marked experimental and remains disabled by default. Users must explicitly opt in before the command can run.
Modifications / 改动点
1.
/compactuser commandRegister
/compactin the built-in command plugin and reuse the existing command-management behavior:llm_compresscontext strategy.The live user-visible flow remains intentionally concise:
The generated summary is never printed into the chat. WebChat additionally receives an
agent_statsevent so the context ring immediately reflects the estimated size of the compressed history.WebChat marks the progress status as ephemeral. The live client replaces it with the terminal result, while persisted history after a successful run contains only:
2. Opt-in experimental setting
Add the following configuration value:
{ "agent_runner": { "runner_type": "local", "config": { "compression": { "enable_manual_context_compression": false } } } }Configuration behavior:
false, so upgrading does not silently enable the feature for existing users.agent_runner.runner_type=localandagent_runner.config.compression.overflow_strategy=llm_compress.false, matching the embedded-runner semantics introduced by refactor: embed agent runner configuration in profiles #9821.provider_settings.enable_manual_context_compressionvalues are migrated into the embedded local compression config during the existing v2-to-v3 migration.3. Reuse the existing compression flow
Add an optional
force_compressargument toContextManager.process(). Its default remainsfalse, preserving all existing automatic callers.With
force_compress=True, the manual command:_run_compression(), the existingTokenCounter, and the LLM summary compressor; andAutomatic compression retains its existing half-truncation protection. Only the manual path disables that fallback so a failed
/compactoperation cannot destructively truncate the original history.4. Preserve the latest complete turn
Add an optional
preserve_latest_roundbehavior to the LLM summary compressor. It defaults to disabled and therefore does not alter automatic compression output.When enabled by manual compression:
agent_runner.config.compression.keep_recent_ratio, without splitting logical turns.This reduces the risk of changing the user's most recent requirements, tool state, or editable response.
5. Checkpoint-aware history persistence
The manual command reads the persisted database history and uses the existing checkpoint utilities during conversion:
bind_checkpoint_messages()associates persisted checkpoints with their messages.dump_messages_with_checkpoints()serializes the compressed result.6. Token-benefit validation and non-destructive failure
Estimate tokens both before and after compression:
On success, the compressed history and
token_usage=0are saved in the same database update. The next normal model response refreshes token state using the provider's actual usage information.7. Session locking, concurrency revalidation, and stop handling
/compactacquires the same unified-message-origin (UMO) session lock used by normal local Agent requests:/new, or modifies the same history through the Dashboard/API, the result is discarded./stoprequest prevents the compressed result from being written and explicitly reports that the original history was preserved.Dashboard and API history updates do not acquire this session lock. The additional conversation-ID and history comparisons therefore protect against external changes that occur while the LLM summary is being generated.
8. Safe verification after database exceptions
If
update_conversation()raises an exception, the command does not immediately assume that persistence failed. It safely reloads the database state once:This covers cases where a database update commits successfully but the client still receives an exception.
9. WebChat statistics, transient progress, and log privacy
webchat_ephemeralchain type and remains visible to connected clients.agent_statsevent is sent on a best-effort basis after persistence and after leaving the session lock.10. Reuse provider resolution
Refactor the existing context-compression provider resolver so both normal Agent construction and
/compactcan call it without changing its selection semantics:Existing automatic compression provider selection remains unchanged.
Scope and Compatibility
This PR intentionally keeps the following boundaries:
The implementation is aligned with the embedded Agent Runner configuration introduced by refactor: embed agent runner configuration in profiles #9821.
Only AstrBot's built-in local Agent Runner is supported.
Dify, Coze, Alibaba Cloud Bailian, DeerFlow, and other remote runners own their context remotely; this PR does not attempt to rewrite remote history.
No dedicated Compress now ChatUI button is added.
No backend API or OpenAPI schema is added.
The automatic 82% compression threshold is unchanged.
The configurable automatic token thresholds proposed in [Feature]Token-Threshold Context Compression #8348 and [Feature] 上下文压缩阈值可配置化 #9252 are out of scope.
The generated summary is not exposed in chat.
No dependency or lockfile is changed.
No database schema is changed.
Existing legacy manual-compression values are migrated into the embedded local runner config; missing values remain disabled.
Default behavior for all existing automatic compression callers remains unchanged.
This is NOT a breaking change. / 这不是一个破坏性变更。
Changed Files and Size
This PR changes 26 tracked files relative to
fe3d77568b88ea3be83b2190d515da0a039da399:astrbot/builtin_stars/builtin_commands/commands/conversation.py/compactgates, locking, compression, persistence, and feedbackastrbot/builtin_stars/builtin_commands/main.py/compactastrbot/core/agent/context/compressor.pyastrbot/core/agent/context/config.pyastrbot/core/agent/context/manager.pyastrbot/core/agent/context/token_counter.pyastrbot/core/astr_main_agent.pyastrbot/core/config/agent_runner.pyastrbot/core/utils/migra_helper.pyastrbot/core/config/default.pyconfig-metadata.jsonfilesPatch size:
Most added lines are regression tests. The implementation adds no remote-runner compatibility layer, UI component, or dependency.
Screenshots or Test Results / 运行截图或测试结果
Test Environment
25H226200.91683.1224.16.011.19.07.6.4fe3d77568b88ea3be83b2190d515da0a039da399(#9821)6e2bf2b559f75afa665e5d71346b2062c84bca85Verification Steps
llm_compressunder its embedded compression config, and enable Manual Context Compression (Experimental)./compactwhile the context is below the automatic 82% threshold.Feature Regression
The final feature-related run covers context management, token counting,
/compact, the Tool Loop runner, provider resolution, session locking, checkpoints, Dashboard SSE, Dashboard WebSocket, and OpenAPI WebSocket:Result:
This includes automatic compression behavior, forced compression without half truncation, latest-round and checkpoint preservation, all command gates, provider failures, token-benefit checks, stop handling, concurrent history changes, database verification states, log privacy, transient progress, durable terminal results, context statistics, and the streaming-disconnect persistence regression.
Embedded Agent Runner Configuration Regression
The #9821 configuration and lifecycle suites were run against the adapted implementation:
Result:
These tests cover normalization, local defaults, legacy migration, config save/load, profile round trips, runner switching, metadata conversion, and all three locale resources. A dedicated regression fixture represents a pre-feature v2 default local-runner root without the new boolean field, preventing legacy Agent Runner settings from being discarded during migration.
Token Metrics Regression
Focused compatibility coverage for the current token-usage boundary:
The Tool Loop and
ContextManager.process()retain the upstreamtrusted_token_usageinterface.ContextManagerpasses that value toTokenCounteronly asreported_token_usagefor the automatic compression threshold calculation. Once compression is triggered, the logged before/after metrics use independent estimates from the actual message lists and do not inherit provider-reported usage. The forced manual path uses local estimates throughout.Full Repository Regression
Result on Windows:
The 32 failures are in Windows-specific path separator, CRLF, symlink, local shell, file URI, and sandbox skill tests. None of the 15 failing test files is changed by this PR, their intersection with the 26-file PR diff is zero, and no related production path appears in the diff. All suites covering files changed by this PR pass as listed above.
Formatting and Static Checks
All three Chinese, English, and Russian
config-metadata.jsonfiles were also validated as parseable JSON.GitHub CI
All current checks passed on
6e2bf2b559f75afa665e5d71346b2062c84bca85, including the Python CodeQL analysis and final CodeQL gate, the full unit-test job, Dashboard build, formatting, and the 15-job Python 3.10–3.14 smoke-test matrix across Ubuntu, macOS, and Windows.Dashboard Production Build
The three commands in the Dashboard
buildscript were run directly against the installed dependencies so pnpm 11 would not rewrite the repository's existing lockfile:Result:
Manual WebUI and Runtime Verification
The final rebased checkout was launched with
main.py --webui-dir dashboard/distand the manual WebUI/E2E flow passed. The embedded local compression setting saved and reloaded correctly, localized labels rendered without raw i18n keys,/compactcompleted successfully, transient progress was not persisted, the context ring updated, and page refresh preserved only the command plus one terminal result.One successful run recorded:
Both automatic and forced compression paths retain their token-change logs:
Observed behavior:
WebChat compression and Context Ring change
QQ Official Bot
Failure preserves the original history
The compression prompt was temporarily changed to request expansion, verifying that output without a token reduction is never persisted:
The following failure paths were also verified:
None of these tested paths reduced the context ring, half-truncated history, or damaged subsequent recall.
Original history and Context Ring remain unchanged after failure
Session isolation and concurrency
/compacttwice in the same session did not restore stale history or drop the latest turn./compactwaited for it and then compressed history containing the completed response./newor modifying history through another entry point during compression prevented persistence:Stop behavior
A WebChat stop request cannot immediately cancel every in-flight provider request, but it prevents the result from being persisted. After the provider returned, the command reported:
History and the context ring remained unchanged. This protects persisted state, although a slow provider request may still run to completion and incur cost, as noted under Known Limitations.
Configuration and i18n
agent_runner.config.compression.enable_manual_context_compression.dashboard/distwas used to verify the Chinese, English, and Russian labels and risk guidance, with no raw i18n keys shown.手动上下文压缩(实验性),Manual Context Compression (Experimental), andРучное сжатие контекста (экспериментальная функция), with complete localized risk guidance in each language.Observed configuration:
Simplified Chinese (
zh-CN)English (
en-US)Russian (
ru-RU)Known Limitations
ConversationManager.update_conversation(), the final update is not a database-level compare-and-swap. This path mitigates concurrent changes by re-reading and comparing the active conversation and persisted history immediately before the update.Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Enable users to safely request experimental manual LLM-based context compression for local conversations through the
/compactcommand.New Features:
/compactcommand for manually compressing local Agent Runner conversation context with an LLM.Bug Fixes:
Enhancements:
Tests: