Claude Code 하네스 엔지니어링 한국어판 읽은 후기

최근 재밌는 사이트를 하나 발견했다. Claude Code Harness 한국어판(claude-code-harness-ko.vercel.app) 이라는 곳인데, 유민수 개발자가 한국어로 옮기고 웹 형태로 재구성한 학습용 아카이브다. 원문은 중국 개발자 ZhangHandong이 유출된 Claude Code TypeScript 소스 코드를 리버스 엔지니어링해서 "驾驭工程 (하네스 엔지니어링)"이라는 책으로 정리했다.
총 7개 파트, 30개 장, 45개 문서로 구성되어 있다. Claude Code v2.1.88의 복원 소스 코드를 기반으로 에이전트 루프, 프롬프트 엔지니어링, 컨텍스트 관리, 프롬프트 캐싱, 보안/권한, 고급 하위시스템까지 전부 해부해둔다. 분량이 방대해서 전부 읽긴 어렵지만, 개인적으로 가장 흥미로웠던 25장 — 하네스 엔지니어링 6대 원칙을 중심으로 정리해본다.
"하네스 엔지니어링"이란?
Harness는 원래 말 등에 씌우는 "마구(馬具)"를 뜻하는 단어다. 책의 중국어 제목이 "마구(马书)"처럼 발음되기 때문에 비공식적으로 "The Horse Book"으로도 불린다. 의미는 직관적이다. LLM이라는 강력한 말을 길들여서 원하는 방향으로 달리게 하는 설계 기법이다.
저자의 핵심 주장은 단순하다. "AI 에이전트 시스템에서 동작을 제어하는 가장 좋은 방법은 더 많은 코드를 작성하는 것이 아니라, 더 나은 제약 조건을 설계하는 것이다."
이 관점에서 Claude Code를 해부해보면 소스 코드 곳곳에 재사용 가능한 엔지니어링 원칙들이 숨어 있다. 아래는 저자가 23개 장의 분석에서 뽑아낸 6가지 핵심 원칙이다.
원칙 1 — 프롬프트를 컨트롤 플레인으로 쓴다
코드의 if/else 분기로 동작을 통제하지 말고 시스템 프롬프트로 모델의 행동을 유도하라는 것이다. Claude Code의 동작 지침 대부분은 코드가 아닌 프롬프트 텍스트에 들어 있다.
// restored-src/src/constants/prompts.ts:203
"Don't create helpers, utilities, or abstractions for one-time operations.
Don't design for hypothetical future requirements.
Three similar lines of code is better than a premature abstraction."이 문장은 주석이 아니라 모델에게 전송되는 실제 지시문이다. 모델이 오버엔지니어링을 하는지 코드 레벨에서 탐지하는 건 사실상 불가능하다. 대신 자연어로 "그러지 마"라고 직접 말하는 쪽이 훨씬 효율적이다.
흥미롭게도 Claude Code는 모든 동작 스위치를 기본 시스템 프롬프트에 인라인하지 않는다.system-reminder 메시지는 일종의 대역 외(out-of-band) 제어 채널로 작동한다. Plan Mode의 멀티스테이지 워크플로, Todo 리마인더, Read 도구의 빈 파일 경고 등은 메인 시스템 프롬프트를 수정하지 않고도 조건부로 주입된다.
- 안정된 헌법은 시스템 프롬프트에 (캐시 친화적)
- 런타임 스위치는
system-reminder에 (단명하고 교체 가능)
안티패턴은 하드코딩된 동작 탐지기다. 원하지 않는 행동마다 인터셉터를 붙이다 보면 결국 거대한 규칙 엔진이 탄생한다. 모델의 발전 속도는 절대 따라잡지 못한다.
원칙 2 — 캐시 인식 설계는 협상 불가능하다
일반적인 Claude Code 세션에서 시스템 프롬프트는 약 11,000 토큰, 40개 이상의 도구 스키마는 추가로 ~20,000 토큰을 차지한다.모든 프롬프트 변경은 cache_creation 토큰으로 측정되는 비용을 발생시킨다.
Claude Code는 SYSTEM_PROMPT_DYNAMIC_BOUNDARY라는 마커로 시스템 프롬프트를 정적/동적 영역으로 분리한다. 이 경계 앞은 scope: 'global'로 크로스-조직 캐싱이 가능하다. 뒤는 세션별 동적 콘텐츠다.
캐시 중단을 감지하기 위해 팀은 거의 20개 필드를 추적한다 (systemHash, toolsHash, perToolHashes, betas 등). 주석에는 BigQuery에서 추출한 정량 데이터까지 박혀 있다.
/** Per-tool schema hash. Diffed to name which tool's description changed
* when toolSchemasChanged but added=removed=0 (77% of tool breaks per
* BQ 2026-03-22). */더 극단적인 건 베타 헤더 래칭이다. 한 번이라도 전송된 베타 헤더는 기능이 꺼져도 세션 내내 계속 전송된다. 이유는 단 하나 — 전송을 중단하면 요청 서명이 바뀌어서 50~70K 토큰의 캐시 접두사가 무효화되기 때문이다.
과거에 에이전트 목록을 시스템 프롬프트에 인라인했더니 전체 cache_creation 토큰의 10.2%를 혼자 먹어치웠던 사례도 있었다. 해결책은 캐시 세그먼트 바깥의 system-reminder 메시지로 옮기는 것이었다.
원칙 3 — Fail Closed, 명시적으로만 열어라
시스템의 기본값은 항상 가장 안전한 선택이어야 하고 위험한 동작은 명시적으로 선언할 때만 허용해야 한다.
buildTool() 팩토리의 기본값을 보면 철학이 명확히 드러난다.
// restored-src/src/Tool.ts:748-761
const TOOL_DEFAULTS = {
isConcurrencySafe: () => false, // 기본적으로 동시성 불안전
isReadOnly: () => false, // 기본적으로 쓰기 가능
isDestructive: () => false, // 그러나 파괴적이지는 않음
checkPermissions: () => ({ behavior: 'allow' }),
toAutoClassifierInput: () => '', // YOLO 분류기 건너뜀
}새 도구는 기본적으로 동시 실행에 안전하지 않다고 간주된다.isConcurrencySafe: true를 명시하지 않은 도구는 직렬 대기열에 들어간다. 심지어 isConcurrencySafe()가 예외를 던지면 catch 블록도 보수적으로 false를 반환한다.
권한 모드도 동일하다: default → acceptEdits → plan → bypassPermissions 순서로 점점 허용 범위가 넓어지지만, 시스템 기본값은 가장 제한적인 default다. 사용자가 적극적으로 완화해야 한다.
YOLO 분류기의 거부 추적에도 같은 원칙이 적용된다. 3회 연속 또는 총 20회의 분류기 거부 이후 시스템은 자동으로 수동 사용자 확인으로 폴백한다.자동화된 의사결정이 신뢰할 수 없을 때 인간의 의사결정으로 돌아가라.
원칙 4 — 모든 것을 A/B 테스트하라
Claude Code 소스에는 89개의 기능 플래그가 있고 상당수가 A/B 테스트용이다. 가장 직관적인 스테이징 장치는 USER_TYPE === 'ant' 게이트다 — Anthropic 내부 직원에게만 먼저 노출되는 방식이다.
// restored-src/src/constants/prompts.ts:205-213
...(process.env.USER_TYPE === 'ant' ? [
"Default to writing no comments. Only add one when the WHY is non-obvious...",
// @[MODEL LAUNCH]: capy v8 thoroughness counterweight (PR #24302)
// un-gate once validated on external via A/B
"Before reporting a task complete, verify it actually works...",
] : []),"un-gate once validated on external via A/B" 주석이 워크플로를 그대로 보여준다.내부 검증 → A/B 테스트 → 전체 배포의 단계적 출시다.
더 세밀한 통제는 GrowthBook 통합이 맡는다. tengu_* 접두사의 기능 플래그들은 원격 설정 서버를 통해 퍼센티지 기반 점진적 롤아웃을 지원한다. 특히 캐시를 깨지 않도록 _CACHED_MAY_BE_STALE, _CACHED_WITH_REFRESH같은 "캐시 인식 A/B 테스트" 전략까지 들어 있다.
안티패턴은 빅뱅 릴리스다. AI 에이전트 영역에서 동작 변경의 영향은 보통 "크래시"가 아니라 "미묘하게 별로" 또는 "너무 공격적"이다. 정량 지표와 대조군 없이는 감지 자체가 어렵다.
원칙 5 — 고치기 전에 관측하라
문제를 해결하기 전에 먼저 전체 그림을 볼 수 있도록 관측 인프라를 구축하라는 원칙이다.
대표 사례는 promptCacheBreakDetection.ts다. 이 시스템은 아무 문제도 해결하지 않는다.오직 관측하고 보고할 뿐이다.
- 호출 전:
recordPromptState()가 거의 20개 필드의 스냅샷 캡처 - 호출 후:
checkResponseForCacheBreak()가 전/후 상태 비교 - 설명 생성: "시스템 프롬프트 변경됨", "TTL 만료 가능성 있음" 같은 사람이 읽을 수 있는 이유로 변환
- Diff 생성:
createPatch()로 프롬프트 상태의 전/후 차이 출력
재밌게도 이건 데이터 기반 설계다. 프로덕션 데이터로 추적할 필드를 골랐다. "대부분의 도구 스키마 변경은 특정 도구의 description 변경에서 비롯된다"는 사실을 발견한 뒤 타겟팅된 도구별 해시를 추가했다.
안티패턴은 직관으로 고치기다. 캐시 적중률이 떨어졌다고 마지막 변경을 그냥 롤백하는 건 위험하다. 진짜 원인은 베타 헤더 스위치, TTL 만료, MCP 도구 목록 변경일 수도 있다.
원칙 6 — 안정성을 위해 래치하라
한번 어떤 상태에 들어가면 흔들리지 마라. 상태 스래싱(thrashing)은 차선의 상태보다 더 해롭다.
가장 극적인 예는 자동 압축 회로 차단기다.
// restored-src/src/services/compact/autoCompact.ts:67-70
// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures
// (up to 3,272) in a single session, wasting ~250K API calls/day globally.
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 33번 연속 실패하면 시스템은 "압축 중단" 상태로 래치된다. 주석에 박힌 BigQuery 데이터(1,279개 세션, 하루 250K API 호출 낭비)가 엔지니어링 근거로 그대로 기록되어 있다.
베타 헤더 래칭, 캐시 TTL 자격 래칭(should1hCacheTTL()) 등도 같은 패턴이다. 세션 내내 한 번 결정된 값은 바뀌지 않는다. 바뀌면 캐시가 깨지고, 캐시가 깨지면 돈이 샌다.
안티패턴은 상태 스래싱이다. 매 요청마다 설정을 다시 계산하면 상태가 진동하고 캐싱 시스템에서는 적중률이 0에 수렴한다.
6대 원칙 한눈에 보기
| 원칙 | 핵심 | 안티패턴 |
|---|---|---|
| 프롬프트 = 컨트롤 플레인 | 자연어로 행동 유도 | 하드코딩된 탐지기 |
| 캐시 인식 디자인 | 정적/동적 경계 분리 | 빈번한 프롬프트 변경 |
| Fail Closed | 기본값은 가장 안전하게 | 기본 Open, 사후 수습 |
| 모든 것 A/B 테스트 | 점진적 롤아웃 | 빅뱅 릴리스 |
| 고치기 전 관측 | 데이터 기반 진단 | 직관에 의한 수정 |
| 안정성 위한 래치 | 결정 후 흔들지 않기 | 매 요청마다 재계산 |
읽고 나서
AI 에이전트를 만든다는 건 결국 거대한 확률 공간을 길들이는 일이다. 모델을 바꾸거나 프롬프트를 조금 고쳐서 해결될 문제가 아니다. 캐시 경계선, 권한 기본값, 관측 인프라 같은 "주변부"가 사실상 에이전트의 품질을 결정한다는 게 이 책의 일관된 주장이다.
모든 설계 결정에 BigQuery 데이터가 붙어 있다는 점이 특히 인상적이었다. "왜 3회로 정했나요?" → "1,279개 세션에서 50회 이상 연속 실패가 관측됐고, 하루 250K API 호출이 낭비됐거든요." 이런 정량적 근거가 소스 코드 주석에 박혀 있는 제품은 흔하지 않다.
나처럼 Claude Code를 일상적으로 쓰는 입장에서는, 왜 어떤 동작은 은근히 막히고 왜 어떤 기능은 특정 조건에서만 켜지는지에 대한 "설계 의도"를 이해할 수 있었다. 직접 에이전트를 만들 계획이 없더라도 "잘 만들어진 LLM 제품"이 내부에서 어떻게 생겼는지 들여다볼 가치는 충분하다.
30장 전체는 분량이 꽤 많으니 서문에 정리된 독자별 추천 경로를 따라가는 걸 추천한다.
- Path A (에이전트 빌더): 1장 → 3장 → 5장 → 9장 → 20장 → 25~27장 → 30장
- Path B (보안 엔지니어): 16장 → 17장 → 18장 → 19장 → 4장 → 25장
- Path C (성능 최적화): 9장 → 11~12장 → 13~15장 → 21장