date
slug
author
status
tags(최대 3개)
summary
type
thumbnail
category
updatedAt
AI 에이전트를 오래 사용하면 규칙 파일이 계속 늘어납니다. 에이전트가 실수할 때마다
CLAUDE.md나 AGENTS.md 같은 지침 파일에 한 줄씩 추가하기 때문입니다. 이렇게 손으로 추가한 규칙은 어떤 사건 때문에 생겼는지 기록이 남지 않고, 잘못 추가했을 때 되돌릴 방법도 없습니다.이 문제를 해결하기 위해
/refine을 사용하고 있습니다. /refine은 Prime Intellect의 prime-agent에 포함된 기능으로, 세션에서 관찰한 사실을 근거로 하네스의 보조 상태를 기록하고 필요하면 되돌리는 도구입니다. 이 글에서는 /refine을 하네스에 적용하면서 어떻게 조정해 쓰게 되었는지 소개합니다.원본 /refine의 구조
/refine은 세션에서 관찰한 사실을 근거로, 하네스 상태에 보조 프롬프트, 메모리, 스킬 설명, 서브에이전트 명세 네 종류를 기록합니다.
/refine의 처리 흐름. 무엇을 바꿀지는 모델이 판단하고, refine.py는 판단 없이 검증과 기록, 되돌리기만 담당합니다.기록은 다음 원칙을 따릅니다.
- base 프롬프트 불변: 기본 시스템 프롬프트는 수정하지 않고, 보조 상태만 덧붙입니다.
- 근거 기반 변경: 이번 세션에서 실제로 일어난 사건이 있어야 엔트리를 만듭니다.
- 스냅샷과 rollback: 변경마다 전후 상태를 기록하고, id 하나로 되돌릴 수 있습니다.
매 세션에 주입되는 개요에는 상한이 있습니다. 종류별로 엔트리 6개, 엔트리당 180자까지만 보여 주고, 전문은 필요할 때 디스크에서 읽습니다. 자동으로 생성되는 텍스트가 매 세션 프롬프트에 들어가기 때문에, 상한이 없으면 개요가 계속 커집니다.
상한 방식의 한계
상한은 개요의 크기를 제한할 뿐, 개요에 어떤 엔트리를 남길지는 정하지 않습니다. 실제로 운영해 보니 세 가지 문제가 생겼습니다.
- 중복 엔트리: 같은 실수가 다른 작업에서 다시 발생하면 비슷한 엔트리가 하나 더 생성됩니다. 개요에는 종류별로 6개만 노출되는데, 내용이 같은 엔트리 두 개가 그중 두 자리를 차지합니다.
- 제외 기준의 부재: 일곱 번째 엔트리가 생기면 어떤 엔트리를 개요에서 뺄지 정하는 기준이 없습니다.
- 승격 경로의 부재: 여러 번 반복해서 확인된 엔트리도 계속 보조 상태로 남습니다. 상시 규칙 디렉터리로 옮기는 경로가 없습니다.
이 문제들은 엔트리의 재발 빈도를 측정하지 않는 공통점이 있습니다.
GC 방식의 적용
해결하는 방법으로 떠올린 것은 JVM의 세대별 가비지 컬렉션(generational GC)입니다. 이 방식은 객체를 생존 기간에 따라 eden, survivor, tenured 영역으로 분류하고, 오래 살아남은 객체일수록 앞으로도 오래 사용된다고 가정합니다. 여러 시점에 걸쳐 재발한 엔트리일수록 앞으로도 필요할 가능성이 높으니, 엔트리에도 같은 가정을 적용할 수 있다고 판단했습니다.
생존 기간에 해당하는 값으로는 age를 두었습니다. age는 같은 관찰이 다시 기록될 때만 1씩 증가합니다.

각 상태의 의미는 다음과 같습니다.

같은 종류의 엔트리가 6개를 넘으면, 개요에는 age가 높은 엔트리부터 6개만 넣습니다. age가 같으면 최근에 만든 엔트리를 우선합니다. GC를 실행하면 중복 엔트리가 병합 후보로 표시됩니다. 엔트리 수를 줄이려고 병합하지는 않고, 내용이 같을 때만 병합합니다.
하루에 같은 실수가 네 번 기록된 경우는 그 세션만의 사정일 수 있으므로, 승격 조건에 기간을 함께 두어 여러 시점에 걸쳐 재발한 엔트리만 승격되게 했습니다.
cold 엔트리 보존 원칙
cold 엔트리도 GC와 같은 방식으로 처리하려고 하니 문제가 있었습니다. GC는 참조가 없는 객체를 수거합니다. 참조가 없는 객체는 다시 사용될 수 없으므로 수거해도 안전합니다. 규칙은 다릅니다. 30일 동안 재발하지 않은 규칙은 필요 없는 규칙일 수도 있고, 잘 지켜지고 있는 규칙일 수도 있습니다. 기록만으로는 둘을 구분할 수 없습니다.

재발하지 않는다고 삭제하면 잘 지켜지는 규칙부터 사라집니다. 그래서 cold 엔트리는 개요에서만 빼고 본문은 디스크에 남겨 둡니다. 같은 실수가 다시 기록되면 age가 올라 survivor로 돌아옵니다. cold 처리는 age 1 엔트리에만 적용하고, 한 번이라도 재발한 엔트리는 cold로 분류하지 않습니다.
프롬프트 주입 기준
개요에 엔트리가 하나 추가될 때마다 기존 엔트리의 영향력은 그만큼 약해집니다. 그래서 수명 관리와 별도로, 엔트리를 만들기 전에 확인하는 기준도 두었습니다.
에이전트가 다른 방법으로 알아낼 수 있는 사실은 프롬프트에 넣지 않습니다.
코드를 읽거나 명령을 실행하거나 검사를 돌려서 알 수 있는 사실은 프롬프트 대신 검사 쪽에 둡니다. 코드에 관한 사실은 린터나 테스트에, 도구 호출에 관한 사실은 훅에, 스크립트에 관한 사실은 스크립트의 오류 메시지에 넣습니다.
기준을 통과하는 것은 대부분 판단 방식입니다. 파일 위치나 명령어 옵션은 검색하면 나오지만, "원인을 단정하기 전에 무엇을 확인해야 하는가"는 어떤 파일에도 없습니다.
실제 엔트리 사례
엔트리 하나를 예로 들겠습니다.
원인을 역추론했다면, 그 원인이 결과의 모양을 만들 수 있는지 소스로 확인하기 전까지 단정하지 않는다.
모양은 필드의 유무, 값, 문자열 형태처럼 눈으로 확인할 수 있는 속성을 말합니다.

첫 번째 사례는 상담 대화 기록을 분석할 때 나왔습니다. 답변 바로 뒤에 상담원 이관 기록이 있어서, 사용자가 답변 아래 버튼을 눌러 이관을 요청했다고 판단했습니다. 하지만 그 이관 기록에는 입력값이 담겨 있었고, 그 버튼은 입력값을 보내지 않습니다. 버튼의 호출 코드만 확인했어도 바로 알 수 있었던 사실이었고, 이미 팀에 공유한 판단을 정정해야 했습니다.
두 번째 사례는 운영 결함을 수정할 때 나왔습니다. 결함을 찾아 테스트로 재현하고 배포했지만 증상은 그대로였습니다. 이 기능은 로그인 여부에 따라 서로 다른 코드로 처리되는데, 제보된 요청은 제가 수정하지 않은 쪽 코드로 처리되었기 때문입니다. APM 트레이스를 확인하고 나서야 수정한 코드가 실행되지 않았다는 사실을 알았습니다. 코드가 그 결과를 만들 수 있는지와 그 요청이 실제로 그 코드를 실행했는지는 따로 확인해야 한다는 내용을 이때 엔트리에 추가했습니다.
세 번째 사례는 한 달 뒤 다른 작업에서 나왔습니다. 세션 간 메시지 가드의 오탐 원인을 추정했는데, 추정한 원인으로는 실제 오탐 형태를 설명할 수 없었습니다. 실제 트랜스크립트로 가드를 다시 실행해 보니 원인은 주소 키 불일치였습니다.
앞선 사례는 도메인과 작업 종류가 모두 다릅니다. 작업이 달라도 같은 실수가 반복되었으므로, 이 엔트리는 특정 작업이 아니라 판단 방식에 관한 규칙이라고 볼 수 있습니다. age는 이 반복을 기록한 값입니다.
운영 기록
약 7주 동안 기록된 refinement는 55건이고, 편집은 총 67건 입니다.

GC 방식은 운영 후반에 추가했습니다. 그 전에는 재발도 update로 기록했기 때문에, 도입 시점에 기존 이력을 바탕으로 엔트리마다 재발 기록을 다시 만들었습니다.
그 직후 첫 GC 실행에서 엔트리 5개가 승격 조건을 충족했고, 승인을 거쳐 상시 규칙이 되었습니다. "레이블이 아니라 실행 기록으로 판정한다", "부재 판정은 대상의 소스로 확인한다" 같은 규칙이 이때 승격되었습니다.
직접 적용해 볼 수 있는 원샷 프롬프트
자신의 하네스에 적용해 보고 싶다면, 아래 프롬프트를 Claude Code나 Codex에 그대로 붙여 넣어 보세요.
에이전트가 지금 쓰고 있는 하네스 구조를 먼저 읽고, 경로를 어디에 맞춰야 하는지 알려 준 뒤 구현합니다. 상한값과 기간 조건은 직접 써 보면서 각자의 사용 패턴에 맞게 조정하면 됩니다.
내 AI 에이전트 하네스에 "규칙 수명 관리" 장치를 만들어 줘. 세션에서 겪은 실수를 규칙 엔트리로 기록하고, 재발 빈도에 따라 매 세션 프롬프트에 넣을 규칙을 고르는 장치야. ## 0. 설정값 확인 (구현 전에 반드시 먼저) 내 현재 하네스 구조(지침 파일, 규칙 디렉터리, 스킬 디렉터리)를 읽고, 아래 설정값마다 내 환경에 맞는 추천값을 기본값과 함께 제시해 줘. 내가 고르거나 확인한 값으로 구현하고, 내가 따로 정하지 않은 값은 기본값을 쓴다. | 설정 | 기본값 | 의미 | |---|---|---| | STATE_DIR | ~/.harness-state | 엔트리, 메타데이터, 이력을 두는 디렉터리 | | RULES_DIR | 기존 규칙 디렉터리가 있으면 그곳, 없으면 STATE_DIR/rules | 승격된 상시 규칙 파일을 두는 디렉터리 | | INSTRUCTION_FILE | 쓰고 있는 지침 파일. CLAUDE.md 가 있으면 그것, 없으면 AGENTS.md, 둘 다 없으면 CLAUDE.md 를 새로 만든다 | 개요를 불러오게 연결할 파일 | | OVERVIEW_LIMIT | 종류별 6개, 엔트리당 180자 | 매 세션에 주입하는 개요의 상한 | | TENURE | age 4 이상, 첫 기록과 마지막 기록 사이 14일 이상 | 승격 후보 조건 | | COLD_AFTER | 30일 | age 1 엔트리를 cold 로 분류하는 기간 | | MERGE_SIMILARITY | 본문 유사도 0.5 이상 | 병합 후보로 낼 중복 엔트리의 기준 | ## 1. 저장 구조 - STATE_DIR/entries/<kind>/<id>.md : 엔트리 본문. kind 는 prompt, memory 두 가지로 시작한다 - STATE_DIR/index.json : 엔트리 메타데이터 (id, kind, title, age, hits[{at, evidence}], created_at) - STATE_DIR/history.jsonl : 변경마다 전후 스냅샷 한 줄. refinement id 로 rollback 할 수 있어야 한다 - STATE_DIR/overview.md : 매 세션에 주입할 개요. INSTRUCTION_FILE 에서 이 파일을 불러오게 연결한다. INSTRUCTION_FILE 의 기존 내용은 수정하지 않는다 ## 2. 스크립트 refine.py (판단 없이 검증, 기록, 되돌리기만 한다) - apply <proposal.json> : 아래 동작을 검증하고 기록한 뒤 개요를 다시 렌더한다 - create : age 1, hits 에 근거 1건 - reinforce : 같은 실수가 다시 기록되면 age +1, hits 에 근거 추가 - update : 문구 수정. age 는 올리지 않는다 - merge : 중복 엔트리를 하나로 합치고 age 를 더한다 - promote : --approved 없이는 거부한다. 엔트리를 RULES_DIR 의 새 파일로 옮긴다 - delete : 엔트리 삭제 - rollback <refinement-id> : 스냅샷으로 되돌린다 - gc : 읽기 전용 보고. 아래 상태를 계산해서 출력한다 - eden : age 1, 마지막 기록 후 COLD_AFTER 이내 - survivor : age 2 이상 - cold : age 1, 마지막 기록 후 COLD_AFTER 경과. 개요에서만 제외하고 삭제하지 않는다 - 승격 후보 : TENURE 조건 충족 - 종류별 OVERVIEW_LIMIT 를 넘으면 본문 유사도가 MERGE_SIMILARITY 이상인 엔트리 쌍을 병합 후보로 낸다 - 렌더 : kind 마다 cold 를 뺀 엔트리를 age 내림차순(같으면 최근 생성 우선)으로 정렬해 OVERVIEW_LIMIT 만큼만 개요에 쓴다. 나머지는 개수만 적는다. cold 판정은 시각에 따라 바뀌므로 apply, rollback 때뿐 아니라 세션 시작 훅에서도 렌더한다 ## 3. 절차 (/refine 스킬) 1. 이번 세션에서 실제로 일어난 실수가 있는지 본다. 특정 턴, 명령, 로그를 가리킬 수 없으면 "후보 없음"으로 끝낸다 2. 코드, 명령, 검사로 알 수 있는 사실이면 엔트리 대신 린터, 테스트, 훅을 제안하고 멈춘다 3. 기존 엔트리와 같은 실수면 create 대신 reinforce 한다 4. 변경 내용(diff)을 보여 주고 내 승인을 받은 뒤 apply 한다 5. 마지막에 gc 를 실행해 승격 후보와 병합 후보를 보고한다. 승격은 규칙 초안 전문을 보여 주고 승인받는다 ## 4. 검증 상태 전이(eden → survivor → 승격 후보, eden → cold, cold → survivor)와 rollback 을 테스트로 검증해 줘.
이정민 Backend Engineer
판단은 사람이 실행은 AI가 맡는 구조를 만듭니다.
속도와 임팩트, 둘 다 포기하지 않습니다.
