Skip to content

feat(server): event feed for followers - #216

Merged
tusharbhardwaj-bk merged 1 commit into
expbkmainfrom
t3code/perf-8-event-feed
Sep 26, 2026
Merged

tusharbhardwaj-bk merged 1 commit into
expbkmainfrom
t3code/perf-8-event-feed

Conversation

@tusharbhardwaj-bk

@tusharbhardwaj-bk tusharbhardwaj-bk commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The Linear bridge learns about thread and project changes by polling bkt3. In a 648 s prod trace:

  • 183 × GET /api/orchestration/shell: ~623 KB each, 420 ms average server time, 2.6 s max.
  • 346 × full GET /api/orchestration/threads/:id.

That is ~26 MB/min of JSON and 20–30% of the server's single event loop. The shell snapshot also competes with every client's pings and reconnects.

Fix

A new fork route, GET /api/orchestration/events?after=<seq>&limit=<1..1000, default 500>&wait=<0..25 s, default 0>, implementing section 1 of the bridge API contract (~/perf-audit-bkt3/80-api-contract.md).

What it returns:

  • A page of events after after, read from the sequence primary key in ascending order.
  • Each event is shaped exactly like the WebSocket replayEvents RPC: project events carry the resolved repository identity, and activity payloads go through projectActivityEvent.
  • Body: { events, nextAfter, headSequence }.

How waiting works:

  • When nothing is newer than after, the request subscribes to the engine's live event stream before reading, then parks until an event past after is published or wait elapses.
  • There are no timers polling the DB.

Status codes:

  • 410 { error: "cursor-invalid", headSequence } when after > headSequence, meaning the database was replaced. The event log is never pruned, so an old cursor is always served.
  • 400 { error: "invalid-request", detail } for bad parameters.
  • 403 insufficient_scope without orchestration:read, the same scope as /api/orchestration/shell.

Access and wiring:

  • Team-mode tokens only receive events for threads and projects they can access, via OrchestrationAccessControl: one lookup per aggregate per page, not a shell rebuild.
  • nextAfter still advances past hidden events, so a follower never re-reads them.
  • Files: fork modules orchestration/eventFeedHttp.expbkt3.ts and orchestration/forkRouteAuth.expbkt3.ts, the bearer auth that PR 9 reuses.
  • Registration is one line inside the existing fork block PlannotatorAndMcpRoutesLive in server.ts, plus a marked import.

Evidence

eventFeedHttp.expbkt3.test.ts, 7 tests:

  • query parsing and bounds;
  • range + order + limit + nextAfter/headSequence;
  • 410 for a cursor ahead of the log;
  • a parked read is woken by the next dispatch (the test asserts the read is still pending before the dispatch, and fails if waiting is disabled);
  • the wait elapsing returns an empty page (TestClock);
  • team-actor filtering advances the cursor;
  • over real HTTP: 200 JSON body, 400, 410, 403.

vp run typecheck in apps/server is clean, lint is clean, and the fork-marker check passes.

After the expbkt3 deploy I will curl the endpoint with a token minted on expbkt3.

Model/harness: Claude Opus 5.5 via Claude Code in T3 Code.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

GET /api/orchestration/events?after=<seq>&limit=<1..1000>&wait=<0..25>
returns events after a cursor in sequence order, shaped like the WebSocket
replay. An empty read is held until the engine publishes a newer event or
the wait elapses (woken by the live event stream, no polling). A cursor
ahead of the log gets 410 cursor-invalid.

This lets the Linear bridge stop polling the full shell every 2 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Sep 26, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 13.9 KiB — 15.1 KiB ✅
Codex Thread snapshot wire — 7.3 KiB — 7.8 KiB ✅
Codex Live turn WebSocket wire — 6.5 KiB — 7.8 KiB ✅
Codex Live turn WebSocket decoded — 57.1 KiB — 66.4 KiB ✅
Codex Live turn messages — 10 — 21 ✅
Claude Total thread wire — 13.8 KiB — 15.1 KiB ✅
Claude Thread snapshot wire — 7.3 KiB — 7.8 KiB ✅
Claude Live turn WebSocket wire — 6.5 KiB — 7.8 KiB ✅
Claude Live turn WebSocket decoded — 57.9 KiB — 66.4 KiB ✅
Claude Live turn messages — 9 — 21 ✅

Baseline: unavailable · PR result: 50de16c · 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: 114.9 KiB
  • Claude decoded thread snapshot: 115.6 KiB

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

@tusharbhardwaj-bk
tusharbhardwaj-bk merged commit e17f0ad into expbkmain Sep 26, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR 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