perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

perf: make streaming projection and activity appends incremental - #9152

Merged
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental
Sep 2, 2026
Merged

perf: make streaming projection and activity appends incremental#9152
t3dotgg merged 4 commits into
mainfrom
t3code/streaming-projection-incremental

Conversation

@t3dotgg

@t3dotggt3dotgg commented Sep 2, 2026

Copy link
Copy Markdown
Member

Takes over #6608 by @x1xhlol.

Problem

A streaming turn emits one thread.activity-appended event per tool step and one thread.message-sent per delta. On the server, each user message still ran the full refreshThreadShellSummary, which reads every message body in the thread. On the client, every appended activity re-filtered and re-sorted the whole activity array, so a long turn cost O(n² log n) across the stream.

Fix

Server (ProjectionPipeline.ts): thread.message-sent now updates the thread row directly. It bumps updatedAt and advances latestUserMessageAt only when a newer user message arrives. That is the only summary field a message can change, and the message projector never rewrites createdAt of an existing message, so the running maximum matches the full recompute. Activities keep the existing lifecycle-kind gate from #8150.

Client (threadReducer.ts): thread.activity-appended takes an O(1) append path when the current array was produced by this reducer (so it is known to be activityOrder sorted), the new activity sorts at or after the tail, and its id is unseen. A WeakMap keyed by array identity holds the id set and moves forward with each append. Out-of-order arrivals, re-deliveries, resolvable context-window updates (which supersede earlier ones for the same turn), and snapshot-loaded arrays all fall through to the existing filter and sort path, which rebuilds the index.

Rebase notes

Main moved under the original branch:

Audit of every field refreshThreadShellSummary computes and which events can change it:

FieldSource rowsEvents that write themStill refreshes
latestUserMessageAtmessages (role user)thread.message-sent (user), thread.revertedfolded directly / yes
pendingApprovalCountpending approvalsapproval.requested, approval.resolved, provider.approval.respond.failed, thread.approval-response-requestedyes
pendingUserInputCountuser-input lifecycle activitiesuser-input.requested, user-input.resolved, provider.user-input.respond.failedyes
hasActionableProposedPlanlatestTurnId plus plan rowsthread.proposed-plan-upserted, thread.session-set, thread.turn-diff-completed, thread.revertedyes

Measurement

Reducer benchmark, N in-order thread.activity-appended events on one thread, vitest on this machine, best of 2:

Nmainthis branch
2,00061 ms (31 us per append)12 ms (6 us per append)
10,0001,180 ms (118 us per append)70 ms (7 us per append)

The per-append cost on this branch stays flat as the thread grows. Server side, each user message no longer runs the four summary queries, including the full message-body read.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts packages/client-runtime/src/state/threadReducer.test.ts: 58 passed
  • pnpm typecheck in apps/server and packages/client-runtime: clean
  • vp lint on the four changed files: clean

The original commits are cherry-picked with @x1xhlol as author.

Change authored by Claude Fable 5.1 running in Claude Code, based on work by @x1xhlol.


Note

Medium Risk
Changes projection correctness for latestUserMessageAt and client activity ordering; behavior is heavily tested but touches hot paths during streaming.

Overview
Streaming turns were doing redundant work on every event: user messages triggered a full thread shell summary recompute (including scanning all message bodies), and each thread.activity-appended on the client re-sorted the entire activity list.

On the server (ProjectionPipeline.ts), thread.message-sent now updates projection_threads in place—updatedAt always advances; latestUserMessageAt only moves forward for newer user messages. The shouldRefreshThreadShellSummary gate no longer treats user messages as refresh triggers; lifecycle activities and other events still use the full refresh path.

On the client (threadReducer.ts), in-order live thread.activity-appended events can take an O(1) append when the activities array was produced by this reducer (tracked via a WeakMap id set), the new row sorts at or after the tail, and the id is new. Out-of-order delivery, re-deliveries, context-window supersession, and snapshot-loaded arrays still use filter, dedupe, and sort.

Tests add end-to-end shell summary assertions (user vs streaming assistant vs tool vs user-input activities) and reducer cases for reordering, snapshot repair, and redelivery.

Reviewed by Cursor Bugbot for commit c5ce425. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Make thread projection and client activity appends incremental

  • Server projection now treats every thread.message-sent event as eligible to refresh the thread shell summary, and the applyProjectsProjection handler for thread.message-sent updates updatedAt on every event while advancing latestUserMessageAt only for newer user messages.
  • Client threadReducer adds a WeakMap-backed activity-id index and an in-order fast path for activity-appended events: unseen activities that sort at or after the tail are appended without a full re-sort. The fallback deduplicates re-delivered activity ids, removes superseded context-window activities, and sorts the result.
  • Adds regression and integration tests covering shell-summary field persistence, out-of-order activity sorting, snapshot ordering repair, and activity redelivery deduplication.
  • Behavioral Change: shouldRefreshThreadShellSummary in ProjectionPipeline.ts no longer returns false for any thread.message-sent event; activity refresh is now restricted to approval and user-input lifecycle kinds only.

Macroscope summarized c5ce425.

x1xhloland others added 4 commits September 1, 2026 17:49
…vity
User messages no longer refresh the shell summary, so the stale user-input
test now appends the last lifecycle activity through the pipeline to force
the read it checks. Also drops a comment duplicated by the rebase.
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.4 KiB13.5 KiB+7 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−7 B (−0.1%)7.3 KiB
CodexLive turn WebSocket wire6.5 KiB6.6 KiB+14 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded57.0 KiB57.0 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.5 KiB−21 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−4 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.6 KiB−17 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ea71a19 · PR result: c5ce425 · 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: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

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

@macroscopeapp

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This production performance refactor changes how thread summaries and streamed activity histories are maintained, including gates that skip substantial existing work and a new client fast path. The added tests cover key ordering and summary cases, but the cross-layer hot-path behavior warrants human review.

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

@t3dotgg
t3dotgg merged commit c2283ce into mainSep 2, 2026
23 checks passed
@t3dotgg
t3dotgg deleted the t3code/streaming-projection-incremental branch September 2, 2026 01:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@t3dotgg@x1xhlol