Record reasoning too, and say why it needs more than one event

3d5577e2a7f3 · AtlantisPleb · · parent acf1cdf6ff0e

Record reasoning too, and say why it needs more than one event

An earlier draft left reasoning out of the transcript and called that a saving.
It was wrong twice. Reasoning is sent back to the model now, because a model
that cannot see how it reached its last answer reasons its way there again. And
it is the largest single thing a session produces — 150,322 characters against
8,232 characters of answer in one measured session — so a transcript without it
is a summary with the working removed, which is precisely the part worth keeping
for training, for review, or for understanding what a run did.

The bar is a full ATIF export. A transcript that cannot reproduce the session,
or that has to reach outside the server for part of it, is not a record of
anything.

That makes reasoning the one thing here a single event cannot hold: the payload
cap is 16,384 bytes and the largest single reasoning block measured is 38,791
characters. The cap is not the wrong size — a bounded event is the point of an
append-only evidence table — so reasoning is stored in ordered parts and
reassembled on read. The event count per turn is therefore variable, which the
cursor added in the last change already handles.

Only deltas are dropped, and only because a delta is how a reply arrived rather
than what it is.

What a resumed session **loads** is now stated as a separate question from what
it stores. Storing everything is settled; whether a long thread is replayed
whole, condensed, or replayed from a point forward is about context budgets, and
it stays answerable only because the whole record is there to choose from.

4153 tests pass.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

pushed
by user · WAL seq 291 · 2026-08-24T18:36:47.828222Z

Changed files

  • modified docs/2026-08-24-coder-account-integration-audit.md

Diff

1 file changed, +47 -19

docs/2026-08-24-coder-account-integration-audit.md modified +47 -19

@@ -156,36 +156,63 @@ Measured against four real coder sessions from the same afternoon:

156 156
| `17-08-32` | 4 | 5 | 4 | 9 | 8,418 B |
157 157
| `17-24-37` | 6 | 0 | 6 | 6 | 0 B |
158 158
159
The payload cap is not the binding constraint: the largest tool result observed
160
was 8.4 KB, and the transcript already bounds a result to 4,000 characters
161
before it reaches a model, so the same bound applied to an event keeps every one
162
of them comfortably inside 16 KB. A 30 KB shell output would not fit, which is
163
why the bound is applied rather than assumed.
159
For tool results the payload cap is not the binding constraint: the largest
160
observed was 8.4 KB, and the transcript already bounds a result to 4,000
161
characters before it reaches a model, so the same bound applied to an event
162
keeps every one of them comfortably inside 16 KB. A 30 KB shell output would not
163
fit, which is why the bound is applied rather than assumed. Reasoning is the
164
exception, and it has its own section below.
164 165
165 166
**The listing cap was the binding constraint.** Turn-level persistence fits
166 167
inside fifty with room to spare; tool-level passes it on an ordinary working
167
session. So `list_events/2` now takes an `:after` cursor and the route publishes
168
session, and recording reasoning in parts pushes it further still. So `list_events/2` now takes an `:after` cursor and the route publishes
168 169
each event's id, because a history that cannot be read back is not persistence.
169 170
That is the one server change this section required, and it is done.
170 171
171 172
### What to record
172 173
174
Everything a full ATIF export needs, because that is the bar: a thread's
175
transcript should be able to reproduce the session, and an export that has to
176
reach outside the server for part of it is not a record of anything.
177
173 178
Recorded, in the order they happen:
174 179
175 180
- `turn.user` — what the reader asked.
181
- `turn.reasoning` — what the model thought, in order, before it answered.
176 182
- `tool.ran` — one event per call, carrying the tool, its arguments, and its
177 183
  bounded result. Call and result are one event rather than two: they are one
178 184
  fact, and splitting them doubles the count against the cap for nothing.
179 185
- `turn.assistant` — the answer, with the turn's token usage and call count.
180 186
181
Not recorded: text deltas, reasoning deltas, and the interface's own notices.
182
Deltas are how a reply arrives, not what it is, and a transcript that stores the
183
arrival cannot be read back as the thing. Reasoning is display-only for the same
184
reason it is not on the model transcript. Notices never reached a model.
187
**Reasoning is recorded, and it is not optional.** An earlier draft of this
188
document left it out and called that a saving, on the grounds that it is not
189
sent back to a model. That was wrong twice over. It is sent back now — a model
190
that cannot see how it reached its last answer reasons its way there again — and
191
it is the largest single part of what a session produces: 150,322 characters
192
against 8,232 characters of answer in one measured session. A transcript without
193
it is a summary with the working removed, and the working is what makes a
194
trajectory worth keeping for training, for review, or for understanding what a
195
run actually did.
196
197
Not recorded: text and reasoning **deltas**, and the interface's own notices.
198
A delta is how a reply arrived rather than what it is, and a transcript that
199
stores the arrival cannot be read back as the thing. Notices never reached a
200
model. That is the whole of what is dropped.
201
202
### Reasoning does not fit in one event
203
204
The payload cap is 16,384 bytes and the largest single reasoning block measured
205
is **38,791 characters**. So reasoning is the one thing here that a single event
206
cannot hold, and the cap is not the wrong size — a bounded event is the point of
207
an append-only evidence table.
208
209
It is stored in ordered parts: `turn.reasoning` events carrying `part`, `of`,
210
and a slice bounded well inside the cap, reassembled in order on read. The
211
alternative is raising the constraint, which trades a bound that holds for every
212
event against one case, and the case is the one that will keep growing.
185 213
186
That is roughly ATIF's step shape, which is not a coincidence: `/export` already
187
writes a session in exactly this granularity, and the two should not disagree
188
about what a session was.
214
This also means the event count per turn is no longer fixed, which the cursor
215
already handles.
189 216
190 217
### Resume
191 218

@@ -202,12 +229,13 @@ follows:

202 229
`GET /api/v3/threads` is the picker's list and `GET /api/v3/threads/{id}/events`
203 230
is the transcript it replays. Neither needs anything new.
204 231
205
The one open question is what a resumed session sends to the model. The
206
transcript is evidence of what happened, not a chat history in a provider's
207
shape, so replaying it verbatim is not obviously right — and a long thread would
208
reintroduce exactly the context-size problem that bounding tool results just
209
solved. My instinct is that a resumed session replays the turns and the tool
210
results, not the deltas, and bounds them the same way a live session does.
232
The one open question is what a resumed session **loads**, which is separate
233
from what it stores and should be decided separately. Everything is recorded;
234
that is settled. Whether a long thread is replayed whole, or condensed, or
235
replayed from some point forward, is a question about context budgets and about
236
what a model needs to carry on — and it is answerable only because the whole
237
record is there to choose from. A decision to store less would have foreclosed
238
it.
211 239
212 240
## What I would do first
213 241

This page updates live while a promote is in flight · changelog