Skip to content

feat(cursor): commit tool-suspended checkpoints for external models - #2652

Merged
lidge-jun merged 4 commits into
codex/cursor-gap-2from
codex/cursor-gap-3
Aug 26, 2026
Merged

feat(cursor): commit tool-suspended checkpoints for external models#2652
lidge-jun merged 4 commits into
codex/cursor-gap-2from
codex/cursor-gap-3

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

  • Root cause of the cursor route's per-tool-round full context replay (G1 in devlog/_plan/260826_cursor_responses_gap): a turn that emits a client tool call set emittedClientTool=true and commitCapturedCheckpoint refused to commit ANY checkpoint, so the next trailing-toolResult request always fell back to full flattened-text replay (live probe: input_tokens 268 → 10,383 across one tool boundary; every agentic session pays this on every round).
  • Now commits a tool-suspended checkpoint when three conditions hold: the model is an external wire model, the newest captured checkpoint bytes arrived AFTER the client tool call was emitted (ordering proof), and the usual replay-safety guards pass. The commit is marked checkpointUsable:false so only the immediate covered-prefix + trailing-toolResult continuation (existing request-builder path, already tested) can consume it. Native composer models keep the old refusal; pre-tool captures are refused by the ordering guard.

Stacked on #2651. Design: 050_checkpoint_continuity.md (research disproved the earlier trailing_tool_result hypothesis).

Verification

  • bun test tests/cursor-tool-suspended-checkpoint.test.ts — 3 pass (external commit w/ usable=false, native refusal, ordering guard).
  • bun test tests/cursor-adapter.test.ts tests/cursor-request-builder.test.ts tests/responses-state.test.ts tests/cursor-live-transport.test.ts — 205 pass 0 fail.
  • bun x tsc --noEmit — clean.

Checklist

  • Focused tests green
  • Typecheck clean
  • Fail-closed paths preserved (replayUnsafe, contextUsageStoreCheckpoints, empty capture)
  • Devlog updated (050)

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 26, 2026 03:36
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 184a1974-8f51-4fc5-bf0a-07e38a7332cb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

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

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eda8380c36

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/adapters/cursor.ts
Comment on lines +185 to 188
// A tool-suspended checkpoint is only usable by the immediate trailing-toolResult
// continuation; the request-builder guard keys on checkpointUsable=false for that.
checkpointUsable: !toolSuspendedCommit,
checkpointRef,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist the suspended-only restriction with the checkpoint

The checkpointUsable: false restriction exists only in the returned continuation metadata, while the committed snapshot is also added to the generic prefix index without that restriction. For a stable client thread that sends full history without previous_response_id, lookupPrefixSnapshot can therefore recover this tool-suspended snapshot with no cursorState; if the next request ends in a user message rather than the immediate tool result, the guard at request-builder.ts:427 is bypassed and the request resumes from pending-tool state without appending the uncovered transcript. Store the suspended kind on the snapshot and enforce it for both ref and prefix lookup, or exclude such snapshots from generic prefix recovery.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

Comment thread src/adapters/cursor.ts
Comment on lines 221 to +224
if (message.type === "tool_call_end") emittedClientTool = true;
const captured = capturedCursorCheckpointBytes(activeTransport);
if (captured) lastTransport = { captured };
if (captured) {
if (captured !== lastTransport?.captured) capturedAfterClientTool = emittedClientTool;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a checkpoint after the final emitted tool call

With multiple client tool calls, a checkpoint captured after the first tool_call_end sets capturedAfterClientTool permanently true; a later second call does not reset it, so done can commit the earlier snapshot even when no checkpoint arrived after the second call. Cursor explicitly supports sequential and late sibling calls, and resuming that snapshot with results for all emitted calls can omit the later pending call or trigger an invalid continuation. Reset the ordering proof for every newly emitted tool call and require a newer capture after the last one before committing.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 55 / 80

설명

이 풀 리퀘스트는 Cursor 경로가 도구를 한 번 호출할 때마다 대화 전체를 다시 보내는 이유를 고치려 합니다. 지금 devsrc/adapters/cursor.ts 150-157줄 commitCapturedCheckpointemittedClientTool이 참이면 체크포인트를 절대 저장하지 않습니다. 209줄에서 tool_call_end를 보면 이 깃발이 켜집니다. 그래서 도구 결과가 따라오는 다음 요청은 저장본이 없어 긴 텍스트를 다시 펼칩니다. 실측으로는 입력 토큰이 268에서 10,383으로 뛰었습니다.

새 규칙은 세 가지가 동시에 맞을 때만 저장합니다. 모델이 외부 와이어 모델이고(src/adapters/cursor/discovery.ts 169-171줄 isCursorExternalWireModel), 가장 새로운 체크포인트 바이트가 도구 호출 이후에 왔으며, 기존 안전 검사(replayUnsafe, contextUsageStoreCheckpoints, 빈 캡처)를 통과해야 합니다. 저장은 하지만 checkpointUsablefalse입니다. src/adapters/cursor/request-builder.ts 426-428줄은 마지막 메시지가 toolResult가 아닌데 checkpointUsable === false이면 그 저장본을 쓰지 않습니다. 바로 다음 도구 결과 이어가기만 쓰라는 뜻입니다.

네이티브 composer 모델은 예전처럼 거절합니다. composer-2.5는 같은 파일 185-188줄에서 도구 이어가기가 다르게 동작한다고 이미 적혀 있습니다. 도구보다 먼저 찍힌 캡처도 순서 검사로 거절합니다. 이 점은 테스트 세 개로 고정되어 있습니다.

위험은 저장본의 뜻입니다. 지금 dev에서 checkpointUsable: true(176줄)는 일반 이어가기도 쓸 수 있다는 뜻입니다. 이번 변경은 같은 필드에 false를 넣어 다른 뜻을 만듭니다. 요청 빌더의 기존 가드가 그 뜻을 이미 알고 있어야 합니다. 캡처 비교는 captured !== lastTransport?.captured라서, 전송 층이 같은 버퍼를 재사용하면 순서 깃발이 꺼진 채로 남을 수 있습니다.

이 PR도 지금 dev 위가 아닙니다. 베이스는 codex/cursor-gap-2, 곧 #2651입니다. #2650과 #2651이 먼저 들어가야 합니다. 체크포인트 의미는 토큰을 아끼지만, 잘못된 저장본을 이어가면 대화가 깨집니다.

src/adapters/cursor.ts:153 - 지금 emittedClientTool이면 체크포인트를 전부 버려 도구 라운드마다 전체를 다시 보냅니다.
src/adapters/cursor.ts:176 - 현재 저장본은 항상 checkpointUsable: true라서, false 뜻이 요청 빌더 426줄과 맞는지가 이 변경의 핵심입니다.
경로/심볼 - 캡처 순서 검사가 버퍼 참조 비교라, 같은 배열을 재쓰면 도구 이후 캡처로 보지 못할 수 있습니다.
경로/심볼 - 베이스가 #2651이라 현재 dev(12f5876)에는 혼자 합칠 수 없습니다.

메인테이너의 판단이 필요한 지점

  • 외부 모델에만 이 저장을 허용하는 것이 맞는지, composer-fast처럼 일부 네이티브 모델도 넣을지 정해야 합니다.
  • checkpointUsable: false를 바로 다음 도구 결과에만 쓸지, 한 턴의 여러 도구 라운드까지 쓸지 정해야 합니다.
  • 실측 토큰 감소를 통합 테스트로 남길지, 단위 테스트 세 개로 충분할지 정해야 합니다.

너의 추천

#2650과 #2651이 dev에 들어간 뒤에만 합치세요. 지금 혼자 합치지 마세요. 합치기 전에는 외부 모델의 실제 도구 이어가기가 저장본을 쓰는지, 네이티브 composer는 여전히 거절하는지, 잘못된 순서의 캡처는 버리는지를 한 경로로 더 확인하세요. 참조 비교 대신 캡처가 도구 이벤트 이후에 새로 들어왔는지를 보는 편이 더 안전합니다.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun
lidge-jun force-pushed the codex/cursor-gap-2 branch 2 times, most recently from 4d85df3 to fdd406e Compare August 26, 2026 05:50
fix(cursor): quarantine catalog models whose runs always fail upstream
@lidge-jun
lidge-jun merged commit 13c0a2d into codex/cursor-gap-2 Aug 26, 2026
23 checks passed
@lidge-jun
lidge-jun deleted the codex/cursor-gap-3 branch August 26, 2026 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant