EncBird에 하네스를 한 겹씩 씌워온 과정 — 실전 하네스 엔지니어링

TL;DR

  • 하네스는 에이전트의 자율성을 높인다.
  • 기본 하네스는 작업 전에 최소한으로 갖춘다.
  • 작업에서 얻은 실패는 다음 하네스에 반영한다.

시작하며

이전 글들에서 하네스 엔지니어링이 무엇인지1, 왜 필요한지2를 다뤘다.

컨텍스트가 큰 방향을 잡아주고 하네스가 매 실행 주기마다 오류를 복구해서, 에이전트가 긴 호흡의 태스크를 완주하게 해준다는 이야기였다.

그런데 이 개념을 글로 설명하고 나면 늘 같은 질문을 받는다.

“그래서 실제로는 뭘 어떻게 하면 되는데요?”

하네스라는 단어는 거창한데, 막상 빈 디렉토리 앞에 앉으면 무엇부터 손대야 할지 막막하다.

이 글에서는 내가 혼자 만들고 운영하는 EncBird(GenAI 영어 학습 서비스)와 PixelBank(AI 이미지 편집 서비스)에 하네스를 쌓아온 순서를 정리한다.

서비스뿐 아니라 에이전트용 툴인 ALPS WriterPPT Generator도 같은 방식으로 하네스를 씌워 만들었다.

이 프로젝트들 모두 Nx 모노레포에 ADR-first 워크플로우를 얹은 같은 골격이라, 주로 EncBird를 예로 들되 필요하면 다른 프로젝트도 함께 언급하겠다.

내 프로젝트의 하네스도 처음부터 전체 구조를 설계한 결과는 아니다.

에이전트를 굴리다가 같은 실수나 불편이 반복되면, 그 원인을 규칙·도구·검증으로 옮기면서 지금 형태가 됐다.

0. 출발점: 모델을 제외한 실행 환경을 다룬다

순서를 이야기하기 전에 기준점만 짚어두자.

에이전트 애플리케이션의 핵심 구성요소는 모델 + 컨텍스트 + 도구다. 실제 실행에서는 이 셋을 연결하고 반복시키는 런타임 환경이 함께 동작한다.

flowchart LR
    subgraph A["에이전트 (애플리케이션)"]
        CTX["컨텍스트<br/>(상황 · 기억 · 정보)"] --> LLM["모델 (LLM)<br/>선택 가능 · 내부 변경 불가"]
        LLM -->|도구 호출| TOOL["도구<br/>(외부 시스템 실행)"]
        TOOL -->|결과| CTX
    end
    RUN["런타임 환경<br/>(실행 · 검증 · 재시도)"] --> A
    A --> OUT["검증된 결과물"]

모델은 선택하거나 교체할 수 있지만 내부 동작을 직접 바꿀 수는 없다. 우리가 주로 제어하는 것은 컨텍스트와 도구, 그리고 그 둘이 돌아가는 런타임 환경이다.

이 글에서는 하네스 엔지니어링을 다음 범위로 다룬다.

모델 내부는 직접 바꾸지 않고, 제어 가능한 컨텍스트·도구·런타임 환경으로 코드 생성 과정을 자율화하되, 그 결과가 원래의 비즈니스 요구사항을 잘 반영하게 만드는 일.

컨텍스트 엔지니어링은 큰 방향과 판단 기준을 제공하고, 짧은 실행 주기의 하네스는 검증과 재시도를 자동화한다. 이 글에서는 둘을 합친 넓은 실행 환경을 하네스로 다룬다.3

실제 구축 순서는 다음과 같다.

flowchart TD
    P1["① PRD · ADR<br/>방향과 기준"] --> P2["② AGENTS.md<br/>컨텍스트"]
    P2 --> P3["③ CLI · Skill · MCP<br/>도구"]
    P3 --> P4["④ 린터 · 테스트 · 차단형 Hook<br/>검증"]
    P4 --> P5["⑤ 서브에이전트<br/>분할"]
    P5 --> P6["⑥ 반복 구현<br/>실패를 하네스에 반영"]

처음부터 여섯 단계를 완성해두는 것은 아니다. 첫 작업 전에는 방향과 최소한의 컨텍스트·검증 장치를 갖추고, 실제 작업에서 실패가 드러날 때마다 규칙과 도구를 보강한다.

이 순서를 빈 프로젝트에서 하나씩 따라가 보자.

1. 맨손으로 시작하지 않는다 — PRD와 ADR로 방향을 먼저 세운다

“빈 프로젝트에 에이전트를 켠다”고 했지만, 정말로 맨손으로 시작하는 건 아니다. 새 기능을 만들 때 보통은 이렇게 시킬 것이다.

$ claude
> 결제 모듈에 환불 기능 추가해줘

이러면 에이전트가 일을 하긴 한다.

하지만 무엇을 만드는 서비스인지, 환불 정책이 어떤지, 어떤 제약이 있는지 모른 채 자기가 그럴듯하다고 판단한 방향으로 코드를 쏟아낸다.

사람이 옆에 딱 붙어서 매 줄을 들여다보고 이상하면 끊어야 한다. 하네스가 없으니 사람이 모든 걸 메우는 것이다.

그래서 코드를 짜라고 시키기 전에, 에이전트가 따라갈 방향부터 먼저 만든다.

두 가지다.

PRD — 무엇을 만들지를 모호함 없이 적는다. 에이전트가 흔들리는 가장 큰 이유는 비즈니스 요구사항이 사람의 머릿속에만 있기 때문이다.

그래서 나는 ALPS Writer 같은 도구로 PRD부터 쓴다.

ALPS(Agentic Lean Product Spec)는 사람이 읽고 직관으로 빈틈을 메우는 전통적 PRD와 달리, 에이전트가 모호함 없이 코드를 짤 수 있게 설계된 PRD 포맷이다.

사람이 백지에 쓰는 대신, 에이전트가 9개 섹션을 따라 질문하고 사람이 답하는 구조다.

그리고 “만들지 않을 것(Out of Scope)”을 일급 섹션으로 둬서, 에이전트가 무엇을 하지 말아야 하는지까지 명시한다.

질문 흐름을 고정하면 작성자에 따라 빠뜨리는 항목도 줄일 수 있다.

ADR — 어떻게 만들지의 결정을 기록한다.

PRD가 “무엇”이라면 ADR(Architecture Decision Record)은 “어떻게”의 결정이다.

ALPS Writer는 /feature-to-adr로 PRD의 기능을 ADR 초안으로 넘겨주고, 거기서부터는 adr-writer/adr-new로 새 결정을 쓰고 /adr-impl로 구현하는 사이클을 돈다.

내가 유지하는 규칙은 PRD → ADR → 코드의 단방향 의존성이다.

flowchart RL
    PRD["ALPS / PRD<br/>비즈니스 요구사항<br/>(가장 안정적)"]
    ADR["ADR<br/>아키텍처 결정<br/>(모호한 회색지대)"]
    CODE["코드<br/>구현 세부<br/>(가장 자주 바뀜)"]
    CODE -. 논리적 의존 .-> ADR
    ADR -. 논리적 의존 .-> PRD

코드는 ADR을 만족시키려고 쓰이고, ADR은 PRD를 만족시키려고 쓰인다.

안쪽(PRD)이 바뀌면 바깥쪽(ADR·코드)이 따라오지만, 그 반대는 일어나지 않는다.

코드 리팩토링 한 번에 ADR을 다시 써야 한다면 그 ADR이 구현 세부를 끌어안고 있었다는 뜻이다.

그래서 ADR에는 파일 경로나 코드 조각이 아니라 왜 이렇게 결정했는지와 대안 비교를 남긴다.

PRD와 ADR은 그다음에 추가할 규칙과 검증의 기준점이 된다.

AGENTS.md의 규칙과 가드레일이 검증하는 “올바른 형태”도 PRD·ADR이 정한 방향을 기준으로 판정한다.

방향을 적어두지 않으면 그 뒤의 모든 자동화는 “무엇에 맞춰 자동화하는지” 모르는 채로 돌아간다.

이 단계를 건너뛰고 바로 코드를 시키기 쉽다.

개발 전 과정을 에이전트에게 위임할 생각이 없다면 그래도 된다.1

하지만 위임을 늘리고 싶다면, 방향을 글로 먼저 적는 이 단계가 출발점이다.

그리고 여기서부터, 에이전트를 굴리다 보면 슬슬 짜증나는 지점들이 보이기 시작한다. 그 짜증이 바로 다음 단계의 재료다.

2. 같은 지적을 세 번 하면, AGENTS.md를 연다 (컨텍스트)

가장 먼저 닳는 인내심은 이런 데서 온다.

  • “우리 프로젝트는 pnpm 쓴다니까 자꾸 npm으로 깔지 마”
  • “커밋 메시지는 한국어로 써달라고 했잖아”
  • “API 핸들러는 handlers/ 밑에 둬야 한다고 방금 말했는데”

매번 똑같은 지적을 채팅창에 다시 친다. 그런데 대화는 휘발된다.

다음 세션이 시작되면 에이전트는 이전 교대 근무에 대한 기억 없이 도착하는 신입1처럼 같은 실수를 반복한다.

같은 지적을 세 번쯤 했다면, 그건 채팅에 칠 게 아니라 파일에 남길 신호다. 이게 첫 번째 하네스, AGENTS.md(혹은 CLAUDE.md)다.

처음부터 베스트 프랙티스를 모두 적으면 실제로 쓰지 않는 규칙만 쌓이기 쉽다.

처음에는 최소한의 규칙만 두고, 에이전트가 같은 방식으로 어긋날 때 한 줄씩 추가한다.

EncBird의 루트 AGENTS.md도 그렇게 자랐다.

지금은 이런 항목들이 쌓여 있는데, 하나하나가 “에이전트가 여기서 한 번 사고 쳤다”는 흔적이다.

## Agent Work Protocol
### Principles
- Focus on one feature/bug at a time
- Code must be buildable and pass lint at session end
- Write descriptive commit messages so the next session can
  understand progress from `git log` alone
- Prefer early return: handle errors and edge cases first ...

## Deployment & CI/CD
- A merge to `main` is itself a web deploy, so the agent never
  pushes/merges to `main` without explicit user confirmation.

마지막 줄(“main 머지 = 곧 배포니까 사람 확인 없이 머지하지 마라”)은 에이전트가 무심코 main에 머지해서 의도치 않은 배포를 일으킨 뒤에 추가했다.

프로젝트가 커지면 AGENTS.md도 하나로는 안 된다. EncBird는 Nx 모노레포라 패키지마다 툴체인이 완전히 다른데, 그래서 컨텍스트도 계층으로 쪼갰다.

flowchart TB
    ROOT["AGENTS.md<br/>모노레포 전체 규약 · ADR 워크플로우"]
    ROOT --> WEB["packages/web/AGENTS.md<br/>Nuxt 프론트엔드 규약"]
    ROOT --> API["packages/api-infra/AGENTS.md<br/>CDK 인프라 규약"]
    API --> GO["functions/main/AGENTS.md<br/>Go API 규약"]
    ROOT --> WI["packages/web-infra/AGENTS.md<br/>CloudFront/S3 호스팅 규약"]

루트는 “전체 약속”만 담고, 각 패키지의 구체적인 빌드·린트·컨벤션은 그 디렉토리의 AGENTS.md가 책임진다.

에이전트가 web을 만지면 web의 AGENTS.md만, Go를 만지면 Go의 AGENTS.md만 보면 된다. 이렇게 하면 컨텍스트가 비대해지지 않고, 엉뚱한 패키지의 규칙을 잘못 적용하는 일도 줄어든다.

여기까지 오면 에이전트는 프로젝트의 큰 방향에서 덜 벗어난다. 이게 컨텍스트 엔지니어링의 영역이다. 하지만 곧 컨텍스트만으로 안 되는 벽을 만난다.

3. “할 줄 모르는 일”이 나오면, 도구를 쥐여준다 (도구)

AGENTS.md로 방향은 잡았는데, 에이전트가 애초에 할 수 없는 일들이 보이기 시작한다.

  • DB 스키마를 확인해야 하는데 접근할 방법이 없어서 추측으로 코드를 짠다.
  • 배포 상태를 봐야 하는데 로그를 못 봐서 “아마 됐을 겁니다”로 끝낸다.
  • 결제 연동 스펙을 매번 잘못된 방식으로 호출한다.

에이전트가 입출력하는 건 텍스트(토큰)뿐이다.

외부 세계와 닿으려면 도구라는 창구가 필요하다.4 그래서 두 번째 층은 도구를 쥐여주는 일이다.

CLI, Skill, MCP는 에이전트에게 도구를 제공하는 서로 다른 방법이다. 나는 가볍게 시작해서 필요가 확인될 때 다음 단계로 넘어가는 순서를 권한다.

① 런타임 CLI — 가장 가볍고, 보통 가장 강력하다. 사실 가장 강력한 도구는 이미 깔려 있는 gh, aws, psql 같은 CLI들이다.

에이전트에게 셸을 주면 이것들을 그냥 쓴다. 별도 통합이 필요 없다. EncBird도 대부분의 배포·조회는 aws --profile encbird, cdk, gh, nx, pnpm 같은 CLI를 셸에서 직접 쓰는 것으로 해결한다.

② Skill — 절차를 파일로 굳혀둔다. 도구가 늘어나고 그 사용법이 복잡해지면, 매번 절차를 설명하기도 번거롭고 그 설명이 컨텍스트 창을 잡아먹는다.

Skill은 도구 사용 절차를 외부 파일(SKILL.md)로 빼두고, 필요할 때만 동적으로 적재하는 패턴이다. EncBird의 .claude/skills/에는 20개가 넘는 Skill이 쌓여 있다.

flowchart LR
    S[".claude/skills/"]
    S --> S1["adr-sync<br/>코드-ADR 동기화 점검"]
    S --> S2["web-debug<br/>Playwright로 프론트 버그 진단"]
    S --> S3["api-debug<br/>CloudWatch/DynamoDB로 Go API 디버깅"]
    S --> S4["api-deploy<br/>services.yaml 검증 · 빌드 순서 · 배포"]
    S --> S5["payment-e2e-test<br/>결제 플로우 E2E 테스트"]
    S --> S6["card-news<br/>카드뉴스 이미지 생성"]
    S --> S7["… (그 외 다수)"]

이것들 역시 한 번에 만든 게 아니다.

  • “배포할 때마다 빌드 순서를 틀린다” → api-deploy Skill
  • “프론트 버그 잡을 때마다 같은 디버깅 절차를 설명한다” → web-debug Skill

반복되는 절차가 보일 때마다 하나씩 떼어내 파일로 굳힌 것이다.

③ MCP — 도구 인터페이스와 계약을 표준화한다. 셸 CLI로도, 절차 문서로도 안 되는 영역이 있다.

외부 시스템과 구조화된 방식으로 통신해야 하거나, 여러 도구·여러 에이전트가 공유해야 할 때다.

이때 MCP로 도구의 입력·출력과 호출 계약을 표준화한다. MCP 서버는 로컬 stdio 프로세스로 실행할 수도 있고 원격 서비스로 운영할 수도 있으므로, 반드시 에이전트 프로세스 밖의 독립 서버일 필요는 없다.

대신 여러 에이전트에서 같은 도구를 재사용하기 쉬워지고, 원격 서비스로 운영하면 접근 제어와 배포를 중앙에서 관리할 수 있다. 그만큼 서버 운영과 디버깅 비용도 함께 고려해야 한다.5 EncBird의 .mcp.json에는 직접 짜기 번거로운 연동들이 붙어 있다.

{
  "mcpServers": {
    "tosspayments": { ... },   // 결제 연동 가이드
    "cloudwatch":   { ... },   // 로그 조회
    "analytics-mcp":{ ... },   // GA4 분석
    "pdf-reader":   { ... }
  }
}

내가 사용하는 순서는 CLI → Skill → MCP다.

많은 경우 셸에 CLI 몇 개 쥐여주는 것만으로 충분한데, 처음부터 MCP 서버를 띄우느라 시간을 쓰는 경우를 자주 본다. 도구는 필요가 확인된 뒤에 붙여도 늦지 않다.

이제 에이전트는 방향도 알고(컨텍스트), 손발도 생겼다(도구). 그런데도 결과물을 못 믿겠는 문제가 남는다.

4. “또 같은 실수를?” 싶으면, 가드레일을 친다 (결정적 검증)

도구까지 쥐여줬는데도 에이전트는 한 번에 요구사항을 완벽히 만족시키지 못한다.

그래서 결과가 요구사항에 맞는지 확인하고, 안 맞으면 다시 시키는 피드백 루프가 필요하다.

피드백 루프 자체가 비결정적인 것은 아니다. 린터·테스트처럼 같은 입력에 같은 결과를 내는 검증도 있고, 모델이 요구사항 부합 여부를 판단하는 비결정적 평가도 있다.

문제는 기계적으로 확인할 수 있는 항목까지 모델의 판단에만 맡길 때 생긴다. “이거 린트 통과했어?”라고 물어보는 대신 실제 린터를 실행해야 한다.

똑똑한 모델이라도 컨텍스트 윈도우를 몇 번 돌다 보면 처음의 규칙을 잊는다.

flowchart TB
    REQ["사용자 요청"] --> WORK["에이전트 작업 수행<br/>(컨텍스트 + LLM + 도구)"]
    WORK --> GR{"가드레일<br/>(린터 · 테스트 · 차단형 Hooks)<br/>결정적 검증"}
    GR -->|실패| FB["피드백을 컨텍스트에 추가"]
    GR -->|통과| FL{"요구사항 부합 평가<br/>규칙·테스트 또는 모델 판단"}
    FL -->|No| FB
    FB --> WORK
    FL -->|Yes| DONE["완료"]

그래서 세 번째 층은 가드레일, 즉 결정적 검증 장치다.

비결정적인 LLM의 판단 대신, 기계적으로 통과와 차단을 강제한다.1 EncBird에는 이런 결정적 검증과 프롬프트 기반 절차 유도가 함께 있다.

커밋 시점의 결정적 검증 (git pre-commit hook). 스테이징된 파일을 패키지별로 골라내서, web이면 ESLint + Prettier를, Go면 golangci-lint를 자동으로 돌린다.

에이전트가 어떤 코드를 짰든, 커밋하는 순간 린터를 통과하지 못하면 막힌다.

# scripts/pre-commit (요약)
web_files=$(echo "$staged" | grep -E '^packages/web/.*\.(vue|ts)$')
if [ -n "$web_files" ]; then
  echo "$web_files" | xargs npx eslint --fix
  echo "$web_files" | xargs npx prettier --write
fi
go_files=$(echo "$staged" | grep -E 'functions/main/.*\.go$')
if [ -n "$go_files" ]; then
  (cd packages/api-infra/functions/main && golangci-lint run ./...)
fi

프롬프트 시점의 절차 유도 (Claude Code Hook). EncBird에는 UserPromptSubmit Hook이 하나 걸려 있다.

사용자가 뭔가 요청할 때마다, “이게 신규 기능이나 동작 변경이면 코드부터 짜지 말고 ADR(아키텍처 결정 기록)을 먼저 점검·작성하라”는 지시를 컨텍스트에 주입한다.

// .claude/settings.json
{
  "hooks": {
    "UserPromptSubmit": [{
      "hooks": [{ "type": "command",
        "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/adr-first-reminder.sh" }]
    }]
  }
}

이 Hook을 추가한 이유는 에이전트가 설계를 건너뛰고 바로 코드로 돌진하는 실수를 반복했기 때문이다.

“설계 먼저 해줘”라고 매번 부탁하는 대신, 매 턴마다 환경이 그 지시를 자동으로 끼워 넣게 만들었다.

다만 프롬프트를 주입하는 것만으로 절차가 기계적으로 강제되지는 않는다. 모델이 지시를 놓칠 수 있기 때문이다. 반드시 막아야 하는 절차라면 ADR 존재 여부를 검사하고 조건을 충족하지 못했을 때 실행을 차단하는 검증을 별도로 붙여야 한다.

검증 실패 메시지에는 수정 방법도 함께 적어두는 편이 좋다.

OpenAI의 Codex 팀이 자기 자신을 만들 때 썼던 기법인데, 커스텀 린터가 단순히 “규칙 위반”이라고 하는 대신 “이 패턴 대신 저 패턴을 쓰라”는 지침까지 에러에 담아서, 에이전트가 그걸 읽고 스스로 교정하게 했다.6

린터·테스트처럼 자동 판정할 수 있는 규칙은 프롬프트에만 남겨두지 않는다. 위반하면 빌드가 깨지거나 커밋이 막히도록 환경에서 강제한다.

5. 컨텍스트가 더러워지면, 일을 쪼개 보낸다 (서브에이전트)

여기까지 오면 에이전트 한 마리가 꽤 안정적으로 일한다.

그런데 작업이 길어지고 복잡해지면 새로운 문제가 생긴다. 컨텍스트 오염이다.

긴 작업을 하다 보면 메인 대화창에 온갖 것이 쌓인다.

디버깅하다 찍어본 로그 수백 줄, 탐색하느라 읽은 파일 수십 개, 중간에 시도했다 버린 접근들. 정작 중요한 큰 방향은 이 잡음에 묻힌다.

그러다 컨텍스트가 꽉 차면 compaction(압축)이 일어나고, 그 과정에서 중요한 정보가 손실된다.

네 번째 층은 서브에이전트로 일을 쪼개 보내는 것이다.

EncBird는 모노레포라 패키지마다 툴체인이 완전히 다른데, 그래서 메인 에이전트를 오케스트레이터로 두고 패키지별 작업을 서브에이전트에게 위임하는 구조를 쓴다.

flowchart TB
    ORCH["오케스트레이터 (메인 에이전트)<br/>계획 · API 계약 정의 · 통합"]
    ORCH -->|위임| CDK["CDK 서브에이전트<br/>api-infra/AGENTS.md"]
    ORCH -->|위임| GO["Go API 서브에이전트<br/>functions/main/AGENTS.md"]
    ORCH -->|위임| WEB["Web 서브에이전트<br/>web/AGENTS.md"]
    CDK -.순서.-> GO -.순서.-> WEB

오케스트레이터는 (1) ADR을 읽고 범위를 정하고, (2) 패키지 간 기능이면 인터페이스(엔드포인트·타입·이벤트 페이로드)를 먼저 정의한 다음, (3) 각 서브에이전트에게 계약과 제약을 넘겨 위임하고, (4) 합쳐진 변경을 검토해 통합한다.

각 서브에이전트는 자기 패키지의 AGENTS.md만 읽고, 자기 디렉토리에서만 명령을 실행하며, 다른 패키지의 패턴을 함부로 가져다 쓰지 않는다. 의존성 방향을 따라 CDK → Go API → Web 순으로 진행한다.

이렇게 하면 Go API를 뒤지느라 읽은 수십 개 파일의 잡음은 Go 서브에이전트의 컨텍스트와 함께 버려지고, 오케스트레이터의 컨텍스트는 큰 방향과 각 서브에이전트의 결론만 담은 채 깨끗하게 유지된다.

PixelBank는 백엔드가 Go가 아니라 Python(FastAPI)이라 서브에이전트 구성도 그에 맞게 다르지만, “패키지마다 툴체인이 다르니 각자의 컨텍스트·도구 경계를 가진 서브에이전트로 분리한다”는 골격은 똑같다.

다만 역할만 나눈다고 서브에이전트가 되는 것은 아니다.

이전 글2에서 다뤘듯이, “너는 리뷰어”, “너는 테스터”라고 프롬프트만 바꾸면 각 역할이 같은 컨텍스트와 도구에 의존한다.

EncBird에서는 각 서브에이전트가 자기 패키지의 AGENTS.md와 린트·빌드 명령을 사용한다. 컨텍스트와 도구·가드레일의 경계가 함께 나뉘어야 앞 단계에서 읽은 잡음을 버릴 수 있다.

이 때문에 서브에이전트를 가장 마지막에 두었다.

하나의 에이전트조차 안정적으로 못 굴리는 상태에서 에이전트를 여러 개 붙이면, 불안정한 단위들이 모여 더 불안정한 시스템이 될 뿐이다.2

그래서 컨텍스트와 도구, 가드레일을 먼저 갖춘 뒤 서브에이전트를 나눈다.

6. 반복 구현: 실패를 다음 하네스에 남긴다

앞의 1~5단계에서 방향과 기본 하네스를 갖췄다면, 이제 반복적 기능 구현으로 들어간다.

이 단계의 에이전트는 꽤 자율적으로 일한다. Claude Code가 자기 코드의 90%를, Codex가 100만 줄을 사람 손 없이 써낸 사례에서도 모델을 둘러싼 도구와 검증 환경을 함께 만들었다.6

모델이 바뀌고, 코드베이스가 자라고, 새 요구사항이 들어오면 에이전트는 새로운 방식으로 삐끗한다. 그때마다 다시 한 겹을 덧댄다.

PRD·ADR, 기본 컨텍스트와 최소 검증 장치는 작업 전에 갖춘다. 아래 사이클은 실제 작업에서 새로 배운 내용을 기존 하네스에 반영하는 과정이다.

flowchart LR
    G["① 코드 생성"] --> D["② 디버깅"]
    D --> R["③ 리팩토링"]
    R --> OK["요구사항을 만족하는<br/>형태 도달"]
    OK --> H["④ 학습을 하네스에 반영<br/>(규칙 · 도구 · 가드레일)"]
    H -.다음 요청.-> G

코드 생성 → 디버깅 → 리팩토링을 거쳐 요구사항을 만족하는 형태에 도달한다.

그다음 에이전트가 같은 실수를 반복하지 않도록 이번에 확인한 조건을 하네스에 반영한다.

이번 한 번의 디버깅·리팩토링으로 알아낸 “올바른 형태”를, 규칙(AGENTS.md)이나 가드레일(린터·테스트)로 굳혀두는 것이다.

이걸 안 하면 매번 같은 디버깅과 리팩토링을 반복하게 된다.

반대로 이 작업을 반복하면 다음 요청에서 같은 디버깅과 리팩토링을 줄일 수 있다.

하네스 업데이트는 시간 축에 따라 두 종류로 나눌 수 있다.1

flowchart TB
    subgraph CTX["컨텍스트 업데이트 — 장기적 방향성 유지"]
        C1["에이전트가 프로젝트의 큰 방향·요구사항에서<br/>벗어나지 않도록 지속적으로 업데이트"]
        C2["PRD · 코드베이스 · API 문서<br/>테스트 결과 · ADR · AGENTS.md"]
    end
    subgraph HRN["실행 하네스 업데이트 — 단기 오류 감지·복구"]
        H1["피드백 루프로 스스로 개선하고<br/>가드레일로 결정적 검증을 자동화"]
        H2["도구 · 피드백 루프(Skills·MCP)<br/>가드레일(차단형 Hooks·린터·테스트)"]
    end
    CTX --> ALL["자율화된 개발 환경"]
    HRN --> ALL

컨텍스트 업데이트는 긴 호흡이다.

PRD·ADR·AGENTS.md·코드베이스를 계속 최신으로 유지해서, 에이전트가 며칠짜리 작업을 하더라도 프로젝트의 큰 방향에서 멀어지지 않게 한다.

앞의 1·2단계가 여기에 해당한다. 실행 하네스 업데이트는 짧은 호흡이다. 매 실행 주기마다 도구로 작업하고, 피드백 루프로 스스로 고치고, 가드레일로 결정적으로 검증해서 단기 오류가 누적되지 않게 한다. 3·4·5단계가 여기다.

컨텍스트 업데이트는 장기적인 방향을 유지하고, 실행 하네스 업데이트는 각 실행에서 생긴 오류를 줄인다.1

EncBird의 AGENTS.md에는 ADR-first 피드백 루프를 이렇게 적어두었다.

빠른 사이클을 돌리고 매 패스마다 ADR을 보강하라 — 완벽한 ADR을 처음부터 쓰려고 하지 마라.

  • 같은 지적을 반복하게 되면 → AGENTS.md에 한 줄 추가
  • 못 하는 일이 보이면 → 도구를 하나 붙임 (CLI → Skill → MCP)
  • 같은 실수를 또 하면 → 가드레일(차단형 Hook·린터·테스트)로 기계적 차단
  • 컨텍스트가 더러워지면 → 서브에이전트로 분할

이런 식으로 반복되는 판단을 하네스에 옮기면, 사람은 구현 과정 전체보다 비즈니스 요구사항과 예외를 확인하는 데 시간을 쓸 수 있다.7

마치며

EncBird의 하네스도 최소한의 PRD·ADR과 AGENTS.md로 시작했다.

에이전트가 실수할 때마다 규칙을 더하고, 필요한 작업을 못 할 때 CLI·Skill·MCP를 붙였다. 반복되는 실수는 pre-commit과 Hook으로 줄였고, 컨텍스트가 비대해진 뒤에야 서브에이전트로 쪼갰다.

처음부터 이 구성을 모두 갖출 필요는 없다. 기본 방향과 검증 장치를 마련한 뒤, 실제 작업에서 반복된 문제만 다음 하네스에 남기면 된다.

반복되던 실수를 한 세션 안에서 해결했다면, 그대로 끝내지 말고 이렇게 입력하는 것으로 마무리하자.

지금 수정한 내용의 원인을 분석해서 재발하지 않도록 AGENTS.md와 문서들을 업데이트해줘

방금 확인한 원인과 해결 방법을 파일에 남겨야 다음 세션에서 같은 실수를 줄일 수 있다.


  • #ai
  • #agent
  • #harness-engineering
  • #context-engineering
  • #agentic-development
  • #sub-agent
  • #guardrail