Skip to content
Open
1 change: 1 addition & 0 deletions proxy/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"workflow-agent-id-synthesis": { "enabled": true, "order": 365 },
"image-retry-circuit-breaker": { "enabled": true, "order": 370 },
"read-dedupe": { "enabled": true, "order": 380 },
"insertion-normalization": { "enabled": true, "order": 395 },
"cache-control-normalize": { "enabled": true, "order": 400 },
"messages-cache-breakpoint": { "enabled": true, "order": 410 },
"ttl-management": { "enabled": true, "order": 500 },
Expand Down
1,125 changes: 1,125 additions & 0 deletions proxy/extensions/insertion-normalization.mjs

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions proxy/extensions/message-hash.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// message-hash — content identity for a message, shared by every extension
// that needs to recognise "the same message" across requests.
//
// Hash everything EXCEPT cache_control, because cache_control is what the
// proxy itself mutates: including it would make a message look changed the
// instant we mark it, which is the opposite of an identity.
//
// Not an extension — a primitive. It lived in mid-history-breakpoint-ladder
// until that extension was removed (it manufactured the mid-history
// divergences it was meant to bound); insertion-normalization and
// deferred-tool-rewrite both depend on this function and never depended on
// rung placement, so it moved here rather than dying with its old host.

import { createHash } from "node:crypto";

export function hashMessageContent(msg) {
if (!msg || !Array.isArray(msg.content)) return null;
const stripped = msg.content.map((block) => {
if (!block || typeof block !== "object") return block;
const { cache_control, ...rest } = block;
return rest;
});
return createHash("sha256").update(JSON.stringify(stripped)).digest("hex").slice(0, 16);
}

// Conversation identity: the hash of msgs[0], the one entry nothing appends
// past. Lives here, not in one extension, because BOTH stateful extensions
// need it and the second one learning it late is what this file exists to
// prevent.
//
// History (2026-07-28, one day, twice). insertion-normalization keyed its
// canonical on (session-id, system-prompt) and thrashed: every subagent of a
// session runs the same agent prompt, so one bucket held 39 distinct
// conversations and 100% of conversation switches within a bucket reset
// (60/60) against 1% of same-conversation continuations. Adding this sub-key
// took it to 0 resets across 940 requests.
//
// deferred-tool-rewrite had the IDENTICAL key and did not get the fix, and it
// cost real cache: its tool_addition announcement is anchored to a message
// identity, so under a shared key the anchor belongs to somebody else's
// history, fails to match, and re-anchors to "after the last user message" —
// a different index every request. Measured: output diverging at index 4
// while CC's own history was identical through index 23, twice in one corpus.
//
// The general rule this keeps re-teaching: an identity computed more cheaply
// than the thing it identifies will collide, and the collision presents as
// churn rather than as a bug.
export function conversationSubKey(messages) {
const first = Array.isArray(messages) ? messages[0] : null;
if (!first) return "empty";
const h = hashMessageContent(first);
if (h) return h;
// hashMessageContent covers block-array content only and returns null for
// STRING content — correct for its own callers, but as a bucket key that
// null collapsed every string-content conversation into one shared "empty"
// bucket (56 of 602 requests in the measured capture). A message carrying
// no content at all is the only remaining "empty".
if (first.content === undefined || first.content === null) return "empty";
return createHash("sha256")
.update(JSON.stringify({ role: first.role ?? null, content: first.content }))
.digest("hex")
.slice(0, 16);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_what":"CC#76606-family OSCILLATION evidence, SANITIZED bytes: message[863] (Agent-spawn tool_result, sonnet-queue-recon) across 8 consecutive requests a 148-second window, flipping between hook-reminders-inline and stripped forms. Selected by TIMESTAMP from the live capture (ordinal selection failed twice: capture growth + divergent numbering schemes).","_doc":"MEASURED MINIMUM, with its number, as the fixture-strategy rule requires when a fixture resists the >=10x cut (docs/directives/insertion-normalization-identity-directive.md). This file was never a harvester range dump: harvest already narrowed it to two messages per request (msg863, msg864, out of ~920). All 13 records are load-bearing — the 8 msg863 records ARE the oscillation (5 inline, 3 stripped, in wire order with their timing deltas; dropping the repeats would change the flip count the census reads off this file, 3 rows / 2 flaps per docs/code-reviews/census-flap-joined-report.md), and the 5 msg864 records carry the same flip on the standalone side. Evidence payload 3645 bytes, sanitization claim 2217 more; the only reduction left was whitespace, so the cut is 1.1x, not 10x.","_sanitization":"Rebuilt 2026-07-31. This fixture was committed RAW end to end (its own header said so): operator hook prose, an agent tool_result naming a sub-agent and its session, and two thinking-block signatures of 1170 and 531 base64 chars. Every message now goes through scrubMessage. The MERGED standalone (msg864, role system) is re-joined from the SANITIZED constituents — msg863's two wrapper-stripped reminders — and asserted byte-equal to the plain scrub of the merged string, so the join-hash relation this fixture exists for is carried by construction. tools/harvest.mjs scrubMessage + rebaseTimestamps (one scrubber, no second path). TOKENIZED: every text, per '\\n\\n' segment, as t_<sha12>_<len>, with <system-reminder> WRAPPERS surviving verbatim around a tokenized inner text; nested payloads (block.data, block.source.data, any >64-char string under source) as data_<sha10>; thinking signatures as sig_<sha10>; conversation keys and sids as s-<sha12>, the same token this file's NAME carries. REBASED: every timestamp onto 2000-01-01T00:00:00.000Z + its original delta from this fixture's earliest instant. PRESERVED — this is what the fixture is FOR: equality of equal texts, the '\\n\\n' join and paragraph-prefix relations (scrubText is a homomorphism over '\\n\\n' since bffcb05), tool_use_id/id pairing, message and block ordering, timestamp ordering and spacing. RESIDUAL, accepted (operator ruling 2026-07-31, local operator-controlled traffic): token lengths, paragraph counts, intra-fixture timing deltas. This note is a CLAIM; test/harvest-scrub-relations.test.mjs walks this file and re-checks each absence class mechanically.","_merge_standalone":"msg864 across the same window: CC's MERGED standalone — both hook reminders wrapper-stripped, joined with \\n\\n (627 raw chars), role system; the suppression gap's real shape","requests":[{"ts":"2000-01-01T00:00:00.000Z","msgCount":913,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]},{"type":"text","text":"<system-reminder>\nt_d1d8fb876c64_349\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nt_be53f4f44125_276\n</system-reminder>"}]}},{"ts":"2000-01-01T00:00:28.418Z","msgCount":916,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]},{"type":"text","text":"<system-reminder>\nt_d1d8fb876c64_349\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nt_be53f4f44125_276\n</system-reminder>"}]}},{"ts":"2000-01-01T00:00:34.938Z","msgCount":918,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]},{"type":"text","text":"<system-reminder>\nt_d1d8fb876c64_349\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nt_be53f4f44125_276\n</system-reminder>"}]}},{"ts":"2000-01-01T00:00:50.701Z","msgCount":920,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]},{"type":"text","text":"<system-reminder>\nt_d1d8fb876c64_349\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nt_be53f4f44125_276\n</system-reminder>"}]}},{"ts":"2000-01-01T00:00:50.898Z","msgCount":921,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]}]}},{"ts":"2000-01-01T00:00:58.056Z","msgCount":922,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]},{"type":"text","text":"<system-reminder>\nt_d1d8fb876c64_349\n</system-reminder>"},{"type":"text","text":"<system-reminder>\nt_be53f4f44125_276\n</system-reminder>"}]}},{"ts":"2000-01-01T00:02:06.631Z","msgCount":923,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]}]}},{"ts":"2000-01-01T00:02:28.036Z","msgCount":926,"msg863":{"role":"user","content":[{"tool_use_id":"toolu_012R3rh89mR9guctAZbiaHZe","type":"tool_result","content":[{"type":"text","text":"t_c7b26e448190_289"}]}]}}],"requests_864":[{"ts":"2000-01-01T00:00:50.701Z","msg864":{"role":"assistant","content":[{"type":"thinking","thinking":"","signature":"sig_a9ca36c55c"},{"type":"thinking","thinking":"","signature":"sig_87b5b03e5f"},{"type":"tool_use","id":"toolu_01CWrpni9WFUr3drUrKWs4eF","name":"Read","input":{"file_path":"REDACTED","offset":"REDACTED","limit":"REDACTED"},"caller":{"type":"direct"}}]}},{"ts":"2000-01-01T00:00:50.898Z","msg864":{"role":"system","content":"t_d1d8fb876c64_349\n\nt_be53f4f44125_276"}},{"ts":"2000-01-01T00:00:58.056Z","msg864":{"role":"assistant","content":[{"type":"thinking","thinking":"","signature":"sig_a9ca36c55c"},{"type":"thinking","thinking":"","signature":"sig_87b5b03e5f"},{"type":"tool_use","id":"toolu_01CWrpni9WFUr3drUrKWs4eF","name":"Read","input":{"file_path":"REDACTED","offset":"REDACTED","limit":"REDACTED"},"caller":{"type":"direct"}}]}},{"ts":"2000-01-01T00:02:06.631Z","msg864":{"role":"system","content":"t_d1d8fb876c64_349\n\nt_be53f4f44125_276"}},{"ts":"2000-01-01T00:02:28.036Z","msg864":{"role":"system","content":"t_d1d8fb876c64_349\n\nt_be53f4f44125_276"}}]}
1 change: 1 addition & 0 deletions test/fixtures/harvested/pinned-s-4b6a435234bf-26-28.json

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions test/fixtures/insertion-1405.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"_comment": "Synthetic minimal repro of the 2026-07-27 14:05 splice shape (docs/directives/proxy-insertion-normalization.md). NOT real session content — built by hand for insertion-normalization.test.mjs. `priorMessages` is the 12-entry canonical history from the prior request (arrival order). `incomingMessages` is the NEXT request's messages[] as Claude Code actually sends it: two new user-role entries (a queued operator message and a task-reminder-shaped system-reminder wrapper) spliced in BETWEEN prior canonical index 9 and 10, instead of appended after index 11 where they causally arrived. Expected: classifyInsertion(incomingMessages, canonicalFromPrior) -> action 'normalized', re-serialized to priorMessages (0..11) followed by the two new entries in their incoming relative order (queued-message, then task-reminder).",
"priorMessages": [
{ "role": "user", "content": [{ "type": "text", "text": "turn0-user: investigate the widget cache" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn1-assistant: looking into it" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn2-user: check the config file" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn3-assistant: config looks fine" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn4-user: run the test suite" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn5-assistant: tests pass" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn6-user: add a regression guard" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn7-assistant: guard added" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn8-user: commit the change" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn9-assistant: committed" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn10-user: what's next on the roadmap" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn11-assistant: next is the deploy step" }] }
],
"incomingMessages": [
{ "role": "user", "content": [{ "type": "text", "text": "turn0-user: investigate the widget cache" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn1-assistant: looking into it" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn2-user: check the config file" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn3-assistant: config looks fine" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn4-user: run the test suite" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn5-assistant: tests pass" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn6-user: add a regression guard" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn7-assistant: guard added" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn8-user: commit the change" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn9-assistant: committed" }] },
{ "role": "user", "content": [{ "type": "text", "text": "queued-message: operator says pause before deploy" }] },
{ "role": "user", "content": [{ "type": "text", "text": "<system-reminder>\nThe task tools haven't been used recently.\n</system-reminder>" }] },
{ "role": "user", "content": [{ "type": "text", "text": "turn10-user: what's next on the roadmap" }] },
{ "role": "assistant", "content": [{ "type": "text", "text": "turn11-assistant: next is the deploy step" }] }
]
}
Loading
Loading