@brandry/claude-jsonl-compressor 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,578 +1,635 @@
1
- # Claude Code JSONL Compression Format Notes
2
-
3
- This document describes the empirical format handled by
4
- `@brandry/claude-jsonl-compressor` package `1.0.0` and engine `v10`.
5
-
6
- Claude Code transcript JSONL is an observed internal format, not a published
7
- stable storage API. The rules below are deliberately strict where ambiguity
8
- could restore a rewound branch, break resume topology, or make a tool exchange
9
- incoherent.
10
-
11
- ## Version Domains
12
-
13
- The project keeps four independent version domains:
14
-
15
- | Domain | Current value | Meaning |
16
- | --- | --- | --- |
17
- | Package | `1.0.0` | GitHub/npm release version |
18
- | Compression engine | `v10` | Topology, partition, and rewrite behavior |
19
- | Model-pack schema | `v11` | Evidence-pack and model-summary binding protocol |
20
- | Report schema | `1` | Compression and repair report fields |
21
-
22
- A change to one domain does not imply the other domains changed.
23
-
24
- ## Three Physical and Logical Layers
25
-
26
- A transcript can contain records that play very different roles:
27
-
28
- 1. **Active API-message chain.** `user` and `assistant` records connected from
29
- the selected resume leaf through `parentUuid`. This is the history that the
30
- compressor may summarize or preserve verbatim.
31
- 2. **Control and side records.** `last-prompt`, titles, modes, attachments,
32
- hooks, and `file-history-snapshot` records may exist physically in the file
33
- without becoming ordinary API messages. They require explicit projection or
34
- attribution rules.
35
- 3. **Inactive or unattributed records.** Rewound branches and records that
36
- cannot be structurally attributed to the active branch remain useful only
37
- for diagnostics. Engine v10 excludes their content from every Claude-readable
38
- output layer and from every model-summary evidence channel.
39
-
40
- Physical line order alone is therefore not a definition of current context.
41
-
42
- ### Physical line accounting
43
-
44
- The JSONL framer recognizes records only at physical LF boundaries, removes a
45
- single CR before each LF, and skips blank physical lines. The parser keeps a
46
- trusted side map from each parsed record to its original 1-based physical line
47
- number. `validate_jsonl` and the `Read.pages` repair report use that map, so a
48
- blank line or an excluded branch cannot make an error point at the wrong file
49
- line. Input `_line`, `_mergedLines`, `_mergedUuids`, and
50
- `_validationContentLines` fields are untrusted and ignored; validation rebuilds
51
- record- and content-block provenance in an isolated projection.
52
-
53
- The model evidence pack uses its own `L<number>` logical nonblank-record
54
- coordinates because it is a reserialized evidence view; those anchors are not
55
- claims about physical source-file line numbers. Repair `recordLine`, validator
56
- diagnostic `line` fields, and the source active-chain preflight used by
57
- compression/model-pack generation, in contrast, refer to the original file's
58
- physical lines. The active-chain preflight is a logical projection, so it
59
- carries source-line provenance separately rather than renumbering the chain.
60
-
61
- Validation/report counters and `type`/`sessionId`/`subtype` metadata carried
62
- by validator or system-error diagnostics preserve ordinary short strings.
63
- Synthetic keys use the collision-safe angle-bracket namespace: `<missing>`,
64
- `<null>`, and `<invalid:json-type>`. A real string beginning with `<` is
65
- escaped by adding a second leading `<`; an unusually long real string is
66
- represented by a bounded `<string:length=...;sha256=...>` label that contains
67
- no original characters. Complex
68
- malformed values are labeled by JSON type rather than copied into diagnostics
69
- or model evidence. The same bound is applied recursively to public validator
70
- samples, nested diagnostic metadata, identifier-valued dictionary keys, and
71
- oversized source values embedded in error text. Ordinary error wording and
72
- physical-line locations remain readable when they contain only normal values.
73
-
74
- ## Common Observed Record Types
75
-
76
- | `type` | Observed purpose |
77
- | --- | --- |
78
- | `user` | User content or user-side `tool_result` blocks |
79
- | `assistant` | Assistant text, thinking, `tool_use`, model metadata, and usage |
80
- | `system` | Claude Code events, including `subtype: compact_boundary` |
81
- | `attachment` | Hook, image, file, or environment attachment metadata |
82
- | `last-prompt` | UI/session pointer containing the current `leafUuid` |
83
- | `file-history-snapshot` | File checkpoint metadata, commonly without UUID links |
84
- | `mode` / `permission-mode` | Session mode state |
85
- | `ai-title` / `custom-title` | Conversation title metadata |
86
- | `queue-operation` | Queued-input or editing metadata |
87
- | `agent-name` | Agent label metadata |
88
-
89
- Open-schema handling is required: unknown fields are preserved on retained
90
- records unless the operation explicitly projects a control record.
91
-
92
- ## UUID, Parent, Session, and Resume Relationships
93
-
94
- ### UUID chain
95
-
96
- Message-like records commonly carry:
97
-
98
- - `uuid`: record identity;
99
- - `parentUuid`: preceding record on that branch, or `null` at a root;
100
- - `sessionId`: session identity.
101
-
102
- Engine v10 treats duplicate UUIDs anywhere in the input as blocking because a
103
- UUID index would be ambiguous. A non-null `parentUuid` must be a non-empty
104
- string; any other value is malformed. A missing parent, malformed parent or loop
105
- on the selected active chain is blocking. Physical inversion is blocking except
106
- for same-session `attachment -> attachment` edges on an otherwise complete,
107
- acyclic chain; accepted attachments are emitted in logical parent order.
108
- Equivalent non-UUID-ambiguity damage wholly inside an excluded branch does not
109
- revive that branch.
110
-
111
- Mixed `sessionId` ancestry is not rejected merely because two IDs occur. A
112
- lineage is accepted only when its contiguous session runs never return to an
113
- earlier ID and the final run matches both the selected leaf and authoritative
114
- pointer. Planning forces every earlier run into `summaryIndexes` and preserves
115
- only the final run as recent raw records. A tool relationship that would move
116
- the cut across the lineage transition stops compression. This covers observed
117
- historical branch/resume files without admitting A-B-A or arbitrary cross-session
118
- raw chains.
119
-
120
- ### Authoritative `last-prompt`
121
-
122
- Automatic active-chain mode uses the physically last record whose
123
- `type` is `last-prompt`. Its `leafUuid` is authoritative. The engine does not
124
- skip a malformed latest pointer to search for an older usable pointer because
125
- that could resurrect a state the user already left.
126
-
127
- Strict topology statuses include:
128
-
129
- - `absent`
130
- - `malformed`
131
- - `malformed-parent`
132
- - `duplicate-uuid`
133
- - `dangling`
134
- - `loop`
135
- - `non-monotonic`
136
- - `session-mismatch`
137
- - `extension-limit`
138
- - `extension-branch`
139
- - `extension-unsafe`
140
- - `valid`
141
-
142
- `status` is the coarse category. `reasonCode` is the stable, machine-readable
143
- cause. Current producers emit exactly one of these pairs:
144
-
145
- | `status` | `reasonCode` | Meaning |
146
- | --- | --- | --- |
147
- | `absent` | `last-prompt-absent` | No authoritative `last-prompt` exists |
148
- | `duplicate-uuid` | `duplicate-uuid` | At least one UUID is ambiguous |
149
- | `malformed` | `leaf-uuid-malformed` | The authoritative `leafUuid` is absent, empty or not a string |
150
- | `dangling` | `chain-missing-uuid` | The selected chain references an unknown UUID |
151
- | `loop` | `chain-loop` | The selected chain contains a parent loop |
152
- | `malformed-parent` | `chain-malformed-parent` | A selected record has an invalid non-null `parentUuid` |
153
- | `dangling` | `chain-empty` | Defensive outcome when tracing returns no chain and no more specific error |
154
- | `non-monotonic` | `chain-non-monotonic` | Physical parent order violates the accepted compatibility rule |
155
- | `session-mismatch` | `lineage-unsafe` | Session ancestry is not an accepted one-way lineage |
156
- | `extension-limit` | `extension-limit-exceeded` | Post-pointer records exceed the explicit closure limit |
157
- | `session-mismatch` | `extension-authority-session-missing` | A requested closure has no usable authority session |
158
- | `extension-unsafe` | `extension-record-missing-uuid` | A closure record has no usable UUID |
159
- | `extension-branch` | `extension-record-not-linear-descendant` | A closure record is not the next direct child |
160
- | `session-mismatch` | `extension-record-session-mismatch` | A closure record does not match the authority session |
161
- | `extension-unsafe` | `extension-record-not-safe-closure` | A closure record is not a tool-result-only safe closure |
162
- | `extension-unsafe` | `extension-pending-tool-ids` | The requested closure leaves tool calls unresolved |
163
- | `valid` | `ok` | Strict topology validation succeeded |
164
-
165
- For `lineage-unsafe`, `lineageReason` carries the lower-level lineage diagnosis.
166
- Human-readable `errors` text is diagnostic and is not an enum. Normal
167
- compression/model-pack/repair failures also include `reasonCode=<value>` in the
168
- raised CLI error.
169
-
170
- Report-schema v1 objects and `compactMetadata` are open, additive objects:
171
- consumers must ignore unknown fields. Stable producers include `reasonCode` on
172
- every strict topology result. A pre-stable v1 artifact may lack it; readers
173
- should treat that as legacy/unknown rather than infer `ok`.
174
-
175
- `--resume-leaf UUID` is an explicit recovery override. It is reported as
176
- `active-chain-manual-override`, distinct from default strict `active-chain`.
177
- `--preserve-physical-tail` is a separate legacy compatibility
178
- mode and does not provide active-branch exclusion guarantees.
179
-
180
- Strict failures are zero-write outcomes. The CLI does not prompt or retry in a
181
- different mode. A hosting agent may ask for a new, explicit user confirmation
182
- when one diagnosed recovery control applies, but the initial compression request
183
- is not confirmation. Manually spliced or otherwise ambiguous files normally
184
- require physical-tail compatibility and therefore forfeit branch/rewind
185
- isolation.
186
-
187
- ### Records after `last-prompt`
188
-
189
- The default `--max-post-last-prompt-extension 0` excludes all UUID records
190
- physically after the authoritative pointer. A nonzero value is explicit and
191
- accepts only a direct, physically later, same-session linear closure consisting
192
- of tool-result-only user records that close every pending tool ID. Ordinary
193
- conversation, system/hook records, unrelated results and partial closure are
194
- rejected as unsafe.
195
-
196
- ## Six-Way Source Partition
197
-
198
- After strict topology succeeds, every source line belongs to exactly one set:
199
-
200
- | Set | Meaning | May reach Claude-readable output? |
201
- | --- | --- | --- |
202
- | `summaryIndexes` | Older active-chain records selected for semantic summary | Yes, only through the new summary |
203
- | `rawKeepIndexes` | Recent active-chain records kept byte-semantically as JSON objects | Yes, as recent raw history |
204
- | `sideKeepIndexes` | Explicitly attributed side/checkpoint records | Yes, as non-chain side records |
205
- | `controlProjectionIndexes` | Control metadata used to construct the final projected state | Yes, only through controlled projection |
206
- | `excludedBranchIndexes` | UUID records outside the selected active branch | No |
207
- | `excludedUnattributedIndexes` | Records with no accepted structural attribution | No |
208
-
209
- The sets are mutually exclusive and cover every source index. The legacy
210
- internal/report name `omittedIndexes` is only a compatibility alias for
211
- `summaryIndexes`; it no longer means every physically discarded record.
212
-
213
- Before a model pack or candidate is written, the engine copies only the
214
- authoritative logical active chain, projects its selected leaf into one pointer,
215
- and runs the shared transcript validator on that source view. Malformed old tool
216
- exchanges, duplicate tool IDs, or compact metadata on the active chain therefore
217
- cannot be hidden by summarization. Damage confined to an excluded inactive
218
- branch remains excluded and does not block or enter the summary.
219
-
220
- Inactive and unattributed record bodies are absent from:
221
-
222
- - the model evidence pack;
223
- - model-authored summary validation input;
224
- - deterministic fallback summary input;
225
- - prior-summary verbatim blocks;
226
- - recent raw records;
227
- - side records;
228
- - the final candidate JSONL.
229
-
230
- Reports may expose only counts and content-free line-set digests for excluded
231
- sets.
232
-
233
- ## Compact-Style Output Pair
234
-
235
- The engine emits exactly one current Codex-created compact pair:
236
-
237
- 1. a `system` record with `subtype: "compact_boundary"`;
238
- 2. its direct `user` child with `isCompactSummary: true`.
239
-
240
- Simplified synthetic shape:
241
-
242
- ```json
243
- {"type":"system","subtype":"compact_boundary","uuid":"BOUNDARY_UUID","parentUuid":null,"compactMetadata":{"codexOfflineCompression":true,"codexOfflineCompressionVersion":"v10","modelPackSchemaVersion":11,"reportSchemaVersion":1,"summaryUuid":"SUMMARY_UUID","preserveMode":"active-chain","resumeLeafInfo":{"status":"valid","reasonCode":"ok","selectedLeafUuid":"ACTIVE_LEAF"}}}
244
- {"type":"user","uuid":"SUMMARY_UUID","parentUuid":"BOUNDARY_UUID","isCompactSummary":true,"message":{"role":"user","content":"SUMMARY_TEXT"}}
245
- ```
246
-
247
- The exact open-schema object contains additional observed metadata. The current
248
- pair is followed by the recent active suffix. In strict active-chain mode, only
249
- the first recent record has its parent changed to `SUMMARY_UUID`; every later
250
- recent parent/session edge must already be coherent. A final `last-prompt` is
251
- deep-copied from the authoritative source pointer so unknown fields survive,
252
- then only its projected leaf/session values are updated.
253
-
254
- Validation requires the final pointer's active chain to contain the exact
255
- current boundary and summary, not merely an older compact pair.
256
-
257
- ## Model-Assisted Summary Protocol v11
258
-
259
- Model-assisted semantic summary is the default. Deterministic code owns all
260
- topology, source partitioning, JSONL construction, UUID generation, parent
261
- rewrites, and validation. The model receives only the frozen evidence pack and
262
- writes summary prose.
263
-
264
- ### Evidence binding
265
-
266
- The generated pack contains:
267
-
268
- - a generic `SOURCE_JSONL` label, never the original filename or full local path;
269
- - full source-file SHA-256;
270
- - SHA-256 of the exact active records selected for summary;
271
- - the digest of the displayed evidence-anchor set;
272
- - the digest of all required evidence-coverage groups;
273
- - the digest of an optional external handoff summary;
274
- - a canonical request digest binding all selection/budget/policy options and the
275
- loaded importance/topic/template resource content;
276
- - a digest of the mandatory claim-source map;
277
- - a full-text `L<number>` record for every non-empty older active human message
278
- and every older active assistant `text`/`thinking` message;
279
- - selected line-numbered source/tool/error evidence;
280
- - `H<number>` anchors for displayed handoff lines;
281
- - explicit current-state, chronology, supersession, decision, rationale,
282
- rejected-option, uncertainty, and recent-boundary instructions.
283
-
284
- The model summary must begin with the exact comment copied from the pack:
285
-
286
- ```html
287
- <!-- claude-jsonl-compressor:model-summary v11
288
- source_sha256: SOURCE_SHA256
289
- summary_source_sha256: SUMMARY_SOURCE_SHA256
290
- evidence_anchor_lines_digest: EVIDENCE_ANCHOR_LINES_DIGEST
291
- required_anchor_groups_digest: REQUIRED_ANCHOR_GROUPS_DIGEST
292
- handoff_summary_digest: HANDOFF_SUMMARY_DIGEST_OR_NONE
293
- pack_request_digest: PACK_REQUEST_DIGEST
294
- required_claim_sources_digest: REQUIRED_CLAIM_SOURCES_DIGEST
295
- -->
296
- ```
297
-
298
- Substantive factual lines cite displayed `L<number>` and, when used, `H<number>`
299
- anchors. The exact whole line `Unknown from provided anchors.` is the only
300
- unanchored uncertainty placeholder; a longer line containing that text has no
301
- exemption. Validation rejects wrong digests, invisible or out-of-range anchors,
302
- unanchored substantive lines, too little evidence coverage, and common
303
- no-access boilerplate. It requires at least one cited L anchor from each
304
- generated group. Every non-empty human/assistant semantic record has its own
305
- required L group, and prior summaries remain independently required. It also
306
- requires all nine exact semantic sections printed in the pack, each with an L
307
- or H anchor. A nonempty handoff generates early/middle/late/latest H coverage
308
- groups that must all be cited. Handoff text is not trusted merely because it
309
- contains a special phrase; only generated H anchors are accepted.
310
-
311
- Only the exact leading metadata comment and the exact required headings are
312
- exempt from line grounding. A second/unclosed HTML comment, an extra Markdown
313
- heading, duplicate metadata, renamed/reordered sections, or unsupported body
314
- line is an error. Under `## Evidence and Source Anchors`, the summary must contain
315
- exactly one `### Mandatory Evidence Coverage` subsection. Every mandatory
316
- semantic/prior-summary L anchor appears exactly once as:
317
-
318
- ```text
319
- - L42 support_text_json="exact source substring" disposition=covered
320
- ```
321
-
322
- The JSON string must decode to a meaningful exact substring of that source
323
- record. This blocks a content-free anchor dump and leaves a mechanically
324
- checkable excerpt; it does not prove perfect natural-language interpretation.
325
-
326
- Every active-chain prior compact summary is inserted as a complete dedicated
327
- evidence record. An explicitly supplied external handoff is inserted in full,
328
- one JSON-escaped `H<number> full_text_json` line per source line. Two default
329
- pack ceilings apply together: 500,000 characters and a conservative 150,000
330
- local estimated tokens. The latter leaves working space in a typical 200k
331
- summarizer context. If the complete semantic ledger, prior summaries, handoff,
332
- and minimum structural coverage do not fit, pack generation stops; it does not
333
- trim or sample mandatory evidence. Optional source/tool/system/error evidence
334
- is ranked and added only while both ceilings permit, and truncation is reported.
335
- The caller should raise `--model-pack-char-budget` or
336
- `--model-pack-estimated-token-budget` only when the summarizing model can read
337
- the resulting pack. No external tokenizer is installed. Once a model summary
338
- passes validation, its text is not truncated to meet `--summary-char-budget`;
339
- insufficient composition space is a hard error. The summary character budget
340
- has a hard minimum of 4000, and blank compact summaries are invalid.
341
-
342
- These controls reduce stale-summary and unsupported-claim risk. They do not
343
- mathematically prove that a natural-language summary has perfect semantics.
344
- The deterministic safety appendix and recent raw suffix remain independent
345
- evidence layers.
346
-
347
- ### Weighting and chronology
348
-
349
- The evidence pack requires every non-empty older active human message and every
350
- older active assistant `text`/`thinking` message, regardless of language or
351
- keyword score. Source-text warnings such as U+FFFD are reported but do not make
352
- mandatory records optional. Within the remaining optional source/tool capacity it gives
353
- priority to:
354
-
355
- 1. explicit user constraints and requested outcomes;
356
- 2. final/current decisions and their reasons;
357
- 3. supersessions and chronology needed to avoid reviving old decisions;
358
- 4. model research conclusions, implementation decisions, and rationale;
359
- 5. unresolved risks, exact identifiers, source references, and validation
360
- results;
361
- 6. file/tool content that materially supports the above;
362
- 7. repetitive logs or mechanically recoverable detail.
363
-
364
- Important evidence can be Chinese, English, Japanese, Korean, Arabic, Russian,
365
- Hindi, Greek, Hebrew, Armenian, Thai, Georgian, Ethiopic, Bengali, Tamil,
366
- Telugu, Malayalam, Spanish, French, German, Portuguese, or another language.
367
- All assistant thinking is semantic evidence without a language-keyword gate;
368
- multilingual terms and script detection only improve classification and optional
369
- weighting. Language is not treated as a proxy for importance.
370
-
371
- ## Token Ceilings
372
-
373
- `--model-pack-estimated-token-budget N` limits the evidence pack read by the
374
- summary-authoring model. It defaults to 150,000 and is enforced together with
375
- the character budget. The same non-default value must be supplied when
376
- generating the pack and when applying the model summary. Mandatory semantic and
377
- handoff evidence cannot be dropped to satisfy it; optional evidence can be
378
- truncated and this state is recorded in the pack, compact metadata, and report.
379
-
380
- `--target-estimated-tokens N` uses a dependency-free heuristic over complete
381
- retained structured message payloads, including full thinking,
382
- `tool_use.input`, `tool_result`, and `toolUseResult` data. ASCII is estimated at
383
- one token per four characters;
384
- Han/Kana/Hangul at 1.3 each; non-BMP, symbol and combining-mark code points at
385
- 1.5 each; other non-ASCII at 0.8 each; and each record adds a small allowance.
386
- It narrows the byte-ratio plan and rejects a generated candidate whose
387
- estimated message tokens exceed `N`. This candidate-output gate is independent
388
- of the model-pack reading ceiling.
389
-
390
- This estimate excludes system prompts, tools, MCP schemas, skills, runtime
391
- injections, and any Claude-side accounting. It is a reproducible local planning
392
- gate, not a promise that `/context` or an API relay will report the same total.
393
- `--target-ratio` is only an approximate byte-ratio planning input and is not a
394
- hard publication gate.
395
-
396
- ## Tool Use and Tool Result Pairing
397
-
398
- One API turn may be split across several JSONL records:
399
-
400
- - multiple `tool_use` blocks in one assistant message;
401
- - adjacent assistant fragments sharing one `message.id`;
402
- - split user records carrying `tool_result` blocks;
403
- - hook or attachment records between result fragments.
404
-
405
- Validation rebuilds the selected active chain, filters API messages, merges
406
- compatible assistant and user fragments, and checks tool IDs and order. It
407
- rejects orphan results, wrong IDs, and out-of-order results. The compression cut
408
- cannot divide a required tool-use/result relationship.
409
-
410
- ## File-History Checkpoint Policies
411
-
412
- Conversation topology and file checkpoint metadata are separate planes.
413
- `file-history-snapshot` records commonly lack `uuid` and `parentUuid`, so they
414
- cannot be relinked into the API-message chain.
415
-
416
- Policies:
417
-
418
- - `active-correlated` (default): retain a bounded set only when structural
419
- identifiers correlate the snapshot to recent active records;
420
- - `preserve-recent`: rejected in strict active-chain mode; accepted only with
421
- explicit `--preserve-physical-tail`, which is labeled compatibility mode and
422
- has no inactive-branch isolation guarantee;
423
- - `none`: retain no snapshot side records.
424
-
425
- Preserved snapshots are emitted as side records before the compact pair. JSONL
426
- compression alone does not guarantee complete file-state rewind because Claude
427
- Code checkpoint storage and lifecycle have behavior beyond the message chain.
428
-
429
- ## Repeated Compression
430
-
431
- Old compact pairs on the active chain are part of `summaryIndexes` when they
432
- fall before the recent suffix. Normal repeated compression folds their useful
433
- meaning into one new current summary rather than stacking live compact pairs.
434
-
435
- `compactMetadata.preservedMessages` and `preservedSegment` describe the raw
436
- suffix that existed when that compact pair was created. Later work can extend
437
- that suffix, and a later rewind can leave part of the recorded snapshot on an
438
- inactive branch. A structurally valid historical divergence is therefore a
439
- source warning, not authority to restore those UUIDs and not a topology error.
440
- Selection continues from the current `last-prompt` parent chain. Candidate
441
- publication is stricter: the newly emitted snapshot must match the candidate's
442
- current chain exactly or publication stops before writing.
443
-
444
- `--preserve-prior-summaries-verbatim` is an explicit exception. It attempts to
445
- embed prior `isCompactSummary.message.content` text verbatim inside the one new
446
- summary and permits the summary text to grow to `1.5 * --summary-char-budget`.
447
- If the prior summaries still do not fit, the engine uses the normal folded
448
- model-summary path and reports the fallback reason. Excluded-branch summaries
449
- are never eligible for either path.
450
-
451
- ## Branch Session Files
452
-
453
- Observed `/branch` files are not guaranteed to be full physical copies. They
454
- may retain source UUIDs, rewrite `sessionId`, add `forkedFrom`, relink parents,
455
- and omit source history outside the branch-relevant chain.
456
-
457
- The compressor treats exactly one selected JSONL as authoritative. It does not
458
- merge a branch file with its source. Compress the branch to continue the branch;
459
- compress or archive the source separately when its physical history matters.
460
-
461
- ## External References
462
-
463
- JSONL may contain inline tool results, file excerpts, attachment metadata, or
464
- paths/references to external artifacts such as tool-result storage. Offline
465
- compression reads the selected JSONL and an explicitly supplied handoff file
466
- only. It does not dereference, delete, or rewrite external project files,
467
- `tool-results`, subagent artifacts, settings, or other sessions.
468
-
469
- If a referenced artifact's contents are not embedded in the JSONL or handoff,
470
- the model pack knows only the reference, not the external contents.
471
-
472
- ## `Read.pages` Compatibility Repair
473
-
474
- Claude Code legitimately uses `Read.input.pages`, including for paged document
475
- reads. The independent repair command exists only for a known downstream
476
- compatibility case where historical serialized `pages` members must be removed.
477
- Compression never invokes this repair implicitly.
478
-
479
- The repairer:
480
-
481
- - matches only assistant `tool_use` blocks with exact tool name `Read` and an
482
- `input` object containing `pages`;
483
- - requires a nonempty `file_path`, exactly one later matching `tool_result`, the
484
- same nonempty `sessionId`, and result `sourceToolAssistantUUID` equal to the
485
- tool-use assistant UUID before automatic repair;
486
- - reports pending calls without changing them;
487
- - defaults to the strict active chain; `--scope all` is explicit;
488
- - deletes the exact JSON member byte span without reserializing other data;
489
- - blocks duplicate-key or overlapping-span ambiguity;
490
- - preserves BOM, newline style, Unicode spelling, unknown fields, record count,
491
- UUID/parent signature, and tool ID sequences;
492
- - re-reads the actual published candidate, verifies exact expected bytes and
493
- SHA-256, validates the repair invariants, requires an idempotent second scan
494
- with zero remaining patches, and runs the shared full-transcript
495
- UUID/parent/compact/tool validator on the published bytes.
496
-
497
- Candidate and live replacement modes use the same atomic-write, full-hash,
498
- numbered-backup, source-race, validation, and rollback principles as compression.
499
-
500
- ## Candidate and Live Replacement Transactions
501
-
502
- Candidate mode requires distinct input and output paths. Candidate JSONL,
503
- reports, validation, model packs, summaries and work directories must remain
504
- outside the entire `.claude` tree.
505
-
506
- Live replacement mode requires one existing regular `.jsonl` input under
507
- `.claude/projects`, an external `--work-dir`, and caller acknowledgement
508
- `--confirm-session-closed`. The acknowledgement is an operational assertion,
509
- not process-lock detection. It:
510
-
511
- 1. reads and hashes the complete original bytes;
512
- 2. writes and validates a candidate outside `.claude`;
513
- 3. rechecks that the source bytes did not change;
514
- 4. creates an exclusive `.backup`, `.backup1`, and so on;
515
- 5. validates immutable candidate bytes and writes a unique same-directory stage
516
- with flush/fsync;
517
- 6. captures the actual old target, verifies its full bytes against the frozen
518
- source, and publishes the staged candidate with an atomic no-clobber claim;
519
- 7. verifies published bytes and structure, restoring the captured original on
520
- failure. If restoration fails, the numbered verified backup remains and the
521
- error is promoted.
522
-
523
- If another process recreates the target after capture begins, the transaction
524
- does not overwrite the external target. It preserves the verified numbered
525
- backup and, when necessary, places the captured original in another numbered
526
- backup, then fails without publishing the candidate.
527
-
528
- Before any live staging, backup payload publication, or target move, the
529
- implementation probes file hard-link support in the target directory and, when
530
- configured, the external backup directory. The probe itself briefly creates
531
- and removes unique dot-prefixed temporary files. Every unique live-transaction
532
- temporary path records its observed filesystem identity and frozen bytes.
533
- Cleanup rechecks both and retains a detected mismatch. A mismatch after commit is reported as
534
- `committed-cleanup-failed`; a mismatch while an earlier transaction failure is
535
- already active is appended to that failure. This is a best-effort race check,
536
- not a portable atomic identity-bound delete: standard Python pathname cleanup
537
- cannot eliminate a replacement between its final check and `unlink()` across
538
- Windows, Linux, and macOS. Live operation assumes Claude Code is closed and no
539
- other writer is manipulating the session directory.
540
-
541
- Candidate-mode reports are written with the candidate. Live compression delays
542
- its final sidecar/report until replacement metadata is available, so it does not
543
- leave a stale pre-commit report. If the JSONL commits and validates but final
544
- report publication fails, compression returns exit code 3 with
545
- `operation_state: committed-report-failed`; repair uses the camelCase equivalent
546
- `operationState`. The receipt includes public artifact labels and frozen,
547
- candidate, and published hashes. This is a committed state and is not rolled
548
- back or reported as an ordinary uncommitted failure.
549
-
550
- Once a numbered backup is created and verified, later failure cleanup never
551
- deletes that path. This preserves an audit/recovery asset even when publication
552
- or rollback fails or another process races on a nearby name.
553
-
554
- Claude Code should be closed for that session during replacement. No other
555
- session or Claude settings file is part of the transaction. Parent-directory
556
- fsync is best effort and reported; the project does not promise cross-platform
557
- power-loss atomicity.
558
-
559
- ## Validation Boundary
560
-
561
- The validator checks observed-format coherence, including:
562
-
563
- - parseability and object-per-line structure;
564
- - unique UUIDs and resolvable parent/session links;
565
- - exactly one current compact pair;
566
- - current compact pair reachability from the final pointer;
567
- - compact preserved-message metadata;
568
- - non-empty, unique active tool IDs and tool-use/result pairing after fragment
569
- merging; partial multi-tool ordered subsets are accepted only with an explicit
570
- compatibility warning/count;
571
- - absence of internal planning fields in output;
572
- - source hashes, model-pack metadata, anchor sets, and target estimates where
573
- applicable.
574
-
575
- Passing these checks proves coherence under this project's empirical rules. It
576
- does not make the private transcript format an Anthropic-supported public API.
577
- When runtime testing is explicitly requested, `/resume`, `/context`, recent
578
- conversation rewind, and recent file rewind are separate observations.
1
+ # Claude Code JSONL Compression Format Notes
2
+
3
+ This document describes the empirical format handled by
4
+ `@brandry/claude-jsonl-compressor` package `1.1.0` and engine `v10`.
5
+
6
+ Claude Code transcript JSONL is an observed internal format, not a published
7
+ stable storage API. The rules below are deliberately strict where ambiguity
8
+ could restore a rewound branch, break resume topology, or make a tool exchange
9
+ incoherent.
10
+
11
+ ## Version Domains
12
+
13
+ The project keeps four independent version domains:
14
+
15
+ | Domain | Current value | Meaning |
16
+ | --- | --- | --- |
17
+ | Package | `1.1.0` | GitHub/npm release version |
18
+ | Compression engine | `v10` | Topology, partition, and rewrite behavior |
19
+ | Model-pack schema | `v11` | Evidence-pack and model-summary binding protocol |
20
+ | Report schema | `1` | Compression and repair report fields |
21
+
22
+ A change to one domain does not imply the other domains changed.
23
+
24
+ ## Three Physical and Logical Layers
25
+
26
+ A transcript can contain records that play very different roles:
27
+
28
+ 1. **Active API-message chain.** `user` and `assistant` records connected from
29
+ the selected resume leaf through `parentUuid`. This is the history that the
30
+ compressor may summarize or preserve verbatim.
31
+ 2. **Control and side records.** `last-prompt`, titles, modes, attachments,
32
+ hooks, and `file-history-snapshot` records may exist physically in the file
33
+ without becoming ordinary API messages. They require explicit projection or
34
+ attribution rules.
35
+ 3. **Inactive or unattributed records.** Rewound branches and records that
36
+ cannot be structurally attributed to the active branch remain useful only
37
+ for diagnostics. Engine v10 excludes their content from every Claude-readable
38
+ output layer and from every model-summary evidence channel.
39
+
40
+ Physical line order alone is therefore not a definition of current context.
41
+
42
+ ### Physical line accounting
43
+
44
+ The JSONL framer recognizes records only at physical LF boundaries, removes a
45
+ single CR before each LF, and skips blank physical lines. The parser keeps a
46
+ trusted side map from each parsed record to its original 1-based physical line
47
+ number. `validate_jsonl` and the `Read.pages` repair report use that map, so a
48
+ blank line or an excluded branch cannot make an error point at the wrong file
49
+ line. Input `_line`, `_mergedLines`, `_mergedUuids`, and
50
+ `_validationContentLines` fields are untrusted and ignored; validation rebuilds
51
+ record- and content-block provenance in an isolated projection.
52
+
53
+ The model evidence pack uses its own `L<number>` logical nonblank-record
54
+ coordinates because it is a reserialized evidence view; those anchors are not
55
+ claims about physical source-file line numbers. Repair `recordLine`, validator
56
+ diagnostic `line` fields, and the source active-chain preflight used by
57
+ compression/model-pack generation, in contrast, refer to the original file's
58
+ physical lines. The active-chain preflight is a logical projection, so it
59
+ carries source-line provenance separately rather than renumbering the chain.
60
+
61
+ Validation/report counters and `type`/`sessionId`/`subtype` metadata carried
62
+ by validator or system-error diagnostics preserve ordinary short strings.
63
+ Synthetic keys use the collision-safe angle-bracket namespace: `<missing>`,
64
+ `<null>`, and `<invalid:json-type>`. A real string beginning with `<` is
65
+ escaped by adding a second leading `<`; an unusually long real string is
66
+ represented by a bounded `<string:length=...;sha256=...>` label that contains
67
+ no original characters. Complex
68
+ malformed values are labeled by JSON type rather than copied into diagnostics
69
+ or model evidence. The same bound is applied recursively to public validator
70
+ samples, nested diagnostic metadata, identifier-valued dictionary keys, and
71
+ oversized source values embedded in error text. Ordinary error wording and
72
+ physical-line locations remain readable when they contain only normal values.
73
+
74
+ ## Common Observed Record Types
75
+
76
+ | `type` | Observed purpose |
77
+ | --- | --- |
78
+ | `user` | User content or user-side `tool_result` blocks |
79
+ | `assistant` | Assistant text, thinking, `tool_use`, model metadata, and usage |
80
+ | `system` | Claude Code events, including `subtype: compact_boundary` |
81
+ | `attachment` | Hook, image, file, or environment attachment metadata |
82
+ | `last-prompt` | UI/session pointer containing the current `leafUuid` |
83
+ | `file-history-snapshot` | File checkpoint metadata, commonly without UUID links |
84
+ | `mode` / `permission-mode` | Session mode state |
85
+ | `ai-title` / `custom-title` | Conversation title metadata |
86
+ | `queue-operation` | Queued-input or editing metadata |
87
+ | `agent-name` | Agent label metadata |
88
+
89
+ Open-schema handling is required: unknown fields are preserved on retained
90
+ records unless the operation explicitly projects a control record.
91
+
92
+ ## UUID, Parent, Session, and Resume Relationships
93
+
94
+ ### UUID chain
95
+
96
+ Message-like records commonly carry:
97
+
98
+ - `uuid`: record identity;
99
+ - `parentUuid`: preceding record on that branch, or `null` at a root;
100
+ - `sessionId`: session identity.
101
+
102
+ Engine v10 treats duplicate UUIDs anywhere in the input as blocking because a
103
+ UUID index would be ambiguous. A non-null `parentUuid` must be a non-empty
104
+ string; any other value is malformed. A missing parent, malformed parent or loop
105
+ on the selected active chain is blocking. Physical inversion is blocking except
106
+ for same-session `attachment -> attachment` edges on an otherwise complete,
107
+ acyclic chain; accepted attachments are emitted in logical parent order.
108
+ Equivalent non-UUID-ambiguity damage wholly inside an excluded branch does not
109
+ revive that branch.
110
+
111
+ Mixed `sessionId` ancestry is not rejected merely because two IDs occur. A
112
+ lineage is accepted only when its contiguous session runs never return to an
113
+ earlier ID and the final run matches both the selected leaf and authoritative
114
+ pointer. Planning forces every earlier run into `summaryIndexes` and preserves
115
+ only the final run as recent raw records. A tool relationship that would move
116
+ the cut across the lineage transition stops compression. This covers observed
117
+ historical branch/resume files without admitting A-B-A or arbitrary cross-session
118
+ raw chains.
119
+
120
+ ### Authoritative `last-prompt`
121
+
122
+ Automatic active-chain mode uses the physically last record whose
123
+ `type` is `last-prompt`. Its `leafUuid` is authoritative. The engine does not
124
+ skip a malformed latest pointer to search for an older usable pointer because
125
+ that could resurrect a state the user already left.
126
+
127
+ Strict topology statuses include:
128
+
129
+ - `absent`
130
+ - `malformed`
131
+ - `malformed-parent`
132
+ - `duplicate-uuid`
133
+ - `dangling`
134
+ - `loop`
135
+ - `non-monotonic`
136
+ - `session-mismatch`
137
+ - `extension-limit`
138
+ - `extension-branch`
139
+ - `extension-unsafe`
140
+ - `valid`
141
+
142
+ `status` is the coarse category. `reasonCode` is the stable, machine-readable
143
+ cause. Current producers emit exactly one of these pairs:
144
+
145
+ | `status` | `reasonCode` | Meaning |
146
+ | --- | --- | --- |
147
+ | `absent` | `last-prompt-absent` | No authoritative `last-prompt` exists |
148
+ | `duplicate-uuid` | `duplicate-uuid` | At least one UUID is ambiguous |
149
+ | `malformed` | `leaf-uuid-malformed` | The authoritative `leafUuid` is absent, empty or not a string |
150
+ | `dangling` | `chain-missing-uuid` | The selected chain references an unknown UUID |
151
+ | `loop` | `chain-loop` | The selected chain contains a parent loop |
152
+ | `malformed-parent` | `chain-malformed-parent` | A selected record has an invalid non-null `parentUuid` |
153
+ | `dangling` | `chain-empty` | Defensive outcome when tracing returns no chain and no more specific error |
154
+ | `non-monotonic` | `chain-non-monotonic` | Physical parent order violates the accepted compatibility rule |
155
+ | `session-mismatch` | `lineage-unsafe` | Session ancestry is not an accepted one-way lineage |
156
+ | `extension-limit` | `extension-limit-exceeded` | Post-pointer records exceed the explicit closure limit |
157
+ | `session-mismatch` | `extension-authority-session-missing` | A requested closure has no usable authority session |
158
+ | `extension-unsafe` | `extension-record-missing-uuid` | A closure record has no usable UUID |
159
+ | `extension-branch` | `extension-record-not-linear-descendant` | A closure record is not the next direct child |
160
+ | `session-mismatch` | `extension-record-session-mismatch` | A closure record does not match the authority session |
161
+ | `extension-unsafe` | `extension-record-not-safe-closure` | A closure record is not a tool-result-only safe closure |
162
+ | `extension-unsafe` | `extension-pending-tool-ids` | The requested closure leaves tool calls unresolved |
163
+ | `valid` | `ok` | Strict topology validation succeeded |
164
+
165
+ For `lineage-unsafe`, `lineageReason` carries the lower-level lineage diagnosis.
166
+ Human-readable `errors` text is diagnostic and is not an enum. Normal
167
+ compression/model-pack/repair failures also include `reasonCode=<value>` in the
168
+ raised CLI error.
169
+
170
+ Report-schema v1 objects and `compactMetadata` are open, additive objects:
171
+ consumers must ignore unknown fields. Stable producers include `reasonCode` on
172
+ every strict topology result. A pre-stable v1 artifact may lack it; readers
173
+ should treat that as legacy/unknown rather than infer `ok`.
174
+
175
+ `--resume-leaf UUID` is an explicit recovery override. It is reported as
176
+ `active-chain-manual-override`, distinct from default strict `active-chain`.
177
+ `--preserve-physical-tail` is a separate legacy compatibility
178
+ mode and does not provide active-branch exclusion guarantees.
179
+
180
+ Strict failures are zero-write outcomes. The CLI does not prompt or retry in a
181
+ different mode. A hosting agent may ask for a new, explicit user confirmation
182
+ when one diagnosed recovery control applies, but the initial compression request
183
+ is not confirmation. Manually spliced or otherwise ambiguous files normally
184
+ require physical-tail compatibility and therefore forfeit branch/rewind
185
+ isolation.
186
+
187
+ ### Records after `last-prompt`
188
+
189
+ The default `--max-post-last-prompt-extension 0` excludes all UUID records
190
+ physically after the authoritative pointer. A nonzero value is explicit and
191
+ accepts only a direct, physically later, same-session linear closure consisting
192
+ of tool-result-only user records that close every pending tool ID. Ordinary
193
+ conversation, system/hook records, unrelated results and partial closure are
194
+ rejected as unsafe.
195
+
196
+ ## Six-Way Source Partition
197
+
198
+ After strict topology succeeds, every source line belongs to exactly one set:
199
+
200
+ | Set | Meaning | May reach Claude-readable output? |
201
+ | --- | --- | --- |
202
+ | `summaryIndexes` | Older active-chain records selected for semantic summary | Yes, only through the new summary |
203
+ | `rawKeepIndexes` | Recent active-chain records kept byte-semantically as JSON objects | Yes, as recent raw history |
204
+ | `sideKeepIndexes` | Explicitly attributed side/checkpoint records | Yes, as non-chain side records |
205
+ | `controlProjectionIndexes` | Control metadata used to construct the final projected state | Yes, only through controlled projection |
206
+ | `excludedBranchIndexes` | UUID records outside the selected active branch | No |
207
+ | `excludedUnattributedIndexes` | Records with no accepted structural attribution | No |
208
+
209
+ The sets are mutually exclusive and cover every source index. The legacy
210
+ internal/report name `omittedIndexes` is only a compatibility alias for
211
+ `summaryIndexes`; it no longer means every physically discarded record.
212
+
213
+ Before a model pack or candidate is written, the engine copies only the
214
+ authoritative logical active chain, projects its selected leaf into one pointer,
215
+ and runs the shared transcript validator on that source view. Malformed old tool
216
+ exchanges, duplicate tool IDs, or compact metadata on the active chain therefore
217
+ cannot be hidden by summarization. Damage confined to an excluded inactive
218
+ branch remains excluded and does not block or enter the summary.
219
+
220
+ Inactive and unattributed record bodies are absent from:
221
+
222
+ - the model evidence pack;
223
+ - model-authored summary validation input;
224
+ - deterministic fallback summary input;
225
+ - prior-summary verbatim blocks;
226
+ - recent raw records;
227
+ - side records;
228
+ - the final candidate JSONL.
229
+
230
+ Reports may expose only counts and content-free line-set digests for excluded
231
+ sets.
232
+
233
+ ## Compact-Style Output Pair
234
+
235
+ The engine emits exactly one current Codex-created compact pair:
236
+
237
+ 1. a `system` record with `subtype: "compact_boundary"`;
238
+ 2. its direct `user` child with `isCompactSummary: true`.
239
+
240
+ Simplified synthetic shape:
241
+
242
+ ```json
243
+ {"type":"system","subtype":"compact_boundary","uuid":"BOUNDARY_UUID","parentUuid":null,"compactMetadata":{"codexOfflineCompression":true,"codexOfflineCompressionVersion":"v10","modelPackSchemaVersion":11,"reportSchemaVersion":1,"summaryUuid":"SUMMARY_UUID","preserveMode":"active-chain","resumeLeafInfo":{"status":"valid","reasonCode":"ok","selectedLeafUuid":"ACTIVE_LEAF"}}}
244
+ {"type":"user","uuid":"SUMMARY_UUID","parentUuid":"BOUNDARY_UUID","isCompactSummary":true,"message":{"role":"user","content":"SUMMARY_TEXT"}}
245
+ ```
246
+
247
+ The exact open-schema object contains additional observed metadata. The current
248
+ pair is followed by the recent active suffix. In strict active-chain mode, only
249
+ the first recent record has its parent changed to `SUMMARY_UUID`; every later
250
+ recent parent/session edge must already be coherent. A final `last-prompt` is
251
+ deep-copied from the authoritative source pointer so unknown fields survive,
252
+ then only its projected leaf/session values are updated.
253
+
254
+ Validation requires the final pointer's active chain to contain the exact
255
+ current boundary and summary, not merely an older compact pair.
256
+
257
+ ## Model-Assisted Summary Protocol v11
258
+
259
+ Model-assisted semantic summary is the default. Deterministic code owns all
260
+ topology, source partitioning, JSONL construction, UUID generation, parent
261
+ rewrites, and validation. The model receives only the frozen evidence pack and
262
+ writes summary prose.
263
+
264
+ ### Evidence binding
265
+
266
+ The generated pack contains:
267
+
268
+ - a generic `SOURCE_JSONL` label, never the original filename or full local path;
269
+ - full source-file SHA-256;
270
+ - SHA-256 of the exact active records selected for summary;
271
+ - the digest of the displayed evidence-anchor set;
272
+ - the digest of all required evidence-coverage groups;
273
+ - the digest of an optional external handoff summary;
274
+ - a canonical request digest binding all selection/budget/policy options and the
275
+ loaded importance/topic/template resource content;
276
+ - a digest of the mandatory claim-source map;
277
+ - a full-text `L<number>` record for every non-empty older active human message
278
+ and every older active assistant `text`/`thinking` message;
279
+ - selected line-numbered source/tool/error evidence;
280
+ - `H<number>` anchors for displayed handoff lines;
281
+ - explicit current-state, chronology, supersession, decision, rationale,
282
+ rejected-option, uncertainty, and recent-boundary instructions.
283
+
284
+ The model summary must begin with the exact comment copied from the pack:
285
+
286
+ ```html
287
+ <!-- claude-jsonl-compressor:model-summary v11
288
+ source_sha256: SOURCE_SHA256
289
+ summary_source_sha256: SUMMARY_SOURCE_SHA256
290
+ evidence_anchor_lines_digest: EVIDENCE_ANCHOR_LINES_DIGEST
291
+ required_anchor_groups_digest: REQUIRED_ANCHOR_GROUPS_DIGEST
292
+ handoff_summary_digest: HANDOFF_SUMMARY_DIGEST_OR_NONE
293
+ pack_request_digest: PACK_REQUEST_DIGEST
294
+ required_claim_sources_digest: REQUIRED_CLAIM_SOURCES_DIGEST
295
+ -->
296
+ ```
297
+
298
+ Substantive factual lines cite displayed `L<number>` and, when used, `H<number>`
299
+ anchors. The exact whole line `Unknown from provided anchors.` is the only
300
+ unanchored uncertainty placeholder; a longer line containing that text has no
301
+ exemption. Validation rejects wrong digests, invisible or out-of-range anchors,
302
+ unanchored substantive lines, too little evidence coverage, and common
303
+ no-access boilerplate. It requires at least one cited L anchor from each
304
+ generated group. Every non-empty human/assistant semantic record has its own
305
+ required L group, and prior summaries remain independently required. It also
306
+ requires all nine exact semantic sections printed in the pack, each with an L
307
+ or H anchor. A nonempty handoff generates early/middle/late/latest H coverage
308
+ groups that must all be cited. Handoff text is not trusted merely because it
309
+ contains a special phrase; only generated H anchors are accepted.
310
+
311
+ Only the exact leading metadata comment and the exact required headings are
312
+ exempt from line grounding. A second/unclosed HTML comment, an extra Markdown
313
+ heading, duplicate metadata, renamed/reordered sections, or unsupported body
314
+ line is an error. Under `## Evidence and Source Anchors`, the summary must contain
315
+ exactly one `### Mandatory Evidence Coverage` subsection. Every mandatory
316
+ semantic/prior-summary L anchor appears exactly once as:
317
+
318
+ ```text
319
+ - L42 support_text_json="exact source substring" disposition=covered
320
+ ```
321
+
322
+ The JSON string must decode to a meaningful exact substring of that source
323
+ record. This blocks a content-free anchor dump and leaves a mechanically
324
+ checkable excerpt; it does not prove perfect natural-language interpretation.
325
+
326
+ Every active-chain prior compact summary is inserted as a complete dedicated
327
+ evidence record. An explicitly supplied external handoff is inserted in full,
328
+ one JSON-escaped `H<number> full_text_json` line per source line. Two default
329
+ pack ceilings apply together: 500,000 characters and a conservative 150,000
330
+ local estimated tokens. The latter leaves working space in a typical 200k
331
+ summarizer context. If the complete semantic ledger, prior summaries, handoff,
332
+ and minimum structural coverage do not fit, pack generation stops; it does not
333
+ trim or sample mandatory evidence. Optional source/tool/system/error evidence
334
+ is ranked and added only while both ceilings permit, and truncation is reported.
335
+ The caller should raise `--model-pack-char-budget` or
336
+ `--model-pack-estimated-token-budget` only when the summarizing model can read
337
+ the resulting pack. No external tokenizer is installed. Once a model summary
338
+ passes validation, its text is not truncated to meet `--summary-char-budget`;
339
+ insufficient composition space is a hard error. The summary character budget
340
+ has a hard minimum of 4000, and blank compact summaries are invalid.
341
+
342
+ These controls reduce stale-summary and unsupported-claim risk. They do not
343
+ mathematically prove that a natural-language summary has perfect semantics.
344
+ The deterministic safety appendix and recent raw suffix remain independent
345
+ evidence layers.
346
+
347
+ ### Weighting and chronology
348
+
349
+ The evidence pack requires every non-empty older active human message and every
350
+ older active assistant `text`/`thinking` message, regardless of language or
351
+ keyword score. Source-text warnings such as U+FFFD are reported but do not make
352
+ mandatory records optional. Within the remaining optional source/tool capacity it gives
353
+ priority to:
354
+
355
+ 1. explicit user constraints and requested outcomes;
356
+ 2. final/current decisions and their reasons;
357
+ 3. supersessions and chronology needed to avoid reviving old decisions;
358
+ 4. model research conclusions, implementation decisions, and rationale;
359
+ 5. unresolved risks, exact identifiers, source references, and validation
360
+ results;
361
+ 6. file/tool content that materially supports the above;
362
+ 7. repetitive logs or mechanically recoverable detail.
363
+
364
+ Important evidence can be Chinese, English, Japanese, Korean, Arabic, Russian,
365
+ Hindi, Greek, Hebrew, Armenian, Thai, Georgian, Ethiopic, Bengali, Tamil,
366
+ Telugu, Malayalam, Spanish, French, German, Portuguese, or another language.
367
+ All assistant thinking is semantic evidence without a language-keyword gate;
368
+ multilingual terms and script detection only improve classification and optional
369
+ weighting. Language is not treated as a proxy for importance.
370
+
371
+ ## Token Ceilings
372
+
373
+ `--model-pack-estimated-token-budget N` limits the evidence pack read by the
374
+ summary-authoring model. It defaults to 150,000 and is enforced together with
375
+ the character budget. The same non-default value must be supplied when
376
+ generating the pack and when applying the model summary. Mandatory semantic and
377
+ handoff evidence cannot be dropped to satisfy it; optional evidence can be
378
+ truncated and this state is recorded in the pack, compact metadata, and report.
379
+
380
+ `--target-estimated-tokens N` uses a dependency-free heuristic over complete
381
+ retained structured message payloads, including full thinking,
382
+ `tool_use.input`, `tool_result`, and `toolUseResult` data. ASCII is estimated at
383
+ one token per four characters;
384
+ Han/Kana/Hangul at 1.3 each; non-BMP, symbol and combining-mark code points at
385
+ 1.5 each; other non-ASCII at 0.8 each; and each record adds a small allowance.
386
+ It narrows the byte-ratio plan and rejects a generated candidate whose
387
+ estimated message tokens exceed `N`. This candidate-output gate is independent
388
+ of the model-pack reading ceiling.
389
+
390
+ This estimate excludes system prompts, tools, MCP schemas, skills, runtime
391
+ injections, and any Claude-side accounting. It is a reproducible local planning
392
+ gate, not a promise that `/context` or an API relay will report the same total.
393
+ `--target-ratio` is only an approximate byte-ratio planning input and is not a
394
+ hard publication gate.
395
+
396
+ ## Tool Use and Tool Result Pairing
397
+
398
+ One API turn may be split across several JSONL records:
399
+
400
+ - multiple `tool_use` blocks in one assistant message;
401
+ - adjacent assistant fragments sharing one `message.id`;
402
+ - split user records carrying `tool_result` blocks;
403
+ - hook or attachment records between result fragments.
404
+
405
+ Validation rebuilds the selected active chain, filters API messages, merges
406
+ compatible assistant and user fragments, and checks tool IDs and order. It
407
+ rejects orphan results, wrong IDs, and out-of-order results. The compression cut
408
+ cannot divide a required tool-use/result relationship.
409
+
410
+ ## File-History Checkpoint Policies
411
+
412
+ Conversation topology and file checkpoint metadata are separate planes.
413
+ `file-history-snapshot` records commonly lack `uuid` and `parentUuid`, so they
414
+ cannot be relinked into the API-message chain.
415
+
416
+ Policies:
417
+
418
+ - `active-correlated` (default): retain a bounded set only when structural
419
+ identifiers correlate the snapshot to recent active records;
420
+ - `preserve-recent`: rejected in strict active-chain mode; accepted only with
421
+ explicit `--preserve-physical-tail`, which is labeled compatibility mode and
422
+ has no inactive-branch isolation guarantee;
423
+ - `none`: retain no snapshot side records.
424
+
425
+ Preserved snapshots are emitted as side records before the compact pair. JSONL
426
+ compression alone does not guarantee complete file-state rewind because Claude
427
+ Code checkpoint storage and lifecycle have behavior beyond the message chain.
428
+
429
+ ## Repeated Compression
430
+
431
+ Old compact pairs on the active chain are part of `summaryIndexes` when they
432
+ fall before the recent suffix. Normal repeated compression folds their useful
433
+ meaning into one new current summary rather than stacking live compact pairs.
434
+
435
+ `compactMetadata.preservedMessages` and `preservedSegment` describe the raw
436
+ suffix that existed when that compact pair was created. Later work can extend
437
+ that suffix, and a later rewind can leave part of the recorded snapshot on an
438
+ inactive branch. A structurally valid historical divergence is therefore a
439
+ source warning, not authority to restore those UUIDs and not a topology error.
440
+ Selection continues from the current `last-prompt` parent chain. Candidate
441
+ publication is stricter: the newly emitted snapshot must match the candidate's
442
+ current chain exactly or publication stops before writing.
443
+
444
+ `--preserve-prior-summaries-verbatim` is an explicit exception. It attempts to
445
+ embed prior `isCompactSummary.message.content` text verbatim inside the one new
446
+ summary and permits the summary text to grow to `1.5 * --summary-char-budget`.
447
+ If the prior summaries still do not fit, the engine uses the normal folded
448
+ model-summary path and reports the fallback reason. Excluded-branch summaries
449
+ are never eligible for either path.
450
+
451
+ ## Branch Session Files
452
+
453
+ Observed `/branch` files are not guaranteed to be full physical copies. They
454
+ may retain source UUIDs, rewrite `sessionId`, add `forkedFrom`, relink parents,
455
+ and omit source history outside the branch-relevant chain.
456
+
457
+ The compressor treats exactly one selected JSONL as authoritative. It does not
458
+ merge a branch file with its source. Compress the branch to continue the branch;
459
+ compress or archive the source separately when its physical history matters.
460
+
461
+ ## External References
462
+
463
+ JSONL may contain inline tool results, file excerpts, attachment metadata, or
464
+ paths/references to external artifacts such as tool-result storage. Offline
465
+ compression reads the selected JSONL and an explicitly supplied handoff file
466
+ only. It does not dereference, delete, or rewrite external project files,
467
+ `tool-results`, subagent artifacts, settings, or other sessions.
468
+
469
+ If a referenced artifact's contents are not embedded in the JSONL or handoff,
470
+ the model pack knows only the reference, not the external contents.
471
+
472
+ ## `Read.pages` Compatibility Repair
473
+
474
+ Claude Code legitimately uses `Read.input.pages`, including for paged document
475
+ reads. The independent repair command exists only for a known downstream
476
+ compatibility case where historical serialized `pages` members must be removed.
477
+ Compression never invokes this repair implicitly.
478
+
479
+ The repairer:
480
+
481
+ - matches only assistant `tool_use` blocks with exact tool name `Read` and an
482
+ `input` object containing `pages`;
483
+ - requires a nonempty `file_path`, exactly one later matching `tool_result`, the
484
+ same nonempty `sessionId`, and result `sourceToolAssistantUUID` equal to the
485
+ tool-use assistant UUID before automatic repair;
486
+ - reports pending calls without changing them;
487
+ - defaults to the strict active chain; `--scope all` is explicit;
488
+ - deletes the exact JSON member byte span without reserializing other data;
489
+ - blocks duplicate-key or overlapping-span ambiguity;
490
+ - preserves BOM, newline style, Unicode spelling, unknown fields, record count,
491
+ UUID/parent signature, and tool ID sequences;
492
+ - re-reads the actual published candidate, verifies exact expected bytes and
493
+ SHA-256, validates the repair invariants, requires an idempotent second scan
494
+ with zero remaining patches, and runs the shared full-transcript
495
+ UUID/parent/compact/tool validator on the published bytes.
496
+
497
+ Candidate and live replacement modes use the same atomic-write, full-hash,
498
+ numbered-backup, source-race, validation, and rollback principles as compression.
499
+
500
+ ## Candidate and Live Replacement Transactions
501
+
502
+ Candidate mode requires distinct input and output paths. Candidate JSONL,
503
+ reports, validation, model packs, summaries and work directories must remain
504
+ outside the entire `.claude` tree.
505
+
506
+ Live replacement mode requires one existing regular `.jsonl` input under
507
+ `.claude/projects`, an external `--work-dir`, and caller acknowledgement
508
+ `--confirm-session-closed`. The acknowledgement is an operational assertion,
509
+ not process-lock detection. It:
510
+
511
+ 1. reads and hashes the complete original bytes;
512
+ 2. writes and validates a candidate outside `.claude`;
513
+ 3. rechecks that the source bytes did not change;
514
+ 4. creates an exclusive `.backup`, `.backup1`, and so on;
515
+ 5. validates immutable candidate bytes and writes a unique same-directory stage
516
+ with flush/fsync;
517
+ 6. captures the actual old target, verifies its full bytes against the frozen
518
+ source, and publishes the staged candidate with an atomic no-clobber claim;
519
+ 7. verifies published bytes and structure, restoring the captured original on
520
+ failure. If restoration fails, the numbered verified backup remains and the
521
+ error is promoted.
522
+
523
+ If another process recreates the target after capture begins, the transaction
524
+ does not overwrite the external target. It preserves the verified numbered
525
+ backup and, when necessary, places the captured original in another numbered
526
+ backup, then fails without publishing the candidate.
527
+
528
+ Before any live staging, backup payload publication, or target move, the
529
+ implementation probes file hard-link support in the target directory and, when
530
+ configured, the external backup directory. The probe itself briefly creates
531
+ and removes unique dot-prefixed temporary files. Every unique live-transaction
532
+ temporary path records its observed filesystem identity and frozen bytes.
533
+ Cleanup rechecks both and retains a detected mismatch. A mismatch after commit is reported as
534
+ `committed-cleanup-failed`; a mismatch while an earlier transaction failure is
535
+ already active is appended to that failure. This is a best-effort race check,
536
+ not a portable atomic identity-bound delete: standard Python pathname cleanup
537
+ cannot eliminate a replacement between its final check and `unlink()` across
538
+ Windows, Linux, and macOS. Live operation assumes Claude Code is closed and no
539
+ other writer is manipulating the session directory.
540
+
541
+ Candidate-mode reports are written with the candidate. Live compression delays
542
+ its final sidecar/report until replacement metadata is available, so it does not
543
+ leave a stale pre-commit report. If the JSONL commits and validates but final
544
+ report publication fails, compression returns exit code 3 with
545
+ `operation_state: committed-report-failed`; repair uses the camelCase equivalent
546
+ `operationState`. The receipt includes public artifact labels and frozen,
547
+ candidate, and published hashes. This is a committed state and is not rolled
548
+ back or reported as an ordinary uncommitted failure.
549
+
550
+ Once a numbered backup is created and verified, later failure cleanup never
551
+ deletes that path. This preserves an audit/recovery asset even when publication
552
+ or rollback fails or another process races on a nearby name.
553
+
554
+ Claude Code should be closed for that session during replacement. No other
555
+ session or Claude settings file is part of the transaction. Parent-directory
556
+ fsync is best effort and reported; the project does not promise cross-platform
557
+ power-loss atomicity.
558
+
559
+ ## Validation Boundary
560
+
561
+ The validator checks observed-format coherence, including:
562
+
563
+ - parseability and object-per-line structure;
564
+ - unique UUIDs and resolvable parent/session links;
565
+ - exactly one current compact pair;
566
+ - current compact pair reachability from the final pointer;
567
+ - compact preserved-message metadata;
568
+ - non-empty, unique active tool IDs and tool-use/result pairing after fragment
569
+ merging; partial multi-tool ordered subsets are accepted only with an explicit
570
+ compatibility warning/count;
571
+ - absence of internal planning fields in output;
572
+ - source hashes, model-pack metadata, anchor sets, and target estimates where
573
+ applicable.
574
+
575
+ Passing these checks proves coherence under this project's empirical rules. It
576
+ does not make the private transcript format an Anthropic-supported public API.
577
+ When runtime testing is explicitly requested, `/resume`, `/context`, recent
578
+ conversation rewind, and recent file rewind are separate observations.
579
+
580
+ ## Package 1.1.0 additive controls
581
+
582
+ CLI defaults remain `tool_evidence=excerpt`, `citation_style=legacy`,
583
+ `prior_summary_overflow=fold`, `min_recent_turns=0`. Non-default values add
584
+ `toolEvidence`, `citationStyle`, `priorSummaryOverflow`, `minRecentTurns` to the
585
+ v11 request manifest; defaults omit these keys. This preserves the old manifest
586
+ shape, not cross-version evidence bytes: regenerate packs after upgrading.
587
+
588
+ Full tool evidence uses the existing per-record full_text_json/claim-source
589
+ contract. It is a labelled field ledger with complete tool inputs, API results,
590
+ auxiliary toolUseResult and source assistant identity. An exact_string_alias_of
591
+ line refers only to an identical string of at least 80 characters earlier in
592
+ that record. It never deduplicates by path, meaning, or another branch. All
593
+ complete-tool records are mandatory and may not be sampled to fit. Warning
594
+ characters are retained. Plain injected/unknown user-format prose remains
595
+ mandatory too; it is not attributed to a human. Neither full evidence nor
596
+ source excerpts establish that a model interpreted a fact correctly.
597
+
598
+ Scoped prose references are exactly [@L42] / [@H3]. Coverage entries keep the
599
+ printed L prefix; support_text_json contents are excluded from reference scans.
600
+ The chosen style is bound in both passes. Historical source statements about
601
+ unavailable information are allowed; grounding checks still apply.
602
+
603
+ Strict prior-summary overflow uses the existing 1.5x summary character limit.
604
+ Preflight and pack generation reject when the preserved old block and even an
605
+ empty new layer cannot fit. Actual new-layer size is checked in pass 2. Exact
606
+ preserved spans include trailing whitespace and carry SHA-256 and ordered
607
+ offsets. The legacy fold policy still reports fallback-folded on overflow.
608
+
609
+ Recent-turn protection only considers human-started records after the latest
610
+ compact and within the final session. Tool-boundary adjustment must not cross
611
+ that floor. A positive setting is rejected in physical-tail mode. Reports
612
+ count structural human messages and snapshots, not actual rewind menu points.
613
+
614
+ Title selection is shared by locator and compressor: last valid custom title
615
+ in the final session, otherwise last valid automatic title. Supported fields
616
+ are customTitle/aiTitle, title, then message.content. Only UUID-less, parent-less
617
+ control records may be projected; an unowned title is attributable only when
618
+ the source has one nonempty session ID. A rename after the pointer is control
619
+ metadata, not conversation evidence. Selected metadata retains unknown fields
620
+ and is assigned the output session ID. Prior title projections are removed,
621
+ and the final title is written before the sole final last-prompt. Ambiguous
622
+ unowned titles are counted, never used to override a known owner.
623
+
624
+ Preflight is read-only. Its topology, activeChain, partition, estimates and pack
625
+ fields separate structural validity, selected-source validation and capacity.
626
+ Physical-tail diagnostics are not a recovery authorization. It may return
627
+ nothing-to-summarize without building a pack. Exact source bytes are hashed;
628
+ freeze a numbered backup after successful preflight and compare hashes before
629
+ model work. Candidate and live replacement retain their existing write rules.
630
+
631
+ Cost discipline: one authoring pass and focused self-review by default; use
632
+ additional review when requested or needed to resolve a concrete conflict.
633
+ Honor requested reviewer model/effort/scope, and pass only complete relevant
634
+ evidence from the selected branch. No automatic fan-out, budget escalation,
635
+ external document retrieval, or multi-volume pipeline is part of this package.