Skip to content

standalone-reminder-strip: strip CC bookkeeping that arrives as a whole role:system message - #341

Open
deafsquad wants to merge 2 commits into
cnighswonger:mainfrom
deafsquad:feature/total-tokens-strip
Open

standalone-reminder-strip: strip CC bookkeeping that arrives as a whole role:system message#341
deafsquad wants to merge 2 commits into
cnighswonger:mainfrom
deafsquad:feature/total-tokens-strip

Conversation

@deafsquad

Copy link
Copy Markdown
Contributor

The defect

CC injects a standalone {"role":"system"} message carrying

<total_tokens>14945929 tokens left</total_tokens>

on most turns, then prunes older ones out of mid-history as the
conversation grows. Each prune rewrites the prefix from that index. The count
changes every turn, so no two markers are byte-identical and nothing can dedupe
them.

Measured on one private deployment (CC 2.1.223, 2026-08-15), a single session:

markers in one body 483–529, ~30% of the message list
prune at warm[9] 224,979-token cold write
prune at warm[1362,1441,1508,1537] 771,212-token cold write

It is CC's, not a proxy artifact: "tokens left" appears in the CC 2.1.223
binary and not in an April build.

Why content-strip does not already cover it

BOOKKEEPING_PATTERNS is gated on REMINDER_WRAP_REGEX — it matches
<system-reminder>-wrapped blocks inside a message. This arrives as a whole
unwrapped role:system message, so that gate cannot see it. I checked
before writing a new extension; happy to fold it into content-strip instead
if you would rather, though the message-level vs block-level distinction is
why I did not.

Approach

Strip rather than repair. A marker that never leaves the proxy cannot be
pruned, so once every outbound body omits them CC's pruning of them is
invisible to the cache. The test models that directly: warm and cold bodies
differing only by a pruned marker share a prefix of 1; after the strip, 3.

Safety

  • The subagent usage block is never touched.
    <usage><total_tokens>N</total_tokens><tool_uses>…</usage> inside subagent
    tool_results is parsed back by CC with
    /<total_tokens>(\d+)<\/total_tokens>/ and operators rely on it for
    per-session subagent cost. Safe on three independent grounds: the pattern is
    anchored at ^<total_tokens> so it cannot match a string starting with
    <usage>; it requires " tokens left", which the digits-only form lacks; and
    the gate only inspects role:system messages while that copy lives in a
    role:user tool_result. Four tests guard it, including an end-to-end one.
  • A marker carrying cache_control is kept. Removing a message shifts
    every later index, and a removed message carrying a breakpoint takes the
    breakpoint with it. Two live incidents on the deployment this came from (664k
    and 251k cold) were exactly that, on a different class of standalone
    role:system message. This class has never been observed carrying one, so
    the guard should never fire — it can only under-strip.
  • Both serializations. CC ships the same standalone message as a bare
    string or as a single [{type:"text"}] block and flips between them as
    history ages (122 role:system messages flipped inside one 28-second window in
    the capture). Matching one form reproduces the drift one level down.
  • Mixed messages are left alone — removing one would take real content with
    it.

Default ON

Unlike #340, this one defaults on (CACHE_FIX_STRIP_TOTAL_TOKENS=0 to
disable). It removes bytes the model never reads, and shipping a measured
six-figure cache cost as opt-in seemed the wrong default. Say the word and I
will flip it.

Verification

node --test test/proxy-total-tokens-strip.test.mjs → 14/14. Also ran
session-key-invariants (4/4), proxy-pipeline (15/15) and
proxy-content-strip (22/22) locally; your CI is the authoritative run.
tools/absence-scan.mjs clean over both new files.

Load-bearing?

Yes — it removes messages from the outbound body.

— Claude Opus 5, working with @deafsquad

CC injects a standalone {"role":"system"} message carrying
<total_tokens>N tokens left</total_tokens> on most turns, then prunes older
ones out of mid-history as the conversation grows. Each prune rewrites the
prefix from that index. The count changes every turn, so no two are
byte-identical and nothing can dedupe them.

Measured on one private deployment (CC 2.1.223, 2026-08-15), one session:
483-529 markers in a single body, ~30% of the message list; a prune at warm[9]
cost a 224,979-token cold write, and one at warm[1362,1441,1508,1537] cost
771,212.

content-strip cannot reach it: BOOKKEEPING_PATTERNS matches
<system-reminder>-wrapped blocks INSIDE a message, and this arrives as a whole
unwrapped role:system message.

Stripping beats repairing — a marker that never leaves the proxy cannot be
pruned, so CC's pruning of them becomes invisible to the cache.

Guards the subagent usage block (<usage><total_tokens>N</total_tokens>...) on
three independent grounds, and skips any marker carrying cache_control so a
removal can never collapse the breakpoint set.
Renames total-tokens-strip and widens it from one marker to the location.

CC emits bookkeeping as a WHOLE role:system message as well as a
<system-reminder>-wrapped block. content-strip already owns the vocabulary and
strips the wrapped shape; this covers the same content one location over, by
importing BOOKKEEPING_PATTERNS rather than restating them — so a pattern added
to content-strip is covered in both places without a second edit.

The second measured member is content-strip's own task reminder. It is in
BOOKKEEPING_PATTERNS already but only reachable wrapped, and CC emits it
standalone AND inconsistently between consecutive requests — present at
02:05:22, absent at 02:06:12 on one session. Three cold writes replayed against
their archived predecessors all ended the same way: the warm body carried a
standalone role:system the current body did not. Two broke at exactly that
message, prefix 794->826 and 862->898.

Importing rather than copying also picks up the TodoWrite reminder, the
token/USD counters and the auto-compact countdown in this location for free.

content-strip gains one export line. All guards unchanged: the subagent usage
block, cache_control-carrying messages, mixed content, both serializations.
17/17 here, content-strip 22/22.
@deafsquad deafsquad changed the title total-tokens-strip: drop CC's standalone remaining-budget marker standalone-reminder-strip: strip CC bookkeeping that arrives as a whole role:system message Aug 16, 2026
@deafsquad

Copy link
Copy Markdown
Contributor Author

Widened this before anyone spent review time on the narrow version — sorry for
the moving target, but I think it lands somewhere better.

What changed. total-tokens-stripstandalone-reminder-strip. It was one
marker; it is now the location.

The defect is not really "the budget marker": it is that CC emits bookkeeping as
a whole role:system message as well as a <system-reminder>-wrapped block.
content-strip already owns the vocabulary and covers the wrapped shape. This
covers the same content one location over, by importing
BOOKKEEPING_PATTERNS
rather than restating it — so a pattern added to
content-strip is covered in both places without a second edit, and there is no
copy to go stale. That is the only change to content-strip: one export line.

The second measured member is your own task reminder.
/^The task tools haven't been used recently\./ is already in
BOOKKEEPING_PATTERNS, but only reachable wrapped. CC also emits it standalone,
and emits it inconsistently between consecutive requests — present at
02:05:22, absent at 02:06:12 on one session. Three cold writes replayed against
their archived predecessors all ended the same way: the warm body carried a
standalone role:system the current body did not. Two broke at exactly that
message:

prefix 794 -> 826, breaks at 826: warm[826] "The task tools haven't been used recently…"
prefix 862 -> 898, breaks at 898: warm[898] "The task tools haven't been used recently…"

Importing rather than copying also picks up the TodoWrite reminder, the
token/USD counters and the auto-compact countdown in this location for free —
tested.

Unchanged: every guard from the first revision. The subagent
<usage><total_tokens>…</usage> block is still untouchable on three independent
grounds, a message carrying cache_control is still kept rather than removed,
mixed-content messages are still left alone, and both serializations (bare
string / single text block) are still matched.

17/17 here, proxy-content-strip 22/22, session-key-invariants 4/4,
absence-scan clean over all three touched files.

If you would rather this lived inside content-strip as a second pass than as
a sibling extension, say so and I will move it — the reason I kept it separate
is that content-strip removes blocks and this removes messages, which is a
different operation with a different failure mode (index shift, hence the
cache_control guard).

— Claude Opus 5, working with @deafsquad

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.

1 participant