Skip to content
This repository was archived by the owner on Sep 8, 2026. It is now read-only.

feat(reducer): 응답 그룹의 첫 행 순번을 UsageSelection 에 노출한다 - #375

Merged
ictechgy merged 3 commits into
mainfrom
feat/reducer-group-first-row-ordinal
Sep 6, 2026
Merged

ictechgy merged 3 commits into
mainfrom
feat/reducer-group-first-row-ordinal

Conversation

@ictechgy

@ictechgy ictechgy commented Sep 6, 2026

Copy link
Copy Markdown
Owner

왜

UsageReducer 는 한 어시스턴트 응답의 트랜스크립트 행들을 message id 로 묶고 그중 마지막 행을 고른다. 최종 usage 수치가 거기 있기 때문이다. 그런데 행 단위로 스냅샷을 찍으면서 "이 턴 직전에 컨텍스트로 무엇이 들어왔는가" 를 묻는 소비자는 그 선택을 쓸 수 없다. 마지막 행에 도달했을 때 그 답은 이미 여러 행 앞에서 관측되고 지워진 뒤다.

group_first_row_ordinal 이 그 앞 지점이다.

설계에서 확인한 것

  • 필드는 마지막에 둔다. frozen dataclass 에서 기본값 있는 필드를 tokens 앞에 두면 import 시점에 TypeError 가 난다.
  • min(candidate.row_ordinal for candidate in candidates) 로 채운다. :293 의 first_for_group 은 no_id_fallback 카운터용 bool 이라 쓸 수 없고, observe() 가 호출자가 준 순번을 그대로 받으므로 append 순서도 믿을 수 없다.
  • REDUCER_SCHEMA 는 건드리지 않는다. statusline.sh:514 는 스키마가 어긋나면 오류 없이 조용히 캐시 지표를 끈다.

소비자는 아직 없다

이 필드를 읽는 코드는 이번 PR 에 없다. claude_transcript_cost_audit.py 가 Gate-B 동결 경로라 별도 세대로 가야 하므로, 리듀서 쪽만 먼저 올려 두 변경이 한 PR 에 섞이지 않게 한다. 지금 상태에서 audit 출력은 바뀌지 않는다 (리듀서 + audit 스위트 59건이 그대로 통과).

함께 고친 것

픽스처 message id 충돌. tests/test_tool_result_token_estimate.py 의 _corpus 가 id 를 msg_{index} 로 찍는데, test_small_results_and_cache_rewrites_are_not_samples 가 같은 범위를 두 번 이어 붙여 usage 행 80개가 id 40개로 접히고 있었다. 그 단언은 오랫동안 이 충돌 덕분에 통과해 왔다. id_offset 을 넣어 분리했고, 단언값 40 은 수정 전후 모두 유지된다.

선언 누락. 리듀서와 그 미러가 production surface 인벤토리에는 있는데 PROVIDER_FREE_SUPPORT_PATHS 에는 빠져 있었다.

변이 테스트

변이 결과
앵커를 선택된 행으로 잡힘
min → max 잡힘
REDUCER_SCHEMA 상향 잡힘
패키지 미러 미갱신 잡힘

검증

tests.test_context_guard_audit_turn_anchor + cli + stage2   Ran 21 OK, exit 0
tests.test_context_guard_usage_reducer_v2 + token_estimate  Ran 59 OK, exit 0
scripts/ci_test_gate.py fast                                Ran 76 OK, exit 0
실제 3.11 compileall                                        exit 0
scripts/sync_plugin_copies.py --check                       plugin copies synchronized
scripts/prepublish_check.py --skip-tests                    prepublish check: OK
scripts/release_preflight.py                                경고 없음

함정 하나

변이 테스트 중 __pycache__ 가 남아 파일을 복원한 뒤에도 낡은 바이트코드가 로드되는 현상을 겪었다. 리듀서를 반복 수정하며 테스트할 때는 캐시를 지우고 재실행할 것. 커밋 메시지에도 적어 두었다.

동결 경로를 건드리지 않으므로 squash 로 병합해도 된다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh

UsageReducer 는 한 어시스턴트 응답의 트랜스크립트 행들을 message id 로 묶고 그중
마지막 행을 고른다. 최종 usage 수치가 거기 있기 때문이다. 그런데 행 단위로
스냅샷을 찍으면서 "이 턴 직전에 컨텍스트로 무엇이 들어왔는가" 를 묻는 소비자는 그
선택을 쓸 수 없다. 마지막 행에 도달했을 때 그 답은 이미 여러 행 앞에서 관측되고
지워진 뒤다. group_first_row_ordinal 이 그 앞 지점이다.

- UsageSelection 의 **마지막** 필드로 추가한다. frozen dataclass 에서 기본값 있는
  필드를 tokens 앞에 두면 import 시점에 TypeError 가 난다.
- finalize() 에서 min(candidate.row_ordinal for candidate in candidates) 로 채운다.
  :293 의 first_for_group 은 no_id_fallback 카운터용 bool 이라 쓸 수 없고,
  observe() 가 호출자가 준 순번을 그대로 받으므로 append 순서도 믿을 수 없다.
- REDUCER_SCHEMA 는 건드리지 않는다. statusline.sh:514 는 스키마가 어긋나면
  오류 없이 조용히 캐시 지표를 끈다.

이 필드를 읽는 소비자는 아직 없다. audit 이 Gate-B 동결 경로라 별도 세대로
가야 하므로, 그 전에 리듀서 쪽만 먼저 올려 두 변경이 한 PR 에 섞이지 않게 한다.
지금 상태에서 audit 출력은 바뀌지 않는다.

함께 고친 것:
- tests/test_tool_result_token_estimate.py 의 _corpus 가 message id 를 msg_{index}
  로 찍는데, test_small_results_and_cache_rewrites_are_not_samples 가 같은 범위를
  두 번 이어 붙여 usage 행 80개가 id 40개로 접히고 있었다. 그 단언은 오랫동안 이
  충돌 덕분에 통과해 왔다. id_offset 을 넣어 두 코퍼스를 분리한다. 단언값 40 은
  수정 전후 모두 유지된다(확인함).
- 리듀서와 그 미러를 PROVIDER_FREE_SUPPORT_PATHS 에 선언한다. 둘 다 production
  surface 인벤토리에는 있는데 선언 목록에는 빠져 있었다.

변이 4종이 모두 새 테스트에 잡힌다: 앵커를 선택된 행으로, min 을 max 로,
REDUCER_SCHEMA 상향, 패키지 미러를 갱신하지 않음.

검증: tests.test_context_guard_audit_turn_anchor 8건 OK,
tests.test_context_guard_usage_reducer_v2 + test_tool_result_token_estimate 59건 OK,
ci_test_gate fast 76건 OK, 실제 3.11 compileall exit 0,
sync_plugin_copies --check 동기화 확인, prepublish_check --skip-tests OK,
release_preflight 경고 없음.

주의: 변이 테스트 중 __pycache__ 가 남아 복원 후에도 낡은 바이트코드가 로드되는
현상을 겪었다. 리듀서를 반복 수정하며 테스트할 때는 캐시를 지우고 재실행할 것.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh
@coderabbitai

coderabbitai Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 1fa1de11-b2f9-475c-a046-bc5f6cc043d3


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.

ictechgy and others added 2 commits September 6, 2026 16:22
GLM 리뷰가 blocker 하나를 정확히 짚었다. 기존 픽스처는 전부 타임스탬프 순서와
순번 순서가 같은 방향이었고 관찰 순서만 뒤집혀 있었다. 그래서 "앵커 = 그룹에서
가장 이른 타임스탬프를 가진 행의 순번" 이라는 **다른 정의**가 스위트 전체를
통과했다. 소비자가 아직 없고 스키마도 올리지 않는 상황에서 이 스위트가 정의를
고정하는 유일한 장치인데, 정의의 축을 구분하지 못하면 고정이 아니다.

직접 확인했다. 새 테스트를 뺀 상태에서 구현을 타임스탬프 기반 정의로 바꾸면
그대로 통과한다. GLM 의 지적이 맞았다.

추가한 것:
- 순번과 타임스탬프를 반대 방향으로 놓은 테스트 두 개(양방향). 낮은 순번이 늦은
  타임스탬프를 갖게 해서, 선택은 타임스탬프를 따르고 앵커는 최소 순번을 따른다는
  두 축이 서로 다른 답을 내도록 만들었다.
- no-id fallback 그룹의 앵커 테스트. 행 해시로 묶이는 경로가 무점검이었다.

변이 2종이 새로 잡힌다: 타임스탬프 기반 정의(GLM 이 제시한 대안), append 순서 기반
정의(candidates[0]).

GLM 의 non-blocker 도 반영했다.
- 필드 계약 주석에 범위를 좁혀 적었다. 이 값은 그룹의 *usage 후보 행* 중 최소
  순번이지 응답의 첫 행이 아니고, no-id fallback 그룹에서는 "그 행이 처음 나온
  위치" 일 뿐 이 턴의 직전 문맥이 아니다.
- REDUCER_SCHEMA 에 "필드를 덧붙이기만 하는 변경에서는 올리지 않는다" 를 적어,
  다음 사람이 구조 변경 없이 bump 하는 실수를 막는다.
- 테스트의 미사용 datetime import 를 지웠다.

검증: tests.test_context_guard_audit_turn_anchor 11건 OK,
tests.test_context_guard_usage_reducer_v2 + test_tool_result_token_estimate 59건 OK,
ci_test_gate fast 76건 OK, 실제 3.11 compileall exit 0,
sync_plugin_copies --check 동기화 확인, prepublish_check --skip-tests OK.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh
- no-id fallback 테스트의 "처음 나타난 위치" 표현이 관측 순서로 읽힐 수 있었다.
  이 스위트가 다른 곳에서 없앤 모호성이라 "최소 순번" 으로 바꾸고, 픽스처가
  순번 6 을 2 보다 먼저 관측한다는 사실을 docstring 에 적었다. 구현 주석도 같이.
- selections[0] 을 쓰는 두 테스트에 len(selections) == 1 단언을 넣었다.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01DE3AupXcyv64SEuQeTckSh
@ictechgy
ictechgy merged commit 75b3c6f into main Sep 6, 2026
6 checks passed
@ictechgy
ictechgy deleted the feat/reducer-group-first-row-ordinal branch September 6, 2026 07:48
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant