Skip to content

perf(server): streamed events no longer rewrite the whole thread row - #18890

Merged
t3dotgg merged 1 commit into
pingdotgg:mainfrom
Baker-Harrison:perf/server-thread-activity-patch
Oct 14, 2026
Merged

t3dotgg merged 1 commit into
pingdotgg:mainfrom
Baker-Harrison:perf/server-thread-activity-patch

Conversation

@Baker-Harrison

Copy link
Copy Markdown
Contributor

Problem

ProjectionStoreV2.apply ends with a step that runs for every event that is not a thread.* event. It selects the thread's projection row, schema-decodes the payload, sets updatedAt, schema-encodes it and writes the whole row back. Run, node, provider-turn and turn-item events all take that path, so a streamed turn pays a full decode and encode of the thread row for every flushed update, only to move one timestamp.

Change

The timestamp is patched in place with one statement:

UPDATE orchestration_v2_projection_threads
SET updated_at = ?, payload_json = json_set(payload_json, '$.updatedAt', ?)
WHERE thread_id = ?

The value is encoded with the thread schema's own updatedAt codec, so what is stored is the same string as before. This is the idiom run.background-work-cancelled already uses to patch a single run field. thread.* events still write the full row.

One side effect goes away: the old round trip also rewrote an older-shaped payload into the current encoded shape whenever any event touched the thread. That now happens only on thread.* events. Readers decode through the same schema either way, and the SQL that reads thread payload fields directly (archivedAt, settledOverride, snoozedUntil, pullRequests, and so on) uses IS NULL or json_array_length(...) > 0 checks that treat an absent field like a null or empty one.

Scope and approval

There is no linked issue or discussion. I am submitting this as a focused performance change with no intended change to behavior: the thread's updatedAt and the rest of its payload read back the same as before. If you would rather see an issue first, I will open one.

Verification

New test, dates thread activity by a run event and leaves the rest of the thread alone, in ProjectionStore.test.ts. It applies thread.created and then a run.created five seconds later, and asserts that:

  • the thread reads back equal to the created thread with only updatedAt moved to the run event's time,
  • the shell row carries the same updatedAt,
  • applying the run event issued no SELECT against orchestration_v2_projection_threads.

It fails on main (expected [ Array(1) ] to be empty) and passes on this branch.

Also run, in apps/server:

  • vp test run src/orchestration-v2/ProjectionStore.test.ts: 35 passed.
  • vp test run src/orchestration-v2/runtimeLayer.test.ts src/orchestration-v2/ThreadManagementService.test.ts: 109 passed.
  • vp run typecheck: passes. vp lint on the changed files: no errors.

Timing, from a scratch test that is not part of this PR: applying 5,000 turn-item.updated events to one thread through the test SQLite layer, three rounds each.

Round 1 Round 2 Round 3
main at e24ea1d 553 ms 487 ms 479 ms
This branch 365 ms 324 ms 307 ms

That is about a third less time per applied event in this setup. It is one run on one machine, so read it as a direction, not a benchmark.

Not checked: a real on-disk database, or a running server during a live turn.

Environment: macOS, Node 26.7.0 (the repo asks for ^24.13.1; the install warned and everything ran).

Model and harness: Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

Every run, node, provider-turn and turn-item event read the thread's
projection row, decoded it, re-encoded it and wrote it back just to move
updatedAt. During a streamed turn that is dozens of decode/encode cycles a
second. The timestamp is now patched in place with json_set, the same way
run.background-work-cancelled already patches a single run field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Oct 13, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 13, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f544d9a

Macroscope's review found this PR approvable — This is a localized server performance change that preserves thread activity timestamps while avoiding full payload rewrites during streaming. The accompanying test verifies unchanged thread data, shell consistency, and the reduced SQL access pattern.

Notes:

  • Diff unchanged. Approvability was decided on eligibility alone.

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

@coderabbitai

coderabbitai Bot commented Oct 13, 2026

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 65a157dc-f170-428e-bc0c-d16c9dddba5d
📥 Commits

Reviewing files that changed from the base of the PR and between dc19285 and f544d9a.

📒 Files selected for processing (2)
  • apps/server/src/orchestration-v2/ProjectionStore.test.ts
  • apps/server/src/orchestration-v2/ProjectionStore.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

ProjectionStoreV2 now updates a thread’s updatedAt timestamp directly in SQL when applying relevant events. A projection test checks the updated timestamp, unchanged thread fields, shell snapshot, and absence of a thread-table read.

Changes: Thread timestamp projection

Layer / File(s) Summary
SQL timestamp patch and projection test
apps/server/src/orchestration-v2/ProjectionStore.ts, apps/server/src/orchestration-v2/ProjectionStore.test.ts
The store encodes the event timestamp and patches updated_at and payload_json.updatedAt without reading and re-encoding the thread payload. The test checks that only updatedAt changes and that the shell snapshot reflects it, with no thread-table read.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Suggested reviewers: t3dotgg

Merge Risk: ⚪ Minimal · up to f544d

The timestamp update appears to preserve thread projection behavior while avoiding a thread-row read. No actionable merge-blocking risk remains after normal checks.

### Architecture Summary

Architecture risk: 🔵 Low · up to f544d

The change affects 1 system.

Changed systems: apps/server

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — apps/server (service) was modified; 2 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in apps/server/src/orchestration-v2/ProjectionStore.test.ts: Added a projection test that creates a thread with existing metadata, applies a run event timestamped five seconds later, and checks that only updatedAt changes in the thread projection and shell. It also asserts that applying the event performs no thread-table read.
  • observed — Modified behavior in apps/server/src/orchestration-v2/ProjectionStore.ts: Adds an encoder for updatedAt using the thread schema field, for the SQL timestamp patch.
  • observed — Modified behavior in apps/server/src/orchestration-v2/ProjectionStore.ts: For events that reach the generic activity-timestamp update, replaces reading, decoding, and re-encoding the thread payload with an in-place SQL update to updated_at and payload_json.updatedAt, using the encoded event timestamp.
Pre-merge checks · Passed 4
Passed Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main performance change: streamed events no longer rewrite the entire thread row.
Description check Passed The description includes all required sections. It explains the problem, implementation, scope rationale, focused verification, observed results, limitations, environment, and agent details.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • 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.

AdEx-Partners-DE added a commit to AdEx-Partners-DE/t3code that referenced this pull request Oct 13, 2026
…(adopts upstream pingdotgg#18890)

Own diff of pingdotgg#18890 at f544d9a, applied on stark/main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AdEx-Partners-DE

Copy link
Copy Markdown
Contributor

Numbers from a busy install, to put a size on what this saves. We adopted the change into a fork today (Windows 11); the figures are read-only queries on that server's statev2.sqlite, taken before the change went live.

threads 695
thread row payload_json, median / p90 / max 1,048 B / 1,352 B / 49,851 B
events in the last 24 hours 115,114, of which 111,855 are not thread.*
busiest three threads in those 24 hours 20,252, 15,775 and 11,696 such events, with thread rows of 40,921 B, 35,371 B and 49,851 B

So the typical row is small, but the threads that produce most events are the ones with the largest rows. Multiplying each thread's non-thread.* event count by its current row size gives about 2.65 GB of thread-row payload decoded, encoded and written back in one day under the old path. That is an estimate from today's row sizes, not a measurement of bytes written.

On our branch ProjectionStore.test.ts goes from 34 to 35 passing, runtimeLayer, FoundationPersistence and our queue tests still pass (130), and tsc --noEmit in apps/server is clean.

Not measured yet: the effect on write time or event-loop delay after the change. This server logs event loop stalls of 2 to 7 seconds a few times an hour; I will look at whether that changes once it runs this build, and say so here only if there is a clear difference.

Posted by Claude (Opus 5.5, Claude Code via T3 Code) on behalf of @AdEx-Partners-DE.

@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 13, 2026 — with ChatGPT Codex Connector
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 13, 2026 03:29

Dismissing prior approval to re-evaluate f544d9a

@t3dotgg
t3dotgg merged commit 3b2ebce into pingdotgg:main Oct 14, 2026
32 checks passed
sandscooling pushed a commit to sandscooling/t3code that referenced this pull request Oct 14, 2026
Sync 1014a: 22 upstream commits, including pingdotgg#17561 (full-screen visual
replies), pingdotgg#19226 and pingdotgg#18890 (server perf), and pingdotgg#19213 (Effect 4.0.4).
The fork html-render panel card keeps its slot in MessagesTimeline and
its narrow-layout fallback takes upstream's full-screen callback in
place of the removed onOpen. SessionMcpService calls randomUUIDv4() as
Effect 4.0.4 requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 14, 2026
## What's Changed
* chore(deps): Effect 4.0.4 and OpenCode 2.0.28, dropping the patches they made unnecessary by @juliusmarminge in pingdotgg/t3code#19213
* fix(server): a Claude 403 no longer tells you to log in again by @shivamhwp in pingdotgg/t3code#15872
* fix(server): persist only changed pull request links by @juliusmarminge in pingdotgg/t3code#19248
* fix(server): delegate_task explains that supervised runtime modes pause the child by @saphid in pingdotgg/t3code#14416
* fix(server): Claude subagent progress no longer re-stores the unchanged prompt by @Gigioxx in pingdotgg/t3code#18375
* fix(server): thread list reads no longer freeze the server on large databases by @t3dotgg in pingdotgg/t3code#19226
* perf(server): streamed events no longer rewrite the whole thread row by @Baker-Harrison in pingdotgg/t3code#18890
* docs: close PRs written by Gemini or Muse Wonder by @t3dotgg in pingdotgg/t3code#19286
* fix: update all no longer runs agents twice or loses background work by @t3dotgg in pingdotgg/t3code#19291
* fix(server): enforce one database owner and guard update rollback by @maria-rcks in pingdotgg/t3code#16102
* perf(server): reuse stored turn item positions by @tannerpolley in pingdotgg/t3code#19230
* perf(server): avoid quadratic resource history replay by @saphid in pingdotgg/t3code#18825
* fix(server): repair ownership UUID generation by @t3dotgg in pingdotgg/t3code#19298
* fix(client-runtime): slow config snapshots no longer drop working connections by @saphid in pingdotgg/t3code#14518
* fix(server): bound activity publishes so stalled responses release the worker by @saphid in pingdotgg/t3code#18830
* perf(shared): cached SQLite statements no longer hold large parameters by @t3dotgg in pingdotgg/t3code#19305
* fix(provider-cursor): Cursor text generation no longer leaks SQLite file handles on every call by @t3dotgg in pingdotgg/t3code#19310
* fix(server): make SQLite checkpoints durable on macOS by @t3dotgg in pingdotgg/t3code#19304
* perf(server): Codex plan streaming no longer commits every token by @t3dotgg in pingdotgg/t3code#19317
* fix(server): large file edits no longer store their full diff twice by @t3dotgg in pingdotgg/t3code#19316
* fix(web): settings save again after a stored value fails decode by @t3dotgg in pingdotgg/t3code#19312
* fix(server): HTML and Markdown attachments no longer fail OpenCode turns by @t3dotgg in pingdotgg/t3code#19319
* fix(server): worktree paths no longer vanish on git before 2.36 by @t3dotgg in pingdotgg/t3code#19315
* fix(acp): custom models now reach the agent instead of failing to open by @t3dotgg in pingdotgg/t3code#19313
* fix(server): Claude no longer shows unavailable when its health check can't run in $HOME by @t3dotgg in pingdotgg/t3code#19320
* fix(server): stalled OAuth token requests no longer block credential calls by @t3dotgg in pingdotgg/t3code#19321
* fix(server): steered Claude turns no longer undercount token usage by @t3dotgg in pingdotgg/t3code#19314
* fix(server): closing a Claude session no longer leaves plugin MCP servers running by @t3dotgg in pingdotgg/t3code#19322
* fix(client-runtime): T3 Connect link timeouts explain the relay 504 by @t3dotgg in pingdotgg/t3code#19329
* fix(web): settle shortcut moves to the next thread like the sidebar by @t3dotgg in pingdotgg/t3code#19328
* fix(acp): Copilot's default agent no longer disappears from the Agent menu by @t3dotgg in pingdotgg/t3code#19330
* perf(server): SQLite checkpoints no longer block the event loop by @t3dotgg in pingdotgg/t3code#19324
* fix(mobile): incompatible environments now say whether to update the app or the host by @t3dotgg in pingdotgg/t3code#19331
* fix(web): live reasoning stays scrolled to the newest text while it streams by @t3dotgg in pingdotgg/t3code#19334
* fix(server): T3 Connect no longer stays down after a network change by @t3dotgg in pingdotgg/t3code#19335
* fix(clients): host images say when the connection lacks file access by @t3dotgg in pingdotgg/t3code#19337
* fix(clients): a resumed subagent no longer moves the later turn's Worked for row by @t3dotgg in pingdotgg/t3code#19338
* fix: Claude and Codex no longer time out behind mise wrappers by @t3dotgg in pingdotgg/t3code#19340
* perf(server): turns no longer re-read merged PRs from GitHub by @t3dotgg in pingdotgg/t3code#19344
* docs(settings): explain that provider update checks also gate the model catalog by @fixfon in pingdotgg/t3code#18208
* fix(web): an unsaved citation comment survives a question or approval by @t3dotgg in pingdotgg/t3code#19350
* fix(server): changing Claude effort no longer fails while background agents run by @t3dotgg in pingdotgg/t3code#19341
* fix(server): service updates no longer fail on full disks or large databases by @t3dotgg in pingdotgg/t3code#19311

## New Contributors
* @Baker-Harrison made their first contribution in pingdotgg/t3code#18890
* @tannerpolley made their first contribution in pingdotgg/t3code#19230

**Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261014.3093...v0.0.46-nightly.20261014.3105

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261014.3105
github-actions Bot added a commit to davidvanderklay/t3code-flake that referenced this pull request Oct 14, 2026
## What's Changed
* chore(deps): Effect 4.0.4 and OpenCode 2.0.28, dropping the patches they made unnecessary by @juliusmarminge in pingdotgg/t3code#19213
* fix(server): a Claude 403 no longer tells you to log in again by @shivamhwp in pingdotgg/t3code#15872
* fix(server): persist only changed pull request links by @juliusmarminge in pingdotgg/t3code#19248
* fix(server): delegate_task explains that supervised runtime modes pause the child by @saphid in pingdotgg/t3code#14416
* fix(server): Claude subagent progress no longer re-stores the unchanged prompt by @Gigioxx in pingdotgg/t3code#18375
* fix(server): thread list reads no longer freeze the server on large databases by @t3dotgg in pingdotgg/t3code#19226
* perf(server): streamed events no longer rewrite the whole thread row by @Baker-Harrison in pingdotgg/t3code#18890
* docs: close PRs written by Gemini or Muse Wonder by @t3dotgg in pingdotgg/t3code#19286
* fix: update all no longer runs agents twice or loses background work by @t3dotgg in pingdotgg/t3code#19291
* fix(server): enforce one database owner and guard update rollback by @maria-rcks in pingdotgg/t3code#16102
* perf(server): reuse stored turn item positions by @tannerpolley in pingdotgg/t3code#19230
* perf(server): avoid quadratic resource history replay by @saphid in pingdotgg/t3code#18825
* fix(server): repair ownership UUID generation by @t3dotgg in pingdotgg/t3code#19298
* fix(client-runtime): slow config snapshots no longer drop working connections by @saphid in pingdotgg/t3code#14518
* fix(server): bound activity publishes so stalled responses release the worker by @saphid in pingdotgg/t3code#18830
* perf(shared): cached SQLite statements no longer hold large parameters by @t3dotgg in pingdotgg/t3code#19305
* fix(provider-cursor): Cursor text generation no longer leaks SQLite file handles on every call by @t3dotgg in pingdotgg/t3code#19310
* fix(server): make SQLite checkpoints durable on macOS by @t3dotgg in pingdotgg/t3code#19304
* perf(server): Codex plan streaming no longer commits every token by @t3dotgg in pingdotgg/t3code#19317
* fix(server): large file edits no longer store their full diff twice by @t3dotgg in pingdotgg/t3code#19316
* fix(web): settings save again after a stored value fails decode by @t3dotgg in pingdotgg/t3code#19312
* fix(server): HTML and Markdown attachments no longer fail OpenCode turns by @t3dotgg in pingdotgg/t3code#19319
* fix(server): worktree paths no longer vanish on git before 2.36 by @t3dotgg in pingdotgg/t3code#19315
* fix(acp): custom models now reach the agent instead of failing to open by @t3dotgg in pingdotgg/t3code#19313
* fix(server): Claude no longer shows unavailable when its health check can't run in $HOME by @t3dotgg in pingdotgg/t3code#19320
* fix(server): stalled OAuth token requests no longer block credential calls by @t3dotgg in pingdotgg/t3code#19321
* fix(server): steered Claude turns no longer undercount token usage by @t3dotgg in pingdotgg/t3code#19314
* fix(server): closing a Claude session no longer leaves plugin MCP servers running by @t3dotgg in pingdotgg/t3code#19322
* fix(client-runtime): T3 Connect link timeouts explain the relay 504 by @t3dotgg in pingdotgg/t3code#19329
* fix(web): settle shortcut moves to the next thread like the sidebar by @t3dotgg in pingdotgg/t3code#19328
* fix(acp): Copilot's default agent no longer disappears from the Agent menu by @t3dotgg in pingdotgg/t3code#19330
* perf(server): SQLite checkpoints no longer block the event loop by @t3dotgg in pingdotgg/t3code#19324
* fix(mobile): incompatible environments now say whether to update the app or the host by @t3dotgg in pingdotgg/t3code#19331
* fix(web): live reasoning stays scrolled to the newest text while it streams by @t3dotgg in pingdotgg/t3code#19334
* fix(server): T3 Connect no longer stays down after a network change by @t3dotgg in pingdotgg/t3code#19335
* fix(clients): host images say when the connection lacks file access by @t3dotgg in pingdotgg/t3code#19337
* fix(clients): a resumed subagent no longer moves the later turn's Worked for row by @t3dotgg in pingdotgg/t3code#19338
* fix: Claude and Codex no longer time out behind mise wrappers by @t3dotgg in pingdotgg/t3code#19340
* perf(server): turns no longer re-read merged PRs from GitHub by @t3dotgg in pingdotgg/t3code#19344
* docs(settings): explain that provider update checks also gate the model catalog by @fixfon in pingdotgg/t3code#18208
* fix(web): an unsaved citation comment survives a question or approval by @t3dotgg in pingdotgg/t3code#19350
* fix(server): changing Claude effort no longer fails while background agents run by @t3dotgg in pingdotgg/t3code#19341
* fix(server): service updates no longer fail on full disks or large databases by @t3dotgg in pingdotgg/t3code#19311

## New Contributors
* @Baker-Harrison made their first contribution in pingdotgg/t3code#18890
* @tannerpolley made their first contribution in pingdotgg/t3code#19230

**Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261014.3093...v0.0.46-nightly.20261014.3105

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261014.3105
coreybain pushed a commit to SpiritDevs/pathway that referenced this pull request Oct 15, 2026
Every streamed run, node and turn-item event bumped the thread's activity
timestamp by selecting the thread projection row, decoding the full payload,
re-encoding it and writing it back, on the hot path of every agent stream.

Those pure timestamp bumps now patch the row in place with a single UPDATE
that sets the updated_at column and json_set()s payload_json.$.updatedAt to
the schema-encoded timestamp. A missing row stays a no-op. The
thread.model-reported path, which also changes the model selection, keeps the
full decode/encode. Ported from pingdotgg/t3code#18890.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants