에이전트 자기개선 하네스 (3/12) — self-review 크론 3단계: scan→apply→fix

한 번에 다 하면 빠를 것 같지만, 실제로는 느려지는 이유


핵심 요약

  • self-review를 한 번에 scan+분석+수정하면 과탐(false positive)이 급증한다
  • scan→apply→fix 3단계로 분리하면 각 단계의 역할이 명확해지고, 과탐이 구조적으로 차단된다
  • 배치 제한(2파일, 10분)은 원인 추적을 위한 설계이지, 효율 저하가 아니다

self-review 크론이란

오픈클로(OpenClaw)의 self-review 크론은 코드베이스를 주기적으로 스캔하고, 품질 기준에 미달하는 항목을 자동으로 수정하는 메커니즘입니다.

트리거 조건은 두 가지입니다. 첫째, 커밋이 push될 때 즉시 실행되는 이벤트 기반 트리거. 둘째, 일정 주기로 전체 코드베이스를 검사하는 스케줄 기반 트리거. 두 경로 모두 동일한 3단계 파이프라인을 통과합니다.

초기 설계는 단순했습니다. 크론이 한 번 돌면 스캔→분석→수정을 한 사이클에 전부 처리하는 구조였습니다. 논리적으로는 깔끔합니다. 그러나 실전에서 과탐(false positive)이 대량 발생하면서 구조적 재설계가 필요해졌습니다.


본문

1. 과탐이 발생하는 구조적 원인

단일 파이프라인에서는 스캔 단계가 "의심되는 후보"를 뽑으면, 바로 다음 단계에서 수정이 실행됩니다. 문제는 스캔이 보수적으로 작동한다는 점입니다. "혹시 문제일 수도 있는 것"까지 전부 잡습니다.

이건 스캔의 잘못이 아닙니다. 스캔은 원래 과탐이 많아야 정상입니다. 놓치는 것(false negative)보다 많이 잡는 게(false positive) 낫기 때문입니다. 실제 문제인지 아닌지를 판별하는 건 다음 단계의 역할입니다.

그런데 단일 파이프라인에서는 이 판별 단계가 없거나, 있어도 스캔과 같은 컨텍스트에서 돌기 때문에 같은 편향으로 같은 결론을 내립니다. 스캔이 "문제 같다"고 하면, 분석도 "맞다"고 하고, 수정까지 진행됩니다.

결과적으로 정상 코드가 불필요하게 수정되는 사례가 쌓였습니다.


2. 3단계 분리: scan→apply→fix

해결 방법은 생성과 검증을 물리적으로 분리하는 것이었습니다.

scan (1단계: 후보 목록 생성) - 코드를 스캔해서 "문제일 수 있는 후보" 목록을 생성합니다 - 이 단계의 기준은 의도적으로 느슨합니다 — 놓치는 것보다 많이 잡는 게 목적 - 출력은 후보 목록 파일. 즉시 수정하지 않음

apply (2단계: 필터링 + 수정 큐 등록) - scan 결과를 받아서 실제 문제인지 판별합니다 - 과탐을 걸러내는 핵심 단계 - 실제 문제로 확인된 항목만 수정 큐에 등록

fix (3단계: 배치 수정) - 수정 큐에서 항목을 꺼내서 실제 수정을 실행합니다 - 배치 제한: 2파일, 10분 - 한 번에 많이 바꾸지 않음

각 단계가 독립된 실행 단위입니다. scan이 끝나야 apply가 돌고, apply가 끝나야 fix가 돕니다. 같은 크론 사이클에서 전부 돌 필요가 없습니다.


3. 평가 기준: 각 단계가 무엇을 판단하는가

각 단계는 서로 다른 평가 기준을 적용합니다.

scan 단계의 평가 기준: - 정적 패턴 매칭 (미정의 변수, 사용되지 않는 import, TODO/FIXME 주석 등) - 복잡도 임계값 (함수 길이, 중첩 depth) - 커버리지 공백 (테스트 없는 공개 메서드)

기준은 의도적으로 느슨합니다. 이 단계의 목표는 "놓치지 않기"이며, 과탐은 다음 단계에서 처리합니다.

apply 단계의 평가 기준: - 맥락 분석: TODO가 계획적인가, 미완성인가 - 영향 범위: 수정이 다른 모듈에 파급효과를 미치는가 - 신뢰도 점수: scan이 뽑은 근거가 충분한가 (임계값 미달 시 큐 제외)

fix 단계의 출력 형식:

fix_result:
  file: src/core/agent.py
  change_type: remove_unused_import
  lines_affected: [12, 45]
  confidence: 0.91
  rollback_ref: commit_sha_before

출력에는 변경 타입, 영향 라인, 신뢰도 점수, 롤백 참조가 포함됩니다. fix 단계가 실패하거나 10분을 초과하면 해당 항목은 큐에 남기고 다음 사이클로 넘깁니다.


4. 왜 배치를 2파일로 제한하는가

"한 번에 다 고치면 더 빠르지 않나?"라는 질문이 자연스럽습니다.

핵심은 원인 추적입니다. 한 번에 10개 파일을 수정했는데 이후 다른 곳에서 문제가 생기면, 원인이 10개 수정 중 어디인지 찾아야 합니다. 2개 파일만 수정하면 원인 범위가 좁아집니다.

10분 제한도 같은 맥락입니다. 수정 작업이 예상보다 오래 걸린다는 것은 수정 자체가 단순하지 않다는 신호입니다. 복잡한 수정은 자동화보다 사람이 판단해야 합니다. 10분을 넘기면 해당 항목은 큐에 남기고 다음 사이클로 넘깁니다.

이건 속도를 버리는 게 아닙니다. 디버깅 비용을 선불로 지불하는 것입니다.


5. prescan 과탐 수정: apply 단계 통합 사례

3단계 분리 이전의 대표적 문제는 prescan(스캔 전 사전 검사) 단계에서 특정 패턴을 과탐으로 잡는 케이스였습니다.

prescan이 코드에서 "TODO" 주석을 발견하면 "미완성 코드"로 분류하고 수정 대상에 올렸습니다. 문제는 TODO 중 상당수가 의도적으로 남겨둔 표시였다는 것입니다. "향후 확장 예정", "다음 버전에서 처리" 같은 계획적 TODO까지 전부 "미완성"으로 잡았습니다.

단일 파이프라인에서는 이걸 걸러낼 장치가 없었습니다. 3단계로 분리한 뒤, apply 단계에서 "TODO의 맥락"을 판단하는 규칙을 추가했습니다.

def classify_todo(comment: str, context_lines: list[str]) -> str:
    """
    Returns: 'actionable' | 'planned' | 'ambiguous'
    """
    planned_signals = ["향후", "다음 버전", "v2", "roadmap", "planned"]
    if any(sig in comment for sig in planned_signals):
        return "planned"
    if any(sig in comment for sig in ["fix", "bug", "broken", "FIXME"]):
        return "actionable"
    return "ambiguous"

apply 단계는 planned로 분류된 TODO를 수정 큐에서 제외합니다. ambiguous는 신뢰도 점수를 낮춰서 큐에 등록하고, fix 단계에서 10분 제한이 먼저 걸리면 자동으로 다음 사이클로 넘깁니다.

결과적으로 prescan 과탐률이 크게 줄었고, 의도적 TODO가 사라지는 사고가 없어졌습니다.


6. 오픈클로 → 헤르메스 이전 맥락

오픈클로는 현재 안정적으로 운영 중인 에이전트 플랫폼입니다. self-review 크론의 3단계 분리는 오픈클로 안정화 단계에서 확립된 설계 패턴입니다.

헤르메스(Hermes)는 오픈클로의 차세대 플랫폼으로, OC→HM 이전을 시도했으나 토큰 폭주 문제가 발생해 오픈클로로 회귀했습니다. 현재 헤르메스 재시도가 진행 중이며 검증 단계에 있습니다. 관련 변경사항은 하네스 git 기준 PR#12497에서 추적하고 있습니다.

동일한 self-review 패턴은 헤르메스 재시도(검증 중) 단계에서도 활용 가능한 구조입니다. 플랫폼이 바뀌어도 "생성과 검증을 분리"하는 원칙은 유지됩니다.


시행착오

  • "한 번에 끝내는 게 효율적"이라고 설계 → 과탐 문제로 오히려 수동 복구 작업이 늘어남. 자동화의 목적이 "사람 개입을 줄이는 것"인데, 과탐 수정 때문에 사람 개입이 더 필요해짐
  • apply 단계 도입 전에 scan 기준을 엄격하게 만들려고 시도 → 과탐은 줄었지만 실제 문제도 놓치기 시작함(false negative 증가). 스캔의 목적은 "놓치지 않는 것"이라는 원칙으로 돌아옴
  • 배치 제한 없이 fix를 돌렸을 때, 한 번에 8개 파일을 수정한 적이 있음 → 이후 관련 없어 보이는 곳에서 에러 발생. 8개 수정 중 어디가 원인인지 추적하는 데 시간이 더 들었음

마무리

"생성과 검증을 분리하라"는 원칙은 소프트웨어 전반에 적용되는 이야기입니다. 코드 리뷰가 코딩과 분리되어 있는 이유, CI/CD에서 빌드와 테스트가 별도 스텝인 이유, PR 작성과 머지가 다른 사람의 역할인 이유 — 전부 같은 원리입니다.

self-review 크론의 3단계 분리도 같은 맥락입니다. 한 번에 다 하면 빠른 것 같지만, 과탐 복구 비용까지 합치면 느립니다. scan→apply→fix로 나누면 각 단계가 자기 역할에 집중하고, 문제가 생겨도 어디서 생겼는지 바로 알 수 있습니다.

자동화 파이프라인을 설계할 때, "한 번에 끝내기"보다 "단계별로 끊기"가 더 나은 선택인 경우가 많습니다.

시리즈 전체 안내: 시리즈 목차

댓글

이 블로그의 인기 게시물

"LLM 핵심 학습 (1/6) — 기본: 토큰화·임베딩·어텐션·위치 인코딩"

"LLM 핵심 학습 (2/6) — 파인튜닝: LoRA·QLoRA·증류·Adapter"

"ML 기초 학습 (1/9) — 머신러닝과 sklearn: 학습의 좌표계"