양파개발자 실바의 블로그

하네스 엔지니어링 (2) 데이터 거버넌스와 피드백 루프: 무엇을 보여주고, 실패를 어떻게 자산으로 만들까

하네스 엔지니어링 시리즈 1편 개념과 가드레일 · 2편 데이터 거버넌스와 피드백 루프 (현재 글) · 3편 역할별 레시피와 안티패턴 선행 글: Claude Code 에이전트 팀 입문 · 레거시 프로젝트에 에이전트 팀 투입하기

1편에서 가드레일로 못 하게 막았습니다. 이제 제대로 하게 만들 차례입니다. 남은 두 축을 다룹니다.

구성요소 통제 대상 핵심 질문
가드레일 할 수 있는 일 무엇을 못 하게 막을까 (1편)
데이터 거버넌스 볼 수 있는 정보 무엇을 보여주고 무엇을 숨길까
피드백 루프 완료의 판정 잘했는지 누가 어떻게 확인할까

이 두 축은 사고를 막는 장치가 아니라 품질을 만드는 장치입니다. 가드레일이 없으면 사고가 나고, 이 둘이 없으면 그럴듯하지만 쓸 수 없는 결과물이 나옵니다.

3. 데이터 거버넌스 — 무엇을 보여줄까

에이전트가 엉뚱한 코드를 쓰는 이유는 대개 모델이 부족해서가 아니라 근거가 부족해서입니다. 우리 팀의 컨벤션을 모르니 일반적인 방식으로 쓰고, 이미 있는 유틸리티를 모르니 새로 만듭니다.

그런데 반대 방향의 실패도 있습니다. 다 보여주면 정작 중요한 것이 묻힙니다. 데이터 거버넌스는 이 둘 사이의 균형을 설계하는 일입니다.

3-1. 지식 공급의 3층 구조

Claude Code에서 에이전트에게 지식을 주는 수단은 세 층입니다.

수단 로드 시점 수명 컨텍스트 비용
1층 CLAUDE.md 항상 (매 세션 시작) 영구 매 세션 지속 부담
2층 스킬(Skills) 온디맨드 (필요할 때) 영구, 호출 시만 평소엔 설명문만
3층 스폰 프롬프트 1회성 해당 에이전트 생존 기간 그 에이전트만

핵심 차이는 비용을 언제 내는가입니다.

  • CLAUDE.md에 100줄을 쓰면 → 모든 세션이 100줄만큼 손해를 봅니다
  • 스킬에 100줄을 쓰면 → 평소엔 설명문 한 줄, 쓸 때만 100줄을 씁니다

스킬은 세션 시작 시 이름과 description 컨텍스트에 올라갑니다. 본문은 실제로 호출될 때 로드됩니다. 그래서 긴 참고 자료를 스킬에 넣어도 평소 비용이 거의 없습니다.

“이건 CLAUDE.md인가 스킬인가”

실무에서 가장 헷갈리는 지점입니다. 판단 플로우는 이렇습니다.

① 이것은 사실인가, 절차인가?
   사실 ("테스트는 pytest로 돌린다", "API 핸들러는 src/api/에 있다")
     → 2번으로
   절차 (단계가 있는 작업: "릴리스 배포하기", "신규 포스팅 작성하기")
     → 스킬

② 모든 작업에서 필요한가?
   예 (빌드 명령어, 코딩 스타일, 절대 금지 사항)
     → CLAUDE.md
   아니오, 특정 파일/영역에서만 필요
     → .claude/rules/ + paths 프론트매터

③ 이 대화에서만 필요한가?
   예 → 스폰 프롬프트 (문서화하지 않음)

기준을 한 줄로 압축하면 이렇습니다.

CLAUDE.md는 “항상 참인 사실”, 스킬은 “때때로 하는 절차”.

CLAUDE.md가 절차를 담기 시작하면 비대해집니다. “릴리스 절차”를 CLAUDE.md에 20줄 적어두면, 릴리스와 무관한 999번의 세션이 그 20줄을 지고 갑니다. 이건 스킬로 옮겨야 합니다.

반대로 스킬에 “빌드는 npm run build” 같은 사실을 넣으면, 스킬을 호출하지 않는 대부분의 상황에서 에이전트가 그 사실을 모릅니다.

1층 안의 분기: .claude/rules/

CLAUDE.md가 길어질 때의 공식 해법입니다. .claude/rules/ 디렉터리에 주제별 파일을 두면 CLAUDE.md와 같은 우선순위로 로드됩니다.

your-project/
├── CLAUDE.md              # 항상 필요한 것만
└── .claude/rules/
    ├── api-design.md      # paths 로 범위 지정
    ├── testing.md
    └── frontend.md

여기서 중요한 게 paths 프론트매터입니다.

---
paths:
  - "src/api/**/*.ts"
---

# API 개발 규칙

- 모든 엔드포인트는 입력값 검증을 포함한다
- 표준 에러 응답 포맷을 사용한다
- OpenAPI 문서화 주석을 붙인다

이 규칙은 에이전트가 src/api/ 아래 파일을 읽을 때만 컨텍스트에 들어갑니다. 프론트엔드 작업 중에는 존재하지 않습니다. paths가 없는 rules 파일은 무조건 로드됩니다.

정리하면 1층은 이렇게 세분화됩니다.

위치 로드 시점 적합한 내용
CLAUDE.md 항상 빌드/테스트 명령어, 전역 금지 사항
.claude/rules/*.md (paths 없음) 항상 주제별로 분리하고 싶은 전역 규칙
.claude/rules/*.md (paths 있음) 해당 파일 접근 시 영역별 컨벤션
하위 디렉터리의 CLAUDE.md 그 디렉터리 파일 접근 시 모노레포 패키지별 규칙

3-2. 컨텍스트 예산 관리

하네스는 더하는 것보다 빼는 것이 어렵습니다.

규칙을 추가하는 건 쉽습니다. 문제는 추가할 때마다 기존 규칙의 준수율이 조금씩 떨어진다는 점입니다. 컨텍스트가 길어지면 모델의 주의가 분산되고, 정작 중요한 규칙이 묻힙니다.

공식 권고는 CLAUDE.md 파일당 200줄 이하입니다. 길면 컨텍스트를 더 먹고 준수율이 떨어집니다.

주의할 함정이 하나 있습니다. @path 임포트로 파일을 쪼개도 컨텍스트는 줄지 않습니다. 임포트된 파일은 세션 시작 시 전부 펼쳐져 로드됩니다. 정리에는 도움이 되지만 예산 절감은 아닙니다. 실제로 줄이려면 paths 스코프 rules나 스킬로 옮겨야 합니다.

현재 예산 확인하기

/context를 실행하면 컨텍스트 사용량이 그림으로 표시됩니다. Memory files 항목에서 어떤 파일이 실제로 로드됐는지 확인할 수 있습니다.

여기서 흔히 발견하는 것들입니다.

  • 옛날에 넣고 잊은 규칙 (이미 해결된 문제에 대한 경고)
  • 코드에서 바로 알 수 있는 정보 (디렉터리 구조 설명, 의존성 목록)
  • 서로 모순되는 두 규칙 (모델이 임의로 하나를 고름)

가지치기 기준

각 줄에 이 질문을 던집니다.

“이 줄을 지우면 에이전트가 실수할까?”

  • 한다 → 남깁니다
  • 안 한다 → 지웁니다
  • 모르겠다 → 지웁니다. 실수가 재발하면 그때 다시 넣습니다(4-4의 에스컬레이션)

특히 코드에서 파악 가능한 내용은 지우는 게 낫습니다. 디렉터리 구조나 의존성 목록은 에이전트가 lspackage.json으로 알 수 있고, 게다가 코드가 바뀌면 문서가 거짓말을 하기 시작합니다. 틀린 문서는 없는 문서보다 나쁩니다.

남길 것은 반대입니다.

  • 왜 그렇게 되어 있는지 (코드를 봐도 알 수 없는 배경)
  • 도구 기본값과 다른 우리 팀의 선택
  • 함정 (“이 테이블은 트리거가 있어서 직접 UPDATE 하면 안 됨”)

/doctor 명령이 이 작업을 도와줍니다. 커밋된 CLAUDE.md에서 코드로 유도 가능한 내용을 잘라내고, 함정·근거·기본값과 다른 컨벤션은 남기는 방향으로 정리안을 제안합니다.

3-3. 외부 데이터 연동 — MCP와 CLI

문서만으로는 부족한 정보가 있습니다. DB의 현재 스키마, 이슈트래커의 요구사항, 모니터링의 에러 로그. 이런 살아있는 데이터는 MCP 서버로 연결합니다.

claude mcp add --transport http sentry https://mcp.sentry.dev/mcp

연결하면 에이전트가 “이 에러가 프로덕션에서 몇 번 발생했는지” 직접 조회할 수 있습니다.

도구 목록은 이제 컨텍스트 문제가 아니다

MCP를 많이 붙이면 도구 정의가 컨텍스트를 잡아먹는다는 이야기가 있었는데, 현재는 MCP 툴 서치가 기본 활성화되어 이 문제가 대부분 해소됐습니다.

  • 세션 시작 시 도구 이름과 서버 설명만 로드됩니다
  • 실제 정의(파라미터 스키마)는 에이전트가 필요할 때 검색해서 가져옵니다
  • 서버 수에 대한 고정 상한은 없고, 실질 한계는 컨텍스트 예산입니다

그래서 서버를 여러 개 붙이는 것 자체는 이제 큰 부담이 아닙니다.

지금 실제 문제는 출력량이다

문제는 도구 정의가 아니라 도구가 뱉는 결과로 옮겨갔습니다.

항목 기본값
출력 경고 임계값 10,000 토큰 초과 시 경고
출력 상한 25,000 토큰 (MAX_MCP_OUTPUT_TOKENS로 조정)

“이 테이블 전체를 조회해봐” 한 번으로 컨텍스트의 상당 부분이 날아갑니다. 그래서 MCP는 좁게 물어보게 만드는 것이 중요합니다. CLAUDE.md에 이런 규칙을 두면 도움이 됩니다.

## DB 조회 규칙
- 조회 시 반드시 LIMIT 를 붙인다 (기본 20건)
- SELECT * 금지. 필요한 컬럼만 명시한다
- 스키마 확인은 information_schema 조회 대신 `docs/schema.md` 를 먼저 본다

MCP보다 CLI가 나은 경우

gh, aws, psql 같은 CLI 도구가 이미 있다면, 같은 기능의 MCP 서버보다 나은 경우가 많습니다.

  MCP 서버 CLI 도구 (Bash)
출력 제어 서버가 정한 포맷 그대로 --json, jq, head로 자유롭게 줄임
권한 제어 서버 단위 명령 패턴 단위 (Bash(gh pr view:*))
인증 별도 설정 필요 이미 로그인된 상태를 재사용
적합한 상황 전용 프로토콜, 복잡한 상태 관리 이미 CLI가 있고 출력을 다듬어야 할 때

핵심 이점은 출력 제어입니다. gh pr view 142 --json title,body | jq 로 필요한 필드만 가져오는 게, MCP 서버가 반환하는 전체 페이로드를 받는 것보다 컨텍스트에 훨씬 유리합니다.

에이전트별로 서버 제한하기

서브에이전트마다 필요한 서버만 줄 수 있습니다.

---
name: incident-investigator
description: 프로덕션 장애의 원인을 조사하고 보고
tools: Read, Grep, Glob, Bash
mcpServers:
  - sentry
  - datadog
permissionMode: plan
---

이 조사자는 Sentry와 Datadog만 볼 수 있고, GitHub이나 DB MCP는 없습니다. 1편의 능력 최소화 원칙이 데이터에도 그대로 적용됩니다. 볼 필요 없는 것은 보여주지 않는 게 집중에 유리합니다.

주의: mcpServers는 서브에이전트에만 적용됩니다. 팀원(teammate)으로 실행될 때는 무시되고, 프로젝트·유저 설정의 서버를 그대로 로드합니다(skills 필드도 동일). 1편의 isolation: worktree와 같은 비대칭입니다.

3-4. 민감정보 경계

1편에서 다룬 .env 사례가 데이터 거버넌스의 대표 문제입니다. 요점만 다시 정리하면, 지시로 막지 말고 구조로 막습니다.

{
  "permissions": {
    "deny": [
      "Read(.env*)",
      "Read(**/.env*)",
      "Read(**/*credentials*)",
      "Read(**/id_rsa*)"
    ]
  }
}

패턴으로 표현하기 어려운 조건은 훅으로 처리합니다.

#!/bin/bash
# .claude/hooks/block-secret-read.sh
# PreToolUse (matcher: "Read|Grep|Bash") 로 등록

INPUT=$(cat)
TARGET=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // .tool_input.command // empty')

if echo "$TARGET" | grep -qE '\.env|secrets/|\.pem$|\.p12$'; then
  echo "민감 파일 접근은 차단됩니다. 필요한 환경변수 이름은 .env.example 을 참고하세요." >&2
  exit 2
fi

exit 0

.env.example을 대안으로 제시하는 부분이 중요합니다. 그냥 막으면 에이전트가 다른 우회로를 찾지만, 대안을 주면 그쪽으로 갑니다.

worktree를 쓰는 에이전트에는 .worktreeinclude가 추가 경계선이 됩니다. worktree는 새 체크아웃이라 gitignore된 파일이 없으므로, 여기 적은 것만 에이전트가 볼 수 있습니다.

.env.test
config/local.json

프로덕션 자격증명이 담긴 .env.production을 여기 넣지 않으면, 그 에이전트는 프로덕션에 접근할 방법이 없습니다. 리스트를 짧게 유지하는 것이 그 자체로 가드레일입니다.

3-5. 지식의 축적

여기까지는 사람이 써서 주는 지식이었습니다. 에이전트가 스스로 쌓는 지식도 설계할 수 있습니다.

서브에이전트 memory

---
name: code-reviewer
description: 코드 품질과 컨벤션을 검토
memory: project
tools: Read, Grep, Glob, Bash
permissionMode: plan
---

코드를 리뷰하면서 발견한 패턴, 컨벤션, 반복되는 문제를 메모리에 기록하세요.

memory를 켜면 이 에이전트는 대화를 넘어 유지되는 전용 디렉터리를 갖습니다.

스코프 위치 쓰는 경우
user ~/.claude/agent-memory/<name>/ 모든 프로젝트에 걸친 학습
project .claude/agent-memory/<name>/ 프로젝트 한정 + 버전관리로 팀 공유
local .claude/agent-memory-local/<name>/ 프로젝트 한정, 커밋하지 않음

project가 권장 기본값입니다. 커밋되므로 팀 전체가 같은 축적물을 공유합니다.

동작 방식은 이렇습니다. 메모리 디렉터리의 MEMORY.md가 색인 역할을 하고, 첫 200줄(또는 25KB)까지만 시스템 프롬프트에 포함됩니다. 상세 내용은 주제별 파일로 분리되고 필요할 때 읽습니다. 넘치면 색인을 정리하라는 지시가 함께 들어갑니다.

여기서 3-2의 예산 문제가 반복됩니다. MEMORY.md도 짧아야 합니다. 색인은 한 항목당 한 줄, 내용은 주제 파일로.

조사 산출물을 문서로 남기기

memory는 자동 축적이고, 더 직접적인 방법은 조사 결과를 파일로 내보내는 것입니다.

레거시 프로젝트에 에이전트 팀 투입하기에서 조사자들의 발견을 ARCHITECTURE.md로 모으고, 확정된 내용을 CLAUDE.md로 승격시키는 패턴을 다뤘습니다. 이걸 일반화하면 지식의 승격 경로가 됩니다.

① 1회성 발견 (에이전트의 보고)
     ↓ 재사용 가치가 있으면
② 프로젝트 문서 (docs/ARCHITECTURE.md, docs/schema.md)
     ↓ 모든 작업에서 필요하면
③ CLAUDE.md 또는 .claude/rules/
     ↓ 절차라면
④ 스킬

②단계가 중요합니다. 문서로 남기면 컨텍스트를 쓰지 않으면서 재사용 가능한 상태가 됩니다. 다음 에이전트가 같은 조사를 반복하지 않고 문서를 읽습니다.

조사 에이전트에게 이 지시를 넣어두는 것이 실무 팁입니다.

조사 결과를 docs/findings/<주제>.md 에 정리하세요.
이미 같은 파일이 있으면 먼저 읽고, 새로 알게 된 것만 추가하세요.

“먼저 읽고 추가” 가 핵심입니다. 이게 없으면 매번 처음부터 조사하고 덮어씁니다.


4. 피드백 루프 — 완료를 누가 판정할까

4-1. 검증 수단이 없으면 “그럴듯해 보임”이 완료 신호가 된다

에이전트가 “완료했습니다”라고 할 때, 그 판단의 근거는 무엇일까요? 검증 수단을 주지 않으면 근거는 하나뿐입니다. 코드가 그럴듯해 보인다는 것.

이게 AI 에이전트의 가장 비싼 실패 유형입니다. 에러가 나면 바로 알 수 있지만, “완료했다는 보고”는 사람이 직접 확인하기 전까지 참인지 알 수 없습니다. 그리고 사람은 대개 확인하지 않습니다.

강제력은 4단계로 나뉩니다.

단계 수단 강제력 설정 비용 사람의 주의력 필요
1 프롬프트에 검증 지시 낮음 (advisory) 없음 많이
2 /goal 조건 중간 낮음 중간
3 Stop / TaskCompleted 결정적 중간 거의 없음
4 검증 서브에이전트 독립적 판단 중간 적음

1단계: 프롬프트에 검증 지시

가장 흔하고 가장 약한 방법입니다.

작업 후 `npm run test:auth` 를 실행하고, 100% 통과했을 때만 완료 보고하세요.

없는 것보다는 훨씬 낫습니다. 하지만 1편에서 다룬 그대로, 이건 권고입니다. 테스트가 3개 실패했을 때 “핵심 기능은 동작하며 실패한 테스트는 기존 이슈로 보입니다”라고 보고할 여지가 있습니다.

2단계: /goal 조건

/goal은 조건이 충족될 때까지 에이전트가 턴을 넘어 계속 일하게 만듭니다.

/goal npm test 가 전부 통과할 때까지

프롬프트보다 강한 이유는 종료 조건이 대화에서 분리되기 때문입니다. 컨텍스트가 길어져도 목표는 유지됩니다. 다만 조건 충족 여부의 판정은 여전히 모델이 합니다.

3단계: 훅으로 강제

판정을 코드에게 넘기는 단계입니다. 1편에서 본 종료 코드 2가 여기서 쓰입니다.

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/require-tests.sh",
            "statusMessage": "테스트 확인 중..."
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# .claude/hooks/require-tests.sh
# 소스가 변경됐는데 테스트가 실패하면 종료를 막는다

CHANGED=$(git diff --name-only HEAD -- 'src/**' | wc -l)
if [ "$CHANGED" -eq 0 ]; then
  exit 0
fi

if ! OUTPUT=$(npm test 2>&1); then
  echo "테스트가 실패했습니다. 종료할 수 없습니다:" >&2
  echo "$OUTPUT" | tail -30 >&2
  exit 2
fi

exit 0

이제 테스트가 통과하지 않으면 에이전트가 턴을 끝낼 수 없습니다. 모델의 판단이 개입할 여지가 없습니다. 실패 출력이 stderr로 전달되므로 에이전트는 무엇이 깨졌는지 알고 고치기 시작합니다.

에이전트 팀에서는 이벤트가 다릅니다.

시점 쓰는 곳
Stop 메인 에이전트가 턴을 마치려 할 때 단일 세션
SubagentStop 서브에이전트가 끝나려 할 때 서브에이전트
TaskCompleted 태스크를 완료 처리할 때 팀 (태스크 단위 검증)
TeammateIdle 팀원이 유휴로 전환하려 할 때 팀 (팀원이 일찍 손 놓는 것 방지)

4단계: 검증 서브에이전트

훅으로 판정할 수 없는 것이 있습니다. “요구사항을 충족했는가”, “이 코드가 기존 아키텍처와 맞는가” 같은 판단입니다. 여기는 다른 에이전트가 필요합니다(4-2에서 다룹니다).

설정 비용 vs 사람의 주의력

이 표의 진짜 의미는 여기 있습니다. 하네스 설정 비용과 사람의 주의력은 교환 관계입니다.

1단계만 쓰는 경우
  설정 비용: 0
  대가: 에이전트가 완료 보고할 때마다 사람이 직접 확인해야 함
        → 에이전트 1개면 감당 가능
        → 에이전트 5개면 사람이 병목

3단계까지 올린 경우
  설정 비용: 훅 스크립트 하나 (한 번)
  이득: 사람은 훅을 통과한 결과만 봄
        → 에이전트를 늘려도 사람의 부담은 늘지 않음

1편에서 “에이전트가 N개면 하네스 부재의 비용이 N배”라고 했던 것의 다른 얼굴입니다. 하네스 설정은 한 번 내는 고정비, 사람의 주의력은 매번 내는 변동비입니다. 에이전트를 몇 개 돌릴 계획인지가 어디까지 올릴지를 결정합니다.

실무 기준: 에이전트 1~2개면 1~2단계로 시작합니다. 3개 이상 병렬로 돌릴 계획이면 3단계가 필수입니다.

4-2. 채점자를 분리한다

자기가 쓴 코드를 자기가 채점하면 통과합니다. 사람도 그렇고 에이전트도 그렇습니다. 에이전트는 특히 자기 작업을 정당화하는 방향으로 해석하는 경향이 있습니다. 앞서 본 “실패한 테스트는 기존 이슈로 보입니다”가 그 예입니다.

해법은 구조입니다. 판정자를 작업자와 분리합니다.

[ 분리하지 않은 경우 ]
구현 에이전트 ──> 자기 코드 검토 ──> "완료" (통과율 ~100%)

[ 분리한 경우 ]
구현 에이전트 ──> 산출물 ──> 검증 에이전트 (다른 컨텍스트) ──> 판정
                                   ↑
                          구현 과정을 모름. 결과만 봄

검증 에이전트가 구현 과정을 모르는 것이 약점이 아니라 장점입니다. “왜 그렇게 했는지”에 대한 변명을 못 들었으므로 결과만 봅니다.

정의는 이렇게 씁니다(3편에서 자세히 다룹니다).

---
name: verifier
description: 완료 보고된 작업이 실제로 요구사항을 충족했는지 검증
tools: Read, Grep, Glob, Bash
permissionMode: plan
model: opus
---

요구사항과 산출물을 비교해 검증하세요.

- **정확성과 요구사항 충족만** 지적하세요
- 스타일 취향, 개인적 선호, "더 나은 방법"은 지적하지 마세요
- 요구사항을 충족했다면 그렇다고 보고하세요. 억지로 문제를 찾지 마세요

본문의 세 줄이 다음 항목의 문제를 막기 위한 것입니다.

리뷰어는 문제가 없어도 뭔가를 지적한다

검증 에이전트를 붙이면 새로운 실패 유형이 생깁니다. 리뷰어는 “문제 없음”을 보고하기 어려워합니다. 리뷰를 요청받았으니 뭔가를 찾아야 한다고 판단하고, 없으면 사소한 것을 만들어냅니다.

이게 위험한 이유는 그 지적에 대한 대응입니다.

검증 에이전트: "이 함수는 null 입력을 처리하지 않습니다"
구현 에이전트: 방어 코드 추가
검증 에이전트: "이 방어 코드는 테스트가 없습니다"
구현 에이전트: 불가능한 케이스에 대한 테스트 추가
검증 에이전트: "이 로직은 추상화하면 재사용 가능합니다"
구현 에이전트: 단일 사용처에 인터페이스 도입
──────────────────────────────────────
결과: 50줄로 될 일이 200줄. 전부 "리뷰 반영"이라는 명분을 가짐

과잉 대응이 과잉 설계를 부릅니다. 각 단계가 개별적으로는 타당해 보이는 게 함정입니다.

막는 방법은 세 가지입니다.

  1. 검증 범위를 좁힌다 — “정확성과 요구사항만. 스타일과 개선 제안 제외”
  2. 불가능한 시나리오를 명시적으로 배제한다 — “호출부가 보장하는 조건에 대한 방어 코드는 요구하지 않는다”
  3. 판정을 사람이 한다 — 리뷰 결과를 자동 반영하지 않고, 사람이 반영할 것을 고른다

3번이 가장 확실합니다. 검증 에이전트의 출력은 판결이 아니라 의견으로 취급하는 게 맞습니다.

다관점 리뷰

하나의 리뷰어는 한 종류의 문제에 집중하는 경향이 있습니다. 관점을 나눠 배정하면 겹침 없이 넓게 봅니다. 에이전트 팀 입문의 병렬 코드리뷰 시나리오가 이 구조입니다.

PR #142를 리뷰할 팀원 3명을 만들어 주세요.
- 1명: 보안 관점 (인증/세션, 입력값 검증)
- 1명: 성능 관점 (쿼리, 메모리, N+1)
- 1명: 테스트 커버리지 관점
각자 리뷰한 뒤 발견 사항을 보고하게 하고, 마지막에 종합해 주세요.

여기에 한 겹 더 얹을 수 있습니다. 발견 사항을 다른 에이전트가 반박하게 하는 것입니다.

각 발견 사항에 대해, 그것이 실제 문제가 아닌 이유를 찾아보세요.
반박에 성공한 항목은 목록에서 제거하세요.

앞의 “리뷰어는 뭔가를 지적한다” 문제에 대한 대응입니다. 근거가 약한 지적은 반박 단계에서 걸러집니다.

4-3. 관측 수단 — 무엇이 보이고 무엇이 안 보이는가

피드백 루프는 사람이 상황을 볼 수 있어야 성립합니다. 주요 수단은 이렇습니다.

수단 보여주는 것
에이전트 패널 (프롬프트 입력창 아래) 팀원·서브에이전트 목록과 상태 (작업중/유휴/실패)
Ctrl+T 태스크 리스트 토글 (대기/진행중/완료, 의존 관계)
/context 컨텍스트 사용량, 로드된 메모리 파일
/tasks 이 세션의 백그라운드 작업 (완료된 서브에이전트 포함)
Enter (패널에서 선택 후) 그 에이전트의 전체 대화 내용

여기서 중요한 건 안 보이는 것입니다.

  • 에이전트의 중간 판단 — 최종 보고는 보이지만 왜 그 방향을 골랐는지는 대화를 직접 열어야 보입니다
  • 팀원끼리 주고받은 메시지 — 리드에게 보고된 것만 보이고, 팀원 간 논의는 각자 대화창에 있습니다
  • 누적 토큰 사용량의 실시간 분포 — 어느 에이전트가 얼마나 쓰고 있는지 한눈에 보기 어렵습니다

그리고 이 문제는 팀 규모가 커질수록 나빠집니다.

팀원 3명 → 대화창 3개. 마음먹으면 다 볼 수 있음
팀원 6명 → 대화창 6개. 실제로는 보지 않음
          → 유휴 팀원 행은 30초 후 숨겨지고,
            4명 이상 유휴면 "N idle agents" 한 줄로 접힘
          → 화면에 보이는 것 자체가 줄어듦

관측의 한계가 결론을 하나 만듭니다. 팀을 키울 때는 관측이 아니라 강제에 의존해야 합니다. 3명까지는 사람이 보면서 교정할 수 있지만, 그 이상은 4-1의 3단계(훅)로 올려야 합니다. 화면을 더 열심히 보는 방식으로는 확장되지 않습니다.

4-4. 실패를 하네스에 반영하는 루프

이 시리즈의 결론입니다.

에이전트가 실수를 합니다. 대개 우리는 그 자리에서 지적하고 넘어갑니다. 그러면 같은 실수가 다음 세션에서 다시 나옵니다. 에이전트는 지적받은 것을 기억하지 못하기 때문입니다.

핵심 원칙은 이렇습니다.

같은 실수가 두 번 나오면 프롬프트를 고치지 말고 하네스를 고칩니다.

에스컬레이션 규칙으로 정리하면 이렇습니다.

1회 실수 → 그 자리에서 교정
            (대화로 지적. 문서화하지 않음)

2회 실수 → CLAUDE.md 또는 .claude/rules/ 로 승격
            (advisory. 매 세션 읽히게 만듦)

3회 실수 → 훅으로 승격
            (deterministic. 물리적으로 불가능하게 만듦)

각 단계의 판단 근거가 다릅니다.

단계 의미하는 것
1회 우연일 수 있다. 아직 패턴이 아니다
2회 패턴이다. 모델이 스스로 알 수 없는 정보다 → 문서로 준다
3회 문서로 줘도 안 지켜진다 → 지시로는 해결되지 않는 문제다

3회에서 흔히 하는 잘못된 대응은 CLAUDE.md의 문장을 더 강하게 고치는 것입니다.

<!-- 1차 -->
- 마이그레이션 파일은 직접 수정하지 마세요

<!-- 2차: 더 강하게 -->
- **중요**: 마이그레이션 파일은 절대 직접 수정하지 마세요

<!-- 3차: 더더 강하게 -->
- ⚠️ **절대 금지** ⚠️: 마이그레이션 파일을 어떤 경우에도 수정하지 마세요.
  이는 데이터 손실을 유발합니다. 반드시 사람에게 요청하세요.

세 번째 버전이 첫 번째보다 나은 부분은 거의 없습니다. advisory는 강조해도 advisory입니다. 대신 이렇게 해야 합니다.

{
  "permissions": {
    "deny": ["Edit(migrations/**)", "Write(migrations/**)"]
  }
}

한 줄이고, 완전합니다.

실수를 자산으로 만드는 절차

실무에서 이걸 돌리는 방법입니다. 세션이 끝났을 때 이렇게 물어보면 됩니다.

이 세션에서 내가 교정한 것들을 정리해 주세요.
각각에 대해 CLAUDE.md 규칙으로 만들 것인지, 훅으로 만들 것인지 판단해 주세요.

그리고 판단 기준은 1편의 그것과 같습니다.

“이 규칙이 어겨졌을 때, 나중에 발견해도 괜찮은가?” 괜찮다면 CLAUDE.md. 안 괜찮다면 훅.

하네스도 가지치기해야 한다

여기서 3-2의 문제가 되돌아옵니다. 실수마다 규칙을 추가하면 CLAUDE.md가 계속 자랍니다. 그래서 반대 방향의 루프도 필요합니다.

추가 루프: 실수가 반복되면 → 규칙을 추가하거나 훅으로 올린다
제거 루프: 규칙이 쌓이면   → 지워도 실수가 없는 것을 지운다

제거 루프를 돌리는 방법은 단순합니다. 훅으로 올린 규칙은 CLAUDE.md에서 지웁니다. 훅이 물리적으로 막고 있으므로 문장은 중복입니다. 컨텍스트를 20줄 돌려받습니다.

이 두 루프가 같이 돌면 하네스는 이런 상태로 수렴합니다.

  • CLAUDE.md: 모델이 스스로 알 수 없고, 어겨도 나중에 고칠 수 있는 것들
  • 훅·권한 설정: 어겨지면 안 되는 것들
  • 스킬: 때때로 하는 절차
  • 문서: 재사용 가치가 있는 조사 결과

각자 제 자리에 있고, 어느 것도 다른 것의 역할을 대신하려 하지 않는 상태입니다.


핵심요약

  • 데이터 거버넌스의 3층 구조: CLAUDE.md(항상 로드) / 스킬(온디맨드) / 스폰 프롬프트(1회성). 비용을 언제 내는가가 결정적 차이다 — 스킬은 세션 시작 시 설명문만 올라가고 본문은 호출 시 로드된다.
  • CLAUDE.md는 “항상 참인 사실”, 스킬은 “때때로 하는 절차”. 절차를 CLAUDE.md에 넣으면 무관한 세션이 그 비용을 계속 지불한다. 영역 한정 규칙은 .claude/rules/ + paths로 조건부 로드한다.
  • 컨텍스트 예산은 파일당 200줄이 목표다. @path 임포트로 쪼개도 컨텍스트는 줄지 않는다(전부 로드됨). 가지치기 기준은 “이 줄을 지우면 에이전트가 실수할까”. 코드에서 알 수 있는 정보는 지운다 — 틀린 문서는 없는 문서보다 나쁘다.
  • MCP는 이제 도구 목록이 아니라 출력량이 문제다. 툴 서치가 기본이라 서버를 늘려도 컨텍스트 부담이 적지만, 출력은 기본 25,000토큰까지 들어온다. 출력을 다듬어야 하면 CLI(gh, jq)가 MCP보다 유리하다. mcpServers·skills 필드는 팀원에는 미적용이다.
  • 민감정보는 지시가 아니라 permissions.deny와 훅으로 막는다. 차단할 때 대안(.env.example)을 함께 제시하면 에이전트가 우회로를 찾지 않는다.
  • 지식 축적은 서브에이전트 memory(project 권장)와 조사 산출물 문서화 두 방향이다. 발견 → 문서 → CLAUDE.md → 스킬의 승격 경로를 두고, 조사 지시에 “먼저 읽고 새로 알게 된 것만 추가”를 넣는다.
  • 검증 강제력 4단계: 프롬프트 지시(advisory) → /goalStop·TaskCompleted 훅(결정적) → 검증 서브에이전트(독립 판단). 하네스 설정은 한 번 내는 고정비, 사람의 주의력은 매번 내는 변동비 — 에이전트 3개 이상이면 훅이 필수다.
  • 채점자를 작업자와 분리한다. 다만 리뷰어는 문제가 없어도 뭔가를 지적하므로, 과잉 대응이 과잉 설계를 부른다. 검증 범위를 “정확성과 요구사항”으로 좁히고, 리뷰 결과는 판결이 아니라 의견으로 취급한다.
  • 관측은 확장되지 않는다. 팀원이 늘면 화면에 보이는 것 자체가 줄어든다(유휴 행 숨김, 4명 이상 접힘). 팀을 키울 때는 관측이 아니라 강제에 의존해야 한다.
  • 에스컬레이션 규칙: 1회 그 자리에서 교정 / 2회 CLAUDE.md로 승격 / 3회 훅으로 승격. CLAUDE.md의 문장을 더 강하게 고치는 것은 해법이 아니다 — advisory는 강조해도 advisory다. 훅으로 올린 규칙은 CLAUDE.md에서 지워 컨텍스트를 되찾는다.

3편 예고

1·2편에서 하네스 3대 구성요소를 다뤘습니다. 3편은 바로 쓸 수 있는 형태로 옮깁니다.

  • 역할별 레시피 5종: 조사자 · 구현자 · 검증자 · 마이그레이터 · 문서작성자의 완성된 .claude/agents/*.md 정의 파일과 필드별 선택 근거
  • 서브에이전트 정의를 팀원으로 재사용하기: 어떤 필드가 적용되고 어떤 필드가 무시되는가
  • 안티패턴 5가지: 과잉 하네스, 비대한 CLAUDE.md, 검증 없는 자율성, 지시로 강제하려는 시도, 하네스 복사
  • 하네스 성숙도 자기진단: 0~3단계 체크리스트