feat(intake): stream session transcripts past the 512MB string ceiling - #610
Merged
Conversation
parseCodeAgentJsonl takes the whole transcript as one string. V8 cannot
build a string longer than 536,870,888 characters, so a session above that
size throws ERR_STRING_TOO_LONG and no part of it can be ingested. The
largest real Codex rollout in the local corpus is 696,493,387 bytes and is
invisible to every trace analysis today. The longest sessions are the
long-horizon runs a research program most needs to read.
Add streamCodeAgentJsonlFile, which reads a transcript one line at a time
and retains only the unterminated tail, and parseCodeAgentJsonlFile, which
returns the existing { entries, malformedLines } shape from a file path.
The string entrypoint keeps its signature and behavior.
Both paths call one readCodeAgentJsonlLine helper, so blank-line skipping,
malformed-line counting, and entry parsing cannot drift apart.
The stream breaks lines on \n only, which is what split('\n') does.
node:readline also breaks on a bare \r, which reports a different malformed
count for the same file, so it is not used.
Measured on the 696,493,387-byte rollout: the string path throws
ERR_STRING_TOO_LONG; the stream reads 90,170 entries and 1 malformed line
under a hard 128MB heap cap. On three real 158-189MB sessions, including
one with 2 malformed lines, both paths produce identical per-entry
SHA-256 digests and identical counts.
tangletools
approved these changes
Aug 15, 2026
tangletools
left a comment
Contributor
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 6d0573fc
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
This approval is provisional. It rests on the audit running. If the audit cannot run — for example the CLI bridge rejects it — this approval is dismissed rather than left standing, so an unrun check never reads as a passing one.
tangletools · auto-approval · reason: drewstone_author · 2026-08-15T05:23:57Z
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
parseCodeAgentJsonltakes the whole transcript as one string. V8 cannot build a string longer than 536,870,888 characters, so any session above that size throwsERR_STRING_TOO_LONGand no part of it can be ingested. This is total loss, not degraded reading.The largest real Codex rollout in the local corpus is 696,493,387 bytes (664.2MB) and is invisible to every trace analysis today. The biggest sessions are exactly the long-horizon runs a 24-hour research program most needs analyzed.
The change
streamCodeAgentJsonlFile(path)— async generator over acreateReadStream, yielding oneCodeAgentJsonlLineper non-blank line. Only the unterminated tail is retained, so live memory is bounded by the longest single line, not by file size.parseCodeAgentJsonlFile(path)— returns the existing{ entries, malformedLines }shape from a file path, so a caller that holds every entry keeps working above the ceiling.parseCodeAgentJsonl(jsonl)keeps its exact signature and behavior.Both paths call one
readCodeAgentJsonlLinehelper. Blank-line skipping, malformed-line counting, and entry parsing are shared code, not a copy, so they cannot drift apart.Why not
node:readlinenode:readlinebreaks on a bare\r;split('\n')does not. The same file therefore reports a different malformed-line count on the two paths:The stream breaks on
\nonly. A test pins this, and it fails against areadlineimplementation (see below).Memory proof
The bounded consumer reads the 664.2MB rollout under a hard 128MB heap cap:
The 64MB failure is the
tsx+ TypeScript-import baseline, not the parser: the same script OOMs identically at 64MB on a 2MB file. Peak RSS under the default heap is a lazy-GC high-water mark, not the live set — the hard-cap runs are the real bound.parseCodeAgentJsonlFileon the same file: 90,170 entries, peak RSS 1547.2MB, 4.9s. It clears the string ceiling but still holds every entry; consume the generator directly when memory must stay flat. The doc comment says so.Equivalence proof
Three real Codex sessions, per-entry SHA-256 over
JSON.stringify(per entry rather than one giant string, so the comparison cannot hit the ceiling it is testing):rollout-2026-07-22T20-33-54rollout-2026-07-15T09-14-33rollout-2026-08-01T02-22-50Tests
11 new cases in
tests/contract-code-agent-intake.test.ts, all asserting the file path equals the string path: the committedcodex-exec-0.144.1.jsonlfixture, malformed-line parity, no trailing newline, CRLF, blank and whitespace-only lines, empty file, all-malformed content, bare\r, chunk-spanning entries, and line numbering with early consumer exit.Two are load-bearing, proven by mutation:
node:readline→ the bare-\rcase fails (expected 2 to be 1).expected [] to have a length of 8), and so does "no trailing newline".Verify chain
Run on this branch, rebased onto
origin/mainat8093968. Every step exit 0, no pre-existing failures encountered.pnpm lintpnpm run check:model-idspnpm typecheckpnpm typecheck:examplespnpm typecheck:scriptspnpm testpnpm buildpnpm run verify:packageBuilt artifact re-checked, not just the source: