You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/10/18 07:27:06 UTC

(skywalking-ai-sessionizer) branch main updated: Record what each model call was sent, for comparing histories (#76)

This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-ai-sessionizer.git


The following commit(s) were added to refs/heads/main by this push:
     new 72eef4c  Record what each model call was sent, for comparing histories (#76)
72eef4c is described below

commit 72eef4cd5c3ca31ff1d0cb10ef39324f58eee046
Author: 吴晟 Wu Sheng <wu...@foxmail.com>
AuthorDate: Sun Oct 18 15:27:01 2026 +0800

    Record what each model call was sent, for comparing histories (#76)
    
    A provider body's manifest now says what its call's history was, so a
    later change can show where a call's history changed without reading a
    body. On a request, messages lists its messages in order, one entry of
    two digests each, written whole or against the body its copy came from;
    on a response, reply names the message it produced. A Claude Code
    request also records its system prompt without the billing header, and
    the provider thread it starts or continues. history_form names the
    rules, claude-code/1 or langchain/1, and nothing a runtime recorded is
    left out of a message. A list that would take more than a quarter of
    max_delta_bytes is unavailable as too large, since the budget never
    takes a manifest out. A manifest whose history keys do not read is read
    without them, and still joins its call.
    
    A LangChain model call's record is flagged internal_call when LangChain
    made it for its own purpose, from lc_internal_call in its start's
    metadata, and turn_call otherwise. Only a start decides, and an update
    carries its start's class through the receiver's state. A LangChain
    reply is landed only beside a request read as one prompt.
    
    The index reads all of it, and its schema moves to 17.
    
    Measured on a fresh session of Claude Code 2.1.302: a process's first
    request starts a provider thread and holds the whole message list, and
    every later request continues it and holds only the new messages. With
    each such history built from the call it names, every pair of
    consecutive calls kept the history before it, with the reply next,
    through six resumes, until the compaction. A redacted copy of the
    session is a test fixture. On the LangChain captures, with a new
    streaming case, every one of 80 message ids sent more than once has one
    form. At the default budget the history keys are about 5% of the
    provider files' bytes.
---
 docs/en/adapters/claude-code-glossary.md           |    4 +-
 docs/en/adapters/claude-code-provider-bodies.md    |   31 +-
 docs/en/adapters/kiro-glossary.md                  |    4 +-
 docs/en/adapters/langsmith-glossary.md             |    4 +-
 docs/en/adapters/langsmith.md                      |   28 +-
 docs/en/changes/changes.md                         |   27 +-
 docs/en/concepts-and-designs/glossary.md           |   10 +
 docs/en/formats/session-data.md                    |   74 ++
 internal/adapters/claudecode/glossary.go           |    4 +
 internal/adapters/claudecodeprovider/claude.go     |  192 +++-
 internal/adapters/claudecodeprovider/collect.go    |    3 +-
 .../adapters/claudecodeprovider/history_test.go    |  145 +++
 .../claudecodeprovider/testdata/threads/README.md  |   28 +
 ...2985c8-ab1d-45a0-b1d3-6ff27d7fcb75.request.json |    1 +
 ...15d0c2-d2bf-4351-a9b4-751861b82475.request.json |    1 +
 ...8db1cd-f3bc-4432-83b8-bc9c70b90439.request.json |    1 +
 ...b3a490-585a-41bc-95e7-3c760ffea640.request.json |    1 +
 ...7b306f-d4f9-491b-9d7d-e116888f970f.request.json |    1 +
 ...51b729-b0bc-4985-8ec7-7472c2f3d10d.request.json |    1 +
 ...ba5a23-5af4-4dfa-8dad-10766a9c90b3.request.json |    1 +
 ...315d98-46c0-407b-a988-d07e51c4ff72.request.json |    1 +
 ...760102-b036-475b-b7a7-1d07f8c7b94c.request.json |    1 +
 ...e356f3-7149-440d-a150-6f5df8f28189.request.json |    1 +
 ...175026-faac-45c1-abc0-e4ce7b103510.request.json |    1 +
 ...8f8672-bbfc-4f66-8ff5-e60248e8d74e.request.json |    1 +
 ...00d81e-2378-4bc0-9981-6b3a2fdfa177.request.json |    1 +
 ...a9ce9b-31ae-4ff1-9e30-29223cd5b2cf.request.json |    1 +
 .../testdata/threads/bodies/index.jsonl            |   14 +
 .../req_011Cg91hS9pwQhArnANxwDCC.response.json     |    1 +
 .../req_011Cg91hrQqKed3sUjCTPVnm.response.json     |    1 +
 .../req_011Cg91nX1Qzwp6avWtT2hTz.response.json     |    1 +
 .../req_011Cg91oYFrF3HCoUscT76Va.response.json     |    1 +
 .../req_011Cg91p19cxAxXDddBxCsom.response.json     |    1 +
 .../req_011Cg91pJCYbAQfh8DS8WQzh.response.json     |    1 +
 .../req_011Cg91pTBXWszWaMn2dptYf.response.json     |    1 +
 .../req_011Cg91qVx1BFWosh783Gfuh.response.json     |    1 +
 .../req_011Cg91rsmh9PMmydkaP9YMf.response.json     |    1 +
 .../req_011Cg91sdAdqooUrshuiekNe.response.json     |    1 +
 .../req_011Cg91tEAKSQ1emozbt8Ftx.response.json     |    1 +
 .../req_011Cg91u8N6ELFcKERTr7o58.response.json     |    1 +
 .../req_011Cg91w3ctwRBdcWkyTV2eV.response.json     |    1 +
 .../req_011Cg91xnUsQzMVn15EmxE4p.response.json     |    1 +
 .../testdata/threads/mtimes.json                   |   31 +
 .../2bb28d1c-72a7-4260-a010-f439329e4e76.jsonl     |  161 +++
 .../subagents/agent-a89d833fef85d5b44.jsonl        |   25 +
 .../subagents/agent-a89d833fef85d5b44.meta.json    |    1 +
 internal/adapters/kiro/glossary.go                 |    3 +
 internal/adapters/langsmith/bodies.go              |    7 +-
 internal/adapters/langsmith/collect.go             |   10 +-
 internal/adapters/langsmith/dialect.go             |   21 +-
 internal/adapters/langsmith/glossary.go            |    5 +
 internal/adapters/langsmith/history.go             |  199 ++++
 internal/adapters/langsmith/history_test.go        |  191 ++++
 internal/adapters/langsmith/pending.go             |   13 +-
 internal/adapters/langsmith/reset.go               |    2 +-
 internal/index/codec.go                            |   88 +-
 internal/index/fromrecord.go                       |  120 +-
 internal/index/index.go                            |   84 +-
 internal/index/index_test.go                       |   96 ++
 internal/index/lookup.go                           |   17 +
 pkg/model/term.go                                  |   11 +
 pkg/providerbody/canonical.go                      |  147 +++
 pkg/providerbody/history.go                        |  317 ++++++
 pkg/providerbody/history_test.go                   |  414 +++++++
 pkg/providerbody/providerbody.go                   |   11 +
 pkg/providerbody/session.go                        |   49 +-
 pkg/skeleton/bodies_test.go                        |   63 +-
 tests/apps/langchain/MEASUREMENTS.md               |    8 +-
 tests/apps/langchain/agents.py                     |   41 +-
 tests/apps/langchain/model.py                      |   44 +
 .../langchain/testdata/streaming/001-POST/body.bin |  Bin 0 -> 88068 bytes
 .../testdata/streaming/001-POST/meta.json          | 1192 ++++++++++++++++++++
 tests/claudecode_threads_test.go                   |  287 +++++
 tests/langchain_history_test.go                    |  583 ++++++++++
 .../provider-bodies-too-large.expect.yaml          |    8 +-
 75 files changed, 4789 insertions(+), 56 deletions(-)

diff --git a/docs/en/adapters/claude-code-glossary.md b/docs/en/adapters/claude-code-glossary.md
index 77d8b3f..78b88cc 100644
--- a/docs/en/adapters/claude-code-glossary.md
+++ b/docs/en/adapters/claude-code-glossary.md
@@ -19,7 +19,7 @@ asz conversation -terms native ID   talk · promptId · message.id · tool_use +
 asz conversation -terms both ID     run  (promptId)
 ```
 
-**61 terms: 39 that Claude Code names, 22 this project derives.** That ratio is worth watching. If
+**63 terms: 39 that Claude Code names, 24 this project derives.** That ratio is worth watching. If
 everything had a native name, the model would be a rename of one product rather than a model of
 any. If nothing did, it would have no evidence under it.
 
@@ -91,6 +91,7 @@ than a plausible word that would send a reader looking for a field that does not
 | `hash_only` | only a digest of the bytes was kept |
 | `in_segment` | the activity window a Talk falls in |
 | `input_of` | unavailable; the serialized provider request is not written locally |
+| `internal_call` | no equivalent: a request Claude Code sends for its own purpose, such as a compaction's, joins no call |
 | `omitted` | deliberately not carried |
 | `removed` | no equivalent: asz removes the payloads of a conversation idle longer than storage.retention.metadata_only_after from its own root; Claude Code keeps its transcript as before |
 | `retries` | unavailable; retry identity is not recorded |
@@ -100,6 +101,7 @@ than a plausible word that would send a reader looking for a field that does not
 | `talk` | one readable interaction; derived from where a run was triggered |
 | `too_large` | no equivalent: asz omits a payload whose record alone is larger than a landed file's byte budget, such as a pasted screenshot |
 | `truncated` | part of the bytes, with the full length reported |
+| `turn_call` | every call a transcript records; Claude Code writes no flag for it |
 | `unavailable` | the runtime never wrote it, or the source was pruned |
 | `unknown` | no record of the run states a trigger |
 | `unresolved` | no usable evidence |
diff --git a/docs/en/adapters/claude-code-provider-bodies.md b/docs/en/adapters/claude-code-provider-bodies.md
index e021a0c..e1f9bc5 100644
--- a/docs/en/adapters/claude-code-provider-bodies.md
+++ b/docs/en/adapters/claude-code-provider-bodies.md
@@ -33,8 +33,9 @@ files and memory index, the schemas of every MCP tool, and the environment block
 the operating system. asz lands and sends the bodies as they are, and redacts nothing.
 
 **How large.** In a capture with the full tool list, a main request was 121 to 196 KB, of which the
-tool schemas were 74 KB and the system prompt 10 KB, and a subagent's request 33 to 58 KB. Every
-call sends its chain's whole message list again. So most of a body is bytes the session already has.
+tool schemas were 74 KB and the system prompt 10 KB, and a subagent's request 33 to 58 KB. In
+2.1.260 every call sends its chain's whole message list again; in 2.1.302 a request that starts a
+thread does, below. So most of a body is bytes the session already has.
 
 Earlier messages come back with the same text, but not always spelled the same way. Two things move
 as the list grows. The cache marker, `cache_control`, moves to the newest message. And a message of
@@ -47,6 +48,28 @@ A compaction is the case that really does rewrite the list. It replaces the cont
 and starts the list again, so the list is shorter as well as different, and the conversation records
 it as an `epoch.boundary` step with the summary beside it.
 
+**Threads.** In 2.1.302 a request names a thread the provider keeps. Measured on one session of
+`claude -p`, nine turns each a new process resuming it, 14 requests:
+
+- A process's first request carries `"thread":{"type":"create"}` and holds the whole message list:
+  2, 8, 11, 17, 20, 26, 5 and 8 messages over the main stream's processes.
+- Every later request of the process carries `"thread":{"type":"continue","previous_message_id":…}`,
+  the message id of the response before it, and holds only what is new: here the tool result and a
+  `system`-role message, 2 each. It carries no tools, and of `system` only the billing header. The
+  provider holds the rest.
+- A resumed session's first request starts a new thread with the whole list again. A subagent has a
+  thread of its own. The compaction request carries no thread.
+- With each continue request's history built from the call it names, that call's reply and its own
+  messages, every call's history was the history before it with the reply next, through six resumes,
+  until the compaction. The two spellings above still apply: without the first, 6 of those 10
+  comparisons fail, and without the second, 4.
+- The billing header in `system` differs on every request, since it names the request before, the
+  prompt and the turn; every other system block is sent the same each time.
+
+A redacted copy of this session is `internal/adapters/claudecodeprovider/testdata/threads`, and a
+test lands it through both adapters. [Session Data](../formats/session-data.md#history) gives what
+a manifest records of the thread and of each message.
+
 ## What the adapter lands
 
 **A session for each body.** A request names its session; a session id that is not in Claude Code's
@@ -99,5 +122,5 @@ prompt, were left unjoined.
 ## Not measured
 
 Subagents or workflow agents running at the same time, server-side clearing of old tool results,
-the `thread` field that sends only a tail of the messages, retries, a long session, `--resume` and
-`--fork-session`, Bedrock, Vertex and gateways, and Windows.
+an interactive session, a thread continued from a message other than the last response, retries, a
+long session, `--fork-session`, Bedrock, Vertex and gateways, and Windows.
diff --git a/docs/en/adapters/kiro-glossary.md b/docs/en/adapters/kiro-glossary.md
index 2d341af..c6f1bf5 100644
--- a/docs/en/adapters/kiro-glossary.md
+++ b/docs/en/adapters/kiro-glossary.md
@@ -21,7 +21,7 @@ asz conversation -terms native ID   talk · executionId · tool_call and tool_re
 asz conversation -terms both ID     run  (executionId)
 ```
 
-**61 terms: 24 that Kiro names, 37 this project derives.**
+**63 terms: 24 that Kiro names, 39 this project derives.**
 
 ---
 
@@ -84,6 +84,7 @@ not there.
 | `hash_only` | — | derived here | no equivalent |
 | `in_segment` | — | derived here | derived |
 | `input_of` | — | derived here | no equivalent |
+| `internal_call` | — | derived here | no equivalent: Kiro records no model request, so no call's history is compared |
 | `llm.call` | — | derived here | no equivalent: the engine records what the model said and not which request said it |
 | `message.synthetic` | — | derived here | not read: records the engine writes on recovery end their id in -synthetic, are a tool result, a child's end or a turn's end, and none was measured |
 | `model` | — | derived here | not read: no record says which model answered; session.json names the model chosen |
@@ -102,6 +103,7 @@ not there.
 | `too_large` | — | derived here | no equivalent: asz omits a payload whose record alone is larger than a landed file's byte budget |
 | `trigger` | — | derived here | derived: a user record that is neither a steering note nor written by the engine |
 | `truncated` | — | derived here | not read: the model is sent a preview of a large tool output, and that preview is the result here; the whole output is in tool-outputs, which is not collected |
+| `turn_call` | — | derived here | no equivalent: Kiro records no model request, so no call's history is compared |
 | `unknown` | — | derived here | derived |
 | `unresolved` | — | derived here | derived |
 | `weak_inference` | — | derived here | derived |
diff --git a/docs/en/adapters/langsmith-glossary.md b/docs/en/adapters/langsmith-glossary.md
index 73d85e2..86f00db 100644
--- a/docs/en/adapters/langsmith-glossary.md
+++ b/docs/en/adapters/langsmith-glossary.md
@@ -21,7 +21,7 @@ asz conversation -terms native ID   talk · trace_id · id · run_type: tool
 asz conversation -terms both ID     run  (trace_id)
 ```
 
-**61 terms: 17 that LangChain names, 44 this project derives.** That ratio is worth watching against
+**63 terms: 18 that LangChain names, 45 this project derives.** That ratio is worth watching against
 the Claude Code one. A framework that records its own graph in detail still has no word for a
 conversation boundary, a context reset or a delegated agent — which is the whole reason this model
 exists separately from any product's.
@@ -43,6 +43,7 @@ Each row says where the thing is recorded in a run.
 | `error.api` | `error` | a run | a string with the exception and its traceback |
 | `id` | `id` | a run | the record is the arrival, so a run that is posted and later patched has two |
 | `input_of` | `inputs` | a run |  |
+| `internal_call` | `extra.metadata.lc_internal_call` | the start of a run of type llm | LangChain sets it on a model call it makes for its own purpose, such as SummarizationMiddleware's summary |
 | `llm.call` | `run_type: llm` | a run |  |
 | `message.assistant` | `outputs.generations[].message.kwargs.content` | a run of type llm |  |
 | `message.external` | `inputs.messages` | the root run of a trace | the message that opened the turn has no run of its own |
@@ -104,6 +105,7 @@ not there.
 | `trigger` | — | derived here | no equivalent: derived from a trace's root run |
 | `truncated` | — | derived here | no equivalent: nothing measured arrived truncated |
 | `turn.duration` | — | derived here | no equivalent as a record; a run's own start and end give it |
+| `turn_call` | — | derived here | no equivalent: a run of type llm whose start carries no lc_internal_call |
 | `unavailable` | — | derived here | a run that arrived unfinished: its content is not there yet |
 | `unknown` | — | derived here | derived |
 | `unresolved` | — | derived here | derived: a failed tool whose model call arrived in another request |
diff --git a/docs/en/adapters/langsmith.md b/docs/en/adapters/langsmith.md
index bd1cdbf..0eb2971 100644
--- a/docs/en/adapters/langsmith.md
+++ b/docs/en/adapters/langsmith.md
@@ -89,7 +89,7 @@ the captures, from the Python client, carry the project and the thread.
 
 A run is posted when it starts and patched when it ends, so the same run can reach asz more than
 once. Both clients send the two as one post of the finished run when they fall in one batch, as
-most runs do: in the captured corpus 462 of 476 posts carried their run's end. Each arrival is its
+most runs do: in the captured corpus 487 of 501 posts carried their run's end. Each arrival is its
 own record, with the run as its `call`.
 
 They cannot share one record id. The index keeps the first entry for an id, and in the streaming
@@ -234,10 +234,28 @@ On this wire a body is the framework's view of what it sent — the run's `input
 LangChain's serialized message form — and not the bytes the provider received. The manifest says
 which adapter it came from, and a reader must not take it for the wire.
 
+**What a call's history was.** A request's manifest also lists its messages, one digest each in the
+form `langchain/1`, and a response's names the message it produced, so assembly can tell where a
+call's history changed without reading a body; [Session Data](../formats/session-data.md#history)
+gives the rules. A message is compared as LangChain serialized it, with nothing left out. On the 20
+capture directories, every one of the 80 message ids sent more than once has one form. Of the 79
+replies, 47 come back in a later request as they were produced, 3 come back altered by the
+framework, and 29 never come back. Two of the altered ones gained `name`, the name of the agent
+that wrote them, when a subagent's answer went back to its caller. The third is a loop over
+`model.stream()` that sent back the chunks it joined, which LangChain marks with
+`chunk_position: "last"`; a graph that streams sends back an `AIMessage`, which comes back equal.
+
+**Which calls are the conversation's.** A model call LangChain makes for its own purpose, such as
+`SummarizationMiddleware`'s summary, carries `lc_internal_call` in its run's metadata, and its
+record is flagged `internal_call`; any other model call is flagged `turn_call`. Only a run's start
+carries the whole metadata, so only a start decides, and an update's record carries the class its
+start gave, or none when the receiver does not hold it, as after the start was forgotten or before
+asz wrote the flag. Two starts that disagree leave `internal_call`.
+
 **Where the system prompt and the tools land.** The system message and the tools a model was offered
 reach asz in a model call's `inputs` and `extra`, and in any run the application hands them to, such
-as a decorated function, which the paragraphs below cover. Measured on the 75 model arrivals of the
-captured corpus, every one sent its inputs out of band, and the 43 that offered tools sent them in
+as a decorated function, which the paragraphs below cover. Measured on the 80 model arrivals of the
+captured corpus, every one sent its inputs out of band, and the 46 that offered tools sent them in
 an out-of-band `extra`. The system message lands in the provider bodies, as part of a request's
 inputs, and a reader that [withholds](../formats/asz-view.md#withholding) anything is served no
 body. The tools land nowhere: a request body is its inputs, and the `extra` they came in is not
@@ -247,7 +265,7 @@ Two other records can hold the request itself. A model call that arrives before
 lands that arrival's whole run envelope, whether or not a later arrival finishes it. The envelope
 held neither on the corpus, but a client that put them inside it would land them there. A trace
 whose root is the model call itself, with no message list in its inputs, such as a completion asked
-with a list of prompts, lands those inputs whole as its first input. All 48 trace roots of the
+with a list of prompts, lands those inputs whole as its first input. All 51 trace roots of the
 corpus are chain runs, so none landed this way. Each record is named `system_prompt` when it holds
 inputs of any shape, since they are the request the model was sent, and the envelope also when its
 `extra` holds a prompt by shape, under a key a provider takes it by. It is named `tool_schemas` when
@@ -384,7 +402,7 @@ identity remains unresolved, the update is then not collected. See
   - The middleware summarises inside a turn, so the talk it happens in stays in the epoch where the
     turn began, as it does for a Claude Code compaction in the middle of a turn.
   - The reset lands with the arrival that carries the request. In the captured corpus every model
-    call's first arrival carried its request, 74 of 74. A request that arrives after its call has
+    call's first arrival carried its request, 79 of 79. A request that arrives after its call has
     begun puts that one call in the old epoch.
   - A marked message with no `id` makes no reset. The middleware gives every message one. Without
     it, one summary could not be told from another with the same words.
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 376039f..91e217c 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -113,10 +113,20 @@
   own arguments and result, and a graph's first input, are the application's own, so they are named
   only by their shape: a message of the `system` or `developer` role, a value under a key a provider
   takes the prompt by, or a list of tools. The shapes are listed on the LangChain page. On the
-  captured corpus the client sends both out of band, and all 48 trace roots are chain runs, so the
+  captured corpus the client sends both out of band, and all 51 trace roots are chain runs, so the
   one record named is the arguments of the LangChain JS traced function, which hold its system
   message.
 
+- A model call's record says whether LangChain made it for its own purpose, such as
+  `SummarizationMiddleware`'s summary, from `lc_internal_call` in its start's metadata: it is flagged
+  `internal_call`, and any other model call `turn_call`. Only a start decides, and an update carries
+  its start's class, or none when the receiver no longer holds it.
+- A request's manifest lists its messages in the form `langchain/1`, one digest each, with nothing
+  left out of a message, and a response's the message it produced. On the 20 capture directories,
+  every one of 80 message ids sent more than once has one form. A new capture, `streaming`, streams a
+  graph and a loop over `model.stream()`; the loop sends back the chunks it joined, as
+  `AIMessageChunk`, and a graph an `AIMessage`. See [LangChain](../adapters/langsmith.md).
+
 ## Claude Code
 
 - A headless run's question is on the page. `claude -p` and an Agent SDK application write their
@@ -137,6 +147,19 @@
   records as they are, because landed records are never rewritten. Collecting it into a new root
   names them.
 
+- What each model call was sent is recorded as a list, so a conversation can tell where a call's
+  history changed without reading a body. A provider body's manifest lists the messages its request
+  held, one digest each, the system prompt without its billing header, the message its response
+  produced, and the provider thread the request starts or continues, in the form `claude-code/1`
+  ([Session Data](../formats/session-data.md#history)). Claude Code 2.1.302 continues a thread the
+  provider keeps: a process's first request holds the whole message list, and each later one only
+  the new messages and the id of the response it goes on from. Measured on one session of nine
+  resumed turns, 14 requests: with each such history built from the call it names, every call's
+  history was the one before it with the reply next, through six resumes, until the compaction. A
+  redacted copy of that session is a test. See
+  [Claude Code Provider Bodies](../adapters/claude-code-provider-bodies.md). A body landed before
+  this carries no list.
+
 ## MCP calls
 
 - A call to an MCP server names its server and its tool. Claude Code calls an MCP tool
@@ -275,6 +298,8 @@
   so the session never parsed again; and the 65,537th part of a record took the first part's
   position, so its step replaced the first one's. The index schema moved to 16, and an older index
   is built again from the landed files.
+- The index reads what a provider body records of its call's history, and the class of a model call.
+  Its schema moved to 17, and an older index is built again from the landed files.
 - The OTLP exporter over HTTP keeps its own connections. It shared the process's default HTTP
   transport, so closing it closed every other HTTP client's idle connections, and anything else that
   closed them, such as a test receiver shutting down, could break a request the exporter had just
diff --git a/docs/en/concepts-and-designs/glossary.md b/docs/en/concepts-and-designs/glossary.md
index ec1c251..103c283 100644
--- a/docs/en/concepts-and-designs/glossary.md
+++ b/docs/en/concepts-and-designs/glossary.md
@@ -124,6 +124,16 @@ are what everything above it sees.
 | `time` | when the runtime says it happened |
 | `trigger` | what started the run: `external`, `notification`, or `unknown` |
 
+The class of a model call, which a record carries as a flag where its runtime says it:
+
+| class | is |
+| --- | --- |
+| `turn_call` | a turn of its stream: the next call of the stream goes on from its history |
+| `internal_call` | a call the framework made for its own purpose, such as a summary, which takes **no** part in the history of its stream. Not a turn, and not a child agent either. |
+
+A LangChain call that carries neither flag has an unknown class, and its history is never compared.
+Claude Code writes neither flag, since every call its transcript records is a turn call.
+
 ## Qualification
 
 Nothing is presented as observed unless it was observed.
diff --git a/docs/en/formats/session-data.md b/docs/en/formats/session-data.md
index 4e2dc47..e6ff12a 100644
--- a/docs/en/formats/session-data.md
+++ b/docs/en/formats/session-data.md
@@ -159,6 +159,8 @@ source's fields, never by guessing from text or size, so a reader can act on a f
 | `command` | is a command run in line rather than said to the model |
 | `notice` | is the runtime telling the requester about the session itself |
 | `auxiliary` | names a child stream that is a plain model call the agent made inside its own work, not a child agent |
+| `internal_call` | is a model call the framework made for its own purpose, such as a summary, which takes no part in the history of its stream |
+| `turn_call` | is a model call that is a turn of its stream. A LangChain call that carries neither flag has an unknown class, and its history is never compared. Claude Code writes neither: every call its transcript records is a turn call |
 
 `system_prompt` and `tool_schemas` exist so a server that shows a conversation to the people an
 agent served can withhold what the runtime sent the model, by name rather than by guessing from the
@@ -251,6 +253,7 @@ part that asz builds, the manifest, schema `provider_body/1`:
 | `bytes` limit | a body may claim at most 256 MiB, and a rebuild stops as soon as it passes the size its record claims, so a damaged record cannot make a reader hold more than one body |
 | `chain` | the first sixteen hexadecimal characters of the SHA-256 of the request's first message, with the cache marker taken out. It chooses which earlier body a copy comes from. It is storage, and a join must not rely on it |
 | `model`, `session`, `call`, `request`, `previous_request` | the values the body carries that join it, as its adapter read them, in the model's words: the session it names; on a response, the call it answers and the provider's id for its request; on a request, the call it was sent to when its adapter knew it, and otherwise the provider request id of the call before it in its chain. Empty when the body carries none. The [adapter's glossary](../adapters/claude-code.md#provider-bodies) say [...]
+| `history_form`, `messages`, `system`, `thread`, `reply` | what the body says about its call's history. See [History](#history) |
 
 A reference is a digest or a record id, never a file and a row, because `asz repack` renumbers files
 and rows and carries record lines unchanged. Every reference points at a record that landed before
@@ -295,6 +298,77 @@ and `sha` to its manifest.
 On two captures of Claude Code 2.1.260, the landed `provider_body` files were 25.5% and 21.6% of the
 bytes Claude Code wrote, and all 42 bodies rebuilt exactly.
 
+### History
+
+A manifest also says what its body holds of the call's history, so assembly can tell where a call's
+history changed without reading a body. The history of a call is the message list its request
+records, as the runtime recorded it. For Claude Code that is what was sent to the provider; for
+LangChain it is what LangChain passed to the chat model, never the provider's request.
+
+```json
+{"history_form":"claude-code/1",
+ "thread":{"type":"continue","previous":"msg_011C…"},
+ "system":"3f0c9a1e5b7d2c44",
+ "messages":{"base":"03455f62-….request","keep":11,"add":[{"digest":"9c1e…"},{"digest":"07ab…"}]}}
+```
+
+| Key | Value |
+| --- | --- |
+| `history_form` | the rules the other keys were written under: `claude-code/1` or `langchain/1`. Two lists are compared, and a list is written against a base, only under one form. A rule changes only with a new version. Absent on a body landed before asz wrote these keys, which is never compared |
+| `messages` | on a request, its messages in order, one entry each: `{"id":…,"digest":…}`, where `id` is the first sixteen hexadecimal characters of the SHA-256 of the message id the runtime sent, present only when it sent one, and `digest` the same of the message's form below. Written as `{"entries":[…]}`; or as `{"base":…,"keep":k,"add":[…]}`, the first `k` entries of the list of the record `base` followed by `add`; or as `{"unavailable":…}` with the reason, and for a list too large it [...]
+| `system` | on a Claude Code request, the digest of its `system`: a list of blocks without its billing header, the text block whose text starts `x-anthropic-billing-header:`, and any other value as it is. Absent when nothing else is there |
+| `thread` | on a Claude Code request, the provider thread it starts, `{"type":"create"}`, or continues, `{"type":"continue","previous":…}` with the provider message id it goes on from, at most 128 bytes. Absent when the request carries none. A request that continues a thread holds only what follows that message |
+| `reply` | on a response, the entry of the message it produced, in the same form as a request's messages, or `{"unavailable":…}` |
+
+A list is written against a base only when the base is the body this body's copy comes from, its
+list is in the same form, and the two share a front; otherwise it is written whole. So a list's
+chain is its body's chain, at most 32 deep. A reader checks that the base is an earlier request of
+the session, of the same form, and holds at least `keep` entries, and treats a list that fails as
+unavailable, as it does a list of any other shape. A manifest whose other history keys do not read
+is read without them, as a body that says nothing of its call's history: it still joins its call
+and rebuilds. The budget never takes a manifest out, so a list that would take more than a quarter of
+`max_delta_bytes` as written is `{"unavailable":"too_large","count":…}`; every other key has a fixed
+size, and an entry 53 bytes with an id and 30 without.
+
+Measured on the 20 LangChain capture directories and the Claude Code thread capture (2026-10-18),
+each written with and without the history keys: at the default budget of 2 MiB the keys are 5.3% and
+5.4% of the bytes the `provider_body` files hold, the cap takes no list, the index grows by 13,651
+and 3,581 bytes, and the compressed skeletons of those files by 5,697 and 700. At 4 KiB, the
+smallest budget a scenario uses, the keys are 7.8% and 13.0%, the index grows by 14,293 and 3,989
+bytes and the skeletons by 11,733 and 2,642, and the cap takes 11 of the 79 LangChain lists: the
+longest of a twenty-turn conversation, 19 to 39 messages, whose bodies copy from no earlier body
+under so small a budget.
+
+The reasons a list or a reply is unavailable: `unrecognised_shape`, a body the form does not cover;
+`plural_prompts`, a LangChain request that is not exactly one prompt; `too_large`;
+`no_single_reply`, a response that is not exactly one finished message; and `no_request`, a LangChain
+response whose request did not land. A LangChain reply is written only when the call's request read
+as exactly one prompt: its reply is otherwise unavailable with the request's reason.
+
+A form is canonical JSON of the message: object keys sorted by their UTF-8 bytes, no space between
+tokens, numbers as written, and in strings only the quotation mark, the backslash and the
+characters below U+0020 escaped. Nothing a runtime recorded is left out of a message because a
+provider may not use it: which fields reach a provider depends on its adapter and its mode, which the
+recorded data does not say, and leaving out a field one of them sends hides a change.
+
+- **`claude-code/1`**: a message as sent, with two exceptions, both measured on messages sent again
+  unchanged by Claude Code 2.1.260 and 2.1.302. `cache_control` is left out of the message and of each
+  of its content blocks, since the runtime moves the marker to the newest message. A content list of
+  one block whose only keys are `type` `"text"`, `text` and `cache_control` reads as its `text`
+  string, since a message is sent again collapsed that way; a text block with any other key, such as
+  citations, keeps its form. The reply is `{"role":"assistant","content":…}` with the response's
+  content blocks, and carries no `id`: the response's id is never in a message. A response with no
+  stop reason, or that is not an assistant message, has no reply. On a capture of 2.1.302, removing
+  the first exception broke 6 of 10 comparisons of unchanged history, and removing the second 4.
+- **`langchain/1`**: a message in LangChain's constructor form, `{"lc":1,"type":"constructor","id":[…,
+  class],"kwargs":{…}}`, read as `{"class":…,"kwargs":…}`, with the last element of the path as the
+  class and the kwargs without `id`; every field's type is checked before it is read, so an `lc` of
+  `true` is not 1. Python and JavaScript write the path differently, so only the class is kept. A
+  chunk class keeps its own name. A plain message is `unrecognised_shape`. The reply is the message
+  of the response's one generation. On the 20 capture directories under
+  `tests/apps/langchain/testdata`, every one of 80 message ids sent more than once has one form.
+
+
 ## Closing line
 
 ```json
diff --git a/internal/adapters/claudecode/glossary.go b/internal/adapters/claudecode/glossary.go
index f37fc73..129b833 100644
--- a/internal/adapters/claudecode/glossary.go
+++ b/internal/adapters/claudecode/glossary.go
@@ -139,6 +139,10 @@ var glossary = model.NewGlossary(Dialect,
 		Note: "also a user record with promptSource \"sdk\", no origin and no isMeta - a headless run's prompt - and a run whose records state no origin at all - a locally typed command"},
 	model.Term{Unified: model.TriggerNotification, Native: "origin.kind == \"task-notification\"", Where: "record"},
 	model.Term{Unified: model.TriggerUnknown, Note: "no record of the run states a trigger"},
+
+	model.Term{Unified: model.ClassTurnCall, Note: "every call a transcript records; Claude Code writes no flag for it"},
+	model.Term{Unified: model.ClassInternalCall,
+		Note: "no equivalent: a request Claude Code sends for its own purpose, such as a compaction's, joins no call"},
 )
 
 // Glossary returns what Claude Code calls the things the model names.
diff --git a/internal/adapters/claudecodeprovider/claude.go b/internal/adapters/claudecodeprovider/claude.go
index 27bcde8..fb27532 100644
--- a/internal/adapters/claudecodeprovider/claude.go
+++ b/internal/adapters/claudecodeprovider/claude.go
@@ -104,7 +104,8 @@ func Lift(file string, body []byte) providerbody.Keys {
 
 // BodyOf is a body file as a session cuts it.
 func BodyOf(file string, body []byte) providerbody.Body {
-	return providerbody.Body{ID: ID(file), Role: RoleOf(file), Src: file, Keys: Lift(file, body), Bytes: body}
+	return providerbody.Body{ID: ID(file), Role: RoleOf(file), Src: file, Keys: Lift(file, body), Bytes: body,
+		History: HistoryOf(RoleOf(file), body)}
 }
 
 // firstSystemText is the text of the first system block, or the system
@@ -122,3 +123,192 @@ func firstSystemText(raw json.RawMessage) string {
 	}
 	return ""
 }
+
+// billingHeader starts the system block Claude Code writes its request
+// bookkeeping into. It differs on every request, measured on 2.1.302: it holds
+// the request before, the prompt and the turn.
+const billingHeader = "x-anthropic-billing-header:"
+
+// HistoryOf reads what a body says about its call's history, in the form
+// claude-code/1 (docs/en/formats/session-data.md).
+//
+// A request's messages are compared as sent, with two exceptions measured on
+// 2.1.260 and again on 2.1.302, both for a message sent again unchanged: the
+// cache marker moves to the newest message, so cache_control is left out at a
+// message's depth and at each of its content blocks'; and a content list of
+// one text block is sent again as its plain string, so such a list reads as
+// the string, but only when the block holds nothing besides its type, its text
+// and the marker. A request that continues a provider thread holds only what
+// follows the message it names; the assembler builds the rest.
+func HistoryOf(role string, body []byte) *providerbody.History {
+	h := &providerbody.History{Form: providerbody.FormClaudeCode}
+	v, err := providerbody.Decode(body)
+	head, ok := v.(map[string]any)
+	if err != nil || !ok {
+		h.Unavailable = providerbody.UnrecognisedShape
+		if role == providerbody.RoleResponse {
+			h.Reply = &providerbody.Reply{Unavailable: providerbody.UnrecognisedShape}
+		}
+		return h
+	}
+	switch role {
+	case providerbody.RoleRequest:
+		readRequest(h, head)
+	case providerbody.RoleResponse:
+		h.Reply = replyOf(head)
+	}
+	return h
+}
+
+func readRequest(h *providerbody.History, head map[string]any) {
+	if raw, ok := head["thread"]; ok {
+		t, ok := threadOf(raw)
+		if !ok {
+			h.Unavailable = providerbody.UnrecognisedShape
+			return
+		}
+		h.Thread = t
+	}
+	h.System = systemOf(head["system"])
+	list, ok := head["messages"].([]any)
+	if !ok {
+		h.Unavailable = providerbody.UnrecognisedShape
+		return
+	}
+	entries := make([]providerbody.Entry, 0, len(list))
+	for _, raw := range list {
+		e, ok := entryOf(raw)
+		if !ok {
+			h.Unavailable = providerbody.UnrecognisedShape
+			return
+		}
+		entries = append(entries, e)
+	}
+	h.Messages = entries
+}
+
+// threadOf reads a request's thread: one that creates a thread, or continues
+// one from a message id of a provider's size. Any other type says the request
+// holds something no rule here describes.
+func threadOf(raw any) (*providerbody.Thread, bool) {
+	t, ok := raw.(map[string]any)
+	if !ok {
+		return nil, false
+	}
+	switch typ, _ := t["type"].(string); typ {
+	case providerbody.ThreadCreate:
+		return &providerbody.Thread{Type: typ}, true
+	case providerbody.ThreadContinue:
+		prev, _ := t["previous_message_id"].(string)
+		if prev == "" || len(prev) > providerbody.MaxPrevious {
+			return nil, false
+		}
+		return &providerbody.Thread{Type: typ, Previous: prev}, true
+	}
+	return nil, false
+}
+
+// systemOf is the digest of the system prompt without its billing header, or
+// "" when nothing else is there. A list of blocks loses the text blocks that
+// start with the header and keeps the rest as sent, their cache markers too,
+// which do not move: one form each across every request measured. Any other
+// value is digested as it is.
+func systemOf(raw any) string {
+	value := raw
+	switch s := raw.(type) {
+	case nil:
+		return ""
+	case string:
+		if s == "" {
+			return ""
+		}
+	case []any:
+		var rest []any
+		for _, b := range s {
+			if block, ok := b.(map[string]any); ok && block["type"] == "text" {
+				if text, _ := block["text"].(string); strings.HasPrefix(text, billingHeader) {
+					continue
+				}
+			}
+			rest = append(rest, b)
+		}
+		if len(rest) == 0 {
+			return ""
+		}
+		value = rest
+	}
+	form, err := providerbody.Canonical(value)
+	if err != nil {
+		return ""
+	}
+	return providerbody.EntryOf("", false, form).Digest
+}
+
+// entryOf is one message's entry. Claude Code sends no message id.
+func entryOf(raw any) (providerbody.Entry, bool) {
+	msg, ok := raw.(map[string]any)
+	if !ok {
+		return providerbody.Entry{}, false
+	}
+	form, err := providerbody.Canonical(messageForm(msg))
+	if err != nil {
+		return providerbody.Entry{}, false
+	}
+	return providerbody.EntryOf("", false, form), true
+}
+
+// messageForm is a message as compared: a copy, with the cache marker left out
+// of the message and of each content block, and a lone text block read as its
+// string.
+func messageForm(msg map[string]any) map[string]any {
+	out := make(map[string]any, len(msg))
+	for k, v := range msg {
+		if k != "cache_control" {
+			out[k] = v
+		}
+	}
+	blocks, ok := out["content"].([]any)
+	if !ok {
+		return out
+	}
+	content := make([]any, len(blocks))
+	for i, b := range blocks {
+		block, ok := b.(map[string]any)
+		if !ok {
+			content[i] = b
+			continue
+		}
+		kept := make(map[string]any, len(block))
+		for k, v := range block {
+			if k != "cache_control" {
+				kept[k] = v
+			}
+		}
+		content[i] = kept
+	}
+	out["content"] = content
+	if len(content) == 1 {
+		if block, ok := content[0].(map[string]any); ok && len(block) == 2 && block["type"] == "text" {
+			if text, ok := block["text"].(string); ok {
+				out["content"] = text
+			}
+		}
+	}
+	return out
+}
+
+// replyOf is the message a response produced: its role and content, without
+// the envelope that belongs to the response. The response's id is never in a
+// message, so the reply carries none. A response with no stop reason did not
+// finish, and one that is not an assistant message is not a reply.
+func replyOf(head map[string]any) *providerbody.Reply {
+	content, ok := head["content"].([]any)
+	if !ok || head["role"] != "assistant" || head["type"] != "message" || head["stop_reason"] == nil {
+		return &providerbody.Reply{Unavailable: providerbody.NoSingleReply}
+	}
+	e, ok := entryOf(map[string]any{"role": "assistant", "content": content})
+	if !ok {
+		return &providerbody.Reply{Unavailable: providerbody.UnrecognisedShape}
+	}
+	return &providerbody.Reply{Digest: e.Digest}
+}
diff --git a/internal/adapters/claudecodeprovider/collect.go b/internal/adapters/claudecodeprovider/collect.go
index 4e5bb13..313ea06 100644
--- a/internal/adapters/claudecodeprovider/collect.go
+++ b/internal/adapters/claudecodeprovider/collect.go
@@ -608,7 +608,8 @@ func (c *Collector) collectSession(session string, files []*file, sn *seen, st *
 		return nil
 	}
 	for _, f := range files {
-		rec, err := held.Encode(providerbody.Body{ID: ID(f.name), Role: RoleOf(f.name), Src: f.name, Keys: f.keys, Bytes: f.body})
+		rec, err := held.Encode(providerbody.Body{ID: ID(f.name), Role: RoleOf(f.name), Src: f.name, Keys: f.keys, Bytes: f.body,
+			History: HistoryOf(RoleOf(f.name), f.body)})
 		switch {
 		case errors.Is(err, providerbody.ErrRepeat):
 			done = append(done, f)
diff --git a/internal/adapters/claudecodeprovider/history_test.go b/internal/adapters/claudecodeprovider/history_test.go
new file mode 100644
index 0000000..3ad2ed4
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/history_test.go
@@ -0,0 +1,145 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package claudecodeprovider_test
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/apache/skywalking-ai-sessionizer/internal/adapters/claudecodeprovider"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/providerbody"
+)
+
+func request(t *testing.T, body string) *providerbody.History {
+	t.Helper()
+	h := claudecodeprovider.HistoryOf(providerbody.RoleRequest, []byte(body))
+	if h.Form != providerbody.FormClaudeCode {
+		t.Fatalf("form %q", h.Form)
+	}
+	return h
+}
+
+// TestAMessageSentAgainUnchangedHasOneForm holds the two exceptions the form
+// makes, measured on Claude Code 2.1.260 and 2.1.302: the cache marker moves
+// to the newest message, and a message of one text block is sent again as its
+// string. Anything else in a message counts.
+func TestAMessageSentAgainUnchangedHasOneForm(t *testing.T) {
+	marked := request(t, `{"messages":[{"role":"user","content":[{"type":"text","text":"hi","cache_control":{"type":"ephemeral"}}],"cache_control":{"x":1}}]}`)
+	plain := request(t, `{"messages":[{"role":"user","content":"hi"}]}`)
+	if marked.Messages[0] != plain.Messages[0] {
+		t.Fatalf("a message with the marker and the same message collapsed differ: %v %v", marked.Messages, plain.Messages)
+	}
+	for name, body := range map[string]string{
+		"a citation":   `{"messages":[{"role":"user","content":[{"type":"text","text":"hi","citations":[]}]}]}`,
+		"two blocks":   `{"messages":[{"role":"user","content":[{"type":"text","text":"h"},{"type":"text","text":"i"}]}]}`,
+		"another role": `{"messages":[{"role":"system","content":"hi"}]}`,
+	} {
+		if got := request(t, body); got.Messages[0] == plain.Messages[0] {
+			t.Fatalf("%s reads as the plain message", name)
+		}
+	}
+	// The marker is left out only where it moves: at a message's depth and at
+	// its blocks'. One deeper, inside a tool result, is the result's own.
+	inner := request(t, `{"messages":[{"role":"user","content":[{"type":"tool_result","tool_use_id":"t","content":[{"type":"text","text":"hi","cache_control":{}}]}]}]}`)
+	bare := request(t, `{"messages":[{"role":"user","content":[{"type":"tool_result","tool_use_id":"t","content":[{"type":"text","text":"hi"}]}]}]}`)
+	if inner.Messages[0] == bare.Messages[0] {
+		t.Fatal("a marker inside a tool result was left out")
+	}
+	if m := request(t, `{"messages":[{"role":"user","content":"hi"}]}`).Messages[0]; m.ID != "" {
+		t.Fatalf("a Claude Code message has an id %q; Claude Code sends none", m.ID)
+	}
+}
+
+// TestARequestNamesItsThread. A request that starts a thread or carries none
+// holds its whole list; one that continues names the message it goes on
+// from. A thread no rule here describes leaves the list unavailable.
+func TestARequestNamesItsThread(t *testing.T) {
+	if h := request(t, `{"messages":[]}`); h.Thread != nil || h.Unavailable != "" || h.Messages == nil {
+		t.Fatalf("no thread: %+v", h)
+	}
+	if h := request(t, `{"thread":{"type":"create"},"messages":[]}`); h.Thread == nil || h.Thread.Type != providerbody.ThreadCreate {
+		t.Fatalf("create: %+v", h.Thread)
+	}
+	h := request(t, `{"thread":{"type":"continue","previous_message_id":"msg_1"},"messages":[{"role":"user","content":"x"}]}`)
+	if h.Thread == nil || h.Thread.Type != providerbody.ThreadContinue || h.Thread.Previous != "msg_1" || len(h.Messages) != 1 {
+		t.Fatalf("continue: %+v %+v", h.Thread, h.Messages)
+	}
+	long := strings.Repeat("m", providerbody.MaxPrevious+1)
+	for name, body := range map[string]string{
+		"another type":       `{"thread":{"type":"fork"},"messages":[]}`,
+		"no message id":      `{"thread":{"type":"continue"},"messages":[]}`,
+		"a long message id":  `{"thread":{"type":"continue","previous_message_id":"` + long + `"},"messages":[]}`,
+		"not an object":      `{"thread":"create","messages":[]}`,
+		"no messages":        `{"thread":{"type":"create"}}`,
+		"a message string":   `{"messages":["hi"]}`,
+		"not an object body": `[]`,
+		"not json":           `{`,
+	} {
+		if h := request(t, body); h.Unavailable != providerbody.UnrecognisedShape || h.Messages != nil {
+			t.Fatalf("%s: %+v", name, h)
+		}
+	}
+}
+
+// TestTheSystemPromptLeavesOutItsBillingHeader. The header differs on every
+// request, measured on 2.1.302: it holds the request before, the prompt and
+// the turn. Everything else in the system prompt counts, its markers too.
+func TestTheSystemPromptLeavesOutItsBillingHeader(t *testing.T) {
+	a := request(t, `{"system":[{"type":"text","text":"x-anthropic-billing-header: cc_prev_req=a;"},{"type":"text","text":"You are"}],"messages":[]}`)
+	b := request(t, `{"system":[{"type":"text","text":"x-anthropic-billing-header: cc_prev_req=b;"},{"type":"text","text":"You are"}],"messages":[]}`)
+	if a.System == "" || a.System != b.System {
+		t.Fatalf("two system prompts that differ only in the header: %q %q", a.System, b.System)
+	}
+	if c := request(t, `{"system":[{"type":"text","text":"You are","cache_control":{"type":"ephemeral"}}],"messages":[]}`); c.System == a.System {
+		t.Fatal("a marker on a system block was left out")
+	}
+	if h := request(t, `{"system":[{"type":"text","text":"x-anthropic-billing-header: x;"}],"messages":[]}`); h.System != "" {
+		t.Fatalf("a system prompt of only the header has a digest %q", h.System)
+	}
+	// A system prompt written as one string is digested as that string, and
+	// only a text block is a billing header.
+	form, _ := providerbody.Canonical("hi")
+	if h := request(t, `{"system":"hi","messages":[]}`); h.System != providerbody.EntryOf("", false, form).Digest {
+		t.Fatalf("a string system prompt has the digest %q", h.System)
+	}
+	if h := request(t, `{"system":[{"type":"other","text":"x-anthropic-billing-header: x;"}],"messages":[]}`); h.System == "" {
+		t.Fatal("a block that is not text was taken for the billing header")
+	}
+}
+
+// TestTheReplyIsTheMessageAResponseProduced. Its form is a request message's
+// form, so a reply compares with the message sent back; the response's id is
+// never in a message, so the reply has none.
+func TestTheReplyIsTheMessageAResponseProduced(t *testing.T) {
+	resp := claudecodeprovider.HistoryOf(providerbody.RoleResponse,
+		[]byte(`{"id":"msg_1","type":"message","role":"assistant","model":"m","content":[{"type":"text","text":"hi"}],"stop_reason":"end_turn","usage":{}}`))
+	sent := request(t, `{"messages":[{"role":"assistant","content":"hi"}]}`)
+	if resp.Reply == nil || resp.Reply.Unavailable != "" || resp.Reply.ID != "" || resp.Reply.Digest != sent.Messages[0].Digest {
+		t.Fatalf("reply %+v, sent %+v", resp.Reply, sent.Messages[0])
+	}
+	for name, body := range map[string]string{
+		"unfinished": `{"type":"message","role":"assistant","content":[],"stop_reason":null}`,
+		"an error":   `{"type":"error","error":{"type":"overloaded_error"}}`,
+		"no content": `{"type":"message","role":"assistant","stop_reason":"end_turn"}`,
+		"not json":   `{`,
+	} {
+		if h := claudecodeprovider.HistoryOf(providerbody.RoleResponse, []byte(body)); h.Reply == nil || h.Reply.Unavailable == "" {
+			t.Fatalf("%s: reply %+v", name, h.Reply)
+		}
+	}
+}
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/README.md b/internal/adapters/claudecodeprovider/testdata/threads/README.md
new file mode 100644
index 0000000..88514a9
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/README.md
@@ -0,0 +1,28 @@
+# A Claude Code 2.1.302 session that continues provider threads
+
+One real session of `claude -p` 2.1.302 (entrypoint `sdk-cli`, model `claude-opus-5-5`), captured on
+2026-10-18 with `OTEL_LOG_RAW_API_BODIES` set for the process only. Nine turns, each a new process
+resuming the session: a long tool output, a subagent, the same message sent twice, a file read, a
+`/compact`, and a question after it.
+
+- `bodies/` holds the 28 bodies Claude Code wrote and its `index.jsonl`.
+- `projects/-work/` holds the session's main transcript and its subagent's.
+- `mtimes.json` holds each body's file time in nanoseconds. The provider collector lands bodies in the
+  order of their file times and looks for a response's session among requests near its time, and git
+  keeps no file times, so a test sets them from this file.
+
+Every free-text string was replaced by `r:` and the first sixteen hexadecimal characters of its
+SHA-256, so equal strings stay equal and an empty string stays empty. What an adapter reads for
+structure was kept: every id, link, type, role, time, flag and tool id; the names of built-in tools
+where a call or a definition names them; `metadata.user_id` as a JSON string holding `session_id`,
+with the device and account ids replaced by made-up values; and the billing header's `key=value;`
+form with `cc_prompt_id`, `cc_prev_req`, `cc_prompt_index` and `cc_turn_index` as they were. Paths,
+the person's email, organization, connected servers and every instruction were replaced.
+
+asz at commit f6e3274 builds the same conversation from this copy as from the capture it was made
+from: the same 126 steps with the same states, flags and dropped parts, the same bodies joined to
+the same calls, and the same summary.
+
+What it shows: a process's first request starts a provider thread and holds the whole message list;
+every later request of the process continues the thread from the previous response's message id and
+holds only the new messages. See [Claude Code](../../../../../docs/en/adapters/claude-code.md).
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/052985c8-ab1d-45a0-b1d3-6ff27d7fcb75.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/052985c8-ab1d-45a0-b1d3-6ff27d7fcb75.request.json
new file mode 100644
index 0000000..ca26094
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/052985c8-ab1d-45a0-b1d3-6ff27d7fcb75.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"tool_use_id":"toolu_01CQyLzCENmqZ4cjxe5Q3qg1","type":"tool_result","content":"r:098303ba35ba893e"}]},{"role":"system","content":[{"type":"text","text":"r:b8041a8ee9e3ba33"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties":{"batch":{"type":"array"},"container":{"type":"object","properties":"r:da [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/1b15d0c2-d2bf-4351-a9b4-751861b82475.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/1b15d0c2-d2bf-4351-a9b4-751861b82475.request.json
new file mode 100644
index 0000000..94eeb55
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/1b15d0c2-d2bf-4351-a9b4-751861b82475.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:90351e4350ee90cc"}]},{"role":"system","content":[{"type":"text","text":"r:da3dd30514eba47d"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/3d8db1cd-f3bc-4432-83b8-bc9c70b90439.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/3d8db1cd-f3bc-4432-83b8-bc9c70b90439.request.json
new file mode 100644
index 0000000..e62d6b3
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/3d8db1cd-f3bc-4432-83b8-bc9c70b90439.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]},{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/40b3a490-585a-41bc-95e7-3c760ffea640.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/40b3a490-585a-41bc-95e7-3c760ffea640.request.json
new file mode 100644
index 0000000..4cce4a1
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/40b3a490-585a-41bc-95e7-3c760ffea640.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]},{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/557b306f-d4f9-491b-9d7d-e116888f970f.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/557b306f-d4f9-491b-9d7d-e116888f970f.request.json
new file mode 100644
index 0000000..6d68c6a
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/557b306f-d4f9-491b-9d7d-e116888f970f.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]},{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/5a51b729-b0bc-4985-8ec7-7472c2f3d10d.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/5a51b729-b0bc-4985-8ec7-7472c2f3d10d.request.json
new file mode 100644
index 0000000..eaaea4a
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/5a51b729-b0bc-4985-8ec7-7472c2f3d10d.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":"r:b18f2c122c3ea1fd"},{"role":"system","content":[],"output_config":{"effort":"xhigh"}},{"role":"assistant","content":[{"type":"text","text":"r:565339bc4d33d728"}]},{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:19cf21a5f4433d7b"},{"type":"text","text":"r:2cb99709ec25cef4"},{"type":"text","t [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/60ba5a23-5af4-4dfa-8dad-10766a9c90b3.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/60ba5a23-5af4-4dfa-8dad-10766a9c90b3.request.json
new file mode 100644
index 0000000..aa0328b
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/60ba5a23-5af4-4dfa-8dad-10766a9c90b3.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]},{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/63315d98-46c0-407b-a988-d07e51c4ff72.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/63315d98-46c0-407b-a988-d07e51c4ff72.request.json
new file mode 100644
index 0000000..56e74d3
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/63315d98-46c0-407b-a988-d07e51c4ff72.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]},{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/64760102-b036-475b-b7a7-1d07f8c7b94c.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/64760102-b036-475b-b7a7-1d07f8c7b94c.request.json
new file mode 100644
index 0000000..0f658a3
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/64760102-b036-475b-b7a7-1d07f8c7b94c.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":"r:b18f2c122c3ea1fd"},{"role":"system","content":[],"output_config":{"effort":"xhigh"}},{"role":"assistant","content":[{"type":"text","text":"r:565339bc4d33d728"}]},{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:19cf21a5f4433d7b"},{"type":"text","text":"r:2cb99709ec25cef4"},{"type":"text","t [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/72e356f3-7149-440d-a150-6f5df8f28189.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/72e356f3-7149-440d-a150-6f5df8f28189.request.json
new file mode 100644
index 0000000..0d2e380
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/72e356f3-7149-440d-a150-6f5df8f28189.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"tool_use_id":"toolu_01DvNo1kzGJ7yP9mRYoscRCz","type":"tool_result","content":[{"type":"text","text":"r:da99c1c65d1442f0"}]}]},{"role":"system","content":[{"type":"text","text":"r:f83d65cd838eddc6","cache_control":{"type":"ephemeral","ttl":"1h"}}]}],"system":[{"type":"text","text":"x-anthropic-billing-header: cc_version=r:fe59492fed32cfe9; cc_entrypoint=r:3833805edd07ce5a; cch=r:e7042ac7d09c7bc4; cc_prev_req=req_011Cg91oYF [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/a0175026-faac-45c1-abc0-e4ce7b103510.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/a0175026-faac-45c1-abc0-e4ce7b103510.request.json
new file mode 100644
index 0000000..bd23041
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/a0175026-faac-45c1-abc0-e4ce7b103510.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"tool_use_id":"toolu_01PAWzeCTwBMS5a3dqeypTdU","type":"tool_result","content":"r:622e1bde356c21ea","is_error":false}]},{"role":"system","content":[{"type":"text","text":"r:a51d7bf7d4776229","cache_control":{"type":"ephemeral","ttl":"1h"}}]}],"system":[{"type":"text","text":"x-anthropic-billing-header: cc_version=r:fe59492fed32cfe9; cc_entrypoint=r:3833805edd07ce5a; cch=r:e7042ac7d09c7bc4; cc_prev_req=req_011Cg91hS9pwQhArnA [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/aa8f8672-bbfc-4f66-8ff5-e60248e8d74e.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/aa8f8672-bbfc-4f66-8ff5-e60248e8d74e.request.json
new file mode 100644
index 0000000..710d6a0
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/aa8f8672-bbfc-4f66-8ff5-e60248e8d74e.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]},{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/cc00d81e-2378-4bc0-9981-6b3a2fdfa177.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/cc00d81e-2378-4bc0-9981-6b3a2fdfa177.request.json
new file mode 100644
index 0000000..c0154e2
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/cc00d81e-2378-4bc0-9981-6b3a2fdfa177.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]},{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/dea9ce9b-31ae-4ff1-9e30-29223cd5b2cf.request.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/dea9ce9b-31ae-4ff1-9e30-29223cd5b2cf.request.json
new file mode 100644
index 0000000..4492387
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/dea9ce9b-31ae-4ff1-9e30-29223cd5b2cf.request.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","messages":[{"role":"user","content":[{"tool_use_id":"toolu_01MUg45c5qvfHpTJNswh8SVi","type":"tool_result","content":"r:e39538e7f27a7bf5","is_error":false}]},{"role":"system","content":[{"type":"text","text":"r:8b4cbfa16898b5e0","cache_control":{"type":"ephemeral"}}]}],"system":[{"type":"text","text":"x-anthropic-billing-header: cc_version=r:68e394db57fed5f5; cc_entrypoint=r:3833805edd07ce5a; cch=r:e7042ac7d09c7bc4; cc_is_subagent=r:b5bea41b6c623f7c; cc_prev_re [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/index.jsonl b/internal/adapters/claudecodeprovider/testdata/threads/bodies/index.jsonl
new file mode 100644
index 0000000..5d7a5f1
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/index.jsonl
@@ -0,0 +1,14 @@
+{"timestamp":"2026-10-18T03:43:52.057Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91hS9pwQhArnANxwDCC","message_id":"msg_011Cg91hUMHi9aaAdpm4nt9D","message_uuid":"0867349a-e293-454b-b31c-1684219acbeb","request_file":"3d8db1cd-f3bc-4432-83b8-bc9c70b90439.request.json","response_file":"req_011Cg91hS9pwQhArnANxwDCC.response.json"}
+{"timestamp":"2026-10-18T03:43:56.984Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91hrQqKed3sUjCTPVnm","message_id":"msg_011Cg91hrgDUjch3FayeGVRD","message_uuid":"5a0ee2f4-50b5-4cbf-abe3-414275a29496","request_file":"a0175026-faac-45c1-abc0-e4ce7b103510.request.json","response_file":"req_011Cg91hrQqKed3sUjCTPVnm.response.json"}
+{"timestamp":"2026-10-18T03:44:58.879Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91nX1Qzwp6avWtT2hTz","message_id":"msg_011Cg91nYsYCAyAqc1gtKtps","message_uuid":"c5c5f934-5112-4e0d-9dc0-7b5127285cbc","request_file":"557b306f-d4f9-491b-9d7d-e116888f970f.request.json","response_file":"req_011Cg91nX1Qzwp6avWtT2hTz.response.json"}
+{"timestamp":"2026-10-18T03:45:14.372Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91oYFrF3HCoUscT76Va","message_id":"msg_011Cg91ob2ntzSmSJyh5xZ5W","message_uuid":"b1f41701-2633-4e57-8b9b-5d3bf1043296","request_file":"60ba5a23-5af4-4dfa-8dad-10766a9c90b3.request.json","response_file":"req_011Cg91oYFrF3HCoUscT76Va.response.json"}
+{"timestamp":"2026-10-18T03:45:19.415Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"agent:builtin:general-purpose","model":"claude-opus-5-5","request_id":"req_011Cg91p19cxAxXDddBxCsom","message_id":"msg_011Cg91p23SZrfw1iEn8WXJR","message_uuid":"cb0032bd-8822-4feb-83f1-72eef8beb12a","request_file":"1b15d0c2-d2bf-4351-a9b4-751861b82475.request.json","response_file":"req_011Cg91p19cxAxXDddBxCsom.response.json"}
+{"timestamp":"2026-10-18T03:45:21.735Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"agent:builtin:general-purpose","model":"claude-opus-5-5","request_id":"req_011Cg91pJCYbAQfh8DS8WQzh","message_id":"msg_011Cg91pJSvmdsw719ac8Cfw","message_uuid":"62c75ff1-a03b-4167-b5d3-72f79105f772","request_file":"dea9ce9b-31ae-4ff1-9e30-29223cd5b2cf.request.json","response_file":"req_011Cg91pJCYbAQfh8DS8WQzh.response.json"}
+{"timestamp":"2026-10-18T03:45:26.275Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91pTBXWszWaMn2dptYf","message_id":"msg_011Cg91pTSfA3MdBMui5WD3z","message_uuid":"49c48ceb-d88c-486b-a4bb-181e8c6c2af8","request_file":"72e356f3-7149-440d-a150-6f5df8f28189.request.json","response_file":"req_011Cg91pTBXWszWaMn2dptYf.response.json"}
+{"timestamp":"2026-10-18T03:45:40.100Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91qVx1BFWosh783Gfuh","message_id":"msg_011Cg91qXfwciRBF7iZgo75v","message_uuid":"3e7ac274-d4a7-4ac7-bd29-55f09a7a953a","request_file":"cc00d81e-2378-4bc0-9981-6b3a2fdfa177.request.json","response_file":"req_011Cg91qVx1BFWosh783Gfuh.response.json"}
+{"timestamp":"2026-10-18T03:46:04.780Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91rsmh9PMmydkaP9YMf","message_id":"msg_011Cg91ruJDq5bkhZSKud9UE","message_uuid":"f5580b96-961d-4d91-a466-774bfa0dd81f","request_file":"aa8f8672-bbfc-4f66-8ff5-e60248e8d74e.request.json","response_file":"req_011Cg91rsmh9PMmydkaP9YMf.response.json"}
+{"timestamp":"2026-10-18T03:46:08.651Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91sdAdqooUrshuiekNe","message_id":"msg_011Cg91sdTkoXhyd8g81wXhR","message_uuid":"4d0202f8-06fd-495b-a12b-2023316bdb2c","request_file":"052985c8-ab1d-45a0-b1d3-6ff27d7fcb75.request.json","response_file":"req_011Cg91sdAdqooUrshuiekNe.response.json"}
+{"timestamp":"2026-10-18T03:46:16.428Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91tEAKSQ1emozbt8Ftx","message_id":"msg_011Cg91tFdNFxcUGk1BhhKFD","message_uuid":"c91273ac-bc84-406a-ba9f-a07354a34259","request_file":"63315d98-46c0-407b-a988-d07e51c4ff72.request.json","response_file":"req_011Cg91tEAKSQ1emozbt8Ftx.response.json"}
+{"timestamp":"2026-10-18T03:46:43.067Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"compact","model":"claude-opus-5-5","request_id":"req_011Cg91u8N6ELFcKERTr7o58","message_id":"msg_011Cg91uARrVc2bRVnR6WCnh","message_uuid":"3fa6a7f2-ee40-48fc-87ba-a1fc4075057c","request_file":"40b3a490-585a-41bc-95e7-3c760ffea640.request.json","response_file":"req_011Cg91u8N6ELFcKERTr7o58.response.json"}
+{"timestamp":"2026-10-18T03:47:03.100Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91w3ctwRBdcWkyTV2eV","message_id":"msg_011Cg91w5MKiSux4wsgF96oG","message_uuid":"9de0c79e-57e9-4e82-967e-3565a4511b68","request_file":"64760102-b036-475b-b7a7-1d07f8c7b94c.request.json","response_file":"req_011Cg91w3ctwRBdcWkyTV2eV.response.json"}
+{"timestamp":"2026-10-18T03:47:18.976Z","session_id":"2bb28d1c-72a7-4260-a010-f439329e4e76","query_source":"sdk","model":"claude-opus-5-5","request_id":"req_011Cg91xnUsQzMVn15EmxE4p","message_id":"msg_011Cg91xoh3WUsMX2DtgDatX","message_uuid":"1dcc2530-2383-437e-921f-382f71975059","request_file":"5a51b729-b0bc-4985-8ec7-7472c2f3d10d.request.json","response_file":"req_011Cg91xnUsQzMVn15EmxE4p.response.json"}
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91hS9pwQhArnANxwDCC.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91hS9pwQhArnANxwDCC.response.json
new file mode 100644
index 0000000..810f5e7
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91hS9pwQhArnANxwDCC.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91hUMHi9aaAdpm4nt9D","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01PAWzeCTwBMS5a3dqeypTdU","name":"Bash","input":{"command":"r:75209bf98031b5a4","description":"r:9c927a17790fdf91"},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":21131,"cache_read_input_tokens":10834,"output_tokens":85,"output_to [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91hrQqKed3sUjCTPVnm.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91hrQqKed3sUjCTPVnm.response.json
new file mode 100644
index 0000000..f878029
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91hrQqKed3sUjCTPVnm.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91hrgDUjch3FayeGVRD","type":"message","role":"assistant","content":[{"type":"text","text":"r:a176eeb31e601c38"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":9194,"cache_read_input_tokens":31965,"output_tokens":4,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"r:fe6d3468cf5c [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91nX1Qzwp6avWtT2hTz.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91nX1Qzwp6avWtT2hTz.response.json
new file mode 100644
index 0000000..0d34556
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91nX1Qzwp6avWtT2hTz.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91nYsYCAyAqc1gtKtps","type":"message","role":"assistant","content":[{"type":"text","text":"r:565339bc4d33d728"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1271,"cache_read_input_tokens":41159,"output_tokens":4,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"r:fe6d3468cf5c [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91oYFrF3HCoUscT76Va.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91oYFrF3HCoUscT76Va.response.json
new file mode 100644
index 0000000..204986f
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91oYFrF3HCoUscT76Va.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91ob2ntzSmSJyh5xZ5W","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01DvNo1kzGJ7yP9mRYoscRCz","name":"Agent","input":{"description":"r:ba030c42177005fb","prompt":"r:90351e4350ee90cc","subagent_type":"r:db45ca5bb40691a7","run_in_background":false},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":2118 [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91p19cxAxXDddBxCsom.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91p19cxAxXDddBxCsom.response.json
new file mode 100644
index 0000000..b46833a
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91p19cxAxXDddBxCsom.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91p23SZrfw1iEn8WXJR","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01MUg45c5qvfHpTJNswh8SVi","name":"Bash","input":{"command":"r:c7b68ac37f364473","description":"r:04dde8330395d997"},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":25222,"cache_read_input_tokens":0,"output_tokens":74,"output_tokens [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91pJCYbAQfh8DS8WQzh.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91pJCYbAQfh8DS8WQzh.response.json
new file mode 100644
index 0000000..68bf7a1
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91pJCYbAQfh8DS8WQzh.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91pJSvmdsw719ac8Cfw","type":"message","role":"assistant","content":[{"type":"text","text":"r:6fb187f0e6bfcd8f"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1943,"cache_read_input_tokens":25222,"output_tokens":22,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"r:fe6d3468cf5 [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91pTBXWszWaMn2dptYf.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91pTBXWszWaMn2dptYf.response.json
new file mode 100644
index 0000000..da29e4f
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91pTBXWszWaMn2dptYf.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91pTSfA3MdBMui5WD3z","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"r:1d31bdafd1e990ec","signature":"r:b6791dd02daed547"},{"type":"text","text":"r:1caffe1a404c17b6"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1062,"cache_read_input_tokens":43293,"output_tokens":63,"output_tokens_details":{"thinking_tokens":29},"server_to [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91qVx1BFWosh783Gfuh.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91qVx1BFWosh783Gfuh.response.json
new file mode 100644
index 0000000..804c07f
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91qVx1BFWosh783Gfuh.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91qXfwciRBF7iZgo75v","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"r:1d31bdafd1e990ec","signature":"r:3c3277ab79e65c02"},{"type":"text","text":"r:f2dd2ca50f445c25"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1322,"cache_read_input_tokens":44355,"output_tokens":106,"output_tokens_details":{"thinking_tokens":55},"server_t [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91rsmh9PMmydkaP9YMf.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91rsmh9PMmydkaP9YMf.response.json
new file mode 100644
index 0000000..461825d
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91rsmh9PMmydkaP9YMf.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91ruJDq5bkhZSKud9UE","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01CQyLzCENmqZ4cjxe5Q3qg1","name":"Read","input":{"file_path":"r:5275f68e423224d4"},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1230,"cache_read_input_tokens":45677,"output_tokens":130,"output_tokens_details":{"thinking_tokens": [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91sdAdqooUrshuiekNe.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91sdAdqooUrshuiekNe.response.json
new file mode 100644
index 0000000..8de6385
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91sdAdqooUrshuiekNe.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91sdTkoXhyd8g81wXhR","type":"message","role":"assistant","content":[{"type":"text","text":"r:bbe7430b2e0edc19"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":2499,"cache_read_input_tokens":46907,"output_tokens":65,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"r:fe6d3468cf5 [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91tEAKSQ1emozbt8Ftx.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91tEAKSQ1emozbt8Ftx.response.json
new file mode 100644
index 0000000..880f2b2
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91tEAKSQ1emozbt8Ftx.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91tFdNFxcUGk1BhhKFD","type":"message","role":"assistant","content":[{"type":"text","text":"r:565339bc4d33d728"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":742,"cache_read_input_tokens":49406,"output_tokens":4,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"r:fe6d3468cf5c7 [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91u8N6ELFcKERTr7o58.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91u8N6ELFcKERTr7o58.response.json
new file mode 100644
index 0000000..ef29b73
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91u8N6ELFcKERTr7o58.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91uARrVc2bRVnR6WCnh","type":"message","role":"assistant","content":[{"type":"text","text":"r:36c8d306e99fd79e"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2672,"cache_creation_input_tokens":65,"cache_read_input_tokens":49406,"output_tokens":1780,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"r:fe6d3468 [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91w3ctwRBdcWkyTV2eV.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91w3ctwRBdcWkyTV2eV.response.json
new file mode 100644
index 0000000..2d2ae04
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91w3ctwRBdcWkyTV2eV.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91w5MKiSux4wsgF96oG","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"r:1d31bdafd1e990ec","signature":"r:d854bc1d4535ee29"},{"type":"text","text":"r:ab25049f4ad17472"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":21473,"cache_read_input_tokens":12686,"output_tokens":44,"output_tokens_details":{"thinking_tokens":27},"server_t [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91xnUsQzMVn15EmxE4p.response.json b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91xnUsQzMVn15EmxE4p.response.json
new file mode 100644
index 0000000..c0265c4
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/bodies/req_011Cg91xnUsQzMVn15EmxE4p.response.json
@@ -0,0 +1 @@
+{"model":"claude-opus-5-5","id":"msg_011Cg91xoh3WUsMX2DtgDatX","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"r:1d31bdafd1e990ec","signature":"r:14d61fdcfe3ccf61"},{"type":"text","text":"r:3e670ac609d3a727"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":73,"cache_read_input_tokens":34159,"output_tokens":67,"output_tokens_details":{"thinking_tokens":17},"server_tool [...]
\ No newline at end of file
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/mtimes.json b/internal/adapters/claudecodeprovider/testdata/threads/mtimes.json
new file mode 100644
index 0000000..7a2011f
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/mtimes.json
@@ -0,0 +1,31 @@
+{
+ "052985c8-ab1d-45a0-b1d3-6ff27d7fcb75.request.json": 1792295164798922516,
+ "1b15d0c2-d2bf-4351-a9b4-751861b82475.request.json": 1792295114286506087,
+ "3d8db1cd-f3bc-4432-83b8-bc9c70b90439.request.json": 1792295026154817365,
+ "40b3a490-585a-41bc-95e7-3c760ffea640.request.json": 1792295180631778758,
+ "557b306f-d4f9-491b-9d7d-e116888f970f.request.json": 1792295094791343193,
+ "5a51b729-b0bc-4985-8ec7-7472c2f3d10d.request.json": 1792295229211423646,
+ "60ba5a23-5af4-4dfa-8dad-10766a9c90b3.request.json": 1792295104814051222,
+ "63315d98-46c0-407b-a988-d07e51c4ff72.request.json": 1792295172428579465,
+ "64760102-b036-475b-b7a7-1d07f8c7b94c.request.json": 1792295206835860311,
+ "72e356f3-7149-440d-a150-6f5df8f28189.request.json": 1792295121769073632,
+ "a0175026-faac-45c1-abc0-e4ce7b103510.request.json": 1792295032220276015,
+ "aa8f8672-bbfc-4f66-8ff5-e60248e8d74e.request.json": 1792295151633773956,
+ "cc00d81e-2378-4bc0-9981-6b3a2fdfa177.request.json": 1792295131142426584,
+ "dea9ce9b-31ae-4ff1-9e30-29223cd5b2cf.request.json": 1792295119662049575,
+ "index.jsonl": 1792295238986879605,
+ "req_011Cg91hS9pwQhArnANxwDCC.response.json": 1792295032058530176,
+ "req_011Cg91hrQqKed3sUjCTPVnm.response.json": 1792295036997220846,
+ "req_011Cg91nX1Qzwp6avWtT2hTz.response.json": 1792295098894989890,
+ "req_011Cg91oYFrF3HCoUscT76Va.response.json": 1792295114374018326,
+ "req_011Cg91p19cxAxXDddBxCsom.response.json": 1792295119417587183,
+ "req_011Cg91pJCYbAQfh8DS8WQzh.response.json": 1792295121768924923,
+ "req_011Cg91pTBXWszWaMn2dptYf.response.json": 1792295126289528521,
+ "req_011Cg91qVx1BFWosh783Gfuh.response.json": 1792295140110312933,
+ "req_011Cg91rsmh9PMmydkaP9YMf.response.json": 1792295164782404947,
+ "req_011Cg91sdAdqooUrshuiekNe.response.json": 1792295168660907457,
+ "req_011Cg91tEAKSQ1emozbt8Ftx.response.json": 1792295176442149953,
+ "req_011Cg91u8N6ELFcKERTr7o58.response.json": 1792295203071963278,
+ "req_011Cg91w3ctwRBdcWkyTV2eV.response.json": 1792295223104459116,
+ "req_011Cg91xnUsQzMVn15EmxE4p.response.json": 1792295238986936230
+}
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76.jsonl b/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76.jsonl
new file mode 100644
index 0000000..cf2c2ef
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76.jsonl
@@ -0,0 +1,161 @@
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:43:44.103Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:d5be80cfb0da37b6"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:43:44.104Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":null,"isSidechain":false,"promptId":"3548f798-7acd-4647-a727-56cacd4bfc1b","type":"user","message":{"role":"user","content":"r:d5be80cfb0da37b6"},"uuid":"0c2ecb5b-0a4d-4469-bc1d-02705900056f","timestamp":"2026-10-18T03:43:46.123Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":1},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version": [...]
+{"parentUuid":"0c2ecb5b-0a4d-4469-bc1d-02705900056f","isSidechain":false,"attachment":{"type":"environment","snapshot":{"workingDirectory":"r:b5231b0539a2a70b","isWorktree":false,"isGitRepo":false,"additionalWorkingDirectories":["r:e9671acd244849c5","r:938b99e3330802a9","r:b2f48a3ce07c1d86","r:a5bfa300f0fd2e37","r:097c1e9fdaeb00fd","r:bb64303334efb9fa"],"platform":"r:26ce1a1580f69387","shell":"r:37d2b12d5d9abc2a","osVersion":"r:0c4e80dfb486a406"}},"type":"attachment","uuid":"3af0bdfd-692 [...]
+{"parentUuid":"3af0bdfd-692f-4561-a56d-469da23636dd","isSidechain":false,"attachment":{"type":"model","identity":{"modelId":"r:005912bee6ee4543","marketingName":"r:18966c1a2825e090","knowledgeCutoff":"r:ee00ffb56d50d0d4"},"text":"r:12e0371339493b0e"},"type":"attachment","uuid":"73f76aaa-8c31-4d01-824d-aa13ca3fead3","timestamp":"2026-10-18T03:43:46.122Z","rendered":[{"content":"r:9e62167a3f1f22a7"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2 [...]
+{"parentUuid":"73f76aaa-8c31-4d01-824d-aa13ca3fead3","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":["r:d3b1f39f782677cb","r:997622e5bd498fc8","r:17630c4b25f34cb8","r:ae68c6d579eb9db9","r:e18439493af2b9ea","r:fd2879d0b5009ce9","r:821e582030bb0428","r:4c2e1df4570bebd6","r:e84a697b3c982b1b","r:9bfbb6062b487900","r:a38381ce1013153a","r:48e8ec8b725027b4","r:4fd25d5ab5e16fe3","r:7bd75b80a49c80b9","r:72f859513e8da7b8","r:b09c7a9dcdafec47","r:7b167b971cb91929","r:e [...]
+{"parentUuid":"1d305e56-4bd9-423c-b41d-04f245b571e9","isSidechain":false,"attachment":{"type":"agent_listing_delta","addedTypes":["r:c857d09db23e6822","r:3b73900b8d29f963","r:db45ca5bb40691a7","r:fa8ed0bdabdd6bcb","r:7150d1ed23874efb"],"addedLines":["r:e6e122752bd144ad","r:535d81bfe2a6d10b","r:bd0eb8354b8ecd93","r:a19eeace5ca7decf","r:668c3c654c6cf099"],"builtInTypes":["r:c857d09db23e6822","r:3b73900b8d29f963","r:db45ca5bb40691a7","r:fa8ed0bdabdd6bcb","r:7150d1ed23874efb"],"removedTypes" [...]
+{"parentUuid":"b609861f-08b2-4aad-969a-e556ad8a6461","isSidechain":false,"attachment":{"type":"mcp_instructions_delta","addedNames":["r:380331632fbf1877"],"addedBlocks":["r:8ed75c59eceec373"],"removedNames":[]},"type":"attachment","uuid":"eb52eed1-3813-44e9-bfbd-2be057edfa04","timestamp":"2026-10-18T03:43:46.122Z","rendered":[{"content":"r:906d9926e516c792"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a0 [...]
+{"parentUuid":"eb52eed1-3813-44e9-bfbd-2be057edfa04","isSidechain":false,"attachment":{"type":"skill_listing","content":"r:6735451d0f8dcbaa","skillCount":58,"isInitial":true,"names":["r:7d9ebd5481a5f20e","r:f521dccd06b54b4c","r:8b4f24b5ebbc03f5","r:ce7b8e9a84a4c086","r:8cdd6473d4f6a98c","r:0145c3bd856fbf79","r:f2b66271725cebba","r:83d1fcc1d3d56caa","r:243fe6d24ecb7192","r:372688e7473c85de","r:1b54043b9ea206b5","r:8bbf89b79f35e6ba","r:0172d017b3e69fb8","r:02521988e5dd239c","r:05fe25e1fdf8 [...]
+{"parentUuid":"52a21d04-6444-4c89-a47a-786f3628640c","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"75b5418f-d949-4ad8-90cb-fa9b05a1a967","timestamp":"2026-10-18T03:43:46.122Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"75b5418f-d949-4ad8-90cb-fa9b05a1a967","isSidechain":false,"attachment":{"type":"instructions","files":[{"path":"r:337d2ae0439ad56a","type":"User","content":"r:871366e443fb1dec"}]},"type":"attachment","uuid":"ce3068b7-8123-4c2b-b469-33daca4b3635","timestamp":"2026-10-18T03:43:46.135Z","rendered":[{"content":"r:d65a7e18cbf0a67f"}],"renderedRole":"user","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"," [...]
+{"parentUuid":"ce3068b7-8123-4c2b-b469-33daca4b3635","isSidechain":false,"attachment":{"type":"session_context","context":{"userEmail":"r:499cdb06796ce61e"}},"type":"attachment","uuid":"cd858bd1-a2e6-4c6a-bc1b-ea892c593ac0","timestamp":"2026-10-18T03:43:46.135Z","rendered":[{"content":"r:a48ca1c9bdb26f25"}],"renderedRole":"user","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b51 [...]
+{"parentUuid":"cd858bd1-a2e6-4c6a-bc1b-ea892c593ac0","isSidechain":false,"attachment":{"type":"date","date":"r:aa66931ec9add42a"},"type":"attachment","uuid":"c8a8cd10-68b8-41a9-aae3-7cbecc6a972d","timestamp":"2026-10-18T03:43:46.135Z","rendered":[{"content":"r:01bbdac397744700"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"parentUuid":"c8a8cd10-68b8-41a9-aae3-7cbecc6a972d","isSidechain":false,"attachment":{"type":"credential_org","organizationUuid":"r:c041682af74b618e"},"type":"attachment","uuid":"3e9be1c4-5a6d-4f3d-913b-01b601ae3c4f","timestamp":"2026-10-18T03:43:46.135Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"parentUuid":"3e9be1c4-5a6d-4f3d-913b-01b601ae3c4f","isSidechain":false,"attachment":{"type":"remote_session_change","url":null,"commit":"r:6073d8d45b78c96f","pr":"r:48a19a429e37d2ac","sendUserFileHint":false,"managedCommit":false,"managedPr":false},"type":"attachment","uuid":"01eda05f-4fe2-4ed3-bf29-09decdec0f4c","timestamp":"2026-10-18T03:43:46.136Z","rendered":[{"content":"r:c7c420407202d49d"}],"renderedRole":"user","renderedBesideToolResult":true,"userType":"external","entrypoint":" [...]
+{"parentUuid":"01eda05f-4fe2-4ed3-bf29-09decdec0f4c","isSidechain":false,"attachment":{"type":"prompt_snapshot","systemPrompt":["r:469950f24f4587cb","r:26490c16e8c5f06e","r:ee43af37398581e9","r:3fcd2b200896a716","r:9724415357935973","r:0937acefc6d6d7d1","r:355b8bd7bf094a96","r:c34f6aeb8af321c8","r:9856a95edb9c2bdb","r:4e4e48fb23ceb764","r:2b7db5539346e950","r:41631e7eb968f40d"],"reminderFold":false,"echoWireToolInputs":true,"contextRendering":"r:3cf46ab4906eb199"},"type":"attachment","uu [...]
+{"type":"api-request-shape","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","shapeHash":"r:2a6da8170668ac7b","timestamp":"2026-10-18T03:43:46.151Z","version":"2.1.302","shape":{"system":[{"type":"text","text":"r:0d7062851dd7bd7e"},{"type":"text","text":"r:469950f24f4587cb"},{"type":"text","text":"r:074486f2727a3060"}],"tools":[{"name":"Agent","description":"r:d14609f3da1c0f3e","input_schema":{"$schema":"r:206ae48f6cae3f87","type":"object","properties":{"description":{"description":"r: [...]
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:d4aa490cdd469eb7","message":{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:d5be80cfb0da37b6"}]}}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:720b31186044867d","message":{"role":"system","content":[{"type":"text","text":"r:b1e7c89879721196"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties":{"batch":{"type":"array"},"container":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"},"create" [...]
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"c7db4617-4dbc-41b3-87e2-dfe62192543f","timestamp":"2026-10-18T03:43:46.151Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"type":"last-prompt","lastPrompt":"r:d5be80cfb0da37b6","leafUuid":"0b97efcb-1cb3-43fc-a2f7-e5ede2610dc8","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"0b97efcb-1cb3-43fc-a2f7-e5ede2610dc8","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91hUMHi9aaAdpm4nt9D","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01PAWzeCTwBMS5a3dqeypTdU","name":"Bash","input":{"command":"r:75209bf98031b5a4","description":"r:9c927a17790fdf91"},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creatio [...]
+{"parentUuid":"0867349a-e293-454b-b31c-1684219acbeb","isSidechain":false,"promptId":"3548f798-7acd-4647-a727-56cacd4bfc1b","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01PAWzeCTwBMS5a3dqeypTdU","type":"tool_result","content":"r:622e1bde356c21ea","is_error":false}]},"uuid":"3f9ef83f-b495-49a9-8937-8fd5a85ec538","timestamp":"2026-10-18T03:43:52.210Z","toolUseResult":{"stdout":"r:622e1bde356c21ea","stderr":"","interrupted":false,"isImage":false,"noOutputExpected": [...]
+{"parentUuid":"3f9ef83f-b495-49a9-8937-8fd5a85ec538","isSidechain":false,"attachment":{"type":"prompt_snapshot","systemPrompt":["r:469950f24f4587cb","r:26490c16e8c5f06e","r:ee43af37398581e9","r:3fcd2b200896a716","r:9724415357935973","r:0937acefc6d6d7d1","r:355b8bd7bf094a96","r:c34f6aeb8af321c8","r:9856a95edb9c2bdb","r:4e4e48fb23ceb764","r:2b7db5539346e950","r:41631e7eb968f40d"],"tools":[{"name":"r:11b39c93777e8f1f","description":"r:d14609f3da1c0f3e","schema":{"name":"Agent","description" [...]
+{"parentUuid":"b2becded-28f0-4f55-9ff2-351f5ed30ebb","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":["r:e8549a24c10de901","r:e0d0361842b1b96e","r:391005c180cfba83","r:7f956d661df93e8a","r:f497420862da3cc7","r:5da4c9093c52a0e6","r:d9b8785ee4e668f6","r:4450a9398f725335","r:e442b0105bc7be64","r:29a7393d5dcfcff4","r:ad58432e57e79462","r:56f3804e749ba1d7","r:a7303e0b635aa4b4","r:89d09ba5b7d8781f","r:6885924b8114416c","r:39b2256fcad348d2","r:019c784c908580ae","r:2 [...]
+{"parentUuid":"2f8204c9-0c46-414e-a9a2-f8358fa93e8a","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:be5bcd3d53369d24"},"type":"attachment","uuid":"45d8c661-fa87-4d3c-8b01-330d4fc4a3d6","timestamp":"2026-10-18T03:43:52.213Z","rendered":[{"content":"r:107381ecd2323b96"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:138528b70c7d22d6","message":{"role":"system","content":[{"type":"text","text":"r:a51d7bf7d4776229"}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"1c66ec0f-db43-472d-807c-e3d7b3c9bc9e","timestamp":"2026-10-18T03:43:52.219Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"type":"last-prompt","lastPrompt":"r:d5be80cfb0da37b6","leafUuid":"45d8c661-fa87-4d3c-8b01-330d4fc4a3d6","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"45d8c661-fa87-4d3c-8b01-330d4fc4a3d6","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91hrgDUjch3FayeGVRD","type":"message","role":"assistant","content":[{"type":"text","text":"r:a176eeb31e601c38"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":9194,"cache_read_input_tokens":31965,"output_tokens":4,"output_tokens_details":{"thinking_tokens":0},"server_tool_u [...]
+{"type":"last-prompt","lastPrompt":"r:d5be80cfb0da37b6","leafUuid":"5a0ee2f4-50b5-4cbf-abe3-414275a29496","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.2529558,"totalAPIDuration":10675,"totalAPIDurationWithoutRetries":10672,"totalToolDuration":241,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":13924,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":4,"outputTokens":89,"thinkingTokens":0,"cacheReadInputTokens":42799,"cacheCreationInputTokens":30325,"webSearchRequests":0,"costUSD":0.2529558}},"hasUnknownModelCost":false}
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:44:52.737Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:d8adb66f99157323"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:44:52.738Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"5a0ee2f4-50b5-4cbf-abe3-414275a29496","isSidechain":false,"promptId":"aca15ca3-eb1a-47d8-a776-c5cc565756e6","type":"user","message":{"role":"user","content":"r:d8adb66f99157323"},"uuid":"9d7c5305-1a79-4000-b37f-277c484fbe15","timestamp":"2026-10-18T03:44:54.762Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":2},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7- [...]
+{"parentUuid":"9d7c5305-1a79-4000-b37f-277c484fbe15","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":[],"addedLines":[],"removedNames":["r:e8549a24c10de901","r:e0d0361842b1b96e","r:391005c180cfba83","r:7f956d661df93e8a","r:f497420862da3cc7","r:5da4c9093c52a0e6","r:d9b8785ee4e668f6","r:4450a9398f725335","r:e442b0105bc7be64","r:29a7393d5dcfcff4","r:ad58432e57e79462","r:56f3804e749ba1d7","r:a7303e0b635aa4b4","r:89d09ba5b7d8781f","r:6885924b8114416c","r:39b2256fc [...]
+{"parentUuid":"f055396d-3362-4f3e-96f6-ade2057a484f","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"cbba0352-d777-4e9e-a45c-4d5cde4d994a","timestamp":"2026-10-18T03:44:54.761Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:8edc1b05c2a4ad69","message":{"role":"system","content":[{"type":"text","text":"r:4fdcc78c8b6b24c2"}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"e2d8d563-4d93-424a-9bac-cb5cb3d4163b","timestamp":"2026-10-18T03:44:54.787Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"parentUuid":"cbba0352-d777-4e9e-a45c-4d5cde4d994a","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91nYsYCAyAqc1gtKtps","type":"message","role":"assistant","content":[{"type":"text","text":"r:565339bc4d33d728"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1271,"cache_read_input_tokens":41159,"output_tokens":4,"output_tokens_details":{"thinking_tokens":0},"server_tool_u [...]
+{"type":"last-prompt","lastPrompt":"r:d8adb66f99157323","leafUuid":"c5c5f934-5112-4e0d-9dc0-7b5127285cbc","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"mode","mode":"r:317b32c143692b99","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.2714436,"totalAPIDuration":14770,"totalAPIDurationWithoutRetries":14765,"totalToolDuration":241,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":20091,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":6,"outputTokens":93,"thinkingTokens":0,"cacheReadInputTokens":83958,"cacheCreationInputTokens":31596,"webSearchRequests":0,"costUSD":0.2714436}},"hasUnknownModelCost":false}
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:45:02.754Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:4280cd6f4373341e"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:45:02.755Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"c5c5f934-5112-4e0d-9dc0-7b5127285cbc","isSidechain":false,"promptId":"ecc355f5-464c-4ac2-b8d3-504d499562ac","type":"user","message":{"role":"user","content":"r:4280cd6f4373341e"},"uuid":"1baf1e08-bd81-4ce6-ac16-8a448296651c","timestamp":"2026-10-18T03:45:04.780Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":3},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7- [...]
+{"parentUuid":"1baf1e08-bd81-4ce6-ac16-8a448296651c","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":["r:e8549a24c10de901","r:e0d0361842b1b96e","r:391005c180cfba83","r:7f956d661df93e8a","r:f497420862da3cc7","r:5da4c9093c52a0e6","r:d9b8785ee4e668f6","r:4450a9398f725335","r:e442b0105bc7be64","r:29a7393d5dcfcff4","r:ad58432e57e79462","r:56f3804e749ba1d7","r:a7303e0b635aa4b4","r:89d09ba5b7d8781f","r:6885924b8114416c","r:39b2256fcad348d2","r:019c784c908580ae","r:2 [...]
+{"parentUuid":"e002e382-968e-4327-9407-e5a5081f0017","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"88ff7037-0990-44b4-9740-8215b35ca3ce","timestamp":"2026-10-18T03:45:04.780Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:3973020c8570f846","message":{"role":"system","content":[{"type":"text","text":"r:d3bcd16db228e9c4"}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"63bb5eaf-fadc-4353-a945-7723d9d2d233","timestamp":"2026-10-18T03:45:04.809Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"parentUuid":"88ff7037-0990-44b4-9740-8215b35ca3ce","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91ob2ntzSmSJyh5xZ5W","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01DvNo1kzGJ7yP9mRYoscRCz","name":"Agent","input":{"description":"r:ba030c42177005fb","prompt":"r:90351e4350ee90cc","subagent_type":"r:db45ca5bb40691a7","run_in_background":false},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":nu [...]
+{"parentUuid":"b1f41701-2633-4e57-8b9b-5d3bf1043296","isSidechain":false,"promptId":"ecc355f5-464c-4ac2-b8d3-504d499562ac","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01DvNo1kzGJ7yP9mRYoscRCz","type":"tool_result","content":[{"type":"text","text":"r:da99c1c65d1442f0"}]}]},"uuid":"1a29c65c-6fcd-4630-96b8-1cb59bd09a67","timestamp":"2026-10-18T03:45:21.759Z","toolUseResult":{"status":"completed","prompt":"r:90351e4350ee90cc","agentId":"a89d833fef85d5b44","agentTy [...]
+{"parentUuid":"1a29c65c-6fcd-4630-96b8-1cb59bd09a67","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":[],"addedLines":[],"removedNames":[],"wireHiddenNames":[],"readdedNames":[],"pendingMcpServers":[],"needsAuthMcpServers":["r:38ee17f662de8e6d","r:b518f4321f6bc175","r:31fb2ebd5d87f409","r:fdab4502edacde58","r:0fc3f8d43bb42355","r:b7e8770733015d47","r:a9b66d0da3f97a7b","r:9f16b45bea914d46","r:5004593229cba888","r:144428cb6cf26e38"],"failedMcpServers":[{"name":" [...]
+{"parentUuid":"1a489549-0622-4809-92ec-184f829f8367","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:f0bee6b74a69882a"},"type":"attachment","uuid":"51a99226-5812-4946-90eb-60ac66751641","timestamp":"2026-10-18T03:45:21.762Z","rendered":[{"content":"r:c7e3ff7724b78dbe"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:d6e7065d4b7761d3","message":{"role":"system","content":[{"type":"text","text":"r:f83d65cd838eddc6"}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"e32489dd-a8b0-425b-98b0-0ed2e7ff31af","timestamp":"2026-10-18T03:45:21.767Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"parentUuid":"51a99226-5812-4946-90eb-60ac66751641","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91pTSfA3MdBMui5WD3z","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"r:b6791dd02daed547"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1062,"cache_read_input_tokens":43293,"output_tokens":63,"output_tokens_details":{"thinking_t [...]
+{"parentUuid":"c648ad28-7482-4c15-832d-ce00ad8530e5","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91pTSfA3MdBMui5WD3z","type":"message","role":"assistant","content":[{"type":"text","text":"r:1caffe1a404c17b6"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1062,"cache_read_input_tokens":43293,"output_tokens":63,"output_tokens_details":{"thinking_tokens":29},"server_tool [...]
+{"type":"last-prompt","lastPrompt":"r:4280cd6f4373341e","leafUuid":"49c48ceb-d88c-486b-a4bb-181e8c6c2af8","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.4628786,"totalAPIDuration":36054,"totalAPIDurationWithoutRetries":36043,"totalToolDuration":7972,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":43638,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":14,"outputTokens":503,"thinkingTokens":29,"cacheReadInputTokens":193648,"cacheCreationInputTokens":61941,"webSearchRequests":0,"costUSD":0.4628786}},"hasUnknownModelCost [...]
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:45:29.082Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:e256ee8e7aff6957"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:45:29.083Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"49c48ceb-d88c-486b-a4bb-181e8c6c2af8","isSidechain":false,"promptId":"b3217ede-47d8-41dc-bfb7-bb2fd41d36e2","type":"user","message":{"role":"user","content":"r:e256ee8e7aff6957"},"uuid":"47e1ac5b-1e81-46c8-87e6-dd48227fa80f","timestamp":"2026-10-18T03:45:31.108Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":4},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7- [...]
+{"parentUuid":"47e1ac5b-1e81-46c8-87e6-dd48227fa80f","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":[],"addedLines":[],"removedNames":["r:e8549a24c10de901","r:e0d0361842b1b96e","r:391005c180cfba83","r:7f956d661df93e8a","r:f497420862da3cc7","r:5da4c9093c52a0e6","r:d9b8785ee4e668f6","r:4450a9398f725335","r:e442b0105bc7be64","r:29a7393d5dcfcff4","r:ad58432e57e79462","r:56f3804e749ba1d7","r:a7303e0b635aa4b4","r:89d09ba5b7d8781f","r:6885924b8114416c","r:39b2256fc [...]
+{"parentUuid":"1aec6a6c-112d-4d11-988c-f7da3c703922","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"abaa6b61-9173-4be3-8f6c-4f1d21784d68","timestamp":"2026-10-18T03:45:31.107Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"c5e34c63-f2cd-4089-8f01-1b88d1b6e94f","timestamp":"2026-10-18T03:45:31.139Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"parentUuid":"abaa6b61-9173-4be3-8f6c-4f1d21784d68","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91qXfwciRBF7iZgo75v","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"r:3c3277ab79e65c02"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1322,"cache_read_input_tokens":44355,"output_tokens":106,"output_tokens_details":{"thinking_ [...]
+{"parentUuid":"92a302ed-2172-4122-bb07-2decd08bd916","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91qXfwciRBF7iZgo75v","type":"message","role":"assistant","content":[{"type":"text","text":"r:f2dd2ca50f445c25"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1322,"cache_read_input_tokens":44355,"output_tokens":106,"output_tokens_details":{"thinking_tokens":55},"server_too [...]
+{"type":"last-prompt","lastPrompt":"r:e256ee8e7aff6957","leafUuid":"3e7ac274-d4a7-4ac7-bd29-55f09a7a953a","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"mode","mode":"r:317b32c143692b99","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.4844536,"totalAPIDuration":45018,"totalAPIDurationWithoutRetries":45005,"totalToolDuration":7972,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":54678,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":16,"outputTokens":609,"thinkingTokens":84,"cacheReadInputTokens":238003,"cacheCreationInputTokens":63263,"webSearchRequests":0,"costUSD":0.4844536}},"hasUnknownModelCost [...]
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:45:49.580Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:8a9f060c3d516124"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:45:49.580Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"3e7ac274-d4a7-4ac7-bd29-55f09a7a953a","isSidechain":false,"promptId":"8b69a4ec-1965-4937-b261-57224c1f30f8","type":"user","message":{"role":"user","content":"r:8a9f060c3d516124"},"uuid":"d75322da-af44-447a-be6f-651baf557f75","timestamp":"2026-10-18T03:45:51.602Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":5},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7- [...]
+{"parentUuid":"d75322da-af44-447a-be6f-651baf557f75","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":[],"addedLines":[],"removedNames":["r:b09c7a9dcdafec47","r:7b167b971cb91929","r:e2c69b1f448dfd7b","r:239ebb800620cff2","r:2082bec501d55d6c","r:f52106b59340d2dd","r:c7ff03d8a8669363","r:07822514a195308a"],"wireHiddenNames":[],"readdedNames":[],"pendingMcpServers":["r:96e9ee1dda5dfe80","r:380331632fbf1877","r:aa97858b0bb3373f"],"needsAuthMcpServers":["r:38ee17f6 [...]
+{"parentUuid":"ce4baf61-0579-4285-a0a9-9db379e06ddd","isSidechain":false,"attachment":{"type":"mcp_instructions_delta","addedNames":[],"addedBlocks":[],"removedNames":["r:380331632fbf1877"]},"type":"attachment","uuid":"2f165c41-2b9a-40a9-ae47-eff34086f75b","timestamp":"2026-10-18T03:45:51.602Z","rendered":[{"content":"r:5d47782b6710ecbb"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","ve [...]
+{"parentUuid":"2f165c41-2b9a-40a9-ae47-eff34086f75b","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"f2e7deb0-4b52-42f8-baac-934430e22a2b","timestamp":"2026-10-18T03:45:51.602Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:2ff47f007c59c490","message":{"role":"system","content":[{"type":"text","text":"r:f3c61544c86b59ed"},{"type":"tool_removal","tool":{"type":"tool_reference","name":"r:b09c7a9dcdafec47"}},{"type":"tool_removal","tool":{"type":"tool_reference","name":"r:2082bec501d55d6c"}},{"type":"tool_removal","tool":{"type":"tool_reference","name":"r:07822514a195308a"}}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"6d58378d-5d11-46c1-ab09-d2d776e4b92a","timestamp":"2026-10-18T03:45:51.630Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"parentUuid":"f2e7deb0-4b52-42f8-baac-934430e22a2b","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91ruJDq5bkhZSKud9UE","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01CQyLzCENmqZ4cjxe5Q3qg1","name":"Read","input":{"file_path":"r:5275f68e423224d4"},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1230,"cache_read_ [...]
+{"parentUuid":"f5580b96-961d-4d91-a466-774bfa0dd81f","isSidechain":false,"promptId":"8b69a4ec-1965-4937-b261-57224c1f30f8","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01CQyLzCENmqZ4cjxe5Q3qg1","type":"tool_result","content":"r:098303ba35ba893e"}]},"uuid":"3e4bc452-b9c1-401f-a594-9c285ebcb9ab","timestamp":"2026-10-18T03:46:04.784Z","toolUseResult":{"type":"text","file":{"filePath":"r:5275f68e423224d4","content":"r:77a63ea77d429e1f","numLines":6,"startLine":1,"t [...]
+{"parentUuid":"3e4bc452-b9c1-401f-a594-9c285ebcb9ab","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":["r:e8549a24c10de901","r:e0d0361842b1b96e","r:391005c180cfba83","r:b09c7a9dcdafec47","r:7b167b971cb91929","r:e2c69b1f448dfd7b","r:239ebb800620cff2","r:2082bec501d55d6c","r:f52106b59340d2dd","r:c7ff03d8a8669363","r:07822514a195308a","r:7f956d661df93e8a","r:f497420862da3cc7","r:5da4c9093c52a0e6","r:d9b8785ee4e668f6","r:4450a9398f725335","r:e442b0105bc7be64","r:2 [...]
+{"parentUuid":"32d40415-f10e-48c8-a1fe-dd3a2fcf0a0e","isSidechain":false,"attachment":{"type":"mcp_instructions_delta","addedNames":["r:380331632fbf1877"],"addedBlocks":["r:8ed75c59eceec373"],"removedNames":[]},"type":"attachment","uuid":"de270903-13b5-4e0f-9b97-dc4657187235","timestamp":"2026-10-18T03:46:04.790Z","rendered":[{"content":"r:906d9926e516c792"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a0 [...]
+{"parentUuid":"de270903-13b5-4e0f-9b97-dc4657187235","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:fca8ada8a34737af"},"type":"attachment","uuid":"6a31db5f-8730-4a1f-aeb0-8563cc3a3e14","timestamp":"2026-10-18T03:46:04.791Z","rendered":[{"content":"r:2115d44b0f7285d7"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:816be3374a5626df","message":{"role":"system","content":[{"type":"text","text":"r:b8041a8ee9e3ba33"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties":{"batch":{"type":"array"},"container":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"},"create" [...]
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"6d2f43ea-f6fd-4203-b3a5-d28192bb2461","timestamp":"2026-10-18T03:46:04.797Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"type":"last-prompt","lastPrompt":"r:8a9f060c3d516124","leafUuid":"6a31db5f-8730-4a1f-aeb0-8563cc3a3e14","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"mode","mode":"r:317b32c143692b99","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"6a31db5f-8730-4a1f-aeb0-8563cc3a3e14","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91sdTkoXhyd8g81wXhR","type":"message","role":"assistant","content":[{"type":"text","text":"r:bbe7430b2e0edc19"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":2499,"cache_read_input_tokens":46907,"output_tokens":65,"output_tokens_details":{"thinking_tokens":0},"server_tool_ [...]
+{"type":"last-prompt","lastPrompt":"r:8a9f060c3d516124","leafUuid":"4d0202f8-06fd-495b-a12b-2023316bdb2c","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.5367183999999999,"totalAPIDuration":62026,"totalAPIDurationWithoutRetries":62011,"totalToolDuration":7979,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":73772,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":20,"outputTokens":804,"thinkingTokens":84,"cacheReadInputTokens":330587,"cacheCreationInputTokens":66992,"webSearchRequests":0,"costUSD":0.5367183999999999}},"h [...]
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:46:10.372Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:d8adb66f99157323"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:46:10.373Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"4d0202f8-06fd-495b-a12b-2023316bdb2c","isSidechain":false,"promptId":"0b60c428-f1dd-443e-9f1d-a5d7c13e02c3","type":"user","message":{"role":"user","content":"r:d8adb66f99157323"},"uuid":"0d5ced56-7242-497c-b70f-fc602766bd22","timestamp":"2026-10-18T03:46:12.397Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":6},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7- [...]
+{"parentUuid":"0d5ced56-7242-497c-b70f-fc602766bd22","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":[],"addedLines":[],"removedNames":[],"wireHiddenNames":[],"readdedNames":[],"pendingMcpServers":["r:96e9ee1dda5dfe80"],"needsAuthMcpServers":["r:38ee17f662de8e6d","r:b518f4321f6bc175","r:31fb2ebd5d87f409","r:fdab4502edacde58","r:0fc3f8d43bb42355","r:b7e8770733015d47","r:a9b66d0da3f97a7b","r:9f16b45bea914d46","r:5004593229cba888","r:144428cb6cf26e38"],"failedMc [...]
+{"parentUuid":"f6cdfee0-e9d5-4eb6-902e-844cc425b7b4","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"ca54e047-0fc2-4478-acee-eaee4dbec4ec","timestamp":"2026-10-18T03:46:12.397Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:109191b70713e15f","message":{"role":"system","content":[{"type":"text","text":"r:1a58ac0f37ec3290"}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"67dd426e-36a4-4730-83b2-16f7931d4111","timestamp":"2026-10-18T03:46:12.424Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"parentUuid":"ca54e047-0fc2-4478-acee-eaee4dbec4ec","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91tFdNFxcUGk1BhhKFD","type":"message","role":"assistant","content":[{"type":"text","text":"r:565339bc4d33d728"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":742,"cache_read_input_tokens":49406,"output_tokens":4,"output_tokens_details":{"thinking_tokens":0},"server_tool_us [...]
+{"type":"last-prompt","lastPrompt":"r:d8adb66f99157323","leafUuid":"c91273ac-bc84-406a-ba9f-a07354a34259","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.5526235999999999,"totalAPIDuration":66032,"totalAPIDurationWithoutRetries":66016,"totalToolDuration":7979,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":79855,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":22,"outputTokens":808,"thinkingTokens":84,"cacheReadInputTokens":379993,"cacheCreationInputTokens":67734,"webSearchRequests":0,"costUSD":0.5526235999999999}},"h [...]
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:46:18.588Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:52e657bd1470bb75"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:46:18.589Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"mode","mode":"r:317b32c143692b99","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":null,"logicalParentUuid":"c91273ac-bc84-406a-ba9f-a07354a34259","isSidechain":false,"type":"system","subtype":"compact_boundary","content":"r:b21190c3fcc7228f","isMeta":false,"timestamp":"2026-10-18T03:46:43.070Z","uuid":"99cc8aba-c0d5-4a87-b9df-f0c9774a28a1","level":"info","compactMetadata":{"trigger":"manual","preTokens":50154,"durationMs":22461,"preservedSegment":{"headUuid":"c91273ac-bc84-406a-ba9f-a07354a34259","anchorUuid":"2a077920-521b-491f-bf7b-252934fa5cd6","tailU [...]
+{"parentUuid":"99cc8aba-c0d5-4a87-b9df-f0c9774a28a1","isSidechain":false,"promptId":"ea4185fe-fa7b-4639-8157-40b2a47b64f8","type":"user","message":{"role":"user","content":"r:b18f2c122c3ea1fd"},"isVisibleInTranscriptOnly":true,"isCompactSummary":true,"uuid":"2a077920-521b-491f-bf7b-252934fa5cd6","timestamp":"2026-10-18T03:46:43.069Z","turnPosition":{"promptIndex":0,"turnIndex":6},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010- [...]
+{"parentUuid":"2a077920-521b-491f-bf7b-252934fa5cd6","isSidechain":false,"promptId":"ea4185fe-fa7b-4639-8157-40b2a47b64f8","type":"user","message":{"role":"user","content":"r:ad6a9f3a7e9b11d7"},"isMeta":true,"uuid":"e5833a86-458f-4708-b7cd-b0e8b135e0b0","timestamp":"2026-10-18T03:46:20.608Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358","slug":"r:f1a95acea7c1dbfd"}
+{"parentUuid":"e5833a86-458f-4708-b7cd-b0e8b135e0b0","isSidechain":false,"promptId":"ea4185fe-fa7b-4639-8157-40b2a47b64f8","type":"user","message":{"role":"user","content":"r:902b57e01c12bda3"},"uuid":"a7d470fe-5411-4616-b7c8-1af2be6ae552","timestamp":"2026-10-18T03:46:20.608Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358","slug":"r:f1a95acea7c1dbfd"}
+{"parentUuid":"a7d470fe-5411-4616-b7c8-1af2be6ae552","isSidechain":false,"promptId":"ea4185fe-fa7b-4639-8157-40b2a47b64f8","type":"user","message":{"role":"user","content":"r:7a96f4712a3e32ad"},"uuid":"fc451c8c-c277-427c-b5bb-f5da50957a19","timestamp":"2026-10-18T03:46:43.179Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358","slug":"r:f1a95acea7c1dbfd"}
+{"parentUuid":"fc451c8c-c277-427c-b5bb-f5da50957a19","isSidechain":false,"attachment":{"type":"file","filename":"r:5275f68e423224d4","content":{"type":"text","file":{"filePath":"r:5275f68e423224d4","content":"r:77a63ea77d429e1f","numLines":6,"startLine":1,"totalLines":6}},"displayPath":"r:e39538e7f27a7bf5"},"type":"attachment","uuid":"7cf67bba-e40f-42e6-b111-660ae3a553e8","timestamp":"2026-10-18T03:46:43.074Z","rendered":[{"content":"r:b663e8d548757182"},{"content":"r:2c1371fb6ffb0edd"}] [...]
+{"parentUuid":"7cf67bba-e40f-42e6-b111-660ae3a553e8","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":["r:d3b1f39f782677cb","r:997622e5bd498fc8","r:17630c4b25f34cb8","r:ae68c6d579eb9db9","r:e18439493af2b9ea","r:fd2879d0b5009ce9","r:821e582030bb0428","r:e8549a24c10de901","r:4c2e1df4570bebd6","r:e84a697b3c982b1b","r:9bfbb6062b487900","r:e0d0361842b1b96e","r:391005c180cfba83","r:a38381ce1013153a","r:48e8ec8b725027b4","r:4fd25d5ab5e16fe3","r:7bd75b80a49c80b9","r:7 [...]
+{"parentUuid":"89ae78f8-e224-42de-b294-14fdabf4f473","isSidechain":false,"attachment":{"type":"agent_listing_delta","addedTypes":["r:c857d09db23e6822","r:3b73900b8d29f963","r:db45ca5bb40691a7","r:fa8ed0bdabdd6bcb","r:7150d1ed23874efb"],"addedLines":["r:e6e122752bd144ad","r:535d81bfe2a6d10b","r:bd0eb8354b8ecd93","r:a19eeace5ca7decf","r:668c3c654c6cf099"],"builtInTypes":["r:c857d09db23e6822","r:3b73900b8d29f963","r:db45ca5bb40691a7","r:fa8ed0bdabdd6bcb","r:7150d1ed23874efb"],"removedTypes" [...]
+{"parentUuid":"9e775d8e-4146-429e-8062-34eecfa6f498","isSidechain":false,"attachment":{"type":"mcp_instructions_delta","addedNames":["r:380331632fbf1877"],"addedBlocks":["r:8ed75c59eceec373"],"removedNames":[]},"type":"attachment","uuid":"8dae8506-d91f-4fec-900c-5e86d546bf31","timestamp":"2026-10-18T03:46:43.075Z","rendered":[{"content":"r:906d9926e516c792"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a0 [...]
+{"parentUuid":"8dae8506-d91f-4fec-900c-5e86d546bf31","isSidechain":false,"attachment":{"type":"environment","snapshot":{"workingDirectory":"r:b5231b0539a2a70b","isWorktree":false,"isGitRepo":false,"additionalWorkingDirectories":["r:e9671acd244849c5","r:938b99e3330802a9","r:b2f48a3ce07c1d86","r:a5bfa300f0fd2e37","r:097c1e9fdaeb00fd","r:bb64303334efb9fa"],"platform":"r:26ce1a1580f69387","shell":"r:37d2b12d5d9abc2a","osVersion":"r:0c4e80dfb486a406"}},"type":"attachment","uuid":"ea4fc8fe-b11 [...]
+{"parentUuid":"ea4fc8fe-b115-481e-b5ee-e5edeac90a21","isSidechain":false,"attachment":{"type":"model","identity":{"modelId":"r:005912bee6ee4543","marketingName":"r:18966c1a2825e090","knowledgeCutoff":"r:ee00ffb56d50d0d4"},"text":"r:12e0371339493b0e"},"type":"attachment","uuid":"a7514bdd-c910-42ee-a83c-c95dea16e178","timestamp":"2026-10-18T03:46:43.075Z","rendered":[{"content":"r:9e62167a3f1f22a7"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2 [...]
+{"type":"last-prompt","lastPrompt":"r:d8adb66f99157323","leafUuid":"a7514bdd-c910-42ee-a83c-c95dea16e178","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"mode","mode":"r:317b32c143692b99","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.6091177999999999,"totalAPIDuration":88473,"totalAPIDurationWithoutRetries":88455,"totalToolDuration":7979,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":104377,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":2694,"outputTokens":2588,"thinkingTokens":84,"cacheReadInputTokens":429399,"cacheCreationInputTokens":67799,"webSearchRequests":0,"costUSD":0.6091177999999999} [...]
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:46:44.768Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:75d68791f7f86b0b"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:46:44.769Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"a7514bdd-c910-42ee-a83c-c95dea16e178","isSidechain":false,"promptId":"6bbbd0d4-3ded-482d-9394-0ebee4fd6ac2","type":"user","message":{"role":"user","content":"r:75d68791f7f86b0b"},"uuid":"48ce06f4-11a9-41bc-ad9f-64f1b5ae0817","timestamp":"2026-10-18T03:46:46.803Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":7},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7- [...]
+{"parentUuid":"48ce06f4-11a9-41bc-ad9f-64f1b5ae0817","isSidechain":false,"attachment":{"type":"deferred_tools_delta","addedNames":["r:b09c7a9dcdafec47","r:2082bec501d55d6c","r:07822514a195308a"],"addedLines":["r:b09c7a9dcdafec47","r:2082bec501d55d6c","r:07822514a195308a"],"removedNames":[],"wireHiddenNames":[],"readdedNames":[],"pendingMcpServers":["r:96e9ee1dda5dfe80"],"needsAuthMcpServers":["r:38ee17f662de8e6d","r:b518f4321f6bc175","r:31fb2ebd5d87f409","r:fdab4502edacde58","r:0fc3f8d43 [...]
+{"parentUuid":"de31a898-e344-4682-8ae1-fbda985b7e5c","isSidechain":false,"attachment":{"type":"skill_listing","content":"r:6735451d0f8dcbaa","skillCount":58,"isInitial":true,"names":["r:7d9ebd5481a5f20e","r:f521dccd06b54b4c","r:8b4f24b5ebbc03f5","r:ce7b8e9a84a4c086","r:8cdd6473d4f6a98c","r:0145c3bd856fbf79","r:f2b66271725cebba","r:83d1fcc1d3d56caa","r:243fe6d24ecb7192","r:372688e7473c85de","r:1b54043b9ea206b5","r:8bbf89b79f35e6ba","r:0172d017b3e69fb8","r:02521988e5dd239c","r:05fe25e1fdf8 [...]
+{"parentUuid":"09fa2e5f-516d-4fd2-a198-2a81602064a3","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"801c0b04-a197-475b-aa7b-caab2537295d","timestamp":"2026-10-18T03:46:46.803Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165a [...]
+{"parentUuid":"801c0b04-a197-475b-aa7b-caab2537295d","isSidechain":false,"attachment":{"type":"instructions","files":[{"path":"r:337d2ae0439ad56a","type":"User","content":"r:871366e443fb1dec"}]},"type":"attachment","uuid":"840d949e-65ec-465e-a338-97a8a1872cd3","timestamp":"2026-10-18T03:46:46.816Z","rendered":[{"content":"r:d65a7e18cbf0a67f"}],"renderedRole":"user","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"," [...]
+{"parentUuid":"840d949e-65ec-465e-a338-97a8a1872cd3","isSidechain":false,"attachment":{"type":"session_context","context":{"userEmail":"r:499cdb06796ce61e"}},"type":"attachment","uuid":"4672de4d-cd91-4b19-978a-cb101fd01443","timestamp":"2026-10-18T03:46:46.816Z","rendered":[{"content":"r:a48ca1c9bdb26f25"}],"renderedRole":"user","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b51 [...]
+{"parentUuid":"4672de4d-cd91-4b19-978a-cb101fd01443","isSidechain":false,"attachment":{"type":"date","date":"r:aa66931ec9add42a"},"type":"attachment","uuid":"527867a3-d20b-4da9-be9d-943f6b33c49a","timestamp":"2026-10-18T03:46:46.816Z","rendered":[{"content":"r:01bbdac397744700"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358","slug":"r:f [...]
+{"parentUuid":"527867a3-d20b-4da9-be9d-943f6b33c49a","isSidechain":false,"attachment":{"type":"credential_org","organizationUuid":"r:c041682af74b618e"},"type":"attachment","uuid":"3975ef27-cb7e-4b83-9aa7-946a0512212e","timestamp":"2026-10-18T03:46:46.817Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358","slug":"r:f1a95acea7c1dbfd"}
+{"parentUuid":"3975ef27-cb7e-4b83-9aa7-946a0512212e","isSidechain":false,"attachment":{"type":"remote_session_change","url":null,"commit":"r:6073d8d45b78c96f","pr":"r:48a19a429e37d2ac","sendUserFileHint":false,"managedCommit":false,"managedPr":false},"type":"attachment","uuid":"832fe8ca-b5b5-40d3-aa2e-27719048efe0","timestamp":"2026-10-18T03:46:46.817Z","rendered":[{"content":"r:c7c420407202d49d"}],"renderedRole":"user","renderedBesideToolResult":true,"userType":"external","entrypoint":" [...]
+{"parentUuid":"832fe8ca-b5b5-40d3-aa2e-27719048efe0","isSidechain":false,"attachment":{"type":"prompt_snapshot","systemPrompt":["r:469950f24f4587cb","r:26490c16e8c5f06e","r:ee43af37398581e9","r:3fcd2b200896a716","r:9724415357935973","r:0937acefc6d6d7d1","r:355b8bd7bf094a96","r:c34f6aeb8af321c8","r:9856a95edb9c2bdb","r:4e4e48fb23ceb764","r:2b7db5539346e950","r:41631e7eb968f40d"],"reminderFold":false,"echoWireToolInputs":true,"contextRendering":"r:3cf46ab4906eb199"},"type":"attachment","uu [...]
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:9ce490223c904376","message":{"role":"system","content":[],"output_config":{"effort":"xhigh"}}}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:01646818c0f5503f","message":{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:19cf21a5f4433d7b"},{"type":"text","text":"r:2cb99709ec25cef4"},{"type":"text","text":"r:9a0c529b52650db0"},{"type":"text","text":"r:75d68791f7f86b0b"}]}}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:6874b5e47ca6058c","message":{"role":"system","content":[{"type":"text","text":"r:e6a18554f2b09b3e"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties":{"batch":{"type":"array"},"container":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"},"create" [...]
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"2cc39d21-ac70-40ff-8649-55530ed0aaf7","timestamp":"2026-10-18T03:46:46.832Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"type":"last-prompt","lastPrompt":"r:75d68791f7f86b0b","leafUuid":"bf3b4a69-e6f3-4f96-9593-54de16adff77","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"mode","mode":"r:317b32c143692b99","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"bf3b4a69-e6f3-4f96-9593-54de16adff77","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91w5MKiSux4wsgF96oG","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"r:d854bc1d4535ee29"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":21473,"cache_read_input_tokens":12686,"output_tokens":44,"output_tokens_details":{"thinking_ [...]
+{"parentUuid":"fae4c18b-806f-4a7c-acbe-ab601d2bdf8e","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91w5MKiSux4wsgF96oG","type":"message","role":"assistant","content":[{"type":"text","text":"r:ab25049f4ad17472"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":21473,"cache_read_input_tokens":12686,"output_tokens":44,"output_tokens_details":{"thinking_tokens":27},"server_too [...]
+{"parentUuid":"9de0c79e-57e9-4e82-967e-3565a4511b68","isSidechain":false,"attachment":{"type":"prompt_snapshot","systemPrompt":["r:469950f24f4587cb","r:26490c16e8c5f06e","r:ee43af37398581e9","r:3fcd2b200896a716","r:9724415357935973","r:0937acefc6d6d7d1","r:355b8bd7bf094a96","r:c34f6aeb8af321c8","r:9856a95edb9c2bdb","r:4e4e48fb23ceb764","r:2b7db5539346e950","r:41631e7eb968f40d"],"tools":[{"name":"r:11b39c93777e8f1f","description":"r:d14609f3da1c0f3e","schema":{"name":"Agent","description" [...]
+{"type":"last-prompt","lastPrompt":"r:75d68791f7f86b0b","leafUuid":"c06711dd-c480-4c8e-8142-43dce358fd97","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"mode","mode":"r:317b32c143692b99","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"atis-latch","atis":"","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.7843269999999999,"totalAPIDuration":104743,"totalAPIDurationWithoutRetries":104723,"totalToolDuration":7979,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":122731,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":2696,"outputTokens":2632,"thinkingTokens":111,"cacheReadInputTokens":442085,"cacheCreationInputTokens":89272,"webSearchRequests":0,"costUSD":0.78432699999999 [...]
+{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-18T03:47:07.147Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","content":"r:e256ee8e7aff6957"}
+{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-18T03:47:07.148Z","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"parentUuid":"c06711dd-c480-4c8e-8142-43dce358fd97","isSidechain":false,"promptId":"77253c0c-6cf3-4987-8f76-4c64719130e5","type":"user","message":{"role":"user","content":"r:e256ee8e7aff6957"},"uuid":"ba5388ea-0818-4263-9463-904d51902127","timestamp":"2026-10-18T03:47:09.179Z","permissionMode":"default","promptSource":"sdk","turnOrigin":"sdk","turnPosition":{"promptIndex":0,"turnIndex":8},"userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7- [...]
+{"parentUuid":"ba5388ea-0818-4263-9463-904d51902127","isSidechain":false,"attachment":{"type":"total_tokens_reminder","text":"r:2b7db5539346e950"},"type":"attachment","uuid":"49913f79-b439-4381-9061-8002dae6d454","timestamp":"2026-10-18T03:47:09.179Z","rendered":[{"content":"r:d1b5389a85a66596"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165a [...]
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","hash":"r:38ad42aa3a2c0ef0","message":{"role":"system","content":[{"type":"text","text":"r:2b7db5539346e950"}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","id":"fd0046f8-96f9-4dbb-b488-5b6cbafae246","timestamp":"2026-10-18T03:47:09.208Z","version":"2.1.302","querySource":"sdk","shapeHash":"r:2a6da8170668ac7b","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f1021","r:889fb77d06f97fe0","r:6d84fa167d8f2a22","r:267ac [...]
+{"parentUuid":"49913f79-b439-4381-9061-8002dae6d454","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91xoh3WUsMX2DtgDatX","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"r:14d61fdcfe3ccf61"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":73,"cache_read_input_tokens":34159,"output_tokens":67,"output_tokens_details":{"thinking_tok [...]
+{"parentUuid":"56e10df9-b853-4b7e-a134-d8f8f32d5a12","isSidechain":false,"message":{"model":"claude-opus-5-5","id":"msg_011Cg91xoh3WUsMX2DtgDatX","type":"message","role":"assistant","content":[{"type":"text","text":"r:3e670ac609d3a727"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":73,"cache_read_input_tokens":34159,"output_tokens":67,"output_tokens_details":{"thinking_tokens":17},"server_tool_u [...]
+{"type":"last-prompt","lastPrompt":"r:e256ee8e7aff6957","leafUuid":"1dcc2530-2383-437e-921f-382f71975059","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76"}
+{"type":"cost-state","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","totalCostUSD":0.7930907999999999,"totalAPIDuration":114515,"totalAPIDurationWithoutRetries":114492,"totalToolDuration":7979,"totalLinesAdded":0,"totalLinesRemoved":0,"totalDuration":134582,"startTime":1792295023072,"modelUsage":{"claude-opus-5-5":{"inputTokens":2698,"outputTokens":2699,"thinkingTokens":128,"cacheReadInputTokens":476244,"cacheCreationInputTokens":89345,"webSearchRequests":0,"costUSD":0.79309079999999 [...]
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76/subagents/agent-a89d833fef85d5b44.jsonl b/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76/subagents/agent-a89d833fef85d5b44.jsonl
new file mode 100644
index 0000000..f088465
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76/subagents/agent-a89d833fef85d5b44.jsonl
@@ -0,0 +1,25 @@
+{"parentUuid":null,"isSidechain":true,"promptId":"ecc355f5-464c-4ac2-b8d3-504d499562ac","agentId":"a89d833fef85d5b44","type":"user","message":{"role":"user","content":"r:90351e4350ee90cc"},"uuid":"982ade4f-03ce-4591-89ce-90fb6b6ab3c5","timestamp":"2026-10-18T03:45:14.269Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"parentUuid":"35dc46b7-5078-49d5-9c01-c83d5295f359","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"instructions","files":[{"path":"r:337d2ae0439ad56a","type":"User","content":"r:871366e443fb1dec"}]},"type":"attachment","uuid":"12363deb-80dc-4f26-a5c5-f0298c4abbe4","timestamp":"2026-10-18T03:45:14.277Z","rendered":[{"content":"r:d65a7e18cbf0a67f"}],"renderedRole":"user","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-7 [...]
+{"parentUuid":"982ade4f-03ce-4591-89ce-90fb6b6ab3c5","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"deferred_tools_delta","addedNames":["r:d3b1f39f782677cb","r:997622e5bd498fc8","r:17630c4b25f34cb8","r:ae68c6d579eb9db9","r:e18439493af2b9ea","r:fd2879d0b5009ce9","r:821e582030bb0428","r:e8549a24c10de901","r:4c2e1df4570bebd6","r:e84a697b3c982b1b","r:9bfbb6062b487900","r:e0d0361842b1b96e","r:391005c180cfba83","r:a38381ce1013153a","r:48e8ec8b725027b4","r:4fd25d5ab5e16f [...]
+{"parentUuid":"67b7756f-98c8-4e6d-b9b0-3e4cdf133302","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"environment","snapshot":{"workingDirectory":"r:b5231b0539a2a70b","isWorktree":false,"isGitRepo":false,"additionalWorkingDirectories":["r:e9671acd244849c5","r:938b99e3330802a9","r:b2f48a3ce07c1d86","r:a5bfa300f0fd2e37","r:097c1e9fdaeb00fd","r:bb64303334efb9fa"],"platform":"r:26ce1a1580f69387","shell":"r:37d2b12d5d9abc2a","osVersion":"r:0c4e80dfb486a406"}},"type":"att [...]
+{"parentUuid":"2d4ebe43-6d63-4de8-9714-f09c1fd10190","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"model","identity":{"modelId":"r:005912bee6ee4543","marketingName":"r:18966c1a2825e090","knowledgeCutoff":"r:ee00ffb56d50d0d4"},"text":"r:12e0371339493b0e"},"type":"attachment","uuid":"dce8c04e-695a-4aa6-bb3c-39165f65f08a","timestamp":"2026-10-18T03:45:14.275Z","rendered":[{"content":"r:9e62167a3f1f22a7"}],"renderedRole":"system","userType":"external","entrypoint":"s [...]
+{"parentUuid":"dce8c04e-695a-4aa6-bb3c-39165f65f08a","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"skill_listing","content":"r:6735451d0f8dcbaa","skillCount":58,"isInitial":true,"names":["r:7d9ebd5481a5f20e","r:f521dccd06b54b4c","r:8b4f24b5ebbc03f5","r:ce7b8e9a84a4c086","r:8cdd6473d4f6a98c","r:0145c3bd856fbf79","r:f2b66271725cebba","r:83d1fcc1d3d56caa","r:243fe6d24ecb7192","r:372688e7473c85de","r:1b54043b9ea206b5","r:8bbf89b79f35e6ba","r:0172d017b3e69fb8","r:0252 [...]
+{"parentUuid":"12363deb-80dc-4f26-a5c5-f0298c4abbe4","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"session_context","context":{"userEmail":"r:499cdb06796ce61e"}},"type":"attachment","uuid":"9cbf9750-434e-4d89-9a38-9d184cb2a69d","timestamp":"2026-10-18T03:45:14.277Z","rendered":[{"content":"r:a48ca1c9bdb26f25"}],"renderedRole":"user","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version" [...]
+{"parentUuid":"9cbf9750-434e-4d89-9a38-9d184cb2a69d","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"date","date":"r:aa66931ec9add42a"},"type":"attachment","uuid":"cf68e93e-e811-4932-9598-9a737c4632b2","timestamp":"2026-10-18T03:45:14.277Z","rendered":[{"content":"r:01bbdac397744700"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r: [...]
+{"parentUuid":"cf68e93e-e811-4932-9598-9a737c4632b2","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"credential_org","organizationUuid":"r:c041682af74b618e"},"type":"attachment","uuid":"9ebb90e5-323d-4564-95f2-d8af55efd07c","timestamp":"2026-10-18T03:45:14.278Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302","gitBranch":"r:b5180223165af358"}
+{"parentUuid":"9ebb90e5-323d-4564-95f2-d8af55efd07c","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"remote_session_change","url":null,"commit":"r:6073d8d45b78c96f","pr":"r:48a19a429e37d2ac","sendUserFileHint":false,"managedCommit":false,"managedPr":false},"type":"attachment","uuid":"c0c8f944-c5da-431d-a35e-1a31c16ce2dc","timestamp":"2026-10-18T03:45:14.279Z","rendered":[{"content":"r:c7c420407202d49d"}],"renderedRole":"user","renderedBesideToolResult":true,"userTy [...]
+{"parentUuid":"c0c8f944-c5da-431d-a35e-1a31c16ce2dc","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"prompt_snapshot","systemPrompt":["r:2bdba397170e83b7","r:79ef93928642628c","r:5145202c6f78f0e3","r:2b7db5539346e950"],"reminderFold":false,"echoWireToolInputs":true,"contextRendering":"r:3cf46ab4906eb199"},"type":"attachment","uuid":"d96c5162-a995-487f-8980-6e68264c8f81","timestamp":"2026-10-18T03:45:14.279Z","userType":"external","entrypoint":"sdk-cli","cwd":"r:b52 [...]
+{"type":"api-request-shape","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","agentId":"a89d833fef85d5b44","shapeHash":"r:8a7cb4473e94b8f8","timestamp":"2026-10-18T03:45:14.284Z","version":"2.1.302","shape":{"system":[{"type":"text","text":"r:0d7062851dd7bd7e"},{"type":"text","text":"r:40e32d086053e633"}],"tools":[{"name":"Agent","description":"r:d14609f3da1c0f3e","input_schema":{"$schema":"r:206ae48f6cae3f87","type":"object","properties":{"description":{"description":"r:dd0bb2ec88e4a2 [...]
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","agentId":"a89d833fef85d5b44","hash":"r:59141217a4175ff1","message":{"role":"user","content":[{"type":"text","text":"r:d65a7e18cbf0a67f"},{"type":"text","text":"r:a48ca1c9bdb26f25"},{"type":"text","text":"r:e291f933f8631786"},{"type":"text","text":"r:90351e4350ee90cc"}]}}
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","agentId":"a89d833fef85d5b44","hash":"r:d8031c20cb483b1f","message":{"role":"system","content":[{"type":"text","text":"r:da3dd30514eba47d"},{"type":"tool_addition","tool":{"type":"tool_definition","definition":{"name":"r:b09c7a9dcdafec47","description":"r:3fba3215631f0767","input_schema":{"type":"object","properties":{"batch":{"type":"array"},"container":{"type":"object","properties":{"kind":{"type":"string"}," [...]
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","agentId":"a89d833fef85d5b44","id":"7f3905b1-9a42-4e08-850c-a790ce836a96","timestamp":"2026-10-18T03:45:14.284Z","version":"2.1.302","querySource":"agent:builtin:general-purpose","shapeHash":"r:8a7cb4473e94b8f8","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f [...]
+{"parentUuid":"d96c5162-a995-487f-8980-6e68264c8f81","isSidechain":true,"agentId":"a89d833fef85d5b44","message":{"model":"claude-opus-5-5","id":"msg_011Cg91p23SZrfw1iEn8WXJR","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01MUg45c5qvfHpTJNswh8SVi","name":"Bash","input":{"command":"r:c7b68ac37f364473","description":"r:04dde8330395d997"},"caller":{"type":"direct"}}],"container":null,"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"i [...]
+{"parentUuid":"cb0032bd-8822-4feb-83f1-72eef8beb12a","isSidechain":true,"promptId":"ecc355f5-464c-4ac2-b8d3-504d499562ac","agentId":"a89d833fef85d5b44","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01MUg45c5qvfHpTJNswh8SVi","type":"tool_result","content":"r:e39538e7f27a7bf5","is_error":false}]},"uuid":"71f37750-f84b-45b4-9e3e-524d486ed211","timestamp":"2026-10-18T03:45:19.652Z","sourceToolAssistantUUID":"cb0032bd-8822-4feb-83f1-72eef8beb12a","permissionDecision" [...]
+{"parentUuid":"71f37750-f84b-45b4-9e3e-524d486ed211","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"prompt_snapshot","systemPrompt":["r:2bdba397170e83b7","r:79ef93928642628c","r:5145202c6f78f0e3","r:2b7db5539346e950"],"tools":[{"name":"r:11b39c93777e8f1f","description":"r:d14609f3da1c0f3e","schema":{"name":"Agent","description":"r:d14609f3da1c0f3e","input_schema":{"$schema":"r:206ae48f6cae3f87","type":"object","properties":{"description":{"description":"r:dd0bb2ec [...]
+{"parentUuid":"c6b90365-e2bc-40d0-9e06-50f25a87fe40","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"deferred_tools_delta","addedNames":[],"addedLines":[],"removedNames":[],"wireHiddenNames":[],"readdedNames":[],"pendingMcpServers":[],"needsAuthMcpServers":["r:38ee17f662de8e6d","r:b518f4321f6bc175","r:31fb2ebd5d87f409","r:fdab4502edacde58","r:0fc3f8d43bb42355","r:b7e8770733015d47","r:a9b66d0da3f97a7b","r:9f16b45bea914d46","r:5004593229cba888","r:144428cb6cf26e38"], [...]
+{"parentUuid":"67ec3763-d105-4d6a-89c9-aacb2a87eac5","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"agent_listing_delta","addedTypes":["r:c857d09db23e6822","r:3b73900b8d29f963","r:db45ca5bb40691a7","r:fa8ed0bdabdd6bcb","r:7150d1ed23874efb"],"addedLines":["r:e6e122752bd144ad","r:535d81bfe2a6d10b","r:bd0eb8354b8ecd93","r:a19eeace5ca7decf","r:668c3c654c6cf099"],"builtInTypes":["r:c857d09db23e6822","r:3b73900b8d29f963","r:db45ca5bb40691a7","r:fa8ed0bdabdd6bcb","r:7150 [...]
+{"parentUuid":"d1feca5e-ac5a-41c7-92a9-bfc5ba6712a2","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"mcp_instructions_delta","addedNames":["r:380331632fbf1877"],"addedBlocks":["r:8ed75c59eceec373"],"removedNames":[]},"type":"attachment","uuid":"18b05fcd-a5d6-4513-bfc4-73c2abfd0b6a","timestamp":"2026-10-18T03:45:19.655Z","rendered":[{"content":"r:906d9926e516c792"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sess [...]
+{"parentUuid":"18b05fcd-a5d6-4513-bfc4-73c2abfd0b6a","isSidechain":true,"agentId":"a89d833fef85d5b44","attachment":{"type":"total_tokens_reminder","text":"r:8e1848807ba5824a"},"type":"attachment","uuid":"0a2580a9-74c4-42e1-9e6e-f49d843e941b","timestamp":"2026-10-18T03:45:19.655Z","rendered":[{"content":"r:ee29000f3f88f099"}],"renderedRole":"system","userType":"external","entrypoint":"sdk-cli","cwd":"r:b5231b0539a2a70b","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","version":"2.1.302 [...]
+{"type":"api-request-blob","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","agentId":"a89d833fef85d5b44","hash":"r:9bce8777498e3153","message":{"role":"system","content":[{"type":"text","text":"r:8b4cbfa16898b5e0"}]}}
+{"type":"api-request","sessionId":"2bb28d1c-72a7-4260-a010-f439329e4e76","agentId":"a89d833fef85d5b44","id":"80bedfee-cbba-40b0-a2f2-eb5d36899b2d","timestamp":"2026-10-18T03:45:19.660Z","version":"2.1.302","querySource":"agent:builtin:general-purpose","shapeHash":"r:8a7cb4473e94b8f8","params":{"model":"claude-opus-5-5","betas":["r:baaa2a02279082f4","r:e235d6d0664ee21f","r:577ac1adb0169042","r:e2883688d3d2f9f8","r:e6e336c68ff4c60b","r:f20a313eea75bcd9","r:2adf59b0a6417af2","r:9df70b591a3f [...]
+{"parentUuid":"0a2580a9-74c4-42e1-9e6e-f49d843e941b","isSidechain":true,"agentId":"a89d833fef85d5b44","message":{"model":"claude-opus-5-5","id":"msg_011Cg91pJSvmdsw719ac8Cfw","type":"message","role":"assistant","content":[{"type":"text","text":"r:6fb187f0e6bfcd8f"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":1943,"cache_read_input_tokens":25222,"output_tokens":22,"output_tokens_details":{"thin [...]
diff --git a/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76/subagents/agent-a89d833fef85d5b44.meta.json b/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76/subagents/agent-a89d833fef85d5b44.meta.json
new file mode 100644
index 0000000..87543f7
--- /dev/null
+++ b/internal/adapters/claudecodeprovider/testdata/threads/projects/-work/2bb28d1c-72a7-4260-a010-f439329e4e76/subagents/agent-a89d833fef85d5b44.meta.json
@@ -0,0 +1 @@
+{"agentType":"general-purpose","description":"r:ba030c42177005fb","toolUseId":"toolu_01DvNo1kzGJ7yP9mRYoscRCz","spawnDepth":1,"requestShape":"r:4215153956b54075","requestNonInteractive":true,"finished":true}
\ No newline at end of file
diff --git a/internal/adapters/kiro/glossary.go b/internal/adapters/kiro/glossary.go
index d5b7167..537d6f9 100644
--- a/internal/adapters/kiro/glossary.go
+++ b/internal/adapters/kiro/glossary.go
@@ -126,6 +126,9 @@ var glossary = model.NewGlossary(Dialect,
 	model.Term{Unified: model.TriggerExternal, Native: "user", Where: "messages.jsonl"},
 	model.Term{Unified: model.TriggerNotification, Note: "no equivalent"},
 	model.Term{Unified: model.TriggerUnknown, Note: "derived"},
+
+	model.Term{Unified: model.ClassTurnCall, Note: "no equivalent: Kiro records no model request, so no call's history is compared"},
+	model.Term{Unified: model.ClassInternalCall, Note: "no equivalent: Kiro records no model request, so no call's history is compared"},
 )
 
 // Glossary is what this runtime calls the things the model names.
diff --git a/internal/adapters/langsmith/bodies.go b/internal/adapters/langsmith/bodies.go
index 7171841..63f3579 100644
--- a/internal/adapters/langsmith/bodies.go
+++ b/internal/adapters/langsmith/bodies.go
@@ -222,7 +222,12 @@ func (c *Collector) landBodies(session string, state *storage.SessionState,
 		case providerbody.RoleResponse:
 			keys.Call, keys.Request = b.run.ID, b.run.ID
 		}
-		rec, err := held.Encode(providerbody.Body{ID: id, Role: b.role, Src: src, Keys: keys, Bytes: b.bytes})
+		h := historyOf(b.role, b.bytes)
+		if b.role == providerbody.RoleResponse {
+			h.Reply = eligibleReply(held, b.run.ID, h.Reply)
+		}
+		rec, err := held.Encode(providerbody.Body{ID: id, Role: b.role, Src: src, Keys: keys, Bytes: b.bytes,
+			History: h})
 		switch {
 		case errors.Is(err, providerbody.ErrRepeat):
 			out.repeats++
diff --git a/internal/adapters/langsmith/collect.go b/internal/adapters/langsmith/collect.go
index c515aa5..a4613b3 100644
--- a/internal/adapters/langsmith/collect.go
+++ b/internal/adapters/langsmith/collect.go
@@ -325,6 +325,7 @@ func (c *Collector) convertWith(request Waiting, open *pending, wait bool) (Land
 	runs := make([]Run, len(operations))
 	sessions := make([]string, len(operations))
 	methods := make([]string, len(operations))
+	classes := make([]string, len(operations))
 	// One run is one conversation. Its first arrival in this request
 	// decides, and a later arrival of the same run follows it, so one run is
 	// never split across two; a later arrival that names a conversation
@@ -344,6 +345,7 @@ func (c *Collector) convertWith(request Waiting, open *pending, wait bool) (Land
 		completed, known, found := open.complete(envelope)
 		runs[i] = completed
 		methods[i] = known.Method
+		classes[i] = known.Class
 
 		// Which conversation this belongs to, resolved here rather than
 		// later, because a start and an update to it can be in one batch and
@@ -386,7 +388,7 @@ func (c *Collector) convertWith(request Waiting, open *pending, wait bool) (Land
 		if unsupplied[decides] || partial[decides] {
 			remembered = ""
 		}
-		open.started(completed, methodOf(op), remembered, c.Now())
+		open.started(completed, methodOf(op), callClassOf(op), remembered, c.Now())
 		if partial[decides] {
 			open.partial(op.RunID)
 		}
@@ -513,7 +515,7 @@ func (c *Collector) convertWith(request Waiting, open *pending, wait bool) (Land
 		if retired[i] {
 			// Remembered in its conversation, so a later arrival of it, or
 			// of a run inside it, is known to be retired too.
-			open.started(envelope, methodOf(op), session, c.Now())
+			open.started(envelope, methodOf(op), callClassOf(op), session, c.Now())
 			if !slices.Contains(out.Retired, session) {
 				out.Retired = append(out.Retired, session)
 			}
@@ -523,8 +525,8 @@ func (c *Collector) convertWith(request Waiting, open *pending, wait bool) (Land
 		// update carries none of this. So a run is remembered whether or not
 		// it has finished, and is forgotten only by age. Forgetting it here
 		// lost its start to any replay of this request.
-		open.started(envelope, methodOf(op), session, c.Now())
-		records, err := ConvertRun(op, envelope, methods[i], op.Op == "post", hints)
+		open.started(envelope, methodOf(op), callClassOf(op), session, c.Now())
+		records, err := ConvertRun(op, envelope, methods[i], classes[i], op.Op == "post", hints)
 		if err != nil {
 			return out, &unreadable{fmt.Errorf("run %s: %w", op.RunID, err)}
 		}
diff --git a/internal/adapters/langsmith/dialect.go b/internal/adapters/langsmith/dialect.go
index a2f435c..81c4670 100644
--- a/internal/adapters/langsmith/dialect.go
+++ b/internal/adapters/langsmith/dialect.go
@@ -120,6 +120,9 @@ type decoded struct {
 	inputs   json.RawMessage
 	outputs  json.RawMessage
 	metadata map[string]any
+	// class is a model call's class, internal_call or turn_call, or "" when
+	// no start of the run said.
+	class string
 	// alone says this request held the whole trace and no model call in it.
 	// Then nothing else can be carrying what this run's fields hold.
 	alone bool
@@ -234,19 +237,26 @@ func Convert(op Operation, firstOfTrace bool, hints Hints) ([]sessiondata.Record
 	if err := json.Unmarshal(op.Envelope, &envelope); err != nil {
 		return nil, err
 	}
-	return ConvertRun(op, envelope, "", firstOfTrace, hints)
+	return ConvertRun(op, envelope, "", "", firstOfTrace, hints)
 }
 
 // ConvertRun is Convert with the run already read, and with whatever an
 // earlier arrival supplied that this one left out. An update carries only
 // what changed, so on its own it says neither what kind of run it is nor
 // which conversation it belongs to.
-func ConvertRun(op Operation, run Run, method string, firstOfTrace bool, hints Hints) ([]sessiondata.Record, error) {
+//
+// class is the class the run's start gave it, when this arrival is an update;
+// a start classifies itself.
+func ConvertRun(op Operation, run Run, method, class string, firstOfTrace bool, hints Hints) ([]sessiondata.Record, error) {
 	d, err := decodeOperation(op)
 	if err != nil {
 		return nil, err
 	}
 	d.run = run
+	d.class = class
+	if own := callClassOf(op); own != "" {
+		d.class = own
+	}
 	// An update carries no metadata, so how the run was traced comes from
 	// what its own start said.
 	if method != "" {
@@ -467,6 +477,9 @@ func modelCall(base sessiondata.Record, d decoded) sessiondata.Record {
 	base.From = sessiondata.FromAgent
 	base.Time = timeOf(d)
 	base.Model, _ = d.metadata["ls_model_name"].(string)
+	if d.class != "" {
+		base.Flags = append(base.Flags, d.class)
+	}
 	if d.run.Finished() {
 		base.Flags = append(base.Flags, "finished")
 		message := generationOf(d.outputs)
@@ -495,8 +508,8 @@ func modelCall(base sessiondata.Record, d decoded) sessiondata.Record {
 // every reader. A record named this way may hold more than the system prompt,
 // such as the person's message, which lands on its own record as well.
 //
-// Measured on the 72 model arrivals of the captured corpus: every one sent
-// its inputs out of band, and the 43 that offered tools sent them in an
+// Measured on the 80 model arrivals of the captured corpus: every one sent
+// its inputs out of band, and the 46 that offered tools sent them in an
 // out-of-band extra, so no landed envelope carried either. The envelope
 // carries a field only when the client did not split it out.
 func requestFlags(envelope json.RawMessage) []string {
diff --git a/internal/adapters/langsmith/glossary.go b/internal/adapters/langsmith/glossary.go
index b3d4bdb..58e8df6 100644
--- a/internal/adapters/langsmith/glossary.go
+++ b/internal/adapters/langsmith/glossary.go
@@ -128,6 +128,11 @@ var glossary = model.NewGlossary(Dialect,
 	model.Term{Unified: model.TriggerExternal, Note: "the root run of a trace carries the message that opened it"},
 	model.Term{Unified: model.TriggerNotification, Note: "no equivalent"},
 	model.Term{Unified: model.TriggerUnknown, Note: "derived"},
+
+	model.Term{Unified: model.ClassTurnCall,
+		Note: "no equivalent: a run of type llm whose start carries no lc_internal_call"},
+	model.Term{Unified: model.ClassInternalCall, Native: "extra.metadata.lc_internal_call", Where: "the start of a run of type llm",
+		Note: "LangChain sets it on a model call it makes for its own purpose, such as SummarizationMiddleware's summary"},
 )
 
 // Glossary is what this runtime calls the things the model names.
diff --git a/internal/adapters/langsmith/history.go b/internal/adapters/langsmith/history.go
new file mode 100644
index 0000000..bc18ec0
--- /dev/null
+++ b/internal/adapters/langsmith/history.go
@@ -0,0 +1,199 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package langsmith
+
+import (
+	"encoding/json"
+
+	"github.com/apache/skywalking-ai-sessionizer/pkg/model"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/providerbody"
+)
+
+// historyOf reads what a body says about its call's history, in the form
+// langchain/1 (docs/en/formats/session-data.md).
+//
+// A message is compared as LangChain serialized it, with nothing left out.
+// Which fields reach the provider depends on the provider's adapter and its
+// mode, which a trace does not say, and leaving out a field one of them sends
+// hides a change. Measured on the twenty capture directories under
+// tests/apps/langchain/testdata: every one of 80 message ids sent more than
+// once has one form. A message is read only in the constructor form, the only
+// one a model call's inputs held there; its class is the last element of its
+// path, since Python and JavaScript write the path differently, and a chunk
+// class keeps its own name, as a loop over model.stream() sends it back.
+func historyOf(role string, body []byte) *providerbody.History {
+	h := &providerbody.History{Form: providerbody.FormLangChain}
+	v, err := providerbody.Decode(body)
+	head, ok := v.(map[string]any)
+	if err != nil || !ok {
+		h.Unavailable = providerbody.UnrecognisedShape
+		if role == providerbody.RoleResponse {
+			h.Reply = &providerbody.Reply{Unavailable: providerbody.UnrecognisedShape}
+		}
+		return h
+	}
+	switch role {
+	case providerbody.RoleRequest:
+		h.Messages, h.Unavailable = requestOf(head)
+	case providerbody.RoleResponse:
+		h.Reply = replyOf(head)
+	}
+	return h
+}
+
+// requestOf reads a model call's inputs: exactly one prompt, every message in
+// the constructor form.
+func requestOf(inputs map[string]any) ([]providerbody.Entry, string) {
+	prompts, ok := inputs["messages"].([]any)
+	if !ok {
+		return nil, providerbody.UnrecognisedShape
+	}
+	if len(prompts) != 1 {
+		return nil, providerbody.PluralPrompts
+	}
+	list, ok := prompts[0].([]any)
+	if !ok {
+		return nil, providerbody.PluralPrompts
+	}
+	entries := make([]providerbody.Entry, 0, len(list))
+	for _, m := range list {
+		e, ok := messageEntry(m)
+		if !ok {
+			return nil, providerbody.UnrecognisedShape
+		}
+		entries = append(entries, e)
+	}
+	return entries, ""
+}
+
+// replyOf reads a model call's outputs: exactly one generation list holding
+// exactly one generation, whose message is the reply.
+func replyOf(outputs map[string]any) *providerbody.Reply {
+	gens, ok := outputs["generations"].([]any)
+	if !ok || len(gens) != 1 {
+		return &providerbody.Reply{Unavailable: providerbody.NoSingleReply}
+	}
+	list, ok := gens[0].([]any)
+	if !ok || len(list) != 1 {
+		return &providerbody.Reply{Unavailable: providerbody.NoSingleReply}
+	}
+	gen, ok := list[0].(map[string]any)
+	if !ok {
+		return &providerbody.Reply{Unavailable: providerbody.NoSingleReply}
+	}
+	e, ok := messageEntry(gen["message"])
+	if !ok {
+		return &providerbody.Reply{Unavailable: providerbody.UnrecognisedShape}
+	}
+	return &providerbody.Reply{ID: e.ID, Digest: e.Digest}
+}
+
+// messageEntry is the entry of one constructor-form message: {"lc": 1, "type":
+// "constructor", "id": [..., class], "kwargs": {...}}, read as its class and its
+// kwargs without the id. Every field's type is checked before it is read: an
+// "lc" of true is not 1.
+func messageEntry(raw any) (providerbody.Entry, bool) {
+	m, ok := raw.(map[string]any)
+	if !ok || m["type"] != "constructor" {
+		return providerbody.Entry{}, false
+	}
+	if lc, ok := m["lc"].(json.Number); !ok || string(lc) != "1" {
+		return providerbody.Entry{}, false
+	}
+	path, ok := m["id"].([]any)
+	if !ok || len(path) == 0 {
+		return providerbody.Entry{}, false
+	}
+	for _, p := range path {
+		if _, ok := p.(string); !ok {
+			return providerbody.Entry{}, false
+		}
+	}
+	kwargs, ok := m["kwargs"].(map[string]any)
+	if !ok {
+		return providerbody.Entry{}, false
+	}
+	kept := make(map[string]any, len(kwargs))
+	for k, v := range kwargs {
+		if k != "id" {
+			kept[k] = v
+		}
+	}
+	id, hasID := kwargs["id"].(string)
+	if raw, present := kwargs["id"]; present && raw != nil && !hasID {
+		return providerbody.Entry{}, false
+	}
+	form, err := providerbody.Canonical(map[string]any{"class": path[len(path)-1], "kwargs": kept})
+	if err != nil {
+		return providerbody.Entry{}, false
+	}
+	return providerbody.EntryOf(id, hasID, form), true
+}
+
+// The class of a model call, as a record's flag. A model call LangChain makes
+// for its own purpose, such as SummarizationMiddleware's summary, carries
+// lc_internal_call in its run's metadata (tests/apps/langchain/testdata/
+// summarized), and takes no part in the history of its stream.
+const (
+	classInternal = model.ClassInternalCall
+	classTurn     = model.ClassTurnCall
+)
+
+// callClassOf is the class a run's start gives a model call: internal when its
+// metadata carries lc_internal_call, a turn otherwise, as a start with no
+// metadata at all is. Only a start carries the whole metadata, so an update
+// classifies nothing: a key it lacks says nothing.
+func callClassOf(op Operation) string {
+	if op.Op != "post" {
+		return ""
+	}
+	var extra struct {
+		Metadata map[string]json.RawMessage `json:"metadata"`
+	}
+	if raw, ok := op.Fields["extra"]; ok {
+		_ = json.Unmarshal(raw, &extra)
+	} else {
+		var inline map[string]json.RawMessage
+		if json.Unmarshal(op.Envelope, &inline) == nil {
+			if raw, ok := inline["extra"]; ok {
+				_ = json.Unmarshal(raw, &extra)
+			}
+		}
+	}
+	if _, ok := extra.Metadata["lc_internal_call"]; ok {
+		return classInternal
+	}
+	return classTurn
+}
+
+// eligibleReply is a response's reply only when its run's request landed and
+// read as exactly one prompt: a generation answers the prompts it was asked,
+// and the answer to several prompts, or to a request no rule could read, is
+// not the reply to a history. A list too large still read as one prompt. The
+// request lands from the run's first arrival that carries inputs, before or
+// with the response, in this pass or an earlier one, so the session holds it.
+func eligibleReply(held *providerbody.Session, run string, reply *providerbody.Reply) *providerbody.Reply {
+	m, ok := held.Manifest(run + ":request")
+	switch {
+	case !ok || m.Messages == nil:
+		return &providerbody.Reply{Unavailable: providerbody.NoRequest}
+	case m.Messages.Unavailable == providerbody.PluralPrompts || m.Messages.Unavailable == providerbody.UnrecognisedShape:
+		return &providerbody.Reply{Unavailable: m.Messages.Unavailable}
+	}
+	return reply
+}
diff --git a/internal/adapters/langsmith/history_test.go b/internal/adapters/langsmith/history_test.go
new file mode 100644
index 0000000..e46e807
--- /dev/null
+++ b/internal/adapters/langsmith/history_test.go
@@ -0,0 +1,191 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package langsmith
+
+import (
+	"encoding/json"
+	"slices"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/apache/skywalking-ai-sessionizer/pkg/providerbody"
+)
+
+const ai = `{"lc":1,"type":"constructor","id":["langchain","schema","messages","AIMessage"],"kwargs":{"content":"hi","id":"r1"}}`
+
+func inputs(messages ...string) []byte {
+	return []byte(`{"messages":[[` + strings.Join(messages, ",") + `]]}`)
+}
+
+// TestALangChainMessageIsReadOnlyInTheFormMeasured. Every field's type is
+// checked before it is read, a shape no capture held is unavailable rather
+// than guessed at, and a chunk keeps its own class.
+func TestALangChainMessageIsReadOnlyInTheFormMeasured(t *testing.T) {
+	h := historyOf(providerbody.RoleRequest, inputs(ai))
+	if h.Form != providerbody.FormLangChain || h.Unavailable != "" || len(h.Messages) != 1 || h.Messages[0].ID == "" {
+		t.Fatalf("history %+v", h)
+	}
+	for name, body := range map[string][]byte{
+		"lc true":           inputs(`{"lc":true,"type":"constructor","id":["AIMessage"],"kwargs":{}}`),
+		"lc 1.0":            inputs(`{"lc":1.0,"type":"constructor","id":["AIMessage"],"kwargs":{}}`),
+		"a path of null":    inputs(`{"lc":1,"type":"constructor","id":[null],"kwargs":{}}`),
+		"an empty path":     inputs(`{"lc":1,"type":"constructor","id":[],"kwargs":{}}`),
+		"kwargs a list":     inputs(`{"lc":1,"type":"constructor","id":["AIMessage"],"kwargs":[]}`),
+		"an id of a number": inputs(`{"lc":1,"type":"constructor","id":["AIMessage"],"kwargs":{"id":7}}`),
+		"a plain message":   inputs(`{"type":"ai","content":"hi"}`),
+		"a message 42":      inputs(`42`),
+		"no messages":       []byte(`{"prompts":["hi"]}`),
+		"not json":          []byte(`{`),
+	} {
+		if h := historyOf(providerbody.RoleRequest, body); h.Unavailable != providerbody.UnrecognisedShape {
+			t.Fatalf("%s: %+v", name, h)
+		}
+	}
+	for name, body := range map[string][]byte{
+		"two prompts":     []byte(`{"messages":[[` + ai + `],[` + ai + `]]}`),
+		"a prompt object": []byte(`{"messages":[{}]}`),
+		"no prompt":       []byte(`{"messages":[]}`),
+	} {
+		if h := historyOf(providerbody.RoleRequest, body); h.Unavailable != providerbody.PluralPrompts {
+			t.Fatalf("%s: %+v", name, h)
+		}
+	}
+	// A null id is no id, as LangChain writes a message it gave none.
+	if h := historyOf(providerbody.RoleRequest, inputs(`{"lc":1,"type":"constructor","id":["HumanMessage"],"kwargs":{"content":"q","id":null}}`)); h.Unavailable != "" || h.Messages[0].ID != "" {
+		t.Fatalf("a null id: %+v", h)
+	}
+	chunk := historyOf(providerbody.RoleRequest, inputs(`{"lc":1,"type":"constructor","id":["langchain","schema","messages","AIMessageChunk"],"kwargs":{"content":"hi","id":"r1"}}`))
+	if chunk.Unavailable != "" || chunk.Messages[0].Digest == h.Messages[0].Digest {
+		t.Fatalf("a chunk is not read in its own class: %+v", chunk)
+	}
+	// Python and JavaScript write the path apart; the class is the same.
+	js := historyOf(providerbody.RoleRequest, inputs(`{"lc":1,"type":"constructor","id":["langchain_core","messages","AIMessage"],"kwargs":{"content":"hi","id":"r1"}}`))
+	if js.Messages[0] != h.Messages[0] {
+		t.Fatalf("the JavaScript path reads apart: %v %v", js.Messages, h.Messages)
+	}
+}
+
+// TestEveryFieldOfALangChainMessageCounts. Nothing LangChain recorded is left
+// out of a message, since which fields reach a provider depends on its adapter
+// and its mode: each pair differs in one field a provider may send, or LangChain
+// may keep for itself, and reads as a different message.
+func TestEveryFieldOfALangChainMessageCounts(t *testing.T) {
+	msg := func(class, kwargs string) string {
+		return `{"lc":1,"type":"constructor","id":["langchain","schema","messages","` + class + `"],"kwargs":` + kwargs + `}`
+	}
+	for name, pair := range map[string][2]string{
+		"the role of a chat message": {msg("ChatMessage", `{"content":"x","role":"user"}`), msg("ChatMessage", `{"content":"x","role":"assistant"}`)},
+		"a system message sent as the developer": {msg("SystemMessage", `{"content":"x"}`),
+			msg("SystemMessage", `{"content":"x","additional_kwargs":{"__openai_role__":"developer"}}`)},
+		"a function call's arguments": {msg("AIMessage", `{"content":"","additional_kwargs":{"function_call":{"name":"f","arguments":"{}"}}}`),
+			msg("AIMessage", `{"content":"","additional_kwargs":{"function_call":{"name":"f","arguments":"{\"a\":1}"}}}`)},
+		"a raw tool call under the same parsed one": {
+			msg("AIMessage", `{"content":"","tool_calls":[{"name":"f","args":{},"id":"c"}],"additional_kwargs":{"tool_calls":[{"id":"c","function":{"name":"f","arguments":"{}"}}]}}`),
+			msg("AIMessage", `{"content":"","tool_calls":[{"name":"f","args":{},"id":"c"}],"additional_kwargs":{"tool_calls":[{"id":"c","function":{"name":"f","arguments":"{ }"}}]}}`)},
+		"a tool message's artifact": {msg("ToolMessage", `{"content":"x","tool_call_id":"c","artifact":1}`),
+			msg("ToolMessage", `{"content":"x","tool_call_id":"c","artifact":2}`)},
+		"the response's id": {msg("AIMessage", `{"content":"x","response_metadata":{"id":"resp_1"}}`),
+			msg("AIMessage", `{"content":"x","response_metadata":{"id":"resp_2"}}`)},
+		"a citation in a text block": {
+			msg("AIMessage", `{"content":[{"type":"text","text":"x","annotations":[{"url":"https://a"}]}]}`),
+			msg("AIMessage", `{"content":[{"type":"text","text":"x","annotations":[{"url":"https://b"}]}]}`)},
+	} {
+		a, b := historyOf(providerbody.RoleRequest, inputs(pair[0])), historyOf(providerbody.RoleRequest, inputs(pair[1]))
+		if a.Unavailable != "" || b.Unavailable != "" || a.Messages[0] == b.Messages[0] {
+			t.Fatalf("%s reads as the same message: %+v %+v", name, a, b)
+		}
+	}
+}
+
+// TestALangChainReplyIsTheOneGenerationsMessage.
+func TestALangChainReplyIsTheOneGenerationsMessage(t *testing.T) {
+	one := historyOf(providerbody.RoleResponse, []byte(`{"generations":[[{"text":"hi","message":`+ai+`}]]}`))
+	sent := historyOf(providerbody.RoleRequest, inputs(ai))
+	if one.Reply == nil || one.Reply.Unavailable != "" || one.Reply.ID != sent.Messages[0].ID || one.Reply.Digest != sent.Messages[0].Digest {
+		t.Fatalf("reply %+v, sent %+v", one.Reply, sent.Messages[0])
+	}
+	for name, body := range map[string]string{
+		"two generations":      `{"generations":[[{"message":` + ai + `},{"message":` + ai + `}]]}`,
+		"two generation lists": `{"generations":[[{"message":` + ai + `}],[{"message":` + ai + `}]]}`,
+		"a null generation":    `{"generations":[[null]]}`,
+		"no generations":       `{"output":"hi"}`,
+		"no message":           `{"generations":[[{"text":"hi"}]]}`,
+	} {
+		if h := historyOf(providerbody.RoleResponse, []byte(body)); h.Reply == nil || h.Reply.Unavailable == "" {
+			t.Fatalf("%s: reply %+v", name, h.Reply)
+		}
+	}
+}
+
+func operation(op string, metadata map[string]any) Operation {
+	extra, _ := json.Marshal(map[string]any{"metadata": metadata})
+	return Operation{Op: op, RunID: "r", Envelope: json.RawMessage(`{"id":"r"}`), Fields: map[string]json.RawMessage{"extra": extra}}
+}
+
+// TestAModelCallsClassComesFromItsStart. Only a start carries the whole
+// metadata, so an update classifies nothing; its record carries the class
+// the start gave, held with the run, or none when the start's is not held.
+// Two starts that disagree keep internal_call, the side that compares nothing.
+func TestAModelCallsClassComesFromItsStart(t *testing.T) {
+	if c := callClassOf(operation("post", map[string]any{"lc_internal_call": "true"})); c != classInternal {
+		t.Fatalf("an internal start: %q", c)
+	}
+	if c := callClassOf(operation("post", map[string]any{"ls_model_name": "m"})); c != classTurn {
+		t.Fatalf("a start: %q", c)
+	}
+	if c := callClassOf(Operation{Op: "post", RunID: "r", Envelope: json.RawMessage(`{"id":"r"}`)}); c != classTurn {
+		t.Fatalf("a start with no metadata: %q", c)
+	}
+	if c := callClassOf(operation("patch", map[string]any{"lc_internal_call": "true"})); c != "" {
+		t.Fatalf("an update classified a call: %q", c)
+	}
+
+	now := time.Date(2026, 10, 18, 0, 0, 0, 0, time.UTC)
+	p := &pending{Runs: map[string]pendingRun{}}
+	p.started(Run{ID: "r"}, "", classTurn, "s", now)
+	p.started(Run{ID: "r"}, "", classInternal, "s", now)
+	p.started(Run{ID: "r"}, "", classTurn, "s", now)
+	p.started(Run{ID: "r"}, "", "", "s", now)
+	if c := p.Runs["r"].Class; c != classInternal {
+		t.Fatalf("the class held after a conflict: %q", c)
+	}
+
+	flagsOf := func(op Operation, class string) []string {
+		records, err := ConvertRun(op, Run{ID: "r", TraceID: "r", Type: "llm"}, "", class, false, Hints{})
+		if err != nil {
+			t.Fatal(err)
+		}
+		for _, r := range records {
+			if r.Call == "r" {
+				return r.Flags
+			}
+		}
+		t.Fatal("no model call record")
+		return nil
+	}
+	if f := flagsOf(operation("patch", nil), classInternal); !slices.Contains(f, classInternal) {
+		t.Fatalf("an update did not carry its start's class: %v", f)
+	}
+	if f := flagsOf(operation("patch", nil), ""); slices.Contains(f, classInternal) || slices.Contains(f, classTurn) {
+		t.Fatalf("an update whose start's class is not held was classified: %v", f)
+	}
+	if f := flagsOf(operation("post", map[string]any{"lc_internal_call": "true"}), classTurn); !slices.Contains(f, classInternal) || slices.Contains(f, classTurn) {
+		t.Fatalf("a start did not classify itself: %v", f)
+	}
+}
diff --git a/internal/adapters/langsmith/pending.go b/internal/adapters/langsmith/pending.go
index b3b8c91..3d7cae3 100644
--- a/internal/adapters/langsmith/pending.go
+++ b/internal/adapters/langsmith/pending.go
@@ -79,6 +79,11 @@ type pendingRun struct {
 	// update carries no metadata at all, and without this a decorated
 	// function's completion is read as a graph's.
 	Method string `json:"method,omitempty"`
+	// Class is the class the run's start gave a model call, internal_call or
+	// turn_call. Only a start carries the whole metadata, so an update lacking
+	// lc_internal_call says nothing, and without this its record would carry
+	// no class at all.
+	Class string `json:"class,omitempty"`
 	// Session is the conversation this run's evidence lands in, already
 	// resolved. It is kept rather than derived again because the update
 	// carries none of what deriving it needs.
@@ -209,7 +214,10 @@ func (p *pending) evict(now time.Time) {
 //
 // What is already known is kept when this arrival does not carry it, so a
 // start seen earlier in the same batch is not erased by the update after it.
-func (p *pending) started(run Run, method, session string, now time.Time) {
+//
+// A start's class is kept; when two starts of one run disagree, internal_call
+// is kept, the side that compares nothing.
+func (p *pending) started(run Run, method, class, session string, now time.Time) {
 	known := p.Runs[run.ID]
 	set := func(into *string, value string) {
 		if value != "" {
@@ -224,6 +232,9 @@ func (p *pending) started(run Run, method, session string, now time.Time) {
 	set(&known.Project, run.Session)
 	set(&known.Start, run.Start)
 	set(&known.Method, method)
+	if class == classInternal || (class != "" && known.Class == "") {
+		known.Class = class
+	}
 	set(&known.Session, session)
 	known.At = now.UTC().Format(time.RFC3339Nano)
 	p.Runs[run.ID] = known
diff --git a/internal/adapters/langsmith/reset.go b/internal/adapters/langsmith/reset.go
index c8c6358..f41cdd2 100644
--- a/internal/adapters/langsmith/reset.go
+++ b/internal/adapters/langsmith/reset.go
@@ -54,7 +54,7 @@ const (
 // request first carried the summary, and the summariser's own call, which read
 // the old history, lands before it. That holds when a call's request arrives
 // with its first record, which every model call in the captured corpus did
-// (71 of 71). A request that arrives after its call has begun lands its reset
+// (79 of 79). A request that arrives after its call has begun lands its reset
 // after that call's first record, and landed files are never rewritten, so
 // that one call counts in the old epoch.
 func resets(base sessiondata.Record, d decoded) []sessiondata.Record {
diff --git a/internal/index/codec.go b/internal/index/codec.go
index e14f0e1..10913c2 100644
--- a/internal/index/codec.go
+++ b/internal/index/codec.go
@@ -35,7 +35,8 @@ const (
 	entryWidth = 4 + 4 + 4 + 4 + 1 + 1 + 2 + 8 + 4*9 + 8 + 4 + 4 // 80
 	blockWidth = 4 + 4 + 1 + 4 + 4 + 4 + 4 + 4 + 4               // 33
 	// entry role request previous
-	bodyWidth = 4 + 1 + 4 + 4 // 13
+	bodyWidth    = 4 + 1 + 4 + 4 + 4 + 1 + 4 + 4 + 1 + 4*6 + 1 + 1 + 8 + 8 + 4 + 1 // 74
+	messageWidth = 1 + 8 + 8                                                       // 17
 )
 
 var le = binary.LittleEndian
@@ -117,6 +118,17 @@ func (ix *Index) Write(dir string) error {
 		}
 	}
 
+	if err := writeU32(w, uint32(len(ix.Messages))); err != nil {
+		return err
+	}
+	mbuf := make([]byte, messageWidth)
+	for i := range ix.Messages {
+		encodeMessage(mbuf, &ix.Messages[i])
+		if _, err := w.Write(mbuf); err != nil {
+			return err
+		}
+	}
+
 	if err := w.Flush(); err != nil {
 		return err
 	}
@@ -211,6 +223,25 @@ func Load(dir, session string) (ix *Index, ok bool, err error) {
 		decodeBody(b, &bodies[i])
 	}
 
+	if b, err = read(4); err != nil {
+		return nil, false, err
+	}
+	nMsg := int(le.Uint32(b))
+	messages := make([]Message, nMsg)
+	for i := 0; i < nMsg; i++ {
+		if b, err = read(messageWidth); err != nil {
+			return nil, false, err
+		}
+		decodeMessage(b, &messages[i])
+	}
+	// A body names its entries by position, so one that names past the
+	// table was written by something else; the index is built again.
+	for i := range bodies {
+		if uint64(bodies[i].MsgFirst)+uint64(bodies[i].MsgCount) > uint64(nMsg) {
+			return nil, false, nil
+		}
+	}
+
 	// Entries are in landed order, and a round's watermark takes the entries
 	// at or below it as a prefix (Through). An index whose sequences go back
 	// was extended twice over the same files; it is built again rather than
@@ -222,7 +253,7 @@ func Load(dir, session string) (ix *Index, ok bool, err error) {
 	}
 	return &Index{
 		Session: session, Strings: loadInterner(strs),
-		Entries: entries, Blocks: blocks, Bodies: bodies,
+		Entries: entries, Blocks: blocks, Bodies: bodies, Messages: messages,
 	}, true, nil
 }
 
@@ -256,6 +287,23 @@ func encodeBody(b []byte, x *Body) {
 	b[4] = byte(x.Role)
 	le.PutUint32(b[5:], x.Request)
 	le.PutUint32(b[9:], x.Previous)
+	le.PutUint32(b[13:], x.Form)
+	b[17] = byte(x.Thread)
+	le.PutUint32(b[18:], x.ThreadPrevious)
+	le.PutUint32(b[22:], x.System)
+	b[26] = byte(x.List)
+	le.PutUint32(b[27:], x.ListBase)
+	le.PutUint32(b[31:], x.ListKeep)
+	le.PutUint32(b[35:], x.MsgFirst)
+	le.PutUint32(b[39:], x.MsgCount)
+	le.PutUint32(b[43:], x.ListWhy)
+	le.PutUint32(b[47:], x.ListCount)
+	b[51] = byte(x.Reply)
+	b[52] = boolByte(x.ReplyHasID)
+	le.PutUint64(b[53:], x.ReplyID)
+	le.PutUint64(b[61:], x.ReplyDigest)
+	le.PutUint32(b[69:], x.ReplyWhy)
+	b[73] = boolByte(x.SystemUnknown)
 }
 
 func decodeBody(b []byte, x *Body) {
@@ -263,6 +311,42 @@ func decodeBody(b []byte, x *Body) {
 	x.Role = BodyRole(b[4])
 	x.Request = le.Uint32(b[5:])
 	x.Previous = le.Uint32(b[9:])
+	x.Form = le.Uint32(b[13:])
+	x.Thread = ThreadKind(b[17])
+	x.ThreadPrevious = le.Uint32(b[18:])
+	x.System = le.Uint32(b[22:])
+	x.List = ListKind(b[26])
+	x.ListBase = le.Uint32(b[27:])
+	x.ListKeep = le.Uint32(b[31:])
+	x.MsgFirst = le.Uint32(b[35:])
+	x.MsgCount = le.Uint32(b[39:])
+	x.ListWhy = le.Uint32(b[43:])
+	x.ListCount = le.Uint32(b[47:])
+	x.Reply = ReplyKind(b[51])
+	x.ReplyHasID = b[52] == 1
+	x.ReplyID = le.Uint64(b[53:])
+	x.ReplyDigest = le.Uint64(b[61:])
+	x.ReplyWhy = le.Uint32(b[69:])
+	x.SystemUnknown = b[73] == 1
+}
+
+func encodeMessage(b []byte, m *Message) {
+	b[0] = boolByte(m.HasID)
+	le.PutUint64(b[1:], m.ID)
+	le.PutUint64(b[9:], m.Digest)
+}
+
+func decodeMessage(b []byte, m *Message) {
+	m.HasID = b[0] == 1
+	m.ID = le.Uint64(b[1:])
+	m.Digest = le.Uint64(b[9:])
+}
+
+func boolByte(v bool) byte {
+	if v {
+		return 1
+	}
+	return 0
 }
 
 func decodeEntry(b []byte, e *Entry) {
diff --git a/internal/index/fromrecord.go b/internal/index/fromrecord.go
index 87243e6..6949e6e 100644
--- a/internal/index/fromrecord.go
+++ b/internal/index/fromrecord.go
@@ -18,6 +18,7 @@
 package index
 
 import (
+	"strconv"
 	"time"
 
 	"github.com/apache/skywalking-ai-sessionizer/pkg/changes"
@@ -91,9 +92,100 @@ func bodyOf(in *Interner, rec *sessiondata.Record) *Body {
 	case providerbody.RoleResponse:
 		b.Role = BodyRoleResponse
 	}
+	historyOf(in, m, b)
 	return b
 }
 
+// historyOf reads the manifest's history fields into the body. A value that
+// does not read as the format page says leaves the field unavailable rather
+// than guessed at: a digest that is not sixteen hexadecimal characters is not
+// one, whatever it holds.
+func historyOf(in *Interner, m *providerbody.Manifest, b *Body) {
+	if m.HistoryForm == "" {
+		return
+	}
+	b.Form = in.ID(m.HistoryForm)
+	if m.System != "" {
+		if _, ok := messageOf(providerbody.Entry{Digest: m.System}); ok {
+			b.System = in.ID(m.System)
+		} else {
+			b.SystemUnknown = true
+		}
+	}
+	// A thread no rule describes says the request holds something unknown,
+	// so its list cannot be read as its history.
+	threadUnknown := false
+	if t := m.Thread; t != nil {
+		switch {
+		case t.Type == providerbody.ThreadCreate:
+			b.Thread = ThreadCreate
+		case t.Type == providerbody.ThreadContinue && t.Previous != "" && len(t.Previous) <= providerbody.MaxPrevious:
+			b.Thread, b.ThreadPrevious = ThreadContinue, in.ID(t.Previous)
+		default:
+			threadUnknown = true
+		}
+	}
+	if l := m.Messages; l != nil {
+		unavailable := func(why string, count int) {
+			b.List, b.ListWhy, b.ListCount, b.messages = ListUnavailable, in.ID(why), uint32(count), ""
+		}
+		entries := l.Entries
+		switch {
+		case l.Unavailable != "":
+			unavailable(l.Unavailable, l.Count)
+		case l.Relative():
+			b.List, b.ListBase, b.ListKeep = ListRelative, in.ID(l.Base), uint32(l.Keep)
+			entries = l.Add
+		default:
+			b.List = ListWhole
+		}
+		if b.List != ListUnavailable {
+			encoded := make([]byte, 0, len(entries)*messageWidth)
+			buf := make([]byte, messageWidth)
+			for _, e := range entries {
+				msg, ok := messageOf(e)
+				if !ok {
+					unavailable(providerbody.UnrecognisedShape, 0)
+					encoded = nil
+					break
+				}
+				encodeMessage(buf, &msg)
+				encoded = append(encoded, buf...)
+			}
+			b.messages = string(encoded)
+		}
+		if threadUnknown {
+			unavailable(providerbody.UnrecognisedShape, 0)
+		}
+	}
+	if r := m.Reply; r != nil {
+		if r.Unavailable != "" {
+			b.Reply, b.ReplyWhy = ReplyUnavailable, in.ID(r.Unavailable)
+		} else if msg, ok := messageOf(providerbody.Entry{ID: r.ID, Digest: r.Digest}); ok {
+			b.Reply, b.ReplyHasID, b.ReplyID, b.ReplyDigest = ReplyEntry, msg.HasID, msg.ID, msg.Digest
+		} else {
+			b.Reply, b.ReplyWhy = ReplyUnavailable, in.ID(providerbody.UnrecognisedShape)
+		}
+	}
+}
+
+// messageOf turns an entry's two digests into numbers.
+func messageOf(e providerbody.Entry) (Message, bool) {
+	d, err := strconv.ParseUint(e.Digest, 16, 64)
+	if err != nil || len(e.Digest) != 16 {
+		return Message{}, false
+	}
+	msg := Message{Digest: d}
+	if e.ID != "" {
+		id, err := strconv.ParseUint(e.ID, 16, 64)
+		if err != nil || len(e.ID) != 16 {
+			return Message{}, false
+		}
+		msg.HasID, msg.ID = true, id
+	}
+	return msg, true
+}
+
 // batchOf takes the batch from the record when it names one, and from the file
 // otherwise.
 //
@@ -155,19 +247,21 @@ func triggerOf(t string) Trigger {
 // flagNames is the mapping the adapter writes and the index reads back. It is
 // one table so the two cannot disagree.
 var flagNames = map[string]Flags{
-	"synthetic":      FlagSynthetic,
-	"context_reset":  FlagEpochBoundary,
-	"reset_summary":  FlagEpochSummary,
-	"finished":       FlagStopReason,
-	"external_input": FlagExternalInput,
-	"injected":       FlagInjection,
-	"child_result":   FlagChildResult,
-	"error":          FlagError,
-	"launch_ack":     FlagLaunchAck,
-	"turn_duration":  FlagTurnDuration,
-	"command":        FlagCommand,
-	"notice":         FlagNotice,
-	"auxiliary":      FlagAuxiliary,
+	"synthetic":             FlagSynthetic,
+	"context_reset":         FlagEpochBoundary,
+	"reset_summary":         FlagEpochSummary,
+	"finished":              FlagStopReason,
+	"external_input":        FlagExternalInput,
+	"injected":              FlagInjection,
+	"child_result":          FlagChildResult,
+	"error":                 FlagError,
+	"launch_ack":            FlagLaunchAck,
+	"turn_duration":         FlagTurnDuration,
+	"command":               FlagCommand,
+	"notice":                FlagNotice,
+	"auxiliary":             FlagAuxiliary,
+	model.ClassInternalCall: FlagInternalCall,
+	model.ClassTurnCall:     FlagTurnCall,
 }
 
 // FlagName returns the written form of one flag.
diff --git a/internal/index/index.go b/internal/index/index.go
index da2a5ee..b2024ef 100644
--- a/internal/index/index.go
+++ b/internal/index/index.go
@@ -33,7 +33,7 @@ package index
 
 // Schema is the on-disk index version. Bump it when Entry, Block or Body
 // changes; a mismatch discards the index and rebuilds rather than migrating.
-const Schema = 16
+const Schema = 17
 
 // Kind classifies a record without reading it.
 type Kind uint8
@@ -130,6 +130,14 @@ const (
 	// call that opened it stays a tool step. Set by the adapter from what it
 	// saw directly under the tool; never inferred here.
 	FlagAuxiliary
+	// FlagInternalCall marks a model call the framework made for its own
+	// purpose, which takes no part in the history of its stream, and
+	// FlagTurnCall one that does. Set by the adapter from what the call's start
+	// said. A runtime whose every call is a turn sets neither, as Claude Code
+	// does; a call of a runtime that sets them and carries neither has an
+	// unknown class, and is never compared.
+	FlagInternalCall
+	FlagTurnCall
 )
 
 // Has reports whether every bit in f is set.
@@ -246,6 +254,80 @@ type Body struct {
 	// whose runtime knew its call carries that on its entry's Call, as a
 	// response does, and joins by it.
 	Previous uint32
+
+	// What the manifest says about the call's history (pkg/providerbody,
+	// history.go), so the assembler compares calls from the index alone. Form
+	// is the interned name of the rules the fields were written under; zero
+	// for a body landed before asz wrote them, which is never compared.
+	Form uint32
+	// Thread is the provider thread a request starts or continues, and
+	// ThreadPrevious the interned message id a continue request names.
+	Thread         ThreadKind
+	ThreadPrevious uint32
+	// System is the interned digest of a request's system prompt without its
+	// bookkeeping; zero when the request carries nothing else. SystemUnknown
+	// says the manifest named one that is not a digest, so the prompt is not
+	// known, which is not the same as there being none.
+	System        uint32
+	SystemUnknown bool
+	// List says how a request's message list is written. Its entries are
+	// Messages[MsgFirst:MsgFirst+MsgCount]: the whole list, or what follows
+	// the first ListKeep entries of the list of the record ListBase names.
+	// ListWhy and ListCount say why a list is unavailable, interned, and how
+	// many messages a list too large held.
+	List               ListKind
+	ListBase, ListKeep uint32
+	MsgFirst, MsgCount uint32
+	ListWhy, ListCount uint32
+	// Reply is a response's message, its digests as numbers, or ReplyWhy,
+	// interned, says why there is none.
+	Reply                ReplyKind
+	ReplyHasID           bool
+	ReplyID, ReplyDigest uint64
+	ReplyWhy             uint32
+
+	// messages carries a request's entries from FromRecord to AppendRecord,
+	// which moves them into Index.Messages and clears it. They are encoded
+	// as the index file holds them, in a string, so two bodies compare by
+	// what they hold.
+	messages string
+}
+
+// ThreadKind is the provider thread a request names.
+type ThreadKind uint8
+
+const (
+	ThreadNone ThreadKind = iota
+	ThreadCreate
+	ThreadContinue
+)
+
+// ListKind is how a request's message list is written.
+type ListKind uint8
+
+const (
+	ListAbsent ListKind = iota
+	ListWhole
+	ListRelative
+	ListUnavailable
+)
+
+// ReplyKind is what a response says of its reply.
+type ReplyKind uint8
+
+const (
+	ReplyAbsent ReplyKind = iota
+	ReplyEntry
+	ReplyUnavailable
+)
+
+// Message is one entry of a request's message list: the digests of the id
+// the runtime sent, when it sent one, and of the message's form, as the
+// numbers their sixteen hexadecimal characters spell.
+type Message struct {
+	HasID  bool
+	ID     uint64
+	Digest uint64
 }
 
 // BlockKind classifies a joinable content block.
diff --git a/internal/index/index_test.go b/internal/index/index_test.go
index 8e39798..070e708 100644
--- a/internal/index/index_test.go
+++ b/internal/index/index_test.go
@@ -386,3 +386,99 @@ func TestALongLabelAndManyPartsSurviveTheRoundTrip(t *testing.T) {
 		}
 	}
 }
+
+// TestAProviderBodysHistorySurvivesTheRoundTrip. What a manifest says of its
+// call's history is indexed as it was written: a list whole or against a
+// base, a list that is unavailable and why, a thread, a system digest and a
+// reply. Through keeps the entries of the bodies it keeps, and no others.
+func TestAProviderBodysHistorySurvivesTheRoundTrip(t *testing.T) {
+	const a, b, c = "0000000000000001", "0000000000000002", "00000000000000ff"
+	manifest := func(fields map[string]any) json.RawMessage {
+		m := map[string]any{"schema": "provider_body/1", "sha256": "d", "bytes": 2, "depth": 0,
+			"segments": []any{map[string]any{"lit": "{}"}}, "history_form": "langchain/1"}
+		for k, v := range fields {
+			m[k] = v
+		}
+		raw, err := json.Marshal(m)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return raw
+	}
+	records := []map[string]any{
+		{"role": "request", "system": "5555555555555555", "thread": map[string]any{"type": "create"},
+			"messages": map[string]any{"entries": []any{map[string]any{"id": c, "digest": a}, map[string]any{"digest": b}}}},
+		{"role": "response", "reply": map[string]any{"id": c, "digest": b}},
+		{"role": "request", "thread": map[string]any{"type": "continue", "previous": "msg_1"},
+			"messages": map[string]any{"base": "r1", "keep": 2, "add": []any{map[string]any{"digest": c}}}},
+		{"role": "request", "messages": map[string]any{"unavailable": "too_large", "count": 9}},
+		{"role": "response", "reply": map[string]any{"unavailable": "no_single_reply"}},
+		{"role": "request", "messages": map[string]any{"entries": []any{map[string]any{"digest": "not a digest"}}}},
+		{"role": "request", "thread": map[string]any{"type": "fork"}, "messages": map[string]any{"entries": []any{}}},
+		{"role": "request", "messages": map[string]any{"base": "r1", "keep": 4294967297, "add": []any{}}},
+		{"role": "request", "system": "garbage", "messages": map[string]any{"entries": []any{}}},
+	}
+	hdr := &sessiondata.Header{Kind: sessiondata.KindProviderBody, Session: "sess-1"}
+	ix := index.New("sess-1")
+	for i, fields := range records {
+		rec := &sessiondata.Record{Ord: uint64(i + 1), ID: "r" + string(rune('1'+i)),
+			Parts: []sessiondata.Part{{Kind: sessiondata.PartData, Data: manifest(fields)}}}
+		e, blocks, body := index.FromRecord(ix, hdr, rec, uint32(i+1), 1)
+		ix.AppendRecord(e, blocks, body)
+	}
+	dir := t.TempDir()
+	if err := ix.Write(dir); err != nil {
+		t.Fatal(err)
+	}
+	got, ok, err := index.Load(dir, "sess-1")
+	if err != nil || !ok {
+		t.Fatalf("Load: ok=%v err=%v", ok, err)
+	}
+	s := got.Strings.String
+	first, reply, cont, large, noReply, bad := got.Bodies[0], got.Bodies[1], got.Bodies[2], got.Bodies[3], got.Bodies[4], got.Bodies[5]
+	if s(first.Form) != "langchain/1" || first.Thread != index.ThreadCreate || s(first.System) != "5555555555555555" ||
+		first.List != index.ListWhole || first.MsgCount != 2 {
+		t.Fatalf("the first request: %+v", first)
+	}
+	msgs := got.Messages[first.MsgFirst : first.MsgFirst+first.MsgCount]
+	if !msgs[0].HasID || msgs[0].ID != 0xff || msgs[0].Digest != 1 || msgs[1].HasID || msgs[1].Digest != 2 {
+		t.Fatalf("the first request's entries: %+v", msgs)
+	}
+	if reply.Reply != index.ReplyEntry || !reply.ReplyHasID || reply.ReplyID != 0xff || reply.ReplyDigest != 2 {
+		t.Fatalf("the reply: %+v", reply)
+	}
+	if cont.Thread != index.ThreadContinue || s(cont.ThreadPrevious) != "msg_1" || cont.List != index.ListRelative ||
+		s(cont.ListBase) != "r1" || cont.ListKeep != 2 || cont.MsgCount != 1 || got.Messages[cont.MsgFirst].Digest != 0xff {
+		t.Fatalf("the continue request: %+v", cont)
+	}
+	if large.List != index.ListUnavailable || s(large.ListWhy) != "too_large" || large.ListCount != 9 || large.MsgCount != 0 {
+		t.Fatalf("the list too large: %+v", large)
+	}
+	if noReply.Reply != index.ReplyUnavailable || s(noReply.ReplyWhy) != "no_single_reply" {
+		t.Fatalf("the response with no reply: %+v", noReply)
+	}
+	// A manifest whose list does not read is indexed, with the list unavailable.
+	if bad.List != index.ListUnavailable || s(bad.ListWhy) != "unrecognised_shape" || bad.MsgCount != 0 {
+		t.Fatalf("an unreadable list: %+v", bad)
+	}
+	// A thread no rule describes, and a list keeping more entries than an
+	// index row can count, are unavailable too, never a list read another way.
+	for _, b := range got.Bodies[6:8] {
+		if b.List != index.ListUnavailable || s(b.ListWhy) != "unrecognised_shape" || b.Thread != index.ThreadNone {
+			t.Fatalf("an unknown thread or an overflowing keep: %+v", b)
+		}
+	}
+	// A system that is not a digest is not known, which is not the same as none.
+	if b := got.Bodies[8]; b.System != 0 || !b.SystemUnknown {
+		t.Fatalf("a system that is not a digest: %+v", b)
+	}
+	if first.SystemUnknown {
+		t.Fatalf("a digest was taken for none: %+v", first)
+	}
+	if cut := got.Through(2); len(cut.Messages) != 2 {
+		t.Fatalf("through the first two bodies: %d entries, want the first request's 2", len(cut.Messages))
+	}
+	if len(got.Messages) != 3 {
+		t.Fatalf("%d entries in all, want 3", len(got.Messages))
+	}
+}
diff --git a/internal/index/lookup.go b/internal/index/lookup.go
index 9715570..375b564 100644
--- a/internal/index/lookup.go
+++ b/internal/index/lookup.go
@@ -32,6 +32,9 @@ type Index struct {
 	Blocks  []Block
 	// Bodies holds the provider bodies among the entries, in landed order.
 	Bodies []Body
+	// Messages holds the entries of the bodies' message lists, in the order
+	// their bodies landed; a body names its own with MsgFirst and MsgCount.
+	Messages []Message
 
 	byRecord    map[uint32]int32
 	byMsg       map[uint32][]int32
@@ -54,6 +57,14 @@ func New(session string) *Index {
 func (ix *Index) AppendRecord(e Entry, blocks []Block, body *Body) {
 	if body != nil {
 		body.Entry = uint32(len(ix.Entries))
+		body.MsgFirst = uint32(len(ix.Messages))
+		for b := body.messages; len(b) >= messageWidth; b = b[messageWidth:] {
+			var m Message
+			decodeMessage([]byte(b[:messageWidth]), &m)
+			ix.Messages = append(ix.Messages, m)
+		}
+		body.MsgCount = uint32(len(ix.Messages)) - body.MsgFirst
+		body.messages = ""
 		ix.Bodies = append(ix.Bodies, *body)
 	}
 	ix.Append(e, blocks...)
@@ -121,9 +132,15 @@ func (ix *Index) Through(seq uint64) *Index {
 		blocks = int(last.BlockFirst + last.BlockCount)
 	}
 	bodies := sort.Search(len(ix.Bodies), func(i int) bool { return int(ix.Bodies[i].Entry) >= k })
+	messages := 0
+	if bodies > 0 {
+		last := ix.Bodies[bodies-1]
+		messages = int(last.MsgFirst + last.MsgCount)
+	}
 	return &Index{
 		Session: ix.Session, Strings: ix.Strings,
 		Entries: ix.Entries[:k:k], Blocks: ix.Blocks[:blocks:blocks], Bodies: ix.Bodies[:bodies:bodies],
+		Messages: ix.Messages[:messages:messages],
 	}
 }
 
diff --git a/pkg/model/term.go b/pkg/model/term.go
index b21e6eb..1c5dd99 100644
--- a/pkg/model/term.go
+++ b/pkg/model/term.go
@@ -133,9 +133,20 @@ func Vocabulary() []string {
 		ContentAvailable, ContentRedacted, ContentOmitted, ContentHashOnly,
 		ContentSizeOnly, ContentTruncated, ContentUnavailable, ContentTooLarge, ContentRemoved,
 		TriggerExternal, TriggerNotification, TriggerUnknown,
+		// the class of a model call
+		ClassTurnCall, ClassInternalCall,
 	}
 }
 
+// The class of a model call: a turn of its stream, whose history the next
+// call of the stream goes on from, or a call the framework made for its own
+// purpose, such as a summary, which takes no part in that history. A record
+// carries one as a flag, and a call that cannot be compared names it.
+const (
+	ClassTurnCall     = "turn_call"
+	ClassInternalCall = "internal_call"
+)
+
 // Roles a record plays. These are the names the index and the details format
 // use for a record's identifiers, and a reader meets them whenever it is shown
 // where something came from.
diff --git a/pkg/providerbody/canonical.go b/pkg/providerbody/canonical.go
new file mode 100644
index 0000000..f7c5af8
--- /dev/null
+++ b/pkg/providerbody/canonical.go
@@ -0,0 +1,147 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package providerbody
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"sort"
+	"unicode/utf8"
+)
+
+// Decode reads JSON for a form, keeping every number as it was written, so
+// that a form changes nothing the runtime sent.
+func Decode(b []byte) (any, error) {
+	dec := json.NewDecoder(bytes.NewReader(b))
+	dec.UseNumber()
+	var v any
+	if err := dec.Decode(&v); err != nil {
+		return nil, err
+	}
+	// More is false at a closing bracket, so a value followed by one would
+	// pass it; only the end of the input is the end.
+	if _, err := dec.Token(); !errors.Is(err, io.EOF) {
+		return nil, fmt.Errorf("providerbody: more than one JSON value")
+	}
+	return v, nil
+}
+
+// Canonical writes a value Decode read as canonical JSON: object keys sorted
+// by their UTF-8 bytes, no space between tokens, numbers as they were written,
+// and in strings only what JSON requires escaped: the quotation mark, the
+// backslash, and the characters below U+0020. Go's own encoder also
+// escapes U+2028 and U+2029, which another implementation following the format
+// page would not, so a form's digest is not left to it.
+func Canonical(v any) ([]byte, error) {
+	var buf bytes.Buffer
+	if err := canonical(&buf, v); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+func canonical(buf *bytes.Buffer, v any) error {
+	switch x := v.(type) {
+	case nil:
+		buf.WriteString("null")
+	case bool:
+		if x {
+			buf.WriteString("true")
+		} else {
+			buf.WriteString("false")
+		}
+	case json.Number:
+		buf.WriteString(string(x))
+	case string:
+		quote(buf, x)
+	case []any:
+		buf.WriteByte('[')
+		for i, e := range x {
+			if i > 0 {
+				buf.WriteByte(',')
+			}
+			if err := canonical(buf, e); err != nil {
+				return err
+			}
+		}
+		buf.WriteByte(']')
+	case map[string]any:
+		keys := make([]string, 0, len(x))
+		for k := range x {
+			keys = append(keys, k)
+		}
+		sort.Strings(keys)
+		buf.WriteByte('{')
+		for i, k := range keys {
+			if i > 0 {
+				buf.WriteByte(',')
+			}
+			quote(buf, k)
+			buf.WriteByte(':')
+			if err := canonical(buf, x[k]); err != nil {
+				return err
+			}
+		}
+		buf.WriteByte('}')
+	default:
+		return fmt.Errorf("providerbody: %T is not a decoded JSON value", v)
+	}
+	return nil
+}
+
+func quote(buf *bytes.Buffer, s string) {
+	const hex = "0123456789abcdef"
+	buf.WriteByte('"')
+	for i := 0; i < len(s); {
+		c := s[i]
+		switch {
+		case c == '"':
+			buf.WriteString(`\"`)
+		case c == '\\':
+			buf.WriteString(`\\`)
+		case c == '\n':
+			buf.WriteString(`\n`)
+		case c == '\r':
+			buf.WriteString(`\r`)
+		case c == '\t':
+			buf.WriteString(`\t`)
+		case c == '\b':
+			buf.WriteString(`\b`)
+		case c == '\f':
+			buf.WriteString(`\f`)
+		case c < 0x20:
+			buf.WriteString(`\u00`)
+			buf.WriteByte(hex[c>>4])
+			buf.WriteByte(hex[c&0xf])
+		case c < utf8.RuneSelf:
+			buf.WriteByte(c)
+		default:
+			// Decode has already made the string valid UTF-8, so a run of
+			// bytes is copied as it is.
+			_, n := utf8.DecodeRuneInString(s[i:])
+			buf.WriteString(s[i : i+n])
+			i += n
+			continue
+		}
+		i++
+	}
+	buf.WriteByte('"')
+}
diff --git a/pkg/providerbody/history.go b/pkg/providerbody/history.go
new file mode 100644
index 0000000..6961bf9
--- /dev/null
+++ b/pkg/providerbody/history.go
@@ -0,0 +1,317 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package providerbody
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math"
+)
+
+// What a body says about the history its call was sent, so the assembler can
+// tell where a call's history changed without reading a body.
+//
+// A request's messages are written as a list of digests, one per message, in
+// the form its runtime's adapter defines; a response's reply as one more. Two
+// lists compare only under one form, and a form's rules change only with a new
+// version, so a digest that differs always means the recorded message did.
+
+// The forms an adapter writes. The rules of each are in docs/en/formats/session-data.md.
+const (
+	FormClaudeCode = "claude-code/1"
+	FormLangChain  = "langchain/1"
+)
+
+// Why a list or a reply is unavailable.
+const (
+	// UnrecognisedShape is a request or response the form does not cover.
+	UnrecognisedShape = "unrecognised_shape"
+	// PluralPrompts is a LangChain request that is not exactly one prompt.
+	PluralPrompts = "plural_prompts"
+	// TooLarge is a list over the cap: what it would take is more than a
+	// quarter of the budget a landed file has.
+	TooLarge = "too_large"
+	// NoSingleReply is a response that is not exactly one complete message.
+	NoSingleReply = "no_single_reply"
+	// NoRequest is a LangChain response whose request did not land, so
+	// whether it answered exactly one prompt is not known.
+	NoRequest = "no_request"
+)
+
+// Thread types a Claude Code request carries.
+const (
+	ThreadCreate   = "create"
+	ThreadContinue = "continue"
+)
+
+// MaxPrevious bounds the message id a continue request names. A provider's
+// message id is a few dozen bytes; anything longer is not one.
+const MaxPrevious = 128
+
+// Entry is one message: the digest of the id its runtime sent, when it sent
+// one, and the digest of the message's form. Both are the first sixteen
+// hexadecimal characters of a SHA-256, so an entry has one size whatever the
+// message holds.
+type Entry struct {
+	ID     string `json:"id,omitempty"`
+	Digest string `json:"digest"`
+}
+
+// Messages is a request's message list: whole, or the first Keep entries of
+// the list of the body its copy came from followed by Add, or unavailable
+// with the reason and, for a list too large, how many messages it holds.
+type Messages struct {
+	Entries     []Entry
+	Base        string
+	Keep        int
+	Add         []Entry
+	Unavailable string
+	Count       int
+}
+
+// Relative says the list is written against a base.
+func (m *Messages) Relative() bool { return m.Base != "" }
+
+// MarshalJSON writes the one shape the list has, so an empty whole list reads
+// as a request with no messages, never as an absent one.
+func (m Messages) MarshalJSON() ([]byte, error) {
+	switch {
+	case m.Unavailable != "":
+		return json.Marshal(struct {
+			Unavailable string `json:"unavailable"`
+			Count       int    `json:"count,omitempty"`
+		}{m.Unavailable, m.Count})
+	case m.Base != "":
+		return json.Marshal(struct {
+			Base string  `json:"base"`
+			Keep int     `json:"keep"`
+			Add  []Entry `json:"add"`
+		}{m.Base, m.Keep, nonNil(m.Add)})
+	}
+	return json.Marshal(struct {
+		Entries []Entry `json:"entries"`
+	}{nonNil(m.Entries)})
+}
+
+// UnmarshalJSON reads one of the three shapes. Anything else reads as a list
+// unavailable, unrecognised_shape, never as an error: the manifest still joins
+// its body to its call, and a reader compares nothing it cannot read.
+func (m *Messages) UnmarshalJSON(b []byte) error {
+	if err := m.read(b); err != nil {
+		*m = Messages{Unavailable: UnrecognisedShape}
+	}
+	return nil
+}
+
+func (m *Messages) read(b []byte) error {
+	var raw struct {
+		Entries     *[]Entry `json:"entries"`
+		Base        *string  `json:"base"`
+		Keep        *int     `json:"keep"`
+		Add         *[]Entry `json:"add"`
+		Unavailable *string  `json:"unavailable"`
+		Count       int      `json:"count"`
+	}
+	if err := json.Unmarshal(b, &raw); err != nil {
+		return err
+	}
+	*m = Messages{}
+	switch {
+	case raw.Unavailable != nil && raw.Entries == nil && raw.Base == nil:
+		if *raw.Unavailable == "" || raw.Count < 0 || raw.Count > math.MaxUint32 {
+			return errors.New("providerbody: an unavailable list names no reason, or a count no list holds")
+		}
+		m.Unavailable, m.Count = *raw.Unavailable, raw.Count
+	case raw.Base != nil && raw.Keep != nil && raw.Add != nil && raw.Entries == nil && raw.Unavailable == nil:
+		if *raw.Base == "" || *raw.Keep < 1 || *raw.Keep > math.MaxUint32 {
+			return errors.New("providerbody: a list against a base names no base or keeps nothing")
+		}
+		m.Base, m.Keep, m.Add = *raw.Base, *raw.Keep, *raw.Add
+	case raw.Entries != nil && raw.Base == nil && raw.Unavailable == nil:
+		m.Entries = *raw.Entries
+	default:
+		return errors.New("providerbody: a message list is not exactly one of entries, base or unavailable")
+	}
+	for _, list := range [][]Entry{m.Entries, m.Add} {
+		for _, e := range list {
+			if !isDigest(e.Digest) || (e.ID != "" && !isDigest(e.ID)) {
+				return fmt.Errorf("providerbody: a message entry is not two digests: %q %q", e.ID, e.Digest)
+			}
+		}
+	}
+	return nil
+}
+
+// Reply is the message a response produced, or why there is none to name.
+type Reply struct {
+	ID          string `json:"id,omitempty"`
+	Digest      string `json:"digest,omitempty"`
+	Unavailable string `json:"unavailable,omitempty"`
+}
+
+// Thread is the provider thread a Claude Code request starts or continues.
+type Thread struct {
+	Type string `json:"type"`
+	// Previous is the provider's id of the message a continue request goes
+	// on from, as sent.
+	Previous string `json:"previous,omitempty"`
+}
+
+// History is what an adapter read from one body about its call's history.
+type History struct {
+	Form string
+	// Messages is a request's whole list, nil when Unavailable says why.
+	Messages    []Entry
+	Unavailable string
+	// System is the digest of a Claude Code request's system prompt without
+	// its billing header; empty when it carries nothing else.
+	System string
+	Thread *Thread
+	// Reply is a response's message.
+	Reply *Reply
+}
+
+// EntryOf is the entry of a message whose form is form, in canonical JSON,
+// and whose id is id when hasID says the runtime sent one.
+func EntryOf(id string, hasID bool, form []byte) Entry {
+	e := Entry{Digest: short(form)}
+	if hasID {
+		e.ID = short([]byte(id))
+	}
+	return e
+}
+
+func short(b []byte) string {
+	sum := sha256.Sum256(b)
+	return hex.EncodeToString(sum[:8])
+}
+
+func isDigest(s string) bool {
+	if len(s) != 16 {
+		return false
+	}
+	_, err := hex.DecodeString(s)
+	return err == nil
+}
+
+func nonNil(e []Entry) []Entry {
+	if e == nil {
+		return []Entry{}
+	}
+	return e
+}
+
+// writeHistory puts what the adapter read into the manifest. A request's list
+// is written against the body its copy came from when that body's list is in
+// the same form and the two share a front, so a list's chain is the body's
+// chain and never deeper; otherwise it is written whole. A list that would
+// take more than a quarter of the budget is unavailable as too large, since
+// the budget never takes a manifest out.
+func (s *Session) writeHistory(m *Manifest, h *History) {
+	m.HistoryForm, m.Messages, m.System, m.Thread, m.Reply = "", nil, "", nil, nil
+	if h == nil || h.Form == "" {
+		return
+	}
+	m.HistoryForm, m.System, m.Thread, m.Reply = h.Form, h.System, h.Thread, h.Reply
+	if m.Role != RoleRequest {
+		return
+	}
+	if h.Unavailable != "" {
+		m.Messages = &Messages{Unavailable: h.Unavailable}
+		return
+	}
+	list := &Messages{Entries: nonNil(h.Messages)}
+	if base := copiedFrom(m); base != "" {
+		if prior, form, err := s.ListOf(base); err == nil && form == h.Form {
+			if keep := sharedFront(prior, h.Messages); keep > 0 {
+				list = &Messages{Base: base, Keep: keep, Add: nonNil(h.Messages[keep:])}
+			}
+		}
+	}
+	if s.budget > 0 {
+		written := list.Entries
+		if list.Relative() {
+			written = list.Add
+		}
+		if b, err := json.Marshal(written); err == nil && int64(len(b)) > s.budget/4 {
+			list = &Messages{Unavailable: TooLarge, Count: len(h.Messages)}
+		}
+	}
+	m.Messages = list
+}
+
+// ListOf is the whole message list of the request held under id, and its
+// form. It fails for a body that carries none, one whose list is unavailable,
+// and one written against a base that does not hold: a base that is not a
+// request added before it, or of another form, or shorter than what is kept
+// from it.
+func (s *Session) ListOf(id string) ([]Entry, string, error) {
+	return s.listOf(id, 0)
+}
+
+func (s *Session) listOf(id string, depth int) ([]Entry, string, error) {
+	h, ok := s.records[id]
+	switch {
+	case !ok:
+		return nil, "", fmt.Errorf("providerbody: no body %s", id)
+	case h.m.Role != RoleRequest || h.m.Messages == nil || h.m.HistoryForm == "":
+		return nil, "", fmt.Errorf("providerbody: %s carries no message list", id)
+	case h.m.Messages.Unavailable != "":
+		return nil, "", fmt.Errorf("providerbody: the message list of %s is unavailable: %s", id, h.m.Messages.Unavailable)
+	case !h.m.Messages.Relative():
+		return h.m.Messages.Entries, h.m.HistoryForm, nil
+	case depth >= MaxDepth:
+		return nil, "", fmt.Errorf("providerbody: the message list of %s is more than %d bases deep", id, MaxDepth)
+	}
+	l := h.m.Messages
+	if base, ok := s.records[l.Base]; !ok || base.at >= h.at {
+		return nil, "", fmt.Errorf("providerbody: %s keeps entries of %s, which was not added before it", id, l.Base)
+	}
+	prior, form, err := s.listOf(l.Base, depth+1)
+	if err != nil {
+		return nil, "", err
+	}
+	if form != h.m.HistoryForm || l.Keep > len(prior) {
+		return nil, "", fmt.Errorf("providerbody: %s keeps %d entries of a %s list of %d", id, l.Keep, form, len(prior))
+	}
+	out := make([]Entry, 0, l.Keep+len(l.Add))
+	return append(append(out, prior[:l.Keep]...), l.Add...), form, nil
+}
+
+// copiedFrom is the body a manifest's copy segment names, or "".
+func copiedFrom(m *Manifest) string {
+	for _, seg := range m.Segments {
+		if seg.Copy != nil {
+			return seg.Copy.From
+		}
+	}
+	return ""
+}
+
+// sharedFront is how many entries two lists share from the start, exactly:
+// the same id or none, and the same digest.
+func sharedFront(a, b []Entry) int {
+	n := 0
+	for n < len(a) && n < len(b) && a[n] == b[n] {
+		n++
+	}
+	return n
+}
diff --git a/pkg/providerbody/history_test.go b/pkg/providerbody/history_test.go
new file mode 100644
index 0000000..e7e0b89
--- /dev/null
+++ b/pkg/providerbody/history_test.go
@@ -0,0 +1,414 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package providerbody
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/apache/skywalking-ai-sessionizer/pkg/sessiondata"
+)
+
+// listOf is the history a chain's request carries: one entry per message,
+// its digest standing in for the form an adapter would compute.
+func listOf(c *chain) []Entry {
+	out := make([]Entry, 0, len(c.msgs))
+	for i := range c.msgs {
+		out = append(out, EntryOf("", false, []byte(fmt.Sprintf("message %d of %s", i, c.prompt))))
+	}
+	return out
+}
+
+// grow encodes n requests of one chain, each with its list, and returns their
+// ids, the records it landed, and the list each was given.
+func grow(t *testing.T, s *Session, c *chain, n int, form string) ([]string, []*sessiondata.Record, [][]Entry) {
+	t.Helper()
+	var ids []string
+	var recs []*sessiondata.Record
+	var lists [][]Entry
+	for i := 0; i < n; i++ {
+		c.add("user", fmt.Sprintf("question %d %s", i, strings.Repeat("x", 300)))
+		id := fmt.Sprintf("%s-%03d.request", c.prompt, i)
+		h := &History{Form: form, Messages: listOf(c)}
+		rec, err := s.Encode(Body{ID: id, Role: RoleRequest, Src: id + ".json", Bytes: c.request(), History: h})
+		if err != nil {
+			t.Fatal(err)
+		}
+		ids, recs, lists = append(ids, id), append(recs, rec), append(lists, h.Messages)
+		c.add("assistant", fmt.Sprintf("answer %d", i))
+	}
+	return ids, recs, lists
+}
+
+// reloaded is a session holding recs as a reader holds them: written and read
+// back as landed lines.
+func reloaded(t *testing.T, recs []*sessiondata.Record) *Session {
+	t.Helper()
+	var buf bytes.Buffer
+	w, err := sessiondata.NewWriter(&buf, &sessiondata.Header{Kind: sessiondata.KindProviderBody, Session: "s", Src: ".", Dialect: "d"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, rec := range recs {
+		if err := w.Write(rec); err != nil {
+			t.Fatal(err)
+		}
+	}
+	if err := w.Close(); err != nil {
+		t.Fatal(err)
+	}
+	r, err := sessiondata.NewReader(&buf)
+	if err != nil {
+		t.Fatal(err)
+	}
+	s := NewSession()
+	for range recs {
+		back, err := r.Next()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if err := s.Add(back); err != nil {
+			t.Fatal(err)
+		}
+	}
+	return s
+}
+
+// TestAListIsWrittenAgainstTheBodyItsCopyComesFrom. A request's list shares
+// its front with the list of the body its bytes are copied from, so it keeps
+// what it shares and writes only what is new, and reads back whole.
+func TestAListIsWrittenAgainstTheBodyItsCopyComesFrom(t *testing.T) {
+	s := NewSession()
+	c := newChain("s", "p", "system prompt", 2)
+	ids, _, _ := grow(t, s, c, 5, FormClaudeCode)
+	for i, id := range ids {
+		m, _ := s.Manifest(id)
+		switch base := copiedFrom(m); {
+		case i == 0:
+			if base != "" || m.Messages.Relative() {
+				t.Fatalf("the first request copies from %q and writes its list %+v", base, m.Messages)
+			}
+		case base == "" || m.Messages.Base != base:
+			t.Fatalf("%s copies from %q and writes its list against %q", id, base, m.Messages.Base)
+		case len(m.Messages.Add) != 2:
+			t.Fatalf("%s adds %d entries, want the 2 new messages", id, len(m.Messages.Add))
+		}
+		list, form, err := s.ListOf(id)
+		if err != nil || form != FormClaudeCode {
+			t.Fatalf("%s: %v, form %q", id, err, form)
+		}
+		if want := 2*i + 1; len(list) != want {
+			t.Fatalf("%s reads back %d entries, want %d", id, len(list), want)
+		}
+	}
+}
+
+// TestAListIsWholeWhenItsBaseCannotBeKept. A base that carries no list, or one
+// of another form, gives nothing to keep, so the list is written whole.
+func TestAListIsWholeWhenItsBaseCannotBeKept(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		base *History
+	}{
+		{"no list", nil},
+		{"another form", &History{Form: FormLangChain}},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			s := NewSession()
+			c := newChain("s", "p", "system prompt", 2)
+			c.add("user", "first "+strings.Repeat("x", 300))
+			if tc.base != nil {
+				tc.base.Messages = listOf(c)
+			}
+			if _, err := s.Encode(Body{ID: "a.request", Role: RoleRequest, Bytes: c.request(), History: tc.base}); err != nil {
+				t.Fatal(err)
+			}
+			c.add("assistant", "answer")
+			c.add("user", "second "+strings.Repeat("x", 300))
+			if _, err := s.Encode(Body{ID: "b.request", Role: RoleRequest, Bytes: c.request(),
+				History: &History{Form: FormClaudeCode, Messages: listOf(c)}}); err != nil {
+				t.Fatal(err)
+			}
+			m, _ := s.Manifest("b.request")
+			if copiedFrom(m) != "a.request" {
+				t.Fatalf("the second request copies from %q; the test needs a copy", copiedFrom(m))
+			}
+			if m.Messages.Relative() || len(m.Messages.Entries) != 3 {
+				t.Fatalf("written %+v, want the 3 entries whole", m.Messages)
+			}
+		})
+	}
+}
+
+// TestAListIsNeverDeeperThanItsBody. A body past the depth limit copies
+// nothing, and its list is written whole with it, so a list's chain is its
+// body's chain and every list reads back.
+func TestAListIsNeverDeeperThanItsBody(t *testing.T) {
+	s := NewSession()
+	c := newChain("s", "p", "system prompt", 2)
+	ids, recs, lists := grow(t, s, c, MaxDepth+3, FormClaudeCode)
+	read := reloaded(t, recs)
+	whole := 0
+	for i, id := range ids {
+		m, _ := read.Manifest(id)
+		if m.Messages.Relative() != (copiedFrom(m) != "") {
+			t.Fatalf("%s at depth %d: list relative %v, copy from %q", id, m.Depth, m.Messages.Relative(), copiedFrom(m))
+		}
+		if !m.Messages.Relative() {
+			whole++
+		}
+		list, _, err := read.ListOf(id)
+		if err != nil || fmt.Sprint(list) != fmt.Sprint(lists[i]) {
+			t.Fatalf("%s reads back %v, %v; it was given %v", id, list, err, lists[i])
+		}
+	}
+	if whole != 2 {
+		t.Fatalf("%d lists written whole, want the first and the one past the depth limit", whole)
+	}
+}
+
+// TestABodyThatFallsBackToOnePartWritesItsListWhole. A body whose cut chose a
+// copy, but whose record was still over the budget with every piece taken
+// out, lands whole in one part and copies nothing, so the list written with
+// the copy is written again whole.
+func TestABodyThatFallsBackToOnePartWritesItsListWhole(t *testing.T) {
+	front := `{"messages":[{"role":"user","content":"` + strings.Repeat("a", 6000) + `"}`
+	many := strings.Repeat(`,{"role":"user","content":"short"}`, 600)
+	bodies := []Body{
+		{ID: "a.request", Role: RoleRequest, Bytes: []byte(front + `]}`),
+			History: &History{Form: FormClaudeCode, Messages: []Entry{EntryOf("", false, []byte("a"))}}},
+		{ID: "b.request", Role: RoleRequest, Bytes: []byte(front + many + `]}`),
+			History: &History{Form: FormClaudeCode, Messages: []Entry{EntryOf("", false, []byte("a")), EntryOf("", false, []byte("b"))}}},
+	}
+	for _, budget := range []int64{0, 16 << 10} {
+		s := NewSession()
+		s.Budget(budget)
+		for _, b := range bodies {
+			if _, err := s.Encode(b); err != nil {
+				t.Fatal(err)
+			}
+		}
+		m, _ := s.Manifest("b.request")
+		switch budget {
+		case 0:
+			// With no budget the cut stands: a copy, and the list against it.
+			if copiedFrom(m) != "a.request" || !m.Messages.Relative() {
+				t.Fatalf("with no budget: copy %q, list %+v; the test needs a copy", copiedFrom(m), m.Messages)
+			}
+		default:
+			if copiedFrom(m) != "" || m.Messages.Relative() || len(m.Messages.Entries) != 2 {
+				t.Fatalf("under the budget: copy %q, list %+v, want the list whole", copiedFrom(m), m.Messages)
+			}
+		}
+	}
+}
+
+// TestABodyLandedWholeWritesItsListWhole. A body that cannot be cut lands in
+// one part and copies nothing, so its list has no base to be written against.
+func TestABodyLandedWholeWritesItsListWhole(t *testing.T) {
+	s := NewSession()
+	h := &History{Form: FormLangChain, Messages: []Entry{EntryOf("m1", true, []byte("one"))}}
+	rec, err := s.Encode(Body{ID: "a.request", Role: RoleRequest, Bytes: []byte("not json"), History: h})
+	if err != nil {
+		t.Fatal(err)
+	}
+	m, _ := ManifestOf(rec)
+	if m.Messages == nil || m.Messages.Relative() || len(m.Messages.Entries) != 1 || m.HistoryForm != FormLangChain {
+		t.Fatalf("manifest %+v", m.Messages)
+	}
+}
+
+// TestAListOverTheCapIsUnavailable. The budget never takes a manifest out, so
+// a list that would take more than a quarter of it says it is too large, with
+// how many messages it held, rather than making a record no file can hold. A
+// quarter of the file's budget, its header and closing line included.
+func TestAListOverTheCapIsUnavailable(t *testing.T) {
+	entries := func(n int) []Entry {
+		out := make([]Entry, n)
+		for i := range out {
+			out[i] = EntryOf(fmt.Sprint(i), true, []byte(fmt.Sprint("message ", i)))
+		}
+		return out
+	}
+	// An entry with an id takes 53 bytes, and a comma between two: 18 take
+	// 973 bytes with the brackets, under a quarter of 4,096; 19 take 1,027.
+	for n, want := range map[int]string{18: "", 19: TooLarge} {
+		s := NewSession()
+		s.Budget(4096)
+		if b, _ := json.Marshal(entries(n)); (int64(len(b)) > 1024) != (want == TooLarge) {
+			t.Fatalf("%d entries take %d bytes; the test's numbers are wrong", n, len(b))
+		}
+		rec, err := s.Encode(Body{ID: "a.request", Role: RoleRequest, Bytes: []byte(`{"messages":[]}`),
+			History: &History{Form: FormLangChain, Messages: entries(n)}})
+		if err != nil {
+			t.Fatal(err)
+		}
+		m, _ := ManifestOf(rec)
+		if m.Messages.Unavailable != want || (want == TooLarge && m.Messages.Count != n) {
+			t.Fatalf("%d entries written %+v, want %q", n, m.Messages, want)
+		}
+		if got := RecordBytes(rec); got > 4096-FileOverhead {
+			t.Fatalf("the record takes %d bytes, over the budget", got)
+		}
+		if _, _, err := s.ListOf("a.request"); (err == nil) != (want == "") {
+			t.Fatalf("%d entries read back: %v", n, err)
+		}
+	}
+}
+
+// TestAListAgainstALaterBaseIsUnavailable. A base is a body that landed before
+// the list's own; a list that names one added after it does not read, though
+// both bodies are held and the later one's list is whole.
+func TestAListAgainstALaterBaseIsUnavailable(t *testing.T) {
+	_, early, _ := grow(t, NewSession(), newChain("s", "p", "system prompt", 2), 1, FormClaudeCode)
+	_, later, _ := grow(t, NewSession(), newChain("s", "q", "another prompt", 2), 1, FormClaudeCode)
+	// The early request names the later one as its base, and lands first.
+	first := *early[0]
+	first.Parts = append([]sessiondata.Part(nil), early[0].Parts...)
+	m, _ := ManifestOf(early[0])
+	m.Messages = &Messages{Base: later[0].ID, Keep: 1, Add: []Entry{}}
+	first.Parts[len(first.Parts)-1] = manifestPart(m)
+	read := NewSession()
+	for _, rec := range []*sessiondata.Record{&first, later[0]} {
+		if err := read.Add(rec); err != nil {
+			t.Fatal(err)
+		}
+	}
+	if list, _, err := read.ListOf(later[0].ID); err != nil || len(list) != 1 {
+		t.Fatalf("the later request's own list: %v %v; the test needs it whole", list, err)
+	}
+	if _, _, err := read.ListOf(first.ID); err == nil {
+		t.Fatal("a list against a base added after it read back")
+	}
+}
+
+// TestAListAgainstABaseThatDoesNotHoldIsUnavailable. A reader checks what a
+// list keeps from its base, and a list that keeps more than the base holds,
+// or keeps from a body of another form, reads as unavailable, never as a
+// shorter list.
+func TestAListAgainstABaseThatDoesNotHoldIsUnavailable(t *testing.T) {
+	s := NewSession()
+	c := newChain("s", "p", "system prompt", 2)
+	ids, _, _ := grow(t, s, c, 2, FormClaudeCode)
+	h := s.records[ids[1]]
+	h.m.Messages.Keep = 99
+	if _, _, err := s.ListOf(ids[1]); err == nil {
+		t.Fatal("a list keeping more than its base holds read back")
+	}
+	h.m.Messages.Keep = 1
+	h.m.HistoryForm = FormLangChain
+	if _, _, err := s.ListOf(ids[1]); err == nil {
+		t.Fatal("a list against a base of another form read back")
+	}
+}
+
+// TestAMessageListReadsAsExactlyOneShape.
+func TestAMessageListReadsAsExactlyOneShape(t *testing.T) {
+	e := `{"id":"0123456789abcdef","digest":"fedcba9876543210"}`
+	for _, ok := range []string{
+		`{"entries":[]}`, `{"entries":[` + e + `]}`, `{"base":"a","keep":1,"add":[]}`,
+		`{"unavailable":"too_large","count":3}`, `{"unavailable":"plural_prompts"}`,
+	} {
+		var m Messages
+		if err := json.Unmarshal([]byte(ok), &m); err != nil {
+			t.Fatalf("%s: %v", ok, err)
+		}
+		back, err := json.Marshal(m)
+		if err != nil || string(back) != ok {
+			t.Fatalf("%s wrote back as %s, %v", ok, back, err)
+		}
+	}
+	// Anything else is a list that is unavailable, never a manifest that does
+	// not read: the body still joins its call.
+	for _, bad := range []string{
+		`{}`, `{"entries":[],"base":"a","keep":1,"add":[]}`, `{"base":"a","keep":0,"add":[]}`,
+		`{"base":"","keep":1,"add":[]}`, `{"base":"a","keep":1}`, `{"unavailable":""}`,
+		`{"entries":[{"digest":"short"}]}`, `{"entries":[{"id":"xyz","digest":"fedcba9876543210"}]}`,
+		`[]`, `"entries"`,
+	} {
+		var m Messages
+		if err := json.Unmarshal([]byte(bad), &m); err != nil || m.Unavailable != UnrecognisedShape {
+			t.Fatalf("%s read as %+v, %v", bad, m, err)
+		}
+	}
+}
+
+// TestCanonicalEscapesOnlyWhatJSONRequires. Go's encoder escapes U+2028 and
+// U+2029; another implementation following the format page would not, and
+// a digest has to come out the same in both.
+func TestCanonicalEscapesOnlyWhatJSONRequires(t *testing.T) {
+	v, err := Decode([]byte(`{"z":1.50,"a":["

<&>","\"\\\n\t\u0001é"],"m":null,"b":true}`))
+	if err != nil {
+		t.Fatal(err)
+	}
+	got, err := Canonical(v)
+	if err != nil {
+		t.Fatal(err)
+	}
+	want := "{\"a\":[\"

<&>\",\"\\\"\\\\\\n\\t\\u0001é\"],\"b\":true,\"m\":null,\"z\":1.50}"
+	if string(got) != want {
+		t.Fatalf("canonical\n got %s\nwant %s", got, want)
+	}
+	for _, bad := range []string{`{} {}`, `{"messages":[]} ]`, `{"messages":[]}}`, `[] ]`} {
+		if _, err := Decode([]byte(bad)); err == nil {
+			t.Fatalf("%s read as one value", bad)
+		}
+	}
+}
+
+// TestAManifestWhoseHistoryDoesNotReadStillJoins. A damaged history key costs
+// the history, never the manifest: the body still joins its call and
+// rebuilds, and says nothing of its call's history.
+func TestAManifestWhoseHistoryDoesNotReadStillJoins(t *testing.T) {
+	s := NewSession()
+	rec, err := s.Encode(Body{ID: "a.response", Role: RoleResponse, Keys: Keys{Call: "msg_1"}, Bytes: []byte(`{"id":"msg_1"}`),
+		History: &History{Form: FormClaudeCode, Reply: &Reply{Digest: "0123456789abcdef"}}})
+	if err != nil {
+		t.Fatal(err)
+	}
+	last := &rec.Parts[len(rec.Parts)-1]
+	for _, damaged := range []string{`"reply":"x"`, `"thread":7`, `"system":[]`, `"Reply":"x"`, `"THREAD":7`} {
+		var fields map[string]json.RawMessage
+		_ = json.Unmarshal(last.Data, &fields)
+		k, v, _ := strings.Cut(damaged, ":")
+		fields[strings.Trim(k, `"`)] = json.RawMessage(v)
+		data, _ := json.Marshal(fields)
+		copied := *rec
+		copied.Parts = append([]sessiondata.Part(nil), rec.Parts...)
+		copied.Parts[len(copied.Parts)-1].Data = data
+		m, err := ManifestOf(&copied)
+		if err != nil || m.Call != "msg_1" || m.HistoryForm != "" || m.Reply != nil {
+			t.Fatalf("%s: %+v, %v", damaged, m, err)
+		}
+	}
+	// A list that does not read costs only the list.
+	var fields map[string]json.RawMessage
+	_ = json.Unmarshal(last.Data, &fields)
+	fields["MESSAGES"] = json.RawMessage(`3`)
+	data, _ := json.Marshal(fields)
+	copied := *rec
+	copied.Parts = append([]sessiondata.Part(nil), rec.Parts...)
+	copied.Parts[len(copied.Parts)-1].Data = data
+	m, err := ManifestOf(&copied)
+	if err != nil || m.HistoryForm != FormClaudeCode || m.Reply == nil || m.Messages == nil || m.Messages.Unavailable != UnrecognisedShape {
+		t.Fatalf("a list that does not read: %+v, %v", m, err)
+	}
+}
diff --git a/pkg/providerbody/providerbody.go b/pkg/providerbody/providerbody.go
index 8b93062..8407ce9 100644
--- a/pkg/providerbody/providerbody.go
+++ b/pkg/providerbody/providerbody.go
@@ -96,6 +96,14 @@ type Manifest struct {
 	Request         string `json:"request,omitempty"`
 	PreviousRequest string `json:"previous_request,omitempty"`
 
+	// What the body says about its call's history, in the form HistoryForm
+	// names (history.go). Empty on a body landed before asz wrote them.
+	HistoryForm string    `json:"history_form,omitempty"`
+	Messages    *Messages `json:"messages,omitempty"`
+	System      string    `json:"system,omitempty"`
+	Thread      *Thread   `json:"thread,omitempty"`
+	Reply       *Reply    `json:"reply,omitempty"`
+
 	Segments []Segment `json:"segments"`
 }
 
@@ -147,6 +155,9 @@ type Body struct {
 	Src   string
 	Keys  Keys
 	Bytes []byte
+	// History is what the adapter read from the bytes about the call's
+	// history; nil from an adapter that reads none.
+	History *History
 }
 
 // Digest is the SHA-256 of b in hexadecimal, as a manifest and a copy name
diff --git a/pkg/providerbody/session.go b/pkg/providerbody/session.go
index f0a8de1..dd5723a 100644
--- a/pkg/providerbody/session.go
+++ b/pkg/providerbody/session.go
@@ -24,6 +24,7 @@ import (
 	"fmt"
 	"io/fs"
 	"sort"
+	"strings"
 	"unicode/utf8"
 
 	"github.com/apache/skywalking-ai-sessionizer/pkg/model"
@@ -74,6 +75,11 @@ type Session struct {
 	// maxRecord is the largest line a record Encode returns may take in a
 	// landed file, its newline included; zero means no limit. See Budget.
 	maxRecord int64
+	// budget is the landed file's budget itself, which the cap on a message
+	// list is a share of.
+	budget int64
+	// added counts the records added, so a record knows which came before it.
+	added int
 
 	records map[string]*held
 	pieces  map[string]pieceAt
@@ -100,6 +106,8 @@ type held struct {
 	// removed says rec is a skeleton: the record keeps its place and its
 	// pieces' digests, and none of its bytes.
 	removed bool
+	// at is the record's place among the records added, from zero.
+	at int
 }
 
 type pieceAt struct {
@@ -118,9 +126,9 @@ func NewSession() *Session {
 // its largest pieces taken out until it fits. Zero or less means no limit; a
 // budget no record can fit still takes out every piece it can.
 func (s *Session) Budget(budget int64) {
-	s.maxRecord = 0
+	s.maxRecord, s.budget = 0, 0
 	if budget > 0 {
-		s.maxRecord = max(budget-FileOverhead, 1)
+		s.maxRecord, s.budget = max(budget-FileOverhead, 1), budget
 	}
 }
 
@@ -135,12 +143,42 @@ func ManifestOf(rec *sessiondata.Record) (*Manifest, error) {
 	}
 	last := rec.Parts[len(rec.Parts)-1]
 	var m Manifest
-	if last.Kind != sessiondata.PartData || json.Unmarshal(last.Data, &m) != nil || m.Schema != Schema {
+	if last.Kind != sessiondata.PartData || unmarshalManifest(last.Data, &m) != nil || m.Schema != Schema {
 		return nil, fmt.Errorf("providerbody: the last part of %s is not a %s manifest", rec.ID, Schema)
 	}
 	return &m, nil
 }
 
+// historyKeys are the keys a manifest says its call's history in.
+var historyKeys = []string{"history_form", "messages", "system", "thread", "reply"}
+
+// unmarshalManifest reads a manifest. One whose history keys do not read is
+// read without them, as a body that says nothing of its call's history: it
+// still joins its call and rebuilds, and its history is never compared. A key
+// is matched without case, as the decoder matches it.
+func unmarshalManifest(data []byte, m *Manifest) error {
+	if json.Unmarshal(data, m) == nil {
+		return nil
+	}
+	var fields map[string]json.RawMessage
+	if err := json.Unmarshal(data, &fields); err != nil {
+		return err
+	}
+	for k := range fields {
+		for _, h := range historyKeys {
+			if strings.EqualFold(k, h) {
+				delete(fields, k)
+			}
+		}
+	}
+	rest, err := json.Marshal(fields)
+	if err != nil {
+		return err
+	}
+	*m = Manifest{}
+	return json.Unmarshal(rest, m)
+}
+
 // Holds reports the SHA-256 of the body the session holds under id.
 func (s *Session) Holds(id string) (string, bool) {
 	h, ok := s.records[id]
@@ -229,7 +267,8 @@ func (s *Session) Add(rec *sessiondata.Record) error {
 		}
 		return ErrRepeat
 	}
-	s.records[rec.ID] = &held{rec: rec, m: m, removed: removed}
+	s.records[rec.ID] = &held{rec: rec, m: m, removed: removed, at: s.added}
+	s.added++
 	for i, p := range rec.Parts[:own] {
 		if p.Kind != sessiondata.PartData {
 			continue
@@ -580,6 +619,7 @@ func (s *Session) Encode(b Body) (*sessiondata.Record, error) {
 	fill := map[string][]byte{sum: body}
 	parts, why := s.cut(body, m, fill)
 	if why == "" {
+		s.writeHistory(m, b.History)
 		rec.Parts = append(parts, manifestPart(m))
 		s.fit(rec, fill)
 		// What is left after the pieces go is the manifest, and a body of
@@ -598,6 +638,7 @@ func (s *Session) Encode(b Body) (*sessiondata.Record, error) {
 		// Why the body was not cut is the whole part's text; the manifest
 		// does not repeat it.
 		m.Depth, m.Segments = 0, []Segment{{Part: &zero}}
+		s.writeHistory(m, b.History)
 		rec.Parts = []sessiondata.Part{whole, manifestPart(m)}
 		s.fit(rec, fill)
 		if err := s.roundTrip(rec, fill); err != nil {
diff --git a/pkg/skeleton/bodies_test.go b/pkg/skeleton/bodies_test.go
index ab7117b..6b6e287 100644
--- a/pkg/skeleton/bodies_test.go
+++ b/pkg/skeleton/bodies_test.go
@@ -79,10 +79,13 @@ func conversation() []providerbody.Body {
 			c.msgs = append(c.msgs, map[string]any{"role": "user", "content": text + strings.Repeat(" more context", 150)})
 		}
 		req := compact(map[string]any{"model": "m-1", "system": c.system, "tools": c.tools, "messages": c.msgs})
-		out = append(out, providerbody.Body{ID: fmt.Sprintf("req-%02d", n), Role: providerbody.RoleRequest, Src: fmt.Sprintf("%02d.json", n), Bytes: req})
+		out = append(out, providerbody.Body{ID: fmt.Sprintf("req-%02d", n), Role: providerbody.RoleRequest, Src: fmt.Sprintf("%02d.json", n), Bytes: req,
+			History: &providerbody.History{Form: providerbody.FormClaudeCode, Messages: entriesOf(c.msgs)}})
 		reply := "answer to " + text + strings.Repeat(" in detail", 140)
+		answer := map[string]any{"role": "assistant", "content": []any{map[string]any{"type": "text", "text": reply}}}
 		out = append(out, providerbody.Body{ID: fmt.Sprintf("resp-%02d", n), Role: providerbody.RoleResponse, Src: fmt.Sprintf("%02d.response.json", n),
-			Bytes: compact(map[string]any{"role": "assistant", "content": []any{map[string]any{"type": "text", "text": reply}}})})
+			Bytes: compact(answer), History: &providerbody.History{Form: providerbody.FormClaudeCode,
+				Reply: &providerbody.Reply{Digest: entriesOf([]any{answer})[0].Digest}}})
 		c.msgs = append(c.msgs, map[string]any{"role": "assistant", "content": reply})
 	}
 	call(main, "read the file")
@@ -99,6 +102,16 @@ func conversation() []providerbody.Body {
 	return out
 }
 
+// entriesOf is a message list's entries, each the digest of the message as
+// written, standing in for the form an adapter computes.
+func entriesOf(msgs []any) []providerbody.Entry {
+	out := make([]providerbody.Entry, 0, len(msgs))
+	for _, m := range msgs {
+		out = append(out, providerbody.EntryOf("", false, compact(m)))
+	}
+	return out
+}
+
 func compact(v any) []byte {
 	var buf bytes.Buffer
 	enc := json.NewEncoder(&buf)
@@ -456,3 +469,49 @@ func TestAnIdenticalBodyDoesNotTakeAnotherTip(t *testing.T) {
 		t.Fatalf("an identical body too large to land reads %v, want ErrTooLarge", err)
 	}
 }
+
+// TestAMessageListResolvesFromSkeletons. A skeleton keeps the manifest, its
+// message list with it, so every list a session's requests wrote reads back
+// the same from skeletons alone, with no body bytes at all: the index rebuilt
+// from them compares calls as it did from the bodies.
+func TestAMessageListResolvesFromSkeletons(t *testing.T) {
+	cut := providerbody.NewSession()
+	var landed []*sessiondata.Record
+	for _, b := range conversation() {
+		rec, err := cut.Encode(b)
+		if err != nil {
+			t.Fatal(err)
+		}
+		landed = append(landed, rec)
+	}
+	bare := providerbody.NewSession()
+	for _, rec := range landed {
+		if err := bare.Add(skeleton.Of(bodyHeader, rec)); err != nil {
+			t.Fatal(err)
+		}
+	}
+	relative := 0
+	for _, rec := range landed {
+		m, _ := providerbody.ManifestOf(rec)
+		if m.Role != providerbody.RoleRequest {
+			continue
+		}
+		if m.Messages.Relative() {
+			relative++
+		}
+		want, _, err := cut.ListOf(rec.ID)
+		if err != nil {
+			t.Fatal(err)
+		}
+		got, _, err := bare.ListOf(rec.ID)
+		if err != nil {
+			t.Fatalf("%s from skeletons: %v", rec.ID, err)
+		}
+		if fmt.Sprint(got) != fmt.Sprint(want) {
+			t.Fatalf("%s: %v from skeletons, %v from the bodies", rec.ID, got, want)
+		}
+	}
+	if relative == 0 {
+		t.Fatal("no list was written against a base; the test needs some")
+	}
+}
diff --git a/tests/apps/langchain/MEASUREMENTS.md b/tests/apps/langchain/MEASUREMENTS.md
index 2a125b4..69565a4 100644
--- a/tests/apps/langchain/MEASUREMENTS.md
+++ b/tests/apps/langchain/MEASUREMENTS.md
@@ -40,6 +40,7 @@ was still running.
 | plain | 1 | 6 | 6 | 0 | 0 | 19964 | `thread-plain` |
 | shared-thread-key | 1 | 22 | 22 | 0 | 0 | 99890 | `123` |
 | slow-tool | 2 | 18 | 15 | 3 | 3 | 68470 | `thread-slow` |
+| streaming | 1 | 25 | 25 | 0 | 0 | 88068 | `thread-streaming`, `thread-streaming-loop` |
 | subagent | 1 | 39 | 39 | 0 | 0 | 179370 | `thread-subagent` |
 | subagent-own-thread | 1 | 30 | 30 | 0 | 0 | 125255 | `thread-subagent-parent` |
 | summarized | 1 | 35 | 35 | 0 | 0 | 174152 | `thread-summarized` |
@@ -55,9 +56,9 @@ Counted across every case, by the kind of run that carried them.
 
 | run type | runs | bytes | share |
 | --- | --- | --- | --- |
-| chain | 385 | 3195023 | 79.0% |
-| llm | 75 | 723377 | 17.9% |
-| tool | 27 | 125734 | 3.1% |
+| chain | 404 | 3240325 | 78.9% |
+| llm | 80 | 740882 | 18.0% |
+| tool | 28 | 127449 | 3.1% |
 
 A graph's own `chain` runs carry the whole message list again in their inputs
 and outputs, which is why they hold most of the bytes while saying nothing the
@@ -78,6 +79,7 @@ conversation does not already have.
 | plain | One turn, no tools: the smallest conversation there is. |
 | shared-thread-key | Two applications using one thread key: project alone is not isolation. |
 | slow-tool | A slow tool: runs are posted open and completed later by a patch. |
+| streaming | A streaming model: each reply is put together from chunks. |
 | subagent | An agent as a tool: its model calls have their own context. |
 | subagent-own-thread | A sub-agent invoked with its own thread key: a separate conversation. |
 | summarized | SummarizationMiddleware: the history is replaced by a summary, twice. |
diff --git a/tests/apps/langchain/agents.py b/tests/apps/langchain/agents.py
index 4644c23..213a60b 100644
--- a/tests/apps/langchain/agents.py
+++ b/tests/apps/langchain/agents.py
@@ -36,9 +36,10 @@ BASE = "http://127.0.0.1:8931/v1"
 SYSTEM = "You are a site status assistant.\n" + ("Rule: never guess.\n" * 800)
 
 
-def model(behaviour):
+def model(behaviour, streaming=False):
     """A model whose name tells the stand-in which behaviour to play."""
-    return ChatOpenAI(model=behaviour, base_url=BASE, api_key="stand-in", temperature=0)
+    return ChatOpenAI(model=behaviour, base_url=BASE, api_key="stand-in", temperature=0,
+                      streaming=streaming)
 
 
 @tool
@@ -235,6 +236,41 @@ def case_abandoned_run():
     os._exit(0)
 
 
+def case_streaming():
+    """A streaming model: each reply is put together from chunks.
+
+    A streamed reply can be traced as a chunk class, with an id the client made
+    up for the run, so whether it is sent back as the message it was is a
+    question only a capture answers. The second turn streams the graph itself,
+    as an application showing tokens as they come does. The last two calls
+    stream the model directly, outside any graph, and send the joined chunks
+    back as history, as a hand-written loop does.
+    """
+    from langchain_core.messages import HumanMessage
+    from langsmith import traceable
+
+    agent = create_react_agent(model("tool", streaming=True), [lookup_status],
+                               checkpointer=InMemorySaver())
+    ask(agent, "Check docs-1.", "thread-streaming")
+    config = {"configurable": {"thread_id": "thread-streaming"}}
+    for _ in agent.stream({"messages": [{"role": "user", "content": "Check docs-2."}]},
+                          config=config, stream_mode="messages"):
+        pass
+
+    @traceable(run_type="chain", name="loop", metadata={"thread_id": "thread-streaming-loop"})
+    def loop():
+        chat = model("plain", streaming=True)
+        history = [HumanMessage("Check docs-3.")]
+        reply = None
+        for piece in chat.stream(history):
+            reply = piece if reply is None else reply + piece
+        history += [reply, HumanMessage("And docs-4?")]
+        for _ in chat.stream(history):
+            pass
+
+    loop()
+
+
 def case_traceable_only():
     """No LangChain graph at all: the client's own decorator."""
     from langsmith import traceable
@@ -269,4 +305,5 @@ CASES = {
     "summarized": case_summarized,
     "abandoned-run": case_abandoned_run,
     "traceable-only": case_traceable_only,
+    "streaming": case_streaming,
 }
diff --git a/tests/apps/langchain/model.py b/tests/apps/langchain/model.py
index 4cea843..4e99aef 100644
--- a/tests/apps/langchain/model.py
+++ b/tests/apps/langchain/model.py
@@ -90,6 +90,9 @@ class Handler(BaseHTTPRequestHandler):
         tools = [t["function"]["name"] for t in request.get("tools", [])]
         told = sum(1 for m in request.get("messages", []) if m.get("role") == "tool")
         calls, text = _reply(behaviour, tools, told, index)
+        if request.get("stream"):
+            self._stream(request, behaviour, index, calls, text)
+            return
         message = {"role": "assistant", "content": text}
         if calls:
             message = {"role": "assistant", "content": None, "tool_calls": calls}
@@ -111,6 +114,47 @@ class Handler(BaseHTTPRequestHandler):
         self.wfile.write(body)
 
 
+    def _stream(self, request, behaviour, index, calls, text):
+        """The same reply as server-sent chunks, as a provider streams it.
+
+        The text goes in pieces and each tool call's arguments in two, and the
+        pieces join to exactly what the unstreamed reply holds, so the client
+        has to put the reply together, which is what a streaming case measures.
+        """
+        def chunk(delta, finish=None):
+            return {"id": "chatcmpl-%d" % index, "object": "chat.completion.chunk",
+                    "created": 1700000000, "model": behaviour,
+                    "choices": [{"index": 0, "delta": delta, "finish_reason": finish}]}
+
+        chunks = [chunk({"role": "assistant", "content": ""})]
+        for i, call in enumerate(calls or []):
+            arguments = call["function"]["arguments"]
+            half = len(arguments) // 2
+            chunks.append(chunk({"tool_calls": [{"index": i, "id": call["id"], "type": "function",
+                                                 "function": {"name": call["function"]["name"],
+                                                              "arguments": arguments[:half]}}]}))
+            chunks.append(chunk({"tool_calls": [{"index": i, "function": {"arguments": arguments[half:]}}]}))
+        # Three pieces that join to exactly the text, and none for a reply
+        # that is all tool calls.
+        if text:
+            third = max(len(text) // 3, 1)
+            for i in range(0, len(text), third):
+                chunks.append(chunk({"content": text[i:i + third]}))
+        chunks.append(chunk({}, "tool_calls" if calls else "stop"))
+        if (request.get("stream_options") or {}).get("include_usage"):
+            chunks.append({"id": "chatcmpl-%d" % index, "object": "chat.completion.chunk",
+                           "created": 1700000000, "model": behaviour, "choices": [],
+                           "usage": {"prompt_tokens": 100 + index, "completion_tokens": 10,
+                                     "total_tokens": 110 + index}})
+        body = "".join("data: %s\n\n" % json.dumps(c) for c in chunks) + "data: [DONE]\n\n"
+        body = body.encode()
+        self.send_response(200)
+        self.send_header("Content-Type", "text/event-stream")
+        self.send_header("Content-Length", str(len(body)))
+        self.end_headers()
+        self.wfile.write(body)
+
+
 def serve():
     ThreadingHTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
 
diff --git a/tests/apps/langchain/testdata/streaming/001-POST/body.bin b/tests/apps/langchain/testdata/streaming/001-POST/body.bin
new file mode 100644
index 0000000..34851c3
Binary files /dev/null and b/tests/apps/langchain/testdata/streaming/001-POST/body.bin differ
diff --git a/tests/apps/langchain/testdata/streaming/001-POST/meta.json b/tests/apps/langchain/testdata/streaming/001-POST/meta.json
new file mode 100644
index 0000000..aac9322
--- /dev/null
+++ b/tests/apps/langchain/testdata/streaming/001-POST/meta.json
@@ -0,0 +1,1192 @@
+{
+  "at": 0.59,
+  "body_bytes": 88068,
+  "encoding": "none",
+  "headers": {
+    "Accept": "application/json",
+    "Accept-Encoding": "gzip, deflate",
+    "Connection": "keep-alive",
+    "Content-Length": "88068",
+    "Content-Type": "multipart/form-data; boundary=c67852a05e694ba5b463935cd86eaed5",
+    "Host": "127.0.0.1:8930",
+    "User-Agent": "langsmith-py/0.13.0-httpx2",
+    "x-api-key": "asz-harness"
+  },
+  "method": "POST",
+  "n": 1,
+  "parts": [
+    {
+      "bytes": 342,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e\"",
+        "Content-Length": "342",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e"
+    },
+    {
+      "bytes": 56,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.inputs\"",
+        "Content-Length": "56",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.inputs"
+    },
+    {
+      "bytes": 1000,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.outputs\"",
+        "Content-Length": "1000",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.events"
+    },
+    {
+      "bytes": 496,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.extra\"",
+        "Content-Length": "496",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6b2-7bb3-b927-4bcdf1913f7e.extra"
+    },
+    {
+      "bytes": 466,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a860f7221789\"",
+        "Content-Length": "466",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a860f7221789"
+    },
+    {
+      "bytes": 171,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a860f7221789.inputs\"",
+        "Content-Length": "171",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a860f7221789.inputs"
+    },
+    {
+      "bytes": 398,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a860f7221789.outputs\"",
+        "Content-Length": "398",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a860f7221789.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a860f7221789.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a860f7221789.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a860f7221789.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a860f7221789.extra"
+    },
+    {
+      "bytes": 528,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a87bd0cea962\"",
+        "Content-Length": "528",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a87bd0cea962"
+    },
+    {
+      "bytes": 171,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.inputs\"",
+        "Content-Length": "171",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.inputs"
+    },
+    {
+      "bytes": 398,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.outputs\"",
+        "Content-Length": "398",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cb-7902-ac29-a87bd0cea962.extra"
+    },
+    {
+      "bytes": 593,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da\"",
+        "Content-Length": "593",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da"
+    },
+    {
+      "bytes": 171,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.inputs\"",
+        "Content-Length": "171",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.inputs"
+    },
+    {
+      "bytes": 394,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.outputs\"",
+        "Content-Length": "394",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.events"
+    },
+    {
+      "bytes": 756,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.extra\"",
+        "Content-Length": "756",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-76478aed00da.extra"
+    },
+    {
+      "bytes": 642,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20\"",
+        "Content-Length": "642",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20"
+    },
+    {
+      "bytes": 171,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.inputs\"",
+        "Content-Length": "171",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.inputs"
+    },
+    {
+      "bytes": 145,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.outputs\"",
+        "Content-Length": "145",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.events"
+    },
+    {
+      "bytes": 756,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.extra\"",
+        "Content-Length": "756",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-765ddc9ddb20.extra"
+    },
+    {
+      "bytes": 644,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396\"",
+        "Content-Length": "644",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396"
+    },
+    {
+      "bytes": 196,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.inputs\"",
+        "Content-Length": "196",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.inputs"
+    },
+    {
+      "bytes": 633,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.outputs\"",
+        "Content-Length": "633",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.outputs"
+    },
+    {
+      "bytes": 180,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.events\"",
+        "Content-Length": "180",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.events"
+    },
+    {
+      "bytes": 1503,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.extra\"",
+        "Content-Length": "1503",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.extra"
+    },
+    {
+      "bytes": 281,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.serialized\"",
+        "Content-Length": "281",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6cc-7ec1-aa9e-766b8107f396.serialized"
+    },
+    {
+      "bytes": 533,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3\"",
+        "Content-Length": "533",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3"
+    },
+    {
+      "bytes": 555,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.inputs\"",
+        "Content-Length": "555",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.inputs"
+    },
+    {
+      "bytes": 816,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.outputs\"",
+        "Content-Length": "816",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ae3372d4c3.extra"
+    },
+    {
+      "bytes": 466,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990\"",
+        "Content-Length": "466",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990"
+    },
+    {
+      "bytes": 757,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.inputs\"",
+        "Content-Length": "757",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.inputs"
+    },
+    {
+      "bytes": 198,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.outputs\"",
+        "Content-Length": "198",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.events"
+    },
+    {
+      "bytes": 693,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.extra\"",
+        "Content-Length": "693",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4b3d08c4990.extra"
+    },
+    {
+      "bytes": 530,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4\"",
+        "Content-Length": "530",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4"
+    },
+    {
+      "bytes": 66,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.inputs\"",
+        "Content-Length": "66",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.inputs"
+    },
+    {
+      "bytes": 194,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.outputs\"",
+        "Content-Length": "194",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.events"
+    },
+    {
+      "bytes": 808,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.extra\"",
+        "Content-Length": "808",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d4-7a02-8029-f4ceac9520b4.extra"
+    },
+    {
+      "bytes": 466,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5\"",
+        "Content-Length": "466",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5"
+    },
+    {
+      "bytes": 783,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.inputs\"",
+        "Content-Length": "783",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.inputs"
+    },
+    {
+      "bytes": 255,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.outputs\"",
+        "Content-Length": "255",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dd9243daa1e5.extra"
+    },
+    {
+      "bytes": 528,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf\"",
+        "Content-Length": "528",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf"
+    },
+    {
+      "bytes": 783,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.inputs\"",
+        "Content-Length": "783",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.inputs"
+    },
+    {
+      "bytes": 255,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.outputs\"",
+        "Content-Length": "255",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-dda3088eb9cf.extra"
+    },
+    {
+      "bytes": 593,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23\"",
+        "Content-Length": "593",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23"
+    },
+    {
+      "bytes": 783,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.inputs\"",
+        "Content-Length": "783",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.inputs"
+    },
+    {
+      "bytes": 251,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.outputs\"",
+        "Content-Length": "251",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.events"
+    },
+    {
+      "bytes": 756,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.extra\"",
+        "Content-Length": "756",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddb8ec3bac23.extra"
+    },
+    {
+      "bytes": 642,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31\"",
+        "Content-Length": "642",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31"
+    },
+    {
+      "bytes": 783,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.inputs\"",
+        "Content-Length": "783",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.inputs"
+    },
+    {
+      "bytes": 757,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.outputs\"",
+        "Content-Length": "757",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.events"
+    },
+    {
+      "bytes": 756,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.extra\"",
+        "Content-Length": "756",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d5-72e0-8c13-ddcb08e16d31.extra"
+    },
+    {
+      "bytes": 644,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1\"",
+        "Content-Length": "644",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1"
+    },
+    {
+      "bytes": 921,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.inputs\"",
+        "Content-Length": "921",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.inputs"
+    },
+    {
+      "bytes": 492,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.outputs\"",
+        "Content-Length": "492",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.outputs"
+    },
+    {
+      "bytes": 180,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.events\"",
+        "Content-Length": "180",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.events"
+    },
+    {
+      "bytes": 1483,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.extra\"",
+        "Content-Length": "1483",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.extra"
+    },
+    {
+      "bytes": 281,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.serialized\"",
+        "Content-Length": "281",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d6-7880-ad8b-c01421d89cf1.serialized"
+    },
+    {
+      "bytes": 533,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d8-7f90-a06e-693f24a379ec\"",
+        "Content-Length": "533",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d8-7f90-a06e-693f24a379ec"
+    },
+    {
+      "bytes": 1024,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.inputs\"",
+        "Content-Length": "1024",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.inputs"
+    },
+    {
+      "bytes": 20,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.outputs\"",
+        "Content-Length": "20",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d8-7f90-a06e-693f24a379ec.extra"
+    },
+    {
+      "bytes": 342,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39ba00f4422c\"",
+        "Content-Length": "342",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39ba00f4422c"
+    },
+    {
+      "bytes": 56,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.inputs\"",
+        "Content-Length": "56",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.inputs"
+    },
+    {
+      "bytes": 1374,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.outputs\"",
+        "Content-Length": "1374",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.events"
+    },
+    {
+      "bytes": 496,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.extra\"",
+        "Content-Length": "496",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39ba00f4422c.extra"
+    },
+    {
+      "bytes": 466,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39c6b982c294\"",
+        "Content-Length": "466",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39c6b982c294"
+    },
+    {
+      "bytes": 1157,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39c6b982c294.inputs\"",
+        "Content-Length": "1157",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39c6b982c294.inputs"
+    },
+    {
+      "bytes": 255,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39c6b982c294.outputs\"",
+        "Content-Length": "255",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39c6b982c294.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39c6b982c294.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39c6b982c294.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39c6b982c294.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39c6b982c294.extra"
+    },
+    {
+      "bytes": 528,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39d07eaba268\"",
+        "Content-Length": "528",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39d07eaba268"
+    },
+    {
+      "bytes": 1157,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39d07eaba268.inputs\"",
+        "Content-Length": "1157",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39d07eaba268.inputs"
+    },
+    {
+      "bytes": 255,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39d07eaba268.outputs\"",
+        "Content-Length": "255",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39d07eaba268.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39d07eaba268.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39d07eaba268.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6d9-7893-80ae-39d07eaba268.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6d9-7893-80ae-39d07eaba268.extra"
+    },
+    {
+      "bytes": 593,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1322adcd0f28\"",
+        "Content-Length": "593",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1322adcd0f28"
+    },
+    {
+      "bytes": 1157,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.inputs\"",
+        "Content-Length": "1157",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.inputs"
+    },
+    {
+      "bytes": 251,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.outputs\"",
+        "Content-Length": "251",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.events"
+    },
+    {
+      "bytes": 756,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.extra\"",
+        "Content-Length": "756",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1322adcd0f28.extra"
+    },
+    {
+      "bytes": 642,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-133ee9896551\"",
+        "Content-Length": "642",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-133ee9896551"
+    },
+    {
+      "bytes": 1157,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-133ee9896551.inputs\"",
+        "Content-Length": "1157",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-133ee9896551.inputs"
+    },
+    {
+      "bytes": 1131,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-133ee9896551.outputs\"",
+        "Content-Length": "1131",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-133ee9896551.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-133ee9896551.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-133ee9896551.events"
+    },
+    {
+      "bytes": 756,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-133ee9896551.extra\"",
+        "Content-Length": "756",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-133ee9896551.extra"
+    },
+    {
+      "bytes": 644,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1348ad0833db\"",
+        "Content-Length": "644",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1348ad0833db"
+    },
+    {
+      "bytes": 1409,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1348ad0833db.inputs\"",
+        "Content-Length": "1409",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1348ad0833db.inputs"
+    },
+    {
+      "bytes": 492,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1348ad0833db.outputs\"",
+        "Content-Length": "492",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1348ad0833db.outputs"
+    },
+    {
+      "bytes": 180,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1348ad0833db.events\"",
+        "Content-Length": "180",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1348ad0833db.events"
+    },
+    {
+      "bytes": 1483,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1348ad0833db.extra\"",
+        "Content-Length": "1483",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1348ad0833db.extra"
+    },
+    {
+      "bytes": 281,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6da-7482-a5a4-1348ad0833db.serialized\"",
+        "Content-Length": "281",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6da-7482-a5a4-1348ad0833db.serialized"
+    },
+    {
+      "bytes": 533,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f\"",
+        "Content-Length": "533",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f"
+    },
+    {
+      "bytes": 1398,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.inputs\"",
+        "Content-Length": "1398",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.inputs"
+    },
+    {
+      "bytes": 20,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.outputs\"",
+        "Content-Length": "20",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.outputs"
+    },
+    {
+      "bytes": 117,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.events\"",
+        "Content-Length": "117",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.events"
+    },
+    {
+      "bytes": 695,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.extra\"",
+        "Content-Length": "695",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dc-7f50-b7cd-38296ee8f50f.extra"
+    },
+    {
+      "bytes": 337,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9\"",
+        "Content-Length": "337",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9"
+    },
+    {
+      "bytes": 2,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.inputs\"",
+        "Content-Length": "2",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.inputs"
+    },
+    {
+      "bytes": 15,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.outputs\"",
+        "Content-Length": "15",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.outputs"
+    },
+    {
+      "bytes": 2,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.events\"",
+        "Content-Length": "2",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.events"
+    },
+    {
+      "bytes": 465,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.extra\"",
+        "Content-Length": "465",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7251-b69d-e41b8252d8e9.extra"
+    },
+    {
+      "bytes": 455,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7161-818b-5163b6008a6f\"",
+        "Content-Length": "455",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7161-818b-5163b6008a6f"
+    },
+    {
+      "bytes": 152,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7161-818b-5163b6008a6f.inputs\"",
+        "Content-Length": "152",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7161-818b-5163b6008a6f.inputs"
+    },
+    {
+      "bytes": 516,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7161-818b-5163b6008a6f.outputs\"",
+        "Content-Length": "516",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7161-818b-5163b6008a6f.outputs"
+    },
+    {
+      "bytes": 180,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7161-818b-5163b6008a6f.events\"",
+        "Content-Length": "180",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7161-818b-5163b6008a6f.events"
+    },
+    {
+      "bytes": 962,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7161-818b-5163b6008a6f.extra\"",
+        "Content-Length": "962",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7161-818b-5163b6008a6f.extra"
+    },
+    {
+      "bytes": 282,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6dd-7161-818b-5163b6008a6f.serialized\"",
+        "Content-Length": "282",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6dd-7161-818b-5163b6008a6f.serialized"
+    },
+    {
+      "bytes": 455,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6df-7393-9800-6967b8b51458\"",
+        "Content-Length": "455",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6df-7393-9800-6967b8b51458"
+    },
+    {
+      "bytes": 636,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6df-7393-9800-6967b8b51458.inputs\"",
+        "Content-Length": "636",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6df-7393-9800-6967b8b51458.inputs"
+    },
+    {
+      "bytes": 516,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6df-7393-9800-6967b8b51458.outputs\"",
+        "Content-Length": "516",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6df-7393-9800-6967b8b51458.outputs"
+    },
+    {
+      "bytes": 180,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6df-7393-9800-6967b8b51458.events\"",
+        "Content-Length": "180",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6df-7393-9800-6967b8b51458.events"
+    },
+    {
+      "bytes": 962,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6df-7393-9800-6967b8b51458.extra\"",
+        "Content-Length": "962",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6df-7393-9800-6967b8b51458.extra"
+    },
+    {
+      "bytes": 282,
+      "headers": {
+        "Content-Disposition": "form-data; name=\"post.01a14d5c-e6df-7393-9800-6967b8b51458.serialized\"",
+        "Content-Length": "282",
+        "Content-Type": "application/json"
+      },
+      "name": "post.01a14d5c-e6df-7393-9800-6967b8b51458.serialized"
+    }
+  ],
+  "path": "/runs/multipart",
+  "redacted": true,
+  "wire_bytes": 88068
+}
\ No newline at end of file
diff --git a/tests/claudecode_threads_test.go b/tests/claudecode_threads_test.go
new file mode 100644
index 0000000..24eab15
--- /dev/null
+++ b/tests/claudecode_threads_test.go
@@ -0,0 +1,287 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tests_test
+
+import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/apache/skywalking-ai-sessionizer/internal/adapters/claudecode"
+	"github.com/apache/skywalking-ai-sessionizer/internal/adapters/claudecodeprovider"
+	"github.com/apache/skywalking-ai-sessionizer/internal/index"
+	"github.com/apache/skywalking-ai-sessionizer/internal/parse"
+	"github.com/apache/skywalking-ai-sessionizer/internal/storage"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/model"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/providerbody"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/sessionflow"
+)
+
+// threads is a redacted capture of Claude Code 2.1.302 continuing provider
+// threads; its README says what it holds and how it was made.
+const threads = "../internal/adapters/claudecodeprovider/testdata/threads"
+
+const threadsSession = "2bb28d1c-72a7-4260-a010-f439329e4e76"
+
+// landThreads collects the capture as asz collect does, transcripts first so
+// each response finds the call that claims it, and parses it.
+func landThreads(t *testing.T) (*storage.Zone, *sessionflow.View) {
+	t.Helper()
+	zone := landThreadsBudget(t, 2<<20)
+	if _, err := parse.Session(zone, parse.Options{Conversation: threadsSession, Session: threadsSession,
+		Reindex: index.Rebuild}); err != nil {
+		t.Fatal(err)
+	}
+	return zone, fold(t, zone, threadsSession)
+}
+
+// landThreadsBudget collects the capture into a new root under a budget.
+func landThreadsBudget(t *testing.T, budget int64) *storage.Zone {
+	t.Helper()
+	dir := t.TempDir()
+	if err := os.CopyFS(dir, os.DirFS(threads)); err != nil {
+		t.Fatal(err)
+	}
+	var times map[string]int64
+	raw, err := os.ReadFile(filepath.Join(dir, "mtimes.json"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := json.Unmarshal(raw, &times); err != nil {
+		t.Fatal(err)
+	}
+	var last time.Time
+	for name, ns := range times {
+		at := time.Unix(0, ns)
+		if err := os.Chtimes(filepath.Join(dir, "bodies", name), at, at); err != nil {
+			t.Fatal(err)
+		}
+		if at.After(last) {
+			last = at
+		}
+	}
+	zone := storage.NewZone(filepath.Join(dir, "root"))
+	now := func() time.Time { return last.Add(time.Hour) }
+	local := claudecode.New(filepath.Join(dir, "projects"), zone, budget)
+	local.Now = now
+	if _, err := local.CollectAll(func(claudecode.Session) bool { return true }); err != nil {
+		t.Fatal(err)
+	}
+	bodies := claudecodeprovider.New(filepath.Join(dir, "bodies"), zone, budget)
+	bodies.Now = now
+	st, err := bodies.CollectAll(func(string) claudecodeprovider.Verdict { return claudecodeprovider.Collect })
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, e := range st.Errors {
+		t.Errorf("collect: %v", e)
+	}
+	return zone
+}
+
+// TestAThreadsHistoryIsBuiltFromWhatItContinues holds what the capture showed
+// and the form rests on. A process's first request starts a thread and holds
+// its whole list; every later request of the process continues the thread
+// from the previous response's message and holds only the new messages. With
+// each continue's history built from the call it continues, that call's
+// reply and its own messages, every call's history is the history before it
+// with the reply next, through every resume, until the compaction.
+func TestAThreadsHistoryIsBuiltFromWhatItContinues(t *testing.T) {
+	zone, view := landThreads(t)
+	held, order := heldInOrder(t, zone, threadsSession)
+
+	// What asz at f6e3274 gave on the unredacted capture, before anything
+	// is read of the history: without these, nothing below is compared.
+	calls := view.NodesByKind(model.KindLLMCall)
+	joined := 0
+	type call struct {
+		node              *sessionflow.Node
+		request, response string
+	}
+	var all []call
+	byMessage := map[string]*call{}
+	for _, n := range calls {
+		bodies, err := sessionflow.ProviderBodiesOf(n.Attrs)
+		if err != nil {
+			t.Fatal(err)
+		}
+		c := call{node: n}
+		for _, b := range bodies {
+			joined++
+			id := recordIDAt(t, zone, threadsSession, b.Ref)
+			if b.Role == sessionflow.RoleRequest {
+				c.request = id
+			} else {
+				c.response = id
+			}
+		}
+		all = append(all, c)
+	}
+	if len(calls) != 13 || len(order) != 27 || joined != 26 {
+		t.Fatalf("%d calls, %d bodies landed, %d joined; measured 13, 27 and 26", len(calls), len(order), joined)
+	}
+	// Which ones: the compaction's request lands and joins no call, and its
+	// response, which no transcript record names, waits.
+	compaction := compactionBodies(t)
+	landedIDs := map[string]bool{}
+	for _, id := range order {
+		landedIDs[id] = true
+	}
+	joinedIDs := map[string]bool{}
+	for _, c := range all {
+		joinedIDs[c.request], joinedIDs[c.response] = true, true
+	}
+	if !landedIDs[compaction.request] || joinedIDs[compaction.request] || landedIDs[compaction.response] {
+		t.Fatalf("the compaction's request landed %v and joined %v, its response landed %v; measured landed, unjoined, waiting",
+			landedIDs[compaction.request], joinedIDs[compaction.request], landedIDs[compaction.response])
+	}
+	threadsOf := map[string]int{}
+	for _, id := range order {
+		m, _ := held.Manifest(id)
+		if m.Role != providerbody.RoleRequest {
+			continue
+		}
+		if m.HistoryForm != providerbody.FormClaudeCode {
+			t.Fatalf("%s: form %q", id, m.HistoryForm)
+		}
+		kind := "none"
+		if m.Thread != nil {
+			kind = m.Thread.Type
+		}
+		threadsOf[kind]++
+	}
+	if threadsOf["create"] != 9 || threadsOf["continue"] != 4 || threadsOf["none"] != 1 {
+		t.Fatalf("threads %v; measured 9 created, 4 continued and the compaction's request with none", threadsOf)
+	}
+
+	// The history of each call, built as the design says.
+	reply := func(c *call) providerbody.Entry {
+		m, ok := held.Manifest(c.response)
+		if !ok || m.Reply == nil || m.Reply.Unavailable != "" {
+			t.Fatalf("%s has no reply", c.node.ID)
+		}
+		return providerbody.Entry{ID: m.Reply.ID, Digest: m.Reply.Digest}
+	}
+	var historyOf func(c *call, depth int) []providerbody.Entry
+	historyOf = func(c *call, depth int) []providerbody.Entry {
+		own, _, err := held.ListOf(c.request)
+		if err != nil {
+			t.Fatalf("%s: %v", c.request, err)
+		}
+		m, _ := held.Manifest(c.request)
+		if m.Thread == nil || m.Thread.Type != providerbody.ThreadContinue {
+			return own
+		}
+		from := byMessage[m.Thread.Previous]
+		if from == nil || depth > 64 {
+			t.Fatalf("%s continues %s, which no call here is", c.request, m.Thread.Previous)
+		}
+		return append(append(historyOf(from, depth+1), reply(from)), own...)
+	}
+
+	// Consecutive calls of each stream, in the order their first fragments
+	// landed. The calls are looked up by message once they are in that order.
+	sort.SliceStable(all, func(i, j int) bool {
+		a, b := all[i].node.Ref, all[j].node.Ref
+		return a.Seq < b.Seq || (a.Seq == b.Seq && a.Row < b.Row)
+	})
+	for i := range all {
+		byMessage[strings.TrimPrefix(all[i].node.ID, "call/")] = &all[i]
+	}
+	last := map[string]*call{}
+	kept := 0
+	var others [][2]*call
+	for i := range all {
+		c := &all[i]
+		p := last[c.node.Stream]
+		last[c.node.Stream] = c
+		if p == nil {
+			continue
+		}
+		P, N := historyOf(p, 0), historyOf(c, 0)
+		if len(N) > len(P) && equalEntries(N[:len(P)], P) && N[len(P)] == reply(p) {
+			kept++
+		} else {
+			others = append(others, [2]*call{p, c})
+		}
+	}
+	if kept != 10 || len(others) != 1 {
+		t.Fatalf("%d pairs kept with the reply next, %d not; measured 10 and the one across the compaction", kept, len(others))
+	}
+	// The one other pair spans the compaction: the explicit reset of its
+	// stream lies between its two calls.
+	p, n := others[0][0].node, others[0][1].node
+	before := func(a, b sessionflow.Ref) bool { return a.Seq < b.Seq || (a.Seq == b.Seq && a.Row < b.Row) }
+	spans := false
+	for _, b := range view.NodesByKind(model.KindEpochBoundary) {
+		if b.Stream == n.Stream && b.Ref != nil && before(*p.Ref, *b.Ref) && before(*b.Ref, *n.Ref) {
+			spans = true
+		}
+	}
+	if !spans {
+		t.Fatalf("the pair %s, %s that is not kept spans no reset", p.ID, n.ID)
+	}
+}
+
+func equalEntries(a, b []providerbody.Entry) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := range a {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// compactionBodies names the compaction's two bodies, as the capture's own
+// index of the files it wrote says.
+func compactionBodies(t *testing.T) struct{ request, response string } {
+	t.Helper()
+	raw, err := os.ReadFile(filepath.Join(threads, "bodies", "index.jsonl"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	var out struct{ request, response string }
+	for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
+		var row struct {
+			QuerySource  string `json:"query_source"`
+			RequestFile  string `json:"request_file"`
+			ResponseFile string `json:"response_file"`
+		}
+		if err := json.Unmarshal([]byte(line), &row); err != nil {
+			t.Fatal(err)
+		}
+		if row.QuerySource == "compact" {
+			if out.request != "" {
+				t.Fatal("the capture holds two compactions")
+			}
+			out.request = strings.TrimSuffix(row.RequestFile, ".json")
+			out.response = strings.TrimSuffix(row.ResponseFile, ".json")
+		}
+	}
+	if out.request == "" {
+		t.Fatal("the capture holds no compaction")
+	}
+	return out
+}
diff --git a/tests/langchain_history_test.go b/tests/langchain_history_test.go
new file mode 100644
index 0000000..530e991
--- /dev/null
+++ b/tests/langchain_history_test.go
@@ -0,0 +1,583 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tests_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"slices"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/apache/skywalking-ai-sessionizer/internal/adapters/langsmith"
+	"github.com/apache/skywalking-ai-sessionizer/internal/index"
+	"github.com/apache/skywalking-ai-sessionizer/internal/storage"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/model"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/providerbody"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/sessiondata"
+	"github.com/apache/skywalking-ai-sessionizer/pkg/skeleton"
+)
+
+// heldInOrder is heldBodies with the record ids in the order they landed.
+func heldInOrder(t *testing.T, zone *storage.Zone, session string) (*providerbody.Session, []string) {
+	t.Helper()
+	held := providerbody.NewSession()
+	files, err := storage.LandedFiles(zone, session)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var order []string
+	for _, lf := range files {
+		if lf.Stream != "" || lf.RunID != "" {
+			continue
+		}
+		f, err := sessiondata.Open(lf.Path)
+		if err != nil {
+			t.Fatal(err)
+		}
+		r, err := sessiondata.NewReader(f)
+		if err != nil {
+			f.Close()
+			t.Fatal(err)
+		}
+		for {
+			rec, err := r.Next()
+			if errors.Is(err, io.EOF) {
+				break
+			}
+			if err != nil {
+				t.Fatal(err)
+			}
+			if err := held.Add(rec); err != nil && !errors.Is(err, providerbody.ErrRepeat) {
+				t.Fatalf("%s: %v", rec.ID, err)
+			}
+			order = append(order, rec.ID)
+		}
+		f.Close()
+	}
+	return held, order
+}
+
+// TestEveryLangChainMessageSentAgainHasOneForm holds the measurement the
+// langchain/1 form rests on, over every capture as the collector lands it:
+// with nothing left out of a message, a message sent again under the same id
+// has the same digest, and a reply comes back equal or altered by the
+// framework, never as something the form made different. The numbers are the
+// ones measured with the capture script when the form was designed; a new
+// capture changes them, and this test says by how much.
+func TestEveryLangChainMessageSentAgainHasOneForm(t *testing.T) {
+	cases, err := os.ReadDir(corpus)
+	if err != nil {
+		t.Skipf("no corpus: %v", err)
+	}
+	type key struct{ kase, id string }
+	var calls, messages int
+	forms := map[key]map[string]bool{}
+	sent := map[key]int{}
+	unavailable := map[string]int{}
+	outcome := map[string]int{}
+	for _, c := range cases {
+		if !c.IsDir() {
+			continue
+		}
+		zone, sessions := land(t, c.Name())
+		for _, session := range sessions {
+			held, order := heldInOrder(t, zone, session)
+			// The requests in the order they landed, each with its list.
+			var requests []string
+			lists := map[string][]providerbody.Entry{}
+			for _, id := range order {
+				m, _ := held.Manifest(id)
+				if m.Role != providerbody.RoleRequest {
+					continue
+				}
+				if m.HistoryForm != providerbody.FormLangChain {
+					t.Fatalf("%s/%s: form %q", c.Name(), id, m.HistoryForm)
+				}
+				calls++
+				list, _, err := held.ListOf(id)
+				if err != nil {
+					unavailable["request "+m.Messages.Unavailable]++
+					continue
+				}
+				requests = append(requests, id)
+				lists[id] = list
+				messages += len(list)
+				for _, e := range list {
+					if e.ID == "" {
+						continue
+					}
+					k := key{c.Name(), e.ID}
+					if forms[k] == nil {
+						forms[k] = map[string]bool{}
+					}
+					forms[k][e.Digest] = true
+					sent[k]++
+				}
+			}
+			// Each reply, looked for in the requests of the calls after its own.
+			inOrder := make([][]providerbody.Entry, len(requests))
+			for i, id := range requests {
+				inOrder[i] = lists[id]
+			}
+			for i, id := range requests {
+				m, ok := held.Manifest(strings.TrimSuffix(id, ":request") + ":response")
+				if !ok {
+					continue
+				}
+				r := m.Reply
+				if r == nil || r.Unavailable != "" {
+					unavailable["reply"]++
+					continue
+				}
+				outcome[replyOutcome(providerbody.Entry{ID: r.ID, Digest: r.Digest}, inOrder, i)]++
+			}
+		}
+	}
+	repeated, differing := 0, 0
+	for k, n := range sent {
+		if n > 1 {
+			repeated++
+			if len(forms[k]) > 1 {
+				differing++
+				t.Errorf("%s: message %s was sent with %d forms", k.kase, k.id, len(forms[k]))
+			}
+		}
+	}
+	t.Logf("%d calls, %d messages, %d ids, %d sent more than once, replies %v, unavailable %v",
+		calls, messages, len(sent), repeated, outcome, unavailable)
+	if calls != 79 || messages != 565 || len(sent) != 127 || repeated != 80 || differing != 0 {
+		t.Errorf("measured 79 calls, 565 messages, 127 ids and 80 sent more than once; got %d, %d, %d and %d",
+			calls, messages, len(sent), repeated)
+	}
+	if outcome["back"] != 47 || outcome["back changed"] != 3 || outcome["not sent back"] != 29 || len(unavailable) != 0 {
+		t.Errorf("measured 47 replies back, 3 back changed and 29 not sent back, none unavailable; got %v, unavailable %v",
+			outcome, unavailable)
+	}
+}
+
+// TestAModelCallsClassTravelsFromItsStart, through the receiver and two
+// collectors, the second reading back the state the first saved. A start
+// decides a call's class; its update, which carries no metadata, carries the
+// class its start gave; an update whose start's class is not held, as one
+// remembered before asz kept it, carries none; and of two starts that
+// disagree, internal_call is kept. A reply is landed only beside a request
+// of exactly one prompt.
+func TestAModelCallsClassTravelsFromItsStart(t *testing.T) {
+	zone, post, first := receiveIntoCollector(t)
+	const (
+		trace  = "31313131-3131-7131-8131-313131313130"
+		inner  = "31313131-3131-7131-8131-313131313131"
+		turn   = "31313131-3131-7131-8131-313131313132"
+		mixed  = "31313131-3131-7131-8131-313131313133"
+		plural = "31313131-3131-7131-8131-313131313134"
+		lone   = "31313131-3131-7131-8131-313131313135"
+		odd    = "31313131-3131-7131-8131-313131313136"
+	)
+	root := "20260920T100000000000Z" + trace
+	owner := `"session_name":"asz"`
+	message := `{"lc":1,"type":"constructor","id":["langchain","schema","messages","HumanMessage"],"kwargs":{"content":"q","id":"h1"}}`
+	reply := `{"generations":[[{"text":"a","message":{"lc":1,"type":"constructor","id":["langchain","schema","messages","AIMessage"],"kwargs":{"content":"a","id":"r1"}}}]]}`
+	llm := func(id, extra, inputs string, end bool) string {
+		dotted := root + ".20260920T100001000000Z" + id
+		s := `{"id":"` + id + `","trace_id":"` + trace + `","parent_run_id":"` + trace + `","dotted_order":"` + dotted +
+			`","run_type":"llm","name":"model","start_time":"2026-09-20T10:00:01Z",` + owner
+		if extra != "" {
+			s += `,"extra":` + extra
+		}
+		if inputs != "" {
+			s += `,"inputs":` + inputs
+		}
+		if end {
+			s += `,"end_time":"2026-09-20T10:00:02Z","outputs":` + reply
+		}
+		return s + "}"
+	}
+	patch := func(id string) string {
+		return `{"id":"` + id + `","trace_id":"` + trace + `","end_time":"2026-09-20T10:00:02Z","outputs":` + reply + `}`
+	}
+	internal := `{"metadata":{"thread_id":"t-class","lc_internal_call":"true"}}`
+	plain := `{"metadata":{"thread_id":"t-class"}}`
+	one := `{"messages":[[` + message + `]]}`
+	post(`{"post":[{"id":"` + trace + `","trace_id":"` + trace + `","dotted_order":"` + root + `","run_type":"chain","name":"graph",` +
+		`"start_time":"2026-09-20T10:00:00Z",` + owner + `,"extra":` + plain + `,"inputs":{"messages":[]}},` +
+		llm(inner, internal, one, false) + `,` + llm(turn, plain, one, false) + `,` + llm(mixed, internal, one, false) + `]}`)
+	if _, err := first.Collect(); err != nil {
+		t.Fatal(err)
+	}
+
+	// The state as a collector before this change would have left it: the
+	// turn call's start remembered without its class.
+	statePath := filepath.Join(zone.Root(), langsmith.InboxDir, "open.json")
+	raw, err := os.ReadFile(statePath)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var state map[string]any
+	if err := json.Unmarshal(raw, &state); err != nil {
+		t.Fatal(err)
+	}
+	runs, _ := state["runs"].(map[string]any)
+	held, _ := runs[turn].(map[string]any)
+	if held["class"] != "turn_call" {
+		t.Fatalf("the turn call's start was held as %v", held)
+	}
+	delete(held, "class")
+	raw, _ = json.Marshal(state)
+	if err := os.WriteFile(statePath, raw, 0o644); err != nil {
+		t.Fatal(err)
+	}
+
+	post(`{"post":[` + llm(mixed, plain, one, false) + `,` + llm(plural, plain, `{"messages":[[`+message+`],[`+message+`]]}`, true) + `,` +
+		llm(lone, plain, "", true) + `,` + llm(odd, plain, `{"messages":"invalid"}`, true) +
+		`],"patch":[` + patch(inner) + `,` + patch(turn) + `,` + patch(mixed) + `]}`)
+	second := &langsmith.Collector{Zone: zone, ProviderBodies: true, Now: func() time.Time { return time.Date(2026, 9, 20, 12, 0, 0, 0, time.UTC) }}
+	landed, err := second.Collect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(landed.Sessions) != 1 {
+		t.Fatalf("sessions %v", landed.Sessions)
+	}
+	session := landed.Sessions[0]
+
+	// Every record of each call, start first, by its flags.
+	flags := map[string][][]string{}
+	files, err := storage.LandedFiles(zone, session)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, lf := range files {
+		if lf.Stream == "" {
+			continue
+		}
+		f, err := sessiondata.Open(lf.Path)
+		if err != nil {
+			t.Fatal(err)
+		}
+		r, err := sessiondata.NewReader(f)
+		if err != nil {
+			t.Fatal(err)
+		}
+		for {
+			rec, err := r.Next()
+			if err != nil {
+				break
+			}
+			if rec.Call != "" && rec.From == sessiondata.FromAgent {
+				flags[rec.Call] = append(flags[rec.Call], rec.Flags)
+			}
+		}
+		f.Close()
+	}
+	class := func(f []string) string {
+		for _, c := range []string{model.ClassInternalCall, model.ClassTurnCall} {
+			if slices.Contains(f, c) {
+				return c
+			}
+		}
+		return ""
+	}
+	classes := func(call string) []string {
+		var out []string
+		for _, f := range flags[call] {
+			out = append(out, class(f))
+		}
+		return out
+	}
+	// The index reads the same classes off the same records, by its own bits.
+	ix, ok, err := index.Load(zone.IndexDir(session), session)
+	if err != nil || !ok {
+		t.Fatalf("index: %v %v", ok, err)
+	}
+	indexed := map[string][]string{}
+	for _, e := range ix.Entries {
+		if e.Kind != index.KindAssistant || e.Call == 0 {
+			continue
+		}
+		c := ""
+		switch {
+		case e.Flags.Has(index.FlagInternalCall):
+			c = model.ClassInternalCall
+		case e.Flags.Has(index.FlagTurnCall):
+			c = model.ClassTurnCall
+		}
+		indexed[ix.Strings.String(e.Call)] = append(indexed[ix.Strings.String(e.Call)], c)
+	}
+	for call, want := range map[string][]string{
+		inner: {model.ClassInternalCall, model.ClassInternalCall},
+		turn:  {model.ClassTurnCall, ""},
+		// The second start says turn_call of itself; the update carries what
+		// the state kept of the two: internal_call.
+		mixed: {model.ClassInternalCall, model.ClassTurnCall, model.ClassInternalCall},
+	} {
+		if got := classes(call); fmt.Sprint(got) != fmt.Sprint(want) {
+			t.Errorf("%s: classes %v, want %v", call, got, want)
+		}
+		if got := indexed[call]; fmt.Sprint(got) != fmt.Sprint(want) {
+			t.Errorf("%s: indexed classes %v, want %v", call, got, want)
+		}
+	}
+
+	bodies, _ := heldInOrder(t, zone, session)
+	for call, why := range map[string]string{plural: providerbody.PluralPrompts, lone: providerbody.NoRequest,
+		odd: providerbody.UnrecognisedShape, turn: ""} {
+		m, ok := bodies.Manifest(call + ":response")
+		if !ok || m.Reply == nil || m.Reply.Unavailable != why {
+			t.Errorf("%s: reply %+v, want unavailable %q", call, m, why)
+		}
+	}
+}
+
+// historyCost is what the history fields add to one root: their bytes in the
+// manifests beside every byte the provider files hold; what they add to the
+// index and to the skeletons, each written both ways and measured on disk;
+// and how many lists the cap took.
+type historyCost struct {
+	manifest, landed, index, skeleton int64
+	tooLarge, lists                   int
+}
+
+// withoutHistory is a record as a collector before the history fields wrote
+// it: a manifest with none of them, and no class flag.
+func withoutHistory(t *testing.T, rec *sessiondata.Record) *sessiondata.Record {
+	t.Helper()
+	out := *rec
+	out.Flags = slices.DeleteFunc(slices.Clone(rec.Flags), func(f string) bool {
+		return f == model.ClassInternalCall || f == model.ClassTurnCall
+	})
+	m, err := providerbody.ManifestOf(rec)
+	if err != nil {
+		return &out
+	}
+	m.HistoryForm, m.Messages, m.System, m.Thread, m.Reply = "", nil, "", nil, nil
+	var buf bytes.Buffer
+	enc := json.NewEncoder(&buf)
+	enc.SetEscapeHTML(false)
+	if err := enc.Encode(m); err != nil {
+		t.Fatal(err)
+	}
+	data := bytes.TrimSuffix(buf.Bytes(), []byte{'\n'})
+	out.Parts = append([]sessiondata.Part(nil), rec.Parts...)
+	last := &out.Parts[len(out.Parts)-1]
+	last.Data, last.Bytes = data, len(data)
+	return &out
+}
+
+// storageCost writes a session's index, and the skeletons of its provider
+// files, as landed and with the history taken out, and returns what the
+// history adds to each on disk.
+func storageCost(t *testing.T, zone *storage.Zone, session string) (indexBytes, skeletonBytes int64) {
+	t.Helper()
+	files, err := storage.LandedFiles(zone, session)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var sizes [2]struct{ index, skeleton int64 }
+	for way := range sizes {
+		ix := index.New(session)
+		for _, lf := range files {
+			f, err := sessiondata.Open(lf.Path)
+			if err != nil {
+				t.Fatal(err)
+			}
+			r, err := sessiondata.NewReader(f)
+			if err != nil {
+				t.Fatal(err)
+			}
+			hdr := r.Header()
+			var file bytes.Buffer
+			w, err := sessiondata.NewWriter(&file, &hdr)
+			if err != nil {
+				t.Fatal(err)
+			}
+			for row := uint32(1); ; row++ {
+				rec, err := r.Next()
+				if errors.Is(err, io.EOF) {
+					break
+				}
+				if err != nil {
+					t.Fatal(err)
+				}
+				if way == 1 {
+					rec = withoutHistory(t, rec)
+				}
+				e, blocks, body := index.FromRecord(ix, &hdr, rec, uint32(lf.Seq), row)
+				ix.AppendRecord(e, blocks, body)
+				if err := w.Write(rec); err != nil {
+					t.Fatal(err)
+				}
+			}
+			f.Close()
+			if err := w.Close(); err != nil {
+				t.Fatal(err)
+			}
+			if hdr.Kind == sessiondata.KindProviderBody {
+				var sk bytes.Buffer
+				if err := skeleton.Write(&sk, file.Bytes(), sessiondata.Removed{}); err != nil {
+					t.Fatal(err)
+				}
+				sizes[way].skeleton += int64(sk.Len())
+			}
+		}
+		dir := t.TempDir()
+		if err := ix.Write(dir); err != nil {
+			t.Fatal(err)
+		}
+		info, err := os.Stat(filepath.Join(dir, "entries.bin"))
+		if err != nil {
+			t.Fatal(err)
+		}
+		sizes[way].index = info.Size()
+	}
+	return sizes[0].index - sizes[1].index, sizes[0].skeleton - sizes[1].skeleton
+}
+
+func costOf(t *testing.T, zone *storage.Zone, sessions []string) historyCost {
+	t.Helper()
+	var c historyCost
+	for _, session := range sessions {
+		held, order := heldInOrder(t, zone, session)
+		for _, id := range order {
+			m, _ := held.Manifest(id)
+			if m.HistoryForm == "" {
+				continue
+			}
+			only, err := json.Marshal(struct {
+				Form     string                 `json:"history_form"`
+				Messages *providerbody.Messages `json:"messages,omitempty"`
+				System   string                 `json:"system,omitempty"`
+				Thread   *providerbody.Thread   `json:"thread,omitempty"`
+				Reply    *providerbody.Reply    `json:"reply,omitempty"`
+			}{m.HistoryForm, m.Messages, m.System, m.Thread, m.Reply})
+			if err != nil {
+				t.Fatal(err)
+			}
+			c.manifest += int64(len(only))
+			if m.Messages != nil {
+				c.lists++
+				if m.Messages.Unavailable == providerbody.TooLarge {
+					c.tooLarge++
+				}
+			}
+		}
+		files, err := storage.LandedFiles(zone, session)
+		if err != nil {
+			t.Fatal(err)
+		}
+		for _, lf := range files {
+			if lf.Stream == "" && lf.RunID == "" {
+				info, err := os.Stat(lf.Path)
+				if err != nil {
+					t.Fatal(err)
+				}
+				c.landed += info.Size()
+			}
+		}
+		ix, sk := storageCost(t, zone, session)
+		// Both ways write the new format, so what it adds whatever a body
+		// holds is counted by hand: the 61 bytes each body row gained over
+		// the 13 it had, and the 4 that count the message table.
+		c.index += ix + int64(61*len(order)) + 4
+		c.skeleton += sk
+	}
+	return c
+}
+
+// TestWhatTheHistoryCosts measures the history fields on every capture, at the
+// default budget and at 4 KiB, the smallest budget a scenario uses, and holds
+// what the cap may do: nothing at the default. The numbers are on the Session
+// Data page.
+func TestWhatTheHistoryCosts(t *testing.T) {
+	cases, err := os.ReadDir(corpus)
+	if err != nil {
+		t.Skipf("no corpus: %v", err)
+	}
+	for _, budget := range []int64{2 << 20, 4 << 10} {
+		var lc historyCost
+		for _, c := range cases {
+			if !c.IsDir() {
+				continue
+			}
+			zone := storage.NewZone(t.TempDir())
+			sessions := landIntoBudget(t, zone, c.Name(), budget)
+			got := costOf(t, zone, sessions)
+			lc.manifest, lc.landed, lc.index = lc.manifest+got.manifest, lc.landed+got.landed, lc.index+got.index
+			lc.skeleton += got.skeleton
+			lc.tooLarge, lc.lists = lc.tooLarge+got.tooLarge, lc.lists+got.lists
+		}
+		cc := costOf(t, landThreadsBudget(t, budget), []string{threadsSession})
+		t.Logf("budget %d: LangChain history %d bytes of %d landed (%.1f%%), index +%d, skeletons +%d, %d of %d lists too large; "+
+			"Claude Code history %d bytes of %d landed (%.1f%%), index +%d, skeletons +%d, %d of %d lists too large",
+			budget, lc.manifest, lc.landed, 100*float64(lc.manifest)/float64(lc.landed), lc.index, lc.skeleton, lc.tooLarge, lc.lists,
+			cc.manifest, cc.landed, 100*float64(cc.manifest)/float64(cc.landed), cc.index, cc.skeleton, cc.tooLarge, cc.lists)
+		// At 4 KiB the cap takes the eleven longest lists of the twenty-turn
+		// conversation, 19 to 39 messages, whose bodies copy from no earlier
+		// one under so small a budget; at the default it takes none.
+		want := map[int64]int{2 << 20: 0, 4 << 10: 11}[budget]
+		if lc.tooLarge != want || cc.tooLarge != 0 {
+			t.Errorf("at %d bytes the cap took %d LangChain and %d Claude Code lists; measured %d and 0",
+				budget, lc.tooLarge, cc.tooLarge, want)
+		}
+	}
+}
+
+// replyOutcome is how the reply of the call at own comes back in the requests
+// of the calls after it, requests being every call's list in landed order:
+// equal, under its id altered, or not at all. Its own call's request, and an
+// earlier one's, are never looked in.
+func replyOutcome(reply providerbody.Entry, requests [][]providerbody.Entry, own int) string {
+	result := "not sent back"
+	for _, list := range requests[own+1:] {
+		for _, e := range list {
+			switch {
+			case e == reply:
+				return "back"
+			case reply.ID != "" && e.ID == reply.ID:
+				result = "back changed"
+			}
+		}
+	}
+	return result
+}
+
+// TestAReplyIsLookedForOnlyAfterItsCall holds the measurement's own rule: a
+// reply found only in its own call's request is not sent back.
+func TestAReplyIsLookedForOnlyAfterItsCall(t *testing.T) {
+	r := providerbody.Entry{ID: "0000000000000001", Digest: "000000000000000a"}
+	altered := providerbody.Entry{ID: r.ID, Digest: "000000000000000b"}
+	other := providerbody.Entry{Digest: "000000000000000c"}
+	// The call is the second of three: an earlier request and its own hold the
+	// reply; only the third is looked in.
+	for want, third := range map[string][]providerbody.Entry{
+		"not sent back": {other},
+		"back":          {other, r},
+		"back changed":  {altered},
+	} {
+		requests := [][]providerbody.Entry{{r}, {r}, third}
+		if got := replyOutcome(r, requests, 1); got != want {
+			t.Fatalf("%v: %s, want %s", third, got, want)
+		}
+	}
+}
diff --git a/tests/scenarios/provider-bodies-too-large.expect.yaml b/tests/scenarios/provider-bodies-too-large.expect.yaml
index 97e664b..e90e67f 100644
--- a/tests/scenarios/provider-bodies-too-large.expect.yaml
+++ b/tests/scenarios/provider-bodies-too-large.expect.yaml
@@ -16,15 +16,17 @@
 # under the License.
 
 # Three calls on one stream, each with its request and its response joined
-# to it, a file for each call. Every request cites the first prompt, which is
-# too large to land, so no request can be rebuilt from the root.
+# to it. Every request cites the first prompt, which is too large to land, so
+# no request can be rebuilt from the root, nor copied from, and each writes its
+# message list whole. With the list in its manifest, one call's two bodies no
+# longer fit one file of the budget, so the six bodies take four files.
 checkpoints:
   final:
     kinds: {llm.call: 3}
     view:
       state: verified
       provider_bodies: 6
-      provider_files: 3
+      provider_files: 4
       captured_prompts: 3
 collect:
   # Smaller than the first prompt.