Smarter truncation for oversized script results
Status summary: implemented; unit tests green (typecheck/lint/knip/format clean). CI green on PR #2400. Followup task filed for typed Results<...> handles.
When an agent script returns a big result, renderScriptSettlement slices the pretty-printed JSON at scriptResultHistoryLimit (30k chars) — bisecting JSON mid-key and showing only the start of the payload (often one giant array's first entries). The model learns nothing about overall shape; in this chat the agent had to guess a prior result's structure and got it wrong.
Fix: on overflow, render (1) an inferred TypeScript type of the whole value, (2) a small inspect-style preview (a few items per array, capped strings/depth, valid JSON with truncation markers), (3) the existing spill notice. The full result already spills to script-results/<id>.json; the type tells the agent how to write the follow-up readFile script.
Decisions (grilled with Misha 2026-08-04):
- Drill-in stays the spill-file +
itx.workspace.readFilerecipe. TypedResults<"agent-output-400">cast is a followup (seetasks/typed-script-result-handles.md). - Type inference is hand-rolled (sync, zero deps, Workers-safe) — not quicktype (Workers compat scar tissue: the
quicktype-core>readable-streampnpm override). - Preview builds on the existing
truncateJsonToBytes(was PostHog-only) with an aggressive arrays/strings/depth policy pre-pass. - Budget shrinks on overflow: type ~3k chars + preview ~10k bytes + notice. 30k stays the passthrough threshold, per-agent patchable as before.
Checklist#
-
apps/os/src/lib/infer-json-type.ts—inferJsonType(value, {maxChars})→ TS type text: structural merge of array elements (union cap ~3), cardinality comments (// 1234 items,// ~45k chars), small literal unions for repeated short strings, budget enforced by collapsing deepest subtrees tounknownshape lattice + merge + depth-shrinking renderer; also renders wide keyed maps asRecord<string, T>and drops undefined-valued keys to match the spilled JSON - unit tests
apps/os/src/lib/infer-json-type.test.ts11 tests incl. optionality-from-undefined and budget enforcement - move
apps/os/src/domains/integrations/truncate-json.ts(+ test) →apps/os/src/lib/, update posthog import; no behavior change git mv; only importer was posthog.ts - add
previewJson(value, {maxArrayItems, maxStringChars, maxDepth, maxBytes})to truncate-json — policy pre-pass reusing existing marker idiom, then byte-budget guarantee policy pass over the MeasuredJson tree (tiny ≤100-byte subtrees survive untouched), then truncateJsonToBytes for the ceiling - rewire
renderScriptSettlementJSON overflow branch: type block + preview block + spill notice (recipe nowconst data: Result = JSON.parse(...)); raw-text results keep slice path with ~10k preview; failure branch unchanged;inferJsonTypefailure degrades to no type blockrenderOversizedJsonResultin agent-processor-implementation.ts; budgets 3k type / 8k preview / 10k raw-text, each clamped to historyLimit - update system-prompt one-liner in
agent-defaults.ts(~line 207) - update render expectations in
agent-processor.test.ts(~1040–1140); e2e spill test should pass as-is JSON spill test asserts type block + typed recipe; new test covers array-eliding preview with 400 rows - file followup
tasks/typed-script-result-handles.md
Implementation log#
- 2026-08-04: implemented as specced. Full os unit lane green (2545 passed). Demo render on a Slack-history-shaped 270KB payload: type block ~20 lines with
thread_ts?: stringoptionality and literal unions, preview 3 messages +[truncated 797 items; from 271814 JSON bytes]marker — total rendered item ~2.5KB vs 30KB before.