s08/s15: separate snip_compact trigger and target to avoid repeated transcript writes
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 45/100
Línea de trabajo
Start with the actual snip_compact implementations in S08 and S15 and reproduce the repeated .transcripts/*.jsonl archives using the alternating-message example. Review the existing regression coverage, chapter documentation, and regenerated website data mentioned in the issue. Done means separating the trigger and retained-message thresholds while preserving archive-before-trim behavior, tool boundaries, and coverage across both chapters.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
snip_compact in S08 and S15 uses max_messages=50 both as the trigger threshold and the retained message count. For a history without a tool pair crossing a cut point, the first compaction produces exactly 50 messages. Adding the next assistant/user exchange gives 52 messages and triggers another full transcript write, returning to 50 again.
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.
Because preparation runs before every model call, this can happen on successive tool-use rounds within one user request as well as across separate questions. Each write serializes the entire current history to a new .transcripts/*.jsonl file, with substantial overlap between snapshots.
Reproduction
On main at 0dcafa2, using the actual S08 compactor in a temporary directory with short, alternating user/assistant messages:
Initial history: 51 -> 50 messages, 1 archive
Add 2 messages: 52 -> 50 messages, 2 archives
Add 2 messages: 52 -> 50 messages, 3 archives
...
After 10 such additions: 11 archives total
Calling it again without adding messages does not produce another archive; the issue is the lack of headroom after compaction. This is a write-frequency observation, not a measured latency benchmark.
Proposal
Could trigger_messages and target_messages be configured separately? When the message count reaches trigger_messages, compact it to target_messages. For example, trigger at 50 and trim toward 30, including the archive marker. This leaves about 20 messages of headroom while retaining the current archive-before-trim behavior and tool-call/result boundary protection. No timer, background worker, or new abstraction is needed.
The practical benefit is that, after the history first reaches 50 messages and is compacted to about 30, asking one more question or adding one more exchange does not immediately trigger compaction again. Compaction happens only after the roughly 20-message headroom has been consumed. This avoids repeatedly compacting 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.
The tradeoff is that fewer recent messages remain directly visible (normally 26 instead of 46); older content is still recoverable from the transcript. The exact defaults are open to discussion, and preserving tool pairs may slightly exceed the target. The separate active-request retention concern in #577 is relevant to this tradeoff and is not solved by changing thresholds.
I have prepared a focused implementation for S08 and S15 with regression coverage, synchronized English/Chinese/Japanese chapter documentation, and regenerated website data, and will link it here through a PR.
AI assistance: OpenAI Codex assisted with reproducing the reported behavior and preparing the implementation, tests, documentation, and this issue.
中文版
S08 和 S15 中的 snip_compact 同时使用 max_messages=50 作为触发阈值和压缩后保留的消息数量。对于裁剪位置没有跨越工具调用配对的历史记录,第一次压缩后会恰好保留 50 条消息。随后再加入一组 assistant/user 消息,消息数量会变成 52 条,从而再次触发完整的 transcript 写入,并重新回到 50 条。
核心问题是第一次压缩后没有留下任何消息数量余量。snip_compact 恰好返回 50 条消息;只要随后新增一条或多条消息,历史记录就会变成 51/52 条,并在下一次调用 prepare() 时再次满足压缩条件。一旦首次越过阈值,消息数量就可能持续在 50 -> 51/52 -> 50 之间循环,使后续几乎每个模型轮次或工具调用轮次都写入一份内容高度重叠的 transcript 归档。
由于每次模型调用前都会执行准备流程,这种情况既可能发生在同一个用户请求内连续的工具调用轮次中,也可能发生在不同问题之间。每次写入都会把当前的完整历史记录序列化到一个新的 .transcripts/*.jsonl 文件中,因此不同快照之间存在大量重复内容。
复现
在 main 分支的 0dcafa2 提交上,于临时目录中使用 S08 的实际压缩器,并输入较短且交替出现的 user/assistant 消息:
初始历史:51 -> 50 条消息,1 个归档
新增 2 条消息:52 -> 50 条消息,2 个归档
新增 2 条消息:52 -> 50 条消息,3 个归档
...
如此新增 10 次后:共生成 11 个归档
如果不新增消息而再次调用,则不会生成新的归档;问题在于压缩后没有余量。这是对写入频率的观察,并非经过测量的延迟性能基准。
建议方案
可以分别设置 trigger_messages 与 target_messages:当消息数达到 trigger_messages 时,就将消息条数压缩到 target_messages。例如,在消息数达到 50 时触发,并将消息数裁剪至接近 30,其中包含归档标记。这样可以留出约 20 条消息的余量,同时保留现有的“先归档、后裁剪”行为,以及对工具调用与结果边界的保护。无需引入定时器、后台工作线程或新的抽象层。
这样做最直接的好处是:当历史记录第一次达到 50 条并被压缩到约 30 条后,用户再问一个问题或再增加一轮消息时,不会立刻再次触发压缩。只有在约 20 条消息的余量被逐步用完后,才会进行下一次压缩。这样可以避免后续几乎每个问题或工具调用轮次都重复压缩,减少对完整历史记录的重复序列化、内容高度重叠的 transcript 文件,以及对模型近期上下文的反复重整。
其代价是直接保留在上下文中的近期消息会减少(通常从 46 条降至 26 条);更早的内容仍可从 transcript 中恢复。具体默认值可以继续讨论,而且为了完整保留工具调用配对,最终消息数可能略高于目标值。#577 中关于当前请求内容保留的独立问题与这一取舍有关,单纯修改阈值并不能解决该问题。
我已经为 S08 和 S15 准备了一个范围明确的实现,其中包括回归测试、同步更新的英文/中文/日文章节文档,以及重新生成的网站数据,并会通过 PR 将其关联到本 Issue。
AI 辅助说明:OpenAI Codex 协助复现了所报告的行为,并协助准备实现、测试、文档和本 Issue。
- Lenguaje dominante
- Python
- Estrellas
- 78.6k
- Forks
- 12.6k
- Merge medio
- 3 h 27 min
- PR fusionados (30 d)
- 2
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de shareAI-lab/learn-claude-code
-
[web] Buttons show the default arrow cursor on hover (Tailwind v4 dropped the preflight `cursor: pointer`)Posiblemente ocupada @xbsheng la tomó hace 3 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
shareAI-lab/learn-claude-code#594 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
shareAI-lab/learn-claude-code#591 ·
Los mantenedores suelen responder en 1 día
-
s04/s05/s06 的 README.zh.md 里有几处代码注释仍是英文Posiblemente ocupada @zl1nq la tomó hace 17 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
shareAI-lab/learn-claude-code#589 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
shareAI-lab/learn-claude-code#583 ·
Los mantenedores suelen responder en 1 día
-
[s08] Reactive compaction does not recognize Anthropic's "prompt is too long" errorPosiblemente ocupada @zmylol la tomó hace 39 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
shareAI-lab/learn-claude-code#560 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de shareAI-lab/learn-claude-code
Issues similares
-
area:core-platform effort:S priority:P3 status:in-review type:test
Dificultad 2/5 Menos de una hora Aptitud para principiantes 80/100
randomparity/kdive#3246 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Requantification library converts m/z to mass with the hydrogen-atom mass instead of the proton massAbiertobug
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
nf-core/metaboigniter#130 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
common-workflow-language/cwl-utils#477 ·
Los mantenedores suelen responder en 1 día
-
Add dependabotAbiertofeat
Dificultad 2/5 Menos de una hora Aptitud para principiantes 72/100
open-nudge/opentemplate#204 ·
Los mantenedores suelen responder en 1 día
-
github-stale-ci-pr-closer: KV state is double-wrapped, never persists, and hits the depth-32 limitPosiblemente ocupada @VascoSch92 la tomó hoy. Abiertopriority:medium ready-for-dev
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
OpenHands/extensions#781 ·
Los mantenedores suelen responder en 1 día