fix(server): long agent turns no longer load whole when a thread opens#24589

merged5
main
t3/aa846120
opened8d agobyTtheo
View on GitHubTwo-way sync enabledPR last imported 8d ago

Problem

A thread with any user turn loaded its last 10 user turns with no row or byte limit. One long agent turn could put thousands of rows into a single cold open: #24142 reports 10,454 rows (86 MB stored) that froze the client. No test capped what web and mobile actually load.

Fix

  • Every history window stops at the 1 MiB byte budget. A window that holds a user turn also stops at 10 user turns (20 on older pages) or 500 rows, so ordinary turns stay whole, as V1's 500-activity cap kept them. Inside one turn longer than 500 rows, the window pages with the existing 75-row budget. The snapshot query applies the same rule before it decodes payloads.
  • Builds on #24169 by @luke2x, with their commits kept: budgets inside human turns, interrupt requests that travel with their results, targeted pages that keep their target, and the thread-cache version bump that drops oversized cached snapshots. This PR raises the in-turn cap from 75 to 500 rows so that ordinary threads still open whole.
  • New CI cap on the real client path. The transfer budget test opens a thread whose last user turn holds 900 rows (reasoning, commands, file changes, MCP calls, progress messages, checkpoints, subagents) through /bounded?compactTurnItems=1, then loads one older page. It caps decoded bytes (120 KB) and rows (75). On main this cold open carried all 920 rows, 1.07 MB decoded, and the test fails.
  • Removed payloadBudgetExceeded. No client read it, and it cost an extra full encode for each bounded snapshot. The field was optional in the contract, so older clients do not notice, and newer clients drop it when an older server sends it.
  • The old-client getThreadProjection path now strips tool bodies before it applies the byte budget.
  • docs/internals/performance-regressions.md now describes the real window rules and lists the bodies that still ship.

Real data

Compact bounded cold open (/bounded?compactTurnItems=1) for 1,321 threads in a maintainer's real database, measured with the real server code. Sizes are decoded JSON.

Policyp50p90p99maxmax rowsover 1 MiBopens with older history
main67 KB281 KB1.42 MB5.24 MB1,75420189
75 rows everywhere (#24169)67 KB262 KB338 KB1.77 MB751302
this PR67 KB281 KB768 KB2.35 MB2863218

This PR changes the rows of 33 threads (2.5%) and newly splits 29 of them. Every other thread keeps the same rows. The 75-row policy changes 154 threads and newly splits 113.

Known gap: control records (nodes, runs, checkpoints, subagents) follow the up to 502 rows the snapshot query loads, not the rows that ship. On the three largest split threads that leaves about 0.2 to 1 MB of extra control data. Pruning control records to the shipped window is a follow-up.

Verification

  • vp run test:perf:v2-wire: 16 files, 242 tests passed.
  • vp test run apps/server/integration/transferBudgetV2.integration.test.ts passes, and fails on main.
  • vp run check:changed passes.
  • Not run in a real client. Threads with a turn longer than 500 rows now open at their newest rows and load older rows on scroll, like agent-driven threads already do. Opening inside a folded "Worked for" group can still hit #21957.

Fixes #24142. Supersedes #24169.

Made with Claude Opus 5.5 in Claude Code, running in T3 Code.

🤖 Generated with Claude Code

Checks

23 successful · 11 skipped · 1 neutral
All checks passed
944fe09

Added 5 commits

  • test(history): expect long turns to page by size49984f0
  • test(server): cap the compact cold open of a long agent turn944fe09
  • fix(server): keep user turns whole in thread windows up to 500 rowsa921e62
  • fix(history): enforce page budgets inside human turns and discard oversized cachescc2cd48
  • fix(history): keep targeted pages and interrupt requests intact after boundingec2fc24
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This PR changes the default thread-history loading and pagination behavior across server snapshots, older-page requests, WebSocket responses, and client cache invalidation. The cross-cutting runtime change and new default row limits require human review.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

âś… Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiBâś…
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBâś…
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiBâś…
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiBâś…
CodexLive turn messages220 (0.0%)8âś…
ClaudeTotal thread wire5.0 KiB5.0 KiB+24 B (+0.5%)6.8 KiBâś…
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBâś…
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB+24 B (+2.0%)2.0 KiBâś…
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB+41 B (+0.2%)29.3 KiBâś…
ClaudeLive turn messages12+1 (+100.0%)8âś…

Baseline: 3cc31ed · PR result: 944fe09 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 3cc31ed27c8956b36cee35a50509fb7c6869592b and 944fe094a8c9c15d1e989f010c6efbd411b3ed2e.

External image Files selected for processing: 18
  • apps/server/integration/TransferBudgetReport.integration.ts
  • apps/server/integration/TransferBudgetV2Fixture.integration.ts
  • apps/server/integration/transferBudgetV2.integration.test.ts
  • apps/server/src/orchestration-v2/ProjectionStore.test.ts
  • apps/server/src/orchestration-v2/ProjectionStore.ts
  • apps/server/src/orchestration-v2/ThreadFind.test.ts
  • apps/server/src/orchestration-v2/ThreadStream.test.ts
  • apps/server/src/orchestration-v2/ThreadStream.ts
  • apps/server/src/orchestration-v2/threadHistoryPaging.test.ts
  • apps/server/src/orchestration-v2/threadHistoryPaging.ts
  • apps/server/src/ws.ts
  • docs/internals/performance-regressions.md
  • packages/client-runtime/src/platform/orchestrationCache.test.ts
  • packages/client-runtime/src/platform/orchestrationCache.ts
  • packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts
  • packages/client-runtime/src/state/threads-atoms.test.ts
  • packages/client-runtime/src/state/threads-sync.test.ts
  • packages/contracts/src/orchestrationV2.ts
External image Files with no reviewable changes: 5
  • packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts
  • apps/server/src/orchestration-v2/ThreadStream.test.ts
  • packages/client-runtime/src/state/threads-atoms.test.ts
  • apps/server/src/orchestration-v2/ThreadStream.ts
  • packages/contracts/src/orchestrationV2.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Walkthrough

History snapshots and older pages now enforce row and encoded-byte bounds across human turns, while retaining cursor access to older items and required interrupt requests. The PR adds long-turn integration measurements, removes the payloadBudgetExceeded field from snapshot contracts, and increments the client cache schema version.

Changes: Bounded Thread History

Layer / File(s)Summary
Paging limits and cursor rules
apps/server/src/orchestration-v2/threadHistoryPaging.ts, apps/server/src/orchestration-v2/threadHistoryPaging.test.ts, apps/server/src/orchestration-v2/ThreadFind.test.ts
Paging now applies row and encoded-byte limits to user-turn windows and pages inside long turns. Older-page selection can widen its policy to reach a target. Tests cover long turns, oversized items, and paging limits.
Snapshot and history selection
apps/server/src/orchestration-v2/ProjectionStore.ts, apps/server/src/orchestration-v2/ProjectionStore.test.ts
SQL and in-memory projection paths cap snapshot rows and apply bounded policies to history pages. Pages include matching interrupt requests when needed. Tests cover byte-bounded traversal, targeted pages, and row limits.
Snapshot contract and cache version
apps/server/src/ws.ts, apps/server/src/orchestration-v2/ThreadStream.ts, apps/server/src/orchestration-v2/ThreadStream.test.ts, packages/contracts/src/orchestrationV2.ts, packages/client-runtime/src/platform/orchestrationCache.ts, packages/client-runtime/src/platform/orchestrationCache.test.ts, packages/client-runtime/src/state/*
Snapshot wire projection occurs before bounding. The snapshot contract no longer includes payloadBudgetExceeded, and the client cache schema version changes to 5. Client tests cover rejecting older cached snapshots and loading a fresh snapshot.
Long-turn integration measurement
apps/server/integration/TransferBudgetV2Fixture.integration.ts, apps/server/integration/transferBudgetV2.integration.test.ts, apps/server/integration/TransferBudgetReport.integration.ts, docs/internals/performance-regressions.md
The integration fixture adds a 900-row turn. The test measures its bounded snapshot and an older history page. The report checks decoded bytes and row counts against long-turn limits; the performance notes describe the bounds and wire contents.

Priority: External image Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant TransferBudgetIntegrationTest
3 participant HTTPServer
4 participant ProjectionStore
5 participant ThreadHistoryPaging
6 TransferBudgetIntegrationTest->>HTTPServer: request bounded snapshot
7 HTTPServer->>ProjectionStore: load bounded thread projection
8 ProjectionStore-->>HTTPServer: snapshot and history cursor
9 HTTPServer-->>TransferBudgetIntegrationTest: return snapshot
10 TransferBudgetIntegrationTest->>HTTPServer: request older page with cursor
11 HTTPServer->>ProjectionStore: load history page
12 ProjectionStore->>ThreadHistoryPaging: select rows within page policy
13 ProjectionStore-->>HTTPServer: page and hasMoreHistory
14 HTTPServer-->>TransferBudgetIntegrationTest: return history page

Possibly related issues

  • pingdotgg/t3code#24142 — The issue describes human-turn history bypassing row and byte limits, which this PR addresses in snapshot selection and history paging.

Suggested labels: bugfix, size:L

Suggested reviewers: juliusmarminge, maria-rcks

Merge Risk: External image Minimal · up to 944fe

No merge-blocking issue was established; the change is mergeable after normal checks.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the primary change: preventing long agent turns from loading completely when a thread opens.
Description checkPassedThe description covers the problem, implementation, scope, linked issues, measured impact, verification results, known gaps, and test limitations. The “Fix” heading serves the template’s Change sectio…
Linked Issues checkPassedFor open issue #24142, the PR applies row and encoded-byte limits to user-turn windows in threadHistoryPaging.ts, including the 500-row in-turn limit and the 75-row limit inside longer turns. `Proje…
Out of Scope Changes checkPassedThe changes stay within #24142. The transfer-budget fixtures and tests verify the bounded client path. Cache schema invalidation addresses oversized snapshots created by the bypass. The wire-contract …
Full details: Description check

Explanation

The description covers the problem, implementation, scope, linked issues, measured impact, verification results, known gaps, and test limitations. The “Fix” heading serves the template’s Change section.

Full details: Linked Issues check

Explanation

For open issue #24142, the PR applies row and encoded-byte limits to user-turn windows in threadHistoryPaging.ts, including the 500-row in-turn limit and the 75-row limit inside longer turns. ProjectionStore.ts applies the corresponding limit before payload decoding for SQL and in-memory snapshots. The history path now applies the byte pass, preserves interrupt requests with their results, and preserves targeted-page reachability. ProjectionStore.test.ts verifies byte-bounded paging, exact recovery of 1,000 items without duplicates, targeted pages, and interrupt request retention. The transfer-budget integration test covers a 900-row turn on the client transfer path.

Full details: Out of Scope Changes check

Explanation

The changes stay within #24142. The transfer-budget fixtures and tests verify the bounded client path. Cache schema invalidation addresses oversized snapshots created by the bypass. The wire-contract removal of unused payloadBudgetExceeded, old-client projection ordering, documentation updates, and related fixture updates support the same history-budget behavior. No unrelated product behavior is demonstrated.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
đź§Ş Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.