feat(cursor): repetition breaker for external-model replay priming - #2667
feat(cursor): repetition breaker for external-model replay priming#2667lidge-jun wants to merge 2 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
|
✅ Deterministic PR hygiene checks passed. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3b9eb5df7
ℹ️ 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".
| opts: { messageIndex: number; text?: string }, | ||
| normalized: string, | ||
| ): void => { | ||
| if (externalModel && lastReplayText !== undefined && normalized === lastReplayText && lastReplayEntry) { |
There was a problem hiding this comment.
Detect interleaved repeated tool rounds
When an external-model tool loop has the normal shape assistant A → tool result R → assistant A → tool result R, each entry overwrites lastReplayText, so this comparison is always A versus R or R versus A. Consequently, neither value is collapsed and maxRunLength never reaches 3, meaning the breaker does nothing for the reported commentary-plus-probe loop; track repetition per role or compare complete rounds, and cover an interleaved assistant/tool-result transcript in the regression test.
Useful? React with 👍 / 👎.
| entries.push(rootBlobCandidate({ | ||
| role: "user", | ||
| content: [{ type: "text", text: `[context note] The transcript above contains the same output repeated ${maxRunLength} times in a row. Repeating it again is a failure. Take a DIFFERENT action now, or state plainly what is blocking progress.` }], | ||
| }, "user", {})); |
There was a problem hiding this comment.
Keep the active tool result at the replay tail
When any repetition run reached three and the current request ends in a tool result, appending this synthetic user root after that result prevents the later trailing-toolResult scan from recognizing the active block. Under the 192-blob or 512 KiB replay cap, the current result is then treated as ordinary prior history and can be dropped instead of preserved or truncated, while the continuation action still tells the model that the result is in history; insert the note before the active block or make pruning explicitly retain both.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
| if (externalModel && maxRunLength >= 3) { | ||
| entries.push(rootBlobCandidate({ | ||
| role: "user", | ||
| content: [{ type: "text", text: `[context note] The transcript above contains the same output repeated ${maxRunLength} times in a row. Repeating it again is a failure. Take a DIFFERENT action now, or state plainly what is blocking progress.` }], |
There was a problem hiding this comment.
Stop carrying a resolved repetition warning forward
Because maxRunLength records the maximum over the entire raw history and is never reset when a user message starts a new turn, one old three-output run causes this imperative strategy-change note to be appended to every subsequent request in the conversation. After the user changes tasks and the model makes unrelated progress—and even after replay pruning removes the repeated entries—the prompt still orders the model to take a different action; base the warning on the trailing/current run that actually survives selection rather than any historical run.
Useful? React with 👍 / 👎.
1f2ad63 to
a475dc1
Compare
be38a3b to
ce5a8eb
Compare
ce5a8eb to
d9d84b3
Compare
a475dc1 to
187dec5
Compare
리뷰 · 우선순위 58 / 80설명 이 풀 리퀘스트는 Cursor 바깥 모델이 같은 답을 여러 번 이어서 낼 때, 그 기록이 또 같은 답을 부르지 않게 접습니다. 지금 current dev HEAD 01b5da9f5의 src/adapters/cursor/protobuf-request.ts 197줄부터 338줄 rootPromptMessages는 예전 대화를 글자 덩어리로 다시 만듭니다. 216줄 isCursorExternalWireModel이 참이면 바깥 모델입니다. 221줄부터 263줄 반복문은 사용자, 도우미, 도구 결과를 각각 한 줄로 넣습니다. 같은 도우미 글자가 다섯 번 있어도 다섯 줄이 그대로 다시 재생됩니다. 그래서 모델이 여섯 번째도 같은 줄을 쓰기 쉽습니다. 본문이 말한 kimi-k3의 "원격 ocx 상태를 다시 확인합니다" 반복과 같은 종류입니다. 고침은 그 반복문 바로 앞에 pushDeduped를 둡니다. 바깥 모델일 때만, 바로 앞 줄과 글자가 같으면 새 줄을 넣지 않고 하나 남은 줄에 "N times in a row" 표시를 덮어씁니다. 사용자 말이 나오면 lastReplayText와 lastReplayEntry를 지웁니다. 그래서 사용자 말로 떨어진 같은 글자는 접지 않습니다. 한 번의 이어진 반복이 3번 이상이면 끝에 짧은 명령 한 줄을 붙입니다. 네이티브 composer는 discovery.ts 169-171줄에서 바깥 모델이 아니라서 접히지 않습니다. 도구 호출과 결과의 구조 짝 turns[]는 그대로 둡니다. 테스트는 tests/cursor-repetition-breaker.test.ts 다섯 개입니다. 모델 아이디 grok-4.6-high는 네이티브 composer가 아니므로 바깥 모델입니다. 다섯 번 반복은 한 줄로 접히고, 두 번은 접히되 명령 줄은 없고, 다른 글자는 그대로 두고, 사용자 말로 떨어진 같은 글자는 두 줄로 남습니다. 이 검사는 분명합니다. 라이브 증명은 데브로그에만 있고, 제품 코드가 아닙니다. 다만 이 PR의 베이스는 #2665의 브랜치 codex/cursor-gap-8입니다. 지금 dev 01b5da9 위에 혼자 합치면 #2650부터 #2665까지 부모 스택이 없습니다. collapsedRepeats는 숫자를 세고 아무 데도 쓰지 않습니다. 덮어쓸 때 entries.indexOf(lastReplayEntry)로 위치를 찾습니다. 명령 줄은 가짜 사용자 말이고 messageIndex가 없습니다. 같은 파일 265줄부터 330줄 바깥 모델 히스토리 자르기는 뒤에서부터 사용자 턴을 남깁니다. 잘리면 접힌 표시만 남거나 명령 줄만 남을 수 있습니다. composer-2.5가 접히지 않는지, 도구 결과 접힘이 맞는지는 테스트가 없습니다. 타입은 src/types.ts를 키우지 않습니다. 타입 분리 캠페인 때문에 닫을 이유는 없습니다. 미리보기 배포는 계획에 없습니다. 데브로그 120_repetition_breaker.md는 착지 대상이 아닙니다. src/adapters/cursor/protobuf-request.ts:221 - 지금 HEAD는 같은 도우미 줄을 N번 그대로 다시 붙여 다음 답을 같은 말로 부릅니다 메인테이너의 판단이 필요한 지점
너의 추천 #2650부터 #2665 스택이 먼저 들어간 뒤에 이 PR을 마지막에 합치세요. 지금 dev에 직접 합치지 마세요. 합치기 전에 collapsedRepeats를 지우거나 쓰게 하고, composer-2.5가 접히지 않는 테스트와 도구 결과 접힘 테스트를 넣으세요. 미리보기 배포는 계획에 없습니다. 이 댓글은 grok-bot이 작성했습니다 |
Summary
protobuf-request.ts): consecutive duplicate assistant/tool-result entries collapse into ONE entry carrying "[note: this exact output was produced N times in a row]", and when any run reaches 3+, a single imperative context note is appended ("Repeating it again is a failure. Take a DIFFERENT action now, or state plainly what is blocking progress." — no denial framing per gap-8 discipline). User messages reset the run; native composer models are untouched; tool call/result pairing is unaffected (structured pairing rides turns[], text replay is presentation).Stacked on #2665 (codex/cursor-gap-8) — completes the gap-1..gap-9 chain.
Verification
bun test tests/cursor-repetition-breaker.test.ts— 5 pass (collapse+marker, note at >=3, no note at 2, distinct entries untouched, user-separated duplicates preserved).bun test tests/cursor-blob.test.ts tests/cursor-request-builder.test.ts tests/cursor-adapter.test.ts tests/cursor-tool-continuation.test.ts tests/cursor-protobuf-events.test.ts— 211 pass 0 fail.bun x tsc --noEmit— clean.Checklist