Skip to content

fix(compression): CJK-aware token estimates and stop re-copying stale head summaries - #69302

Merged
teknium1 merged 2 commits into
mainfrom
salvage/52517-cjk-headfix
Jul 22, 2026
Merged

fix(compression): CJK-aware token estimates and stop re-copying stale head summaries#69302
teknium1 merged 2 commits into
mainfrom
salvage/52517-cjk-headfix

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Context compression now budgets CJK/Hangul/Kana text at its real token density and stops re-copying a stale handoff summary into the protected head on every re-compaction. Root causes: estimate_tokens_rough applied the English-centric (len+3)//4 rule to CJK text (undercounting ~4x, inflating the protected tail), and the Phase-4 head-copy loop carried an old protected-head handoff forward verbatim while also inserting its updated replacement, so summaries accumulated across compactions and the live prompt never actually shrank.

Changes

  • agent/model_metadata.py: estimate_tokens_rough counts CJK-dense codepoints (Hangul Jamo/Syllables, CJK ideographs, fullwidth forms/halfwidth kana) as ~1 token each; estimate_messages_tokens_rough / estimate_request_tokens_rough route through it via a new image-stripping token shadow.
  • agent/model_metadata.py (salvage addition): perf gatestr.isascii() O(1) fast path keeps pure-ASCII text bit-identical to the classic (len+3)//4 rule, and the CJK count runs as a single compiled-regex C pass instead of the PR's per-character Python loop.
  • agent/context_compressor.py: Phase-4 head assembly skips the old handoff at summary_idx (_is_context_summary_content guarded) so re-compaction replaces it instead of duplicating it; last_head_role now reads from the assembled head. _estimate_msg_budget_tokens uses the CJK-aware estimator for string content and tool-call envelopes while preserving the Tail-protection token estimate ignores codex_reasoning_items, so Codex/Responses sessions compact too late #55572 _REPLAY_BUDGET_KEYS replay-field accounting.
  • Tests: new tests/agent/test_cjk_token_estimation.py (+ salvage parity/fast-path tests against the per-char reference), continuity regression test for the stale-head-summary fix, and two stale expectations updated to the new intended behavior.

Dropped from the original PR: the compression.hygiene_threshold gateway knob (scope creep — split out).

Perf evidence (timeit, 1MB strings)

Case (len+3)//4 baseline PR per-char loop This PR (gated)
1MB ASCII 0.17 µs ~3.0 s (~2.8×10⁷x) 0.23 µs (1.3x)
1MB Hangul ~2.1 s ~352 ms
1MB mixed EN/KO ~127 ms

Validation

Before After
estimate_tokens_rough("가"*400) 100 400
Re-compaction with protected-head handoff old + new summaries both kept, prompt grows exactly one (updated) summary survives
1MB ASCII tool-output estimate 0.17 µs 0.23 µs (no hot-path regression)

Targeted tests: bash scripts/run_tests.sh tests/agent/ -q -k 'estimate or cjk or continuity or head' → 163 passed, 0 failed; full runs of the 4 touched test files → 144 passed, 0 failed.

Credit

Salvaged from #52517 by @plainOldCode (gateway hygiene knob split out; estimator perf-gated during salvage).

Infographic

cjk-compression-mission-patch

miniadmin and others added 2 commits July 22, 2026 04:53
…st path

The salvaged estimator ran a per-character Python loop on every
estimate_tokens_rough() call — a ~28,000,000x slowdown vs (len+3)//4 on a
1MB ASCII tool output (measured ~3.0s per call). Gate it:

- str.isascii() O(1) fast path keeps pure-ASCII text bit-identical to the
  classic (len+3)//4 rule at ~1.3x baseline cost (0.23us vs 0.17us per
  1MB call).
- Non-ASCII text counts dense CJK chars via a compiled character-class
  regex in C (len(text) - len(re.sub(''))): ~352ms/1MB hangul vs ~2.1s
  for the per-char loop.
- Non-ASCII-but-non-CJK text (accents, Cyrillic, emoji) keeps the classic
  rule.

Also: parity tests against the per-char reference implementation, and
updated two stale expectations that encoded the old behavior (CJK now
counted ~1 token/char; short string content now ceil-divided instead of
floored to 0). The continuity test now detects merged-into-tail summaries
via _is_context_summary_content.
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

running on 42534d6

CI timings

CI timings · View job

Wall time 7m23s vs 7m46s (-4.9%). 7 job(s) slower, 10 faster, 3 unchanged.

  • Python tests / Run tests slice 5/8: -50.0s
  • Python tests / Run tests slice 1/8: +46.0s
  • Python tests / Run tests slice 3/8: -26.0s
  • Python tests / Run tests slice 4/8: -12.0s
  • Python tests / Run tests slice 8/8: +11.0s

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/compression Context compression and continuation sessions sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 22, 2026
@teknium1
teknium1 merged commit ea0fd39 into main Jul 22, 2026
41 checks passed
@teknium1
teknium1 deleted the salvage/52517-cjk-headfix branch July 22, 2026 13:57
teknium1 added a commit that referenced this pull request Jul 22, 2026
The summary_idx head-copy skip (from #69302) dropped the entire merged
handoff message, deleting the genuine prior-tail user content that
#47274's _strip_context_summary_handoff_message correctly unwraps.
Strip handles both shapes: standalone handoffs drop, merged handoffs
keep their real content. Caught by
test_recompression_of_current_merged_handoff_preserves_prior_tail_once
when both PRs landed together.
teknium1 added a commit that referenced this pull request Jul 22, 2026
The summary_idx head-copy skip (from #69302) dropped the entire merged
handoff message, deleting the genuine prior-tail user content that
#47274's _strip_context_summary_handoff_message correctly unwraps.
Strip handles both shapes: standalone handoffs drop, merged handoffs
keep their real content. Caught by
test_recompression_of_current_merged_handoff_preserves_prior_tail_once
when both PRs landed together.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
The summary_idx head-copy skip (from NousResearch#69302) dropped the entire merged
handoff message, deleting the genuine prior-tail user content that
NousResearch#47274's _strip_context_summary_handoff_message correctly unwraps.
Strip handles both shapes: standalone handoffs drop, merged handoffs
keep their real content. Caught by
test_recompression_of_current_merged_handoff_preserves_prior_tail_once
when both PRs landed together.
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
The summary_idx head-copy skip (from NousResearch#69302) dropped the entire merged
handoff message, deleting the genuine prior-tail user content that
NousResearch#47274's _strip_context_summary_handoff_message correctly unwraps.
Strip handles both shapes: standalone handoffs drop, merged handoffs
keep their real content. Caught by
test_recompression_of_current_merged_handoff_preserves_prior_tail_once
when both PRs landed together.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants