독립된 세션이 같은 해결책에 수렴하면, 그게 정답이다

26년 07월 27일

독립적 수렴

이날은 9개 오픈소스 프로젝트의 버그를 수정한 날이다.

가장 인상적인 발견은 “독립된 세션이 같은 해결책에 수렴하면, 그게 정답이다”였다.

그리고 에이전트가 자기 추측을 사실로 보고한 것도 발견했다.

axios의 settle.js 버그를 수정했다.

HTTP 상태 코드를 배열 인덱스로 변환하는 코드가 500번대 오류를 처리하지 못했다.

3개의 독립된 세션이 이 버그를 각각 수정했다.

근데 3개 전부 같은 해결책을 내놓았다.

“500 이상”을 명시적으로 검사하는 거였다.

Gitea에서도 같은 일이 일어났다.

nil 맵 패닉 버그를 2개의 독립 에이전트가 수정했다.

둘 다

같은 6줄 가드를 내놓았다.

수렴이 신뢰의 신호다

서로 다른 세션이, 서로 다른 맥락에서, 같은 버그를 보고 같은 수정을 내놓는다.

이건 그 수정이 “가장 자연스러운 일반화”라는 증거다.

하나의 세션만 보면 “이렇게도 고칠 수 있겠지”다.

근데 3개가 같은 답을 내놓으면 “이게 정답이다”가 된다.

이건 코드 리뷰와도 같다.

한 사람이 제안한 수정은 취향일 수 있다.

근데 세 사람이 독립적으로 같은 수정을 제안하면, 그건 취향이 아니라 정답이다.

읽기가 쓰기를 한다: 숨겨진 부작용

9개 프로젝트에서 같은 버그 패턴이 반복됐다.

“읽기 함수가 읽기만 하지 않는다.” uptime-kuma에서 getter 메서드가 빈 버킷을 푸시하는 부작용이 있었다.

“값을 가져온다”고 생각했는데, 가져오면서 빈 버킷을 만들어낸다.

읽기가 쓰기를 한다.

axios에서도 비슷했다.

connect 단계 타임아웃이 소켓 연결 후에만 작동했다.

“타임아웃을 설정했다”고 생각했는데, 연결 전에는 작동하지 않았다.

읽기와 쓰기를 분리해라

getter는 읽기만 해야 한다.

부작용이 있으면 안 된다.

“값을 가져온다”와 “값을 만든다”를 분리해야 한다.

삭제도 같다.

memos에서 사용자를 삭제할 때 외래 키 제약이 없었다.

사용자를 지웠는데, 그 사용자가 만든 리소스가 남아 있다.

“삭제했다”고 생각했는데, 참조가 남아 있다.

삭제는 cascade를 고려해야 한다.

에이전트가 자기 추측을 사실로 보고했다

가장 뼈아픈 발견이었다.

작업자 에이전트가 “컨텍스트 한계로 실패했다”고 보고했다.

오케스트레이터가 이 보고를 믿고 후속 처리를 했다.

근데 실제로 확인해보니, 컨텍스트 한계와 전혀 상관이 없었다.

실제 상태

실행 상태는 “실패”가 아니라 “일시정지”였다.

종료 코드는 0이었다.

최대 단일 턴 입력은 128k 컨텍스트의 21%였다.

컨텍스트 한계에 가까이 가지도 않았다.

실제 정지 원인은 오케스트레이터 자신의 중단 명령이었다.

추측이 사실로 승격됐다

오케스트레이터가 약한 회의를 표현했다.

“컨텍스트 한계에 가까워지는 것 같다.” 이 회의가 5단계에 걸쳐 승격됐다.

인용하고 재인용하면서 회의를 벗겨냈다.

“가까워지는 것 같다”가 “컨텍스트 한계로 실패했다”가 됐다.

단위 혼란

토큰 합계도 혼란의 원인이었다.

총 토큰 205,837을 “단일 창 점유율”로 해석했다.

근데 이건 28턴에 걸친 누적 청구 합계다.

205k를 128k와 비교하면 “넘쳤다”가 된다.

근데 단위가 다르다.

누적 청구와 단일 창을 비교하면 안 된다.

보고를 믿지 말고 아티팩트를 확인해라

에이전트의 보고는 출발점이지, 결론이 아니다.

“실패했다”고 보고하면, 실제 실행 아티팩트를 확인해야 한다.

종료 코드, 상태, 오류 필드. 보고와 아티팩트가 다를 수 있다.

구조화된 필드를 읽어라

이벤트 로그에서 “error”를 텍스트로 검색하면 수백 개의 위양성이 나온다.

UI 토스트, 테스트 픽스처, CSS overflow. 전부 “error”라는 단어를 포함한다.

제대로 검색하려면 구조화된 필드를 읽어야 한다.

메시지 에러 필드, 메시지 에러 여부 필드, 메시지 종료 사유 필드. 텍스트가 아니라 구조를 읽어야 한다.

8라운드 검증 루프

프론트엔드 성능/접근성 스킬을 포팅했다.

검증자과 reviewer를 병렬로 돌렸다.

7라운드까지 FAIL이었다.

8라운드째에서 둘 다

PASS했다.

병렬 검증의 가치

하나의 검증자가 인프라 장애로 FAIL을 내지 못할 때, 다른 검증자가 실제 결함을 잡는다.

둘이 같이 검증하면, 하나의 장애가 결함을 숨기지 않는다.

7라운드가 걸린 건 인프라 장애 때문이 아니라, 실제 결함이 있었기 때문이다.

결함을 고치고 다시 검증하고 8라운드째에 통과했다.

인내가 필요했다.

마무리

이날의 핵심은 두 가지였다.

첫째, 독립적 수렴을 신뢰하라. 같은 버그를 독립된 세션이 같은 방법으로 고치면, 그게 정답이다.

하나의 제안은 취향이지만 독립적 수렴은 일반화다.

둘째, 에이전트 보고를 믿지 말고 아티팩트를 확인해라. 에이전트가 자기 추측을 사실로 승격시킬 수 있다.

약한 회의가 인용되면서 확정된다.

보고와 실행 아티팩트를 독립적으로 비교해야 한다.

읽기와 쓰기를 분리하고, 보고와 아티팩트를 분리하고, 텍스트와 구조를 분리해라.