Skip to content

fix: add hysteresis to message-count compaction - #582

Open
JiangYuxin-Jim wants to merge 1 commit into
shareAI-lab:mainfrom
JiangYuxin-Jim:fix/snip-compaction-hysteresis
Open

JiangYuxin-Jim wants to merge 1 commit into
shareAI-lab:mainfrom
JiangYuxin-Jim:fix/snip-compaction-hysteresis

Conversation

@JiangYuxin-Jim

@JiangYuxin-Jim JiangYuxin-Jim commented Sep 22, 2026 •

Copy link
Copy Markdown

Closes #581.

When snip_compact trims a history back to the same 50-message threshold that triggers it, each subsequent exchange can cause another full transcript write before the next model call. Separate the trigger and target so normal compaction leaves room for new messages.

The key problem is the zero headroom after the first compaction. snip_compact returns exactly 50 messages; as soon as one or more new messages are appended, the history becomes 51/52 messages and is eligible for compaction again on the next prepare() call. After the threshold is crossed once, the history can repeatedly oscillate 50 -> 51/52 -> 50, causing a largely overlapping transcript archive to be written on nearly every subsequent model turn or tool-use round.

With trigger_messages=50 and target_messages=30, reaching trigger_messages compacts the history to target_messages, leaving roughly 20 messages of headroom. Asking one more question or adding one more exchange therefore does not immediately trigger another compaction; the next compaction happens only after that headroom is consumed. This avoids repeated compaction on nearly every subsequent question or tool-use round, reducing redundant full-history serialization, highly overlapping transcript files, and repeated reshaping of the model's recent context.

Changes

  • In S08 and S15, use trigger_messages=50 and target_messages=30; trigger when the history reaches 50 messages, including when a batch overshoots it.
  • Retain the first three messages, one archive marker, and normally the latest 26 messages. Keep the existing archive-before-trim and tool-call/result boundary protection; preserving a pair can slightly exceed the target.
  • Require 5 <= target_messages < trigger_messages so the target includes at least one recent message and leaves headroom.
  • Test the full preparation path: 49 messages cause no archive, 50 compact to 30, nine subsequent two-message additions create no new archives, and the tenth triggers the next archive. Also cover overshoot, invalid settings, complete transcript preservation, and both tool-pair boundaries.
  • Synchronize the S08/S15 English, Chinese, and Japanese READMEs, update the three S08 diagrams, and regenerate the website course data/assets with the repository script.

Tradeoffs

This keeps fewer recent messages directly in context (26 instead of 46), with older content available from the transcript. The defaults are a proposal for maintainer review. The existing active-request retention concern in #577 remains relevant when trimming more history; this PR does not attempt to solve that separate issue. Callers using the old max_messages keyword should now pass trigger_messages and target_messages; the in-repository call sites and tests are updated.

Validation

  • Python 3.11.16 in the contributor's existing py311 environment: python -m pytest tests -q — 485 passed, 112 subtests passed.
  • npx tsc --noEmit — passed.
  • npm run build — passed; 118 static pages generated.
  • git diff --check — passed.
  • Confirmed that the new hysteresis regression fails on both original implementations before the fix, and that the changed documentation code block is identical across all three languages.

AI assistance: OpenAI Codex assisted with the implementation, regression tests, documentation, verification, and PR preparation, based on the contributor's observation of repeated compaction at the message threshold.


中文版

关闭 #581。

当 snip_compact 将历史记录裁剪回触发压缩的同一个 50 条消息阈值时,之后每新增一轮消息,都可能在下一次模型调用前再次触发完整的 transcript 写入。将触发阈值与目标保留数量分开,可以让正常压缩后为新消息留下余量。

核心问题是第一次压缩后没有留下任何消息数量余量。snip_compact 恰好返回 50 条消息;只要随后新增一条或多条消息,历史记录就会变成 51/52 条,并在下一次调用 prepare() 时再次满足压缩条件。一旦首次越过阈值,消息数量就可能持续在 50 -> 51/52 -> 50 之间循环,使后续几乎每个模型轮次或工具调用轮次都写入一份内容高度重叠的 transcript 归档。

设置 trigger_messages=50 与 target_messages=30 后,当消息数达到 trigger_messages 时,就会将消息条数压缩到 target_messages,从而留下约 20 条消息的余量。因此,用户再问一个问题或再增加一轮消息时,不会立刻再次触发压缩;只有在这些余量被用完后,才会进行下一次压缩。这样可以避免后续几乎每个问题或工具调用轮次都重复压缩,减少对完整历史记录的重复序列化、内容高度重叠的 transcript 文件,以及对模型近期上下文的反复重整。

改动

  • 在 S08 和 S15 中使用 trigger_messages=50 和 target_messages=30;当历史记录达到 50 条消息时触发压缩,也包括一次批量新增使消息数越过该阈值的情况。
  • 保留最前面的 3 条消息、1 条归档标记,以及通常情况下最近的 26 条消息。继续保留现有的“先归档、后裁剪”行为,以及对工具调用与结果边界的保护;为了完整保留一个配对,最终消息数可能略高于目标值。
  • 要求满足 5 <= target_messages < trigger_messages,确保目标数量至少包含一条近期消息,并为后续消息留下余量。
  • 测试完整的准备流程:49 条消息不会产生归档;50 条消息会压缩至 30 条;之后连续 9 次、每次新增 2 条消息都不会产生新归档;第 10 次新增时触发下一次归档。同时覆盖越过阈值、无效配置、完整 transcript 保留,以及两种工具调用配对边界情况。
  • 同步更新 S08/S15 的英文、中文和日文 README,更新 S08 的 3 张图,并使用仓库脚本重新生成网站课程数据与资源。

权衡

此修改会减少直接保留在上下文中的近期消息数量(从 46 条降至 26 条),更早的内容仍可从 transcript 中获取。这些默认值是供维护者审查的建议。裁剪更多历史记录时,#577 中已有的当前请求内容保留问题仍然相关;本 PR 不尝试解决这一独立问题。仍在使用旧 max_messages 关键字的调用方现在应改为传入 trigger_messages 和 target_messages;仓库内的调用位置和测试均已更新。

验证

  • 在贡献者现有 py311 环境的 Python 3.11.16 中运行:python -m pytest tests -q — 485 项测试通过,112 项子测试通过。
  • npx tsc --noEmit — 通过。
  • npm run build — 通过;生成了 118 个静态页面。
  • git diff --check — 通过。
  • 已确认新的滞后机制回归测试在修复前的两处原始实现上均会失败,并确认修改后的文档代码块在三种语言中完全一致。

AI 辅助说明:基于贡献者对消息阈值处重复压缩现象的观察,OpenAI Codex 协助完成了实现、回归测试、文档、验证和 PR 准备工作。

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

s08/s15: separate snip_compact trigger and target to avoid repeated transcript writes

1 participant