perf(web): one-shot session resume — cut conversation switch to a single round trip#162
Merged
Conversation
…gle round trip
Resuming a conversation from the web/desktop UI used to walk six serial
round trips before the timeline could repaint: PATCH mark-as-read →
GET /api/sessions/{id} (full JSONL, disk read #1) → POST /api/sessions
(resume; disk read #2 + ReconstructState) → GET /api/status →
GET /api/goal → GET /api/todos, with ask/approval pending on top. The
pane stayed blank the whole time.
Now:
- POST /api/sessions (resume) is a one-shot reply: it returns the raw
entries alongside goal/todos/status. The server already reads the
JSONL to reconstruct engine state, so the entries ride along — the
file is read once instead of twice, and four follow-up GETs vanish
from the critical path. (sessions.go writeResumeReply + statusSnapshot
factored out of handleStatus so the two payloads can't drift.)
- loadSession drives the whole resume off that one response; the old
endpoints remain as a fallback for older servers (provider field
discriminates), fetched in parallel and never gating the repaint.
- The chat pane swaps to a skeleton the instant the click lands
(sessionLoading + ChatView skeleton), so perceived latency is ~0
instead of blank-until-ready.
- Marking a session read is fire-and-forget — the awaited PATCH was a
round trip on the critical path of every unread open.
- ask/approval pending reconciliation stays, but non-blocking.
Measured (localhost, 500-entry / 1.1MB session, same-project switch):
- critical-path round trips: 6-7 → 1
- server-side session-file reads per resume: 2 → 1
- click → timeline data ready: ~100ms → ~62ms (POST 59-60ms + rebuild
2-3ms + hydrate ~0); small sessions ~51ms
- the savings scale with transport latency: every removed round trip
is a full saved cycle on desktop sidecar IPC or remote links
Verified end-to-end in the browser: switching between a 500-entry
session and a small one renders correctly with a single POST each
(60ms / 1.1MB payload incl. entries); go test + golangci-lint + tsc
all pass.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
需求
优化 web/desktop 切换会话(resume)的加载时间,并给出分阶段测试报告。
优化前的流程(6-7 个串行往返)
点击侧栏会话后的关键路径(全部串行):
PATCH /api/tasks/:id(标记已读,仅未读时)GET /api/sessions/{id}— 全量 JSONL(磁盘读 chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0 #1)POST /api/sessionsresume — 服务端再读一遍 JSONL + ReconstructState(磁盘读 chore(deps): bump github.com/buger/jsonparser from 1.1.1 to 1.1.2 #2)GET /api/statusGET /api/ask/pending+GET /api/approval/pendingGET /api/goalGET /api/todos整个过程页面一片空白,直到全部完成才开始重建 timeline。
优化后
POST /api/sessions一次性返回:entries + goal + todos + status 同一个响应(writeResumeReply;statusSnapshot从 handleStatus 抽出共用,保证两处 payload 不漂移)。服务端本来就要读 JSONL 重建 engine 状态,entries 顺便带回 —— 文件只读一次。resp.provider判别),回退请求并行且不阻塞重绘;entries 读取失败时也会回退 GET(不会出现「有历史却白屏」)。测试报告
测试环境:localhost,同一 project 内切换;大会话 = 500 entries / 1.1MB JSONL / 310 个 timeline 项;浏览器内 fetch 计时(微任务级精度)+ 代码插桩(rebuild/hydrate)。每项 4 次取典型值。
说明:以上为 localhost 最佳情况。桌面端 sidecar IPC / 远程(SSH)链路下每个往返都是一个完整延迟周期,砍掉 5 个往返 + 1 次全量文件读的收益会按比例放大。
验证
make lint-web/golangci-lint/go test ./internal/web/ ./internal/session/全部通过GET /api/sessions/{id}回退端点保留可用对抗评审
已经过一轮对抗评审,修复了:
entries === undefined即回退 GET已知可接受项(评审确认):快速连点两个会话的竞态为既有行为(非本 PR 引入);live resume 时并发写入的末行撕裂解析跳过为既有行为。