본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 26. 21:27

Claude Code의 harness를 4개월 동안 키워온 기록

요약

Claude Code를 사용하며 발생하는 반복적인 오류를 해결하기 위해 4개월간 'harness engineering'을 실천한 기록입니다. CLAUDE.md의 규칙을 구체화하고, 실패 로그를 관리하며, 잊기 쉬운 규칙은 훅(hooks)을 통해 강제하는 체계적인 개발 환경 구축 과정을 다룹니다.

핵심 포인트

  • 실패 로그(failure-log)를 통해 오류의 원인과 대책을 체계적으로 기록
  • 반복되는 오류는 CLAUDE.md의 규칙으로 승격시켜 관리
  • 텍스트 기반 규칙의 한계를 극복하기 위해 훅(hooks)을 활용한 강제성 부여
  • 에이전트의 성능을 높이기 위한 지속적인 환경 개선 프로세스 구축

Claude Code에게 「추측으로 단언하지 말 것」이라고 적었다. CLAUDE.md의 세 번째 줄쯤에, 상당히 이른 단계에서 적었다. 적은 직후의 세션에서는 지켜졌으나, 다음 날 다른 세션에서 AWS 서비스에 대해 물었더니, CodeCommit이 2024년 7월에 신규 이용이 종료되었다고 단언했다 (실제로는 2025년 11월에 부활했다). 지적하자 「죄송합니다, 확인했어야 했습니다」라는 답변이 돌아왔는데, 죄송할 게 아니라 CLAUDE.md에 써놨잖아, 라고 생각하면서도, 뭐 에이전트에게 화를 내봐야 소용없으니 규칙의 문구를 좀 더 구체적으로 다시 썼다. 「툴의 기능·제약」, 「사양명」, 「절차」는 먼저 공식 문서에서 확인할 것, 이라고.

며칠 후, 또 다른 세션에서 이번에는 Terraform의 untaint 명령어를 태연하게 제안해 왔는데, 이것은 v0.15.2에서 deprecated(비권장)된 것이었다. CLAUDE.md에 적혀 있는 것을 잊어버린다.

실패할 때마다 규칙을 다시 썼다. 규칙을 다시 써도 잊어버리면 더 구체적으로 다시 썼다. 그래도 잊어버리는 규칙은 hooks로 강제하도록 했다. 그렇게 하다 보니 4개월이 지났고, CLAUDE.md는 280줄이 되었으며, 지식용 Git 리포지토리(VS Code와 Zed로 열고 있는 평범한 리포지토리다, Obsidian은 사용하지 않는다)에 파일은 100개 이상, 커스텀 스킬은 40개를 넘어서고 있었다.

처음부터 이름이 있었던 것은 아니다. 세컨드 브레인 (Second Brain)을 만들려고 했던 것도 아니고, harness engineering이라는 말도 몰랐다. 그저 세션마다 「무엇이 잘 되었고 무엇이 잘 되지 않았는지」를 기록하고, 다음에 같은 실수를 하지 않도록 환경을 고치는 일을 했을 뿐이다.

failure-log — 실패를 기록하여 환경을 고치기

처음에 한 일은 failure-log.md라는 파일을 만드는 것이었다. 실패가 발생할 때마다 날짜와 증상, 원인과 대책을 기록하고, 대책의 배치 장소(CLAUDE.md에 쓸 것인지, 스킬로 만들 것인지, hook으로 만들 것인지)와 채택 여부를 채워 넣는다.

| 날짜 | 증상 | 원인 | 대책 | 배치 장소 | 채택 여부 | 재발 |
|------|------|------|------|--------|------|------|
| 06-02 | shell heredoc에서 특수 문자가 깨짐 | shell을 통해 생성 | Write 툴로 작성 | CLAUDE.md | 채택 | 3+ |
...

마지막 컬럼인 「재발」은 나중에 추가했다. 같은 패턴이 3번 이상 재발하면 CLAUDE.md의 규칙으로 승격시킨다는 규칙을 세웠다.

4개월 동안 failure-log는 30건을 넘었고, 그중 15건이 CLAUDE.md의 규칙이 되었다. shell의 heredoc에서 특수 문자 이스케이프(escape)가 3번 깨졌기에 Write 툴로 작성하라는 규칙이 생겼고, bot 리뷰의 「범위 외(out of scope)」를 그대로 믿었다가 2번 지적받았기에 스스로 비용 비교를 하라는 규칙이 생겼다.

반성문을 쓰고 있는 것이 아니라, CLAUDE.md를 물리적으로 바꿔 쓰고 있는 것이다.

hooks — 써두어도 잊어버린다면 강제하기

failure-log를 운용하며 가장 효과가 있었던 것은 규칙을 CLAUDE.md에서 hook으로 옮기는 판단이었다. CLAUDE.md에 적은 규칙은 잊어버린다. hooks는 잊어버리지 않는다.

질문 전 게이트 (Question Gate)

Claude Code는 사용자에게 질문을 던질 때, 조사도 하지 않고 선택지만 나열하며 「A와 B 중 어느 것이 좋습니까?」라고 권장이나 근거 없이 물어올 때가 있다. CLAUDE.md에 「질문하기 전에 조사하고, 권장을 덧붙일 것」이라고 적었지만, 처음에는 지켜지다가 일주일 만에 잊어버렸다. 그래서 ask-question-gate.sh라는 PreToolUse hook을 만들었다.

단순한 문자열 매칭이지만, 이것으로 질문의 질이 극적으로 변했다. hook은 bypass(우회)할 수 없다.

메모리 제약 가드 (Memory Constraint Guard)

16GB RAM의 MacBook으로 개발하고 있는데 (개인 개발이라 이것이 한계다), pnpm install이나 tsc, jest...

같은 무거운 명령어를 병렬로 실행하면 swap이 포화된다. 실제로 한 번은 3개의 worktree에서 3개의 에이전트(Agent)를 병렬로 구동했더니 68GB의 swap을 점유하여 PC가 강제 재부팅된 적이 있다. 그 이후로는 Agent의 PreToolUse hook에서 vm_stat를 호출하여, 여유 메모리가 2GB 미만이면 에이전트의 기동을 차단하도록 설정했다.

하드웨어의 물리적 제약을 harness에 내장한 형태다.

hook의 구분 사용

hook종류수행 작업
ask-question-gate.shPreToolUse권장되지 않는 질문을 차단
메모리 제약 가드PreToolUse메모리 부족 시 에이전트 기동 차단
PostToolUse lintPostToolUse작성 후 lint를 실행하여 exit 2로 수정을 강제

Why 행 — 규칙이 모순될 때의 판단 기준

CLAUDE.md에 규칙이 늘어나면 — 280줄이나 되니 당연한 일이지만 — 규칙끼리 모순되는 경우가 발생한다. "설계는 승인을 받은 후 구현한다"라고 적혀 있는데, 동시에 "버그 수정은 즉시 한다"라고도 적혀 있어서, 설계 변경을 동반하는 버그 수정은 어떻게 처리해야 할지 규칙만으로는 판단할 수 없다.

그래서 모든 규칙에 **Why:**를 붙이기로 했다.

### Brainstorming Gate
비자명한(non-trivial) 구현 태스크에서는 코드를 작성하기 전에 설계를 제시하여 사용자의 승인을 얻는다.
승인 불필요: 버그 수정(원인이 명확한 경우), typo, 설정값 변경, 1개 파일 10줄 이하.
...

Why가 있으면 에지 케이스(edge case)에서 에이전트가 스스로 판단할 수 있다. 10줄 정도의 변경이지만 설계 변경을 포함하는 경우 — Why를 읽으면 "섣부른 구현 방지"가 목적임을 알 수 있으므로, 승인을 받아야 한다고 판단할 수 있다. 솔직히 완전한 해결책은 아니라고 생각하지만, 280줄의 CLAUDE.md에서 규칙 간의 모순이 발생했을 때 Why가 없었다면 훨씬 더 끔찍한 상황이 벌어졌을 것이다.

Codex로의 위임 — 코드는 다른 모델에게 맡긴다

Claude Code는 코드를 작성할 수 있지만, 코드 작성에 Claude의 사용량(quota)을 쓰는 것은 아깝다. Claude Code에는 Codex CLI를 플러그인으로 통합할 수 있는 메커니즘이 있어서, 코드 읽기/쓰기가 중심인 태스크 — 코드 리뷰, 버그 수정, 리팩토링(refactor), 테스트 추가 — 는 Codex에 위임하고, Claude는 설계 상담이나 문서 작성, 플래닝(planning)에 전념하도록 역할을 나누어 사용하고 있다.

CLAUDE.md에 태스크 유형별 위임 대상을 적어두었다.

태스크 유형ClaudeCodex
계획·설계·리팩토링 설계
...

나아가 delegation-mode라는 파일로 위임의 강도를 전환하고 있다. max (Max 플랜용)에서는 표준적인 규칙으로 위임하고, pro (Pro 플랜용)에서는 코드 관련 작업뿐만 아니라 장문 분석이나 반복적인 grep/read까지 Codex에 던져서, Claude는 통합 판단과 웹 검색(Web search), 최종 정제 작업만 담당하게 한다. 플랜을 바꿀 때 파일을 하나 수정하는 것만으로 위임 전략이 전환된다.

Dynamic Workflows — $200를 날리며 배운 비용 제어

Claude Code에는 Dynamic Workflows (Ultracode)라는 기능이 있어, 여러 에이전트를 병렬로 fan-out 하여 실행할 수 있다. 이것 자체는 강력하지만, 한 번 사고를 쳤다.

2026년 6월 1일, Ultracode로 보안 조사를 실행했을 때, 5시간 이용 한도와 조직의 Extra Usage ($200)를 하룻밤 사이에 다 써버렸다. 원인은 병렬로 실행되는 모든 에이전트가 부모 세션의 모델(Opus)과 effort (xhigh)를 상속받았기 때문이다. 탐색이나 요약만 수행하는 에이전트에게도 Opus의 xhigh 설정이 할당되었고, 그것이 10개 이상 병렬로 돌아갔다.

병렬 실행을 막으면 Workflows의 가치가 사라지므로, 대신 stage별로 모델을 배분하는 규칙을 만들었다.

// 탐색·추출 → haiku (저렴)
await agent('소스 검색', { model: 'haiku', phase: 'Search' })
// 리뷰·검증 → sonnet (중간)
...

나아가, Review 페이즈(phase)에서는 Codex 플러그인의 에이전트를 병행 실행할 수도 있다. Claude (sonnet)와 Codex가 동일한 코드를 별도로 리뷰하여 다각도로 체크하는 구성이다. Codex 에이전트는 Claude의 이용 한도를 소비하지 않으므로, 비용 측면에서도 합리적이다.

await parallel([
() => agent('Claude로 리뷰', { model: 'sonnet', phase: 'Review' }),
() => agent('Codex로 리뷰', { agentType: 'codex:codex-rescue', phase: 'Review' }),
...

Workflow를 기동하기 전에는 반드시 "총 에이전트 수·최대 병렬 수·각 stage별 모델·비용이 큰 step·정지 조건"을 제시하는 규칙도 CLAUDE.md에 넣었다. $200의 수업료는 비쌌지만, 이 규칙 덕분에 이후로는 같은 사고가 발생하지 않고 있다.

Review Loop — push 전에 전부 수정하기

코드 변경 사항을 push하기 전에, Codex (--effort high) 또는 Claude의 Opus 서브 에이전트(sub-agent)로 사전 리뷰를 실시하여, 지적 사항을 전부 수정한 뒤에 push하도록 하고 있다.

이를 시작한 이유는 단순하다. CI bot의 리뷰로 인해 여러 번 라운드 트립(round-trip)을 경험했기 때문이다. 어떤 PR에서는 6라운드, 다른 PR에서는 15라운드. push한 뒤 bot의 지적을 받고 수정해서 push하고, 또 지적을 받아 수정해서 push하는 반복이 은근히 번거로웠고, CI 실행 시간도 낭비였다. push 전에 로컬에서 리뷰를 돌리면, CI bot에 남는 지적 사항은 "정말로 놓칠 수밖에 없었던 점"뿐이게 된다.

리뷰 관점은 Böckeler의 3가지 카테고리에 따라 정리하고 있다.

카테고리관점수단
Maintainability코드 품질·내부 표준lint / typecheck / test
Architecture Fitness성능·의존 관계·구조 제약벤치마크 (benchmark) / dep-cruiser
Behaviour기능적 정확성기존 테스트와의 정합성·엣지 케이스 (edge case) 확인

Böckeler는 Behaviour (기능적 정확성)의 하네스(harness)가 가장 미성숙하다고 기술하며[1], "AI 생성 테스트에만 의존하는 것은 아직 불충분하다"고 지적했다. 실제로 그렇다고 생각한다. AI가 작성한 테스트는 AI가 작성한 코드를 통과하도록 만들어지기 때문에, 기존 테스트와의 정합성이나 엣지 케이스의 수동 확인은 생략할 수 없다.

플러그인과 스킬의 조합

Claude Code에는 플러그인이나 스킬을 통해 툴을 추가할 수 있는 메커니즘이 있으며, 앞서 언급한 Codex 위임도 그중 하나지만, 그 외에도 몇 가지를 조합하고 있다. Mermaid Chart로 다이어그램을 그리거나, Zenn CLI로 기사 프리뷰를 확인하거나, Typst로 문서를 조판하는 식이다. 40개가 넘는 커스텀 스킬에는 세션을 회고하는 /session-retro, 지식을 Obsidian Vault에 저장하는 /save-knowledge, Vault의 과거 노트로 자신의 판단에 반론을 제기하는 /vault-challenge 등이 있다.

스킬이 늘어나면 CLAUDE.md에 "이 스킬을 언제 사용할지"에 대한 대응표를 작성해 두는 것이 효과적이며, 사용자가 스킬 이름을 말하지 않아도 태스크의 종류에 따라 자동으로 스킬을 선택하게 된다.

사용자의 말·상황호출할 스킬
버그·에러를 조사 중이다/diagnose
...

이것도 결국 CLAUDE.md에 적혀 있는 규칙이므로 추론적(Inferential)이긴 하지만, 스킬 호출 자체의 성공률은 높다. 규칙의 복잡도가 낮기 때문에 ("이 키워드가 나오면 이 스킬을 사용하라"는 단순한 매칭) 잊히지 않는 것이라고 생각한다.

내가 하던 일에 이름이 붙었다

지금까지 써온 것들을 나는 딱히 어떤 방법론에 따라 수행해 온 것은 아니다. 실패를 기록하고 환경을 고친다, 잊히기 쉬운 규칙은 hooks로 강제한다, 규칙에는 이유를 적는다. 그뿐이다.

나중에 알게 된 사실이지만, Mitchell Hashimoto(HashiCorp의 창립자이자 최근에는 Ghostty라는 터미널 에뮬레이터를 만들고 있는 인물)가 같은 일을 하고 있었다[2]. Ghostty 개발 과정에서 AI 에이전트가 실패할 때마다 AGENTS.md에 규칙을 추가해 나간다. 모델을 탓하는 것이 아니라 환경을 고친다. 이것이 나중에 Ratchet Pattern이라고 불리게 된 사고방식이다. 라쳇 렌치(Ratchet wrench)——한쪽 방향으로만 돌아가는 도구. 품질은 올라가기만 할 뿐 내려가지 않는다. 나의 failure-log는 바로 이것이었다.

2026년 4월에 Birgitta Böckeler라는 Thoughtworks의 엔지니어가 Martin Fowler의 사이트에 상당히 잘 정리된 글[1:1]을 썼는데, 여기서 Agent = Model + Harness라는 공식이 등장한다. 모델 이외의 모든 것이 harness라는 것이다. 그리고 이 글을 읽으며 가장 납득이 갔던 부분이 Computational(계산적)과 Inferential(추론적)의 구분이었다.

구분예방 (Feedforward)검증 (Feedback)
Computational (결정론적)OpenRewrite recipeslint, 테스트
Inferential (추론적)CLAUDE.md의 규칙AI 리뷰

CLAUDE.md에 규칙을 적는 것은 Inferential Feedforward로, 모델이 읽고 이해하여 따르기를 기대하는 추론적인 예방책에 불과하다. 반면 hooks는 Computational이며, exit 2를 반환하면 도구의 실행이 차단되므로 모델의 기분과는 상관이 없다. "CLAUDE.md의 규칙이 잊히는 것은 버그가 아니라 설계상의 한계다"——이 문장을 읽었을 때, 내가 해오던 일의 이유를 알 수 있었다. 나는 직관적으로 CLAUDE.md의 한계를 느끼고 hooks로 옮겨가고 있었는데, 그것은 Inferential에서 Computational으로의 이행이었던 것이다.

Böckeler는 "harness가 커졌을 때, 가이드와 센서의 일관성을 어떻게 유지할 것인가"를 미결 과제로 꼽았고[1:2], Why 행은 그 문제에 대한 나만의 잠정적인 답변이었다.

다른 사람들은 어떻게 하고 있는가

내가 하고 있는 일에 이름이 붙었기에, 다른 사람들은 어떻게 하고 있는지 궁금해져서 조사해 보았다. Claude Code의 harness를 고도로 구축한 프로젝트는 영어권에 적어도 두 개가 있었다.

구분obsidian-second-brainECC나의 환경
방향성지식의 자동 진화스케일·보안제약 주도
...

obsidian-second-brain에서 흥미로운 점은 Vault가 스스로 내용을 다시 쓴다는 점이다. 정보를 받아들이면 기존 페이지에 내용을 추가하는 것이 아니라 재구성한다. bi-temporal facts라는 메커니즘도 있어서 "언제 사실이었는가"와 "언제 Vault가 그것을 학습했는가"를 분리하여 추적하며, /obsidian-challenge라는 명령은 Vault의 과거 노트를 사용하여 현재 자신의 판단에 반론을 제기해 준다. 이것은 정말 좋다고 생각하여, 비슷한 스킬을 내 환경에도 만들었다.

ECC[3]는 271개의 스킬과 67개의 서브 에이전트로 구성되어 있으며, AgentShield라는 세 개의 Opus 에이전트(공격자·방어자·감사자)를 통해 red-team 보안 스캔을 수행하는 메커니즘까지 갖추고 있다. failure-log의 "재발 3회 시 승격"이라는 아이디어는 ECC의 instinct confidence scoring——패턴의 신뢰도를 수치로 추적하여 임계값을 넘으면 자동으로 스킬로 승격시키는 메커니즘——에서 빌려온 것이다.

나의 환경은 이 두 곳에 비하면 훨씬 작고 Obsidian에도 의존하지 않는다. 다만 방향성이 다를 뿐이며, 어느 것이 정답이라고 할 수는 없다고 생각한다.

하네스인가 루프인가

Boris Cherny (Anthropic의 Claude Code 책임자)가 인터뷰[4]에서 "나는 더 이상 Claude에게 프롬프트를 작성하지 않는다. Claude에게 프롬프트를 작성하고 무엇을 할지 결정하는 루프(loops)를 실행하고 있다. 나의 일은 루프를 작성하는 것이다."라고 말했으며, Peter Steinberger (PSPDFKit의 창업자이자 현재 OpenAI 소속) 또한 비슷한 내용[5]을 언급했다. 프롬프트를 작성하는 것을 멈추고, 프롬프트를 작성하는 시스템을 설계하라는 것이다.

개념의 진화를 정리하면 다음과 같다.

시기개념포커스
2022~24프롬프트 엔지니어링 (Prompt Engineering)모델에 대한 지시문
...

나의 환경은 하네스(harness)다. failure-log의 사이클을 cron으로 자동화하면 루프(loop)가 되겠지만, 현재는 수동으로 돌리고 있으며, 세션이 끝날 때마다 되돌아보며 실패가 있으면 기록하고, 재발이 쌓이면 harness를 수정한다.

LangChain은 harness의 개선만으로 (모델은 고정한 채) 벤치마크를 13.7포인트 개선했다고 한다[6]. 모델은 알아서 진화한다. harness는 스스로 키워나갈 수밖에 없다.

Harness Engineering for Coding Agent Users, Birgitta Böckeler, 2026-04 ↩︎ ↩︎ ↩︎

My AI Adoption Journey, Mitchell Hashimoto, 2026-02 ↩︎

Everything Claude Code, affaan-m ↩︎

Stop Prompting AI and Start Building Loops: How the Head of Claude Code Stopped Prompting AI, Boris Cherny, 2026-06 ↩︎

Peter Steinberger on X, 2026-06 ↩︎

Improving Deep Agents with Harness Engineering, LangChain Blog ↩︎

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0