창이 가득 찬 뒤, 어디를 자르고 어디를 남길까요
세 타이밍이 분배기 하나를 나누고, 세 구현은 스위치가 고릅니다. 같은 요약은 중간에서 기록의 마지막 항목에 머물러야 해요.
- token_limit_reached를 보거나, 사용자가 Compact를 제출context_window.rs L77
- 샘플 전은 run_pre_sampling_compactturn.rs L1012
- 중간은 should_roll_over, follow-up도 필요turn.rs L458
- 수동은 CompactTask를 올려 현재 turn을 끊음tasks/compact.rs L19
- TokenBudget이 켜지면 빈 창으로, 원격과 로컬은 모두 건너뜀turn.rs L1189
- 아니면 원격 v2 또는 로컬로 분배turn.rs L1201
- 원격 결과는 should_keep 거름망을 지남compact_remote.rs L370
- InitialContextInjection이 넣을지 결정compact.rs L68
- replace_annotated만 history_version을 하나 올림history.rs L298
coding agent에게 같은 스레드에서 서른 턴을 고치게 했어요. 앞 스무 턴은 작업 공간이 어디인지, 어떤 파일을 건드리면 안 되는지 기억했습니다. 서른 번째 턴에 갑자기 “현재 디렉터리가 뭐예요?”라고 묻고, 몇 턴 뒤에는 이미 승인한 npm test를 또 물어요.
사용량이 창 가장자리에 닿으면 시스템이 기록을 눌러야 해요. 언제 누를지와 어떻게 누를지를 같은 판단에 넣으면, 제공자를 바꾸거나 모델을 부르지 않는 빈 창을 더할 때 입구가 모두 따라 바뀝니다. 중간은 누른 뒤에도 샘플을 이어야 하고, 메시지 전에 누르면 다음 턴에야 다시 넣어요. 두 일은 요약 자리도 다르게 요구합니다.
Codex는 타이밍을 CompactionPhase로 써요. 메시지 전의 PreTurn, 도구가 끝난 뒤에도 이어야 하는 MidTurn, 사용자가 직접 하는 StandaloneTurn. 구현은 CompactionImplementation: 로컬에서 한 번 더 샘플, 옛 /responses/compact, 새 compaction_trigger. 분석 이벤트에는 Trigger와 Reason 두 라벨이 더 있어요. 자동 경로는 run_auto_compact를, 수동 경로는 CompactTask를 나눕니다. 아홉 칸은 곱셈표이고, 소스에 아홉 벌 나란한 함수는 없어요.
세 자동 타이밍은 모두 같은 함수를 먼저 물어요. token_limit_reached는 버퍼 뒤 compact 한도나 가득 찬 창, 어느 쪽이든 참입니다. 중간에는 문이 하나 더 있어요. 뒤에서 이어야 하고, 모델이 방금 새 창을 요청했거나 token이 이미 천장에 닿아야 합니다. 가득만 차고 이어지지 않으면 이번 턴은 자연히 끝나고, 다음 사용자 메시지가 오면 사전 샘플을 타요.
출처: codex-rs/core/src/session/context_window.rs 74–91행;codex-rs/core/src/session/turn.rs 458–483행
분배기 첫 줄은 TokenBudget을 봐요. 켜져 있으면 빈 창으로 바꾸고, 원격과 로컬은 모두 건너뜁니다. 꺼져 있으면 제공자 능력으로 고르세요. compaction_trigger를 알고 플래그가 켜지면 원격 v2, V2는 아는데 플래그가 꺼지면 옛 원격 API, Unsupported면 로컬. TokenBudget은 기본 꺼짐, RemoteCompactionV2는 기본 켜짐. 기본 OpenAI 세션은 원격 v2를 타요.
출처: codex-rs/core/src/session/turn.rs 1178–1201행;codex-rs/features/src/lib.rs 1428–1433행、1542–1547행
언제 창을 바꿀지와 창에 무엇을 넣을지는 서로 다른 일이에요. 언어를 바꿔 다시 써도 위상을 먼저 고르고 파이프를 나중에 고릅니다. 제공자가 하나여도 판정과 설치를 먼저 나눌 수 있어요.
서버가 가져온 transcript에는 만료된 developer 지시가 끼어 있을 수 있어요. 거르지 않으면 로컬이 현재 world state로 새로 그린 환경 위에 겹쳐, 누군가 옛 행을 고친 것과 같아집니다. 중간은 누른 뒤에도 같은 턴에서 샘플을 이어야 하고, 모델은 요약이 기록의 마지막 항목이라고 훈련됐어요. 컨텍스트를 요약 뒤에 넣으면 그 훈련 제약이 깨집니다.
거름망은 완전한 소진 match예요. developer, 사용자 내용이 아닌 user 포장, 도구 호출, 압축 트리거를 버립니다. 진짜 사용자 메시지, 지속 hook prompt, assistant, 압축 항목은 남겨요. v2는 같은 함수를 재사용합니다.
출처: codex-rs/core/src/compact_remote.rs 370–397행
그다음 InitialContextInjection이 넣을지 정해요. 사전 샘플과 수동은 DoNotInject: 교체 기록에 초기 컨텍스트가 없고 reference_context_item을 비우며, 다음 일반 turn이 통째로 다시 넣습니다. 중간은 BeforeLastUserMessage: 현재 환경과 권한을 마지막 진짜 사용자 메시지 위에 두고, 요약은 꼬리에 남아요. 삽입 함수에는 바닥도 있습니다. 진짜 사용자가 없으면 요약 앞에, 그것도 없으면 마지막 compaction 항목 앞에 넣어요.
출처: codex-rs/core/src/compact.rs 59–74행
세 구현은 마지막에 모두 replace_compacted_history로 들어가요. live history는 통째로 교체됩니다. history_version은 이때만 하나 오르고, 추가는 버전을 건드리지 않아요. Guardian이 transcript를 재사용할 때 parent_history_version을 대조하고, 버전이 바뀌면 옛 심사 접두사를 재사용할 수 없습니다.
출처: codex-rs/core/src/context_manager/history.rs 298–302행
원격 결과가 로컬 함수가 뱉은 것이 아니면, 문앞에서 지시 포장을 버려요. 삽입 점이 막는 것은 훈련 제약과 다시 넣는 타이밍이고, 두 일은 원래 나뉘어 있습니다. 버전은 다시 쓸 때만 나아가는데, 추가는 매 턴 일어나기 때문이에요.
가끔 사용자는 깨끗한 창만 원하고 요약은 원하지 않아요. 빈 창이 다른 생명주기를 타면 hook과 ContextCompaction 항목이 이 일을 못 봅니다. 스위치는 기본 꺼짐이라, 사용자가 모르는 사이 대화 전체를 잃지 않게 해요.
TokenBudget은 모델과 서버 요약을 건너뛰고 새 창을 설치하며, 요약 필드는 빈 문자열이에요. 새 창에서 모델은 옛 대화를 못 보고, 지금 환경과 권한만 봅니다. 이는 new_context 도구의 계약과 같아요. 창을 바꾸고, 요약하지 않는다. 그래도 압축 생명주기를 타고, pre-compact hook이 멈추면 창은 아직 안 바뀌어요.
출처: codex-rs/core/src/compact_token_budget.rs 21–25행
로컬 경로가 스스로 가득 찬 창을 만나도 자동 압축을 재귀 호출하지 않아요. 가장 오래된 한 줄을 지우고 한 번 더 칩니다. 한 줄만 남았는데도 넘치면 가득 찬 창으로 표시하고 오류를 돌려요. 원격 실패도 로컬로 바꾸지 않습니다. 보통 가득 찬 창이 일으킨 사전 압축과 중간 압축은 fallback을 None으로 넘기고, 첫 원격 실패에서 바로 돌아와요. 타임아웃은 재시도 명단에 없습니다.
출처: codex-rs/core/src/compact_model_fallback.rs 8–20행
압축은 창을 한 번 바꾸는 생명주기예요. 산물은 요약일 수도, 빈 방일 수도 있습니다. 재귀는 스스로 막아야 해요. 수동 작업은 turn 루프에 들어가지 않고, 로컬 초과는 가지치기에 기대며, 중간 경로는 눌러 성공하면 한도 아래로 떨어집니다. 가정이 깨지면 루프는 다시 들어갈 수 있어요.
DSH: 모델을 부르지 않는 그 칼이 먼저 내려앉을 수 있어요
DSH의 압축 가족은 packages/compaction/에 있어요. 압력이 오면 선택적으로 먼저 prune하고 한 번 더 잽니다. prune 뒤에 이미 문턱 아래면 요약은 안 달려요. 두 경로 모두 로컬이고, Codex 같은 compact 클라이언트는 없습니다.
넘침 복구는 replaceGeneration을 재시도 허가로 봐요. prune이 먼저 내려앉고 이어 요약이 던져도, generation이 나아갔으면 새 표면에서 다시 시도할 수 있어요. generation은 replace 계획이 커밋될 때만 오르고, append는 안 올립니다. Codex는 같은 물음에 history_version으로 답하고, 원격은 창 전체를 설치하거나 아예 설치하지 않아요.
Claude Code: 재귀는 라벨이 막고, 세 번 실패하면 손을 멈춤
기본 자동 경로는 여전히 모델을 한 번 더 불러 요약을 써요. 자동 판정은 먼저 재귀를 막습니다. querySource session_memory와 compact는 바로 false를 돌리고, 주석은 이들이 fork된 agent라 다시 치면 교착한다고 밝힙니다. 연속 실패 3번 뒤 회로가 끊기고, 주석은 한 세션이 연속 수천 번 실패한 사고를 기록했어요.
Codex의 Compact 작업은 turn 루프에 들어가지 않아, 그런 라벨이 없어도 돼요. 대가는 중간 경로에 맞먹는 연속 실패 카운터가 없다는 점입니다. 저자는 주석에 내기를 썼어요. 압축이 사용량을 한도 아래로만 누르면 무한 루프를 걱정할 필요 없으니, 카운터를 빼 버렸습니다.
양쪽 소스 모두 확인 · 2026-08-22교체 표 세 장이 각각 무엇을 남기는가
위의 뒤섞인 기록을 중간 원격, 빈 창, 샘플 전 원격으로 각각 보내 보세요. 교체 표 세 장을 쓰세요. 각각 어디를 자르고 어디를 남기는지, 요약이 마지막인지, 초기 컨텍스트가 그때 표에 있는지요.
한 단계 더: 원격이 처음 실패할 때, 왜 보통 가득 찬 창 경로는 로컬로 바꾸지 않고, 모델 전환 사전 압축은 원격을 한 번 더 칠 수 있나요.