View
Harness Engineering이란 무엇인가: AI Agent를 장시간 구동하는 개발자들이 DB와 YAML에 집착하는 이유
DevNinja 2026. 4. 18. 21:40Claude Code나 Cursor Background Agent를 써본 사람이라면 공감할 것이다. 5분짜리 태스크는 잘 돌아가지만, 3시간짜리 태스크를 돌리면 중간에 반드시 문제가 발생한다. 지시사항을 잊거나, 존재하지 않는 파일을 읽었다고 주장하거나, 갑자기 자신이 무엇을 하고 있었는지 리셋된 것처럼 멈춘다.
이 문제를 해결하기 위해 최근 부상하는 개념이 Harness Engineering이다. 핵심은 LLM 자체를 손대는 것이 아니라, LLM을 감싸는 외부 장치(harness)를 엔지니어링하여 안정성을 확보한다는 점이다. 다만 이 harness 구조를 들여다보면 유독 DB와 YAML이 함께 등장한다. 왜 이 둘이 짝을 이루어 나타나는지, 그리고 이것이 AI 환각 증상과 어떻게 연결되는지 한 번에 정리해본다.

출처: enginestories.com
Harness Engineering이란 정확히 무엇인가
Harness Engineering은 LLM 주변을 감싸는 실행 장치를 공학적으로 설계하는 분야이다. 여기서 harness는 말의 안장이나 F1 차량의 보조 장비를 떠올리면 된다. 드라이버(LLM)가 아무리 빠르더라도 혼자 달리면 완주할 수 없다. 피트크루, 텔레메트리, 세이프티카 같은 harness가 붙어야 장거리 레이스에서 살아남는다.
2025년에 들어서면서 Anthropic이 공식 엔지니어링 블로그에 이 주제의 글을 잇따라 공개하기 시작했다. Claude Code 자체가 harness의 레퍼런스 구현이라 할 수 있다. 내부의 파일시스템 접근, 세션 저장, 툴 호출 래퍼, 훅 시스템 등이 모두 harness에 해당한다.
요점은 이것이다. 모델 자체는 이미 충분히 똑똑하다. 문제는 그 똑똑함을 장시간 안정적으로 구동할 바깥 뼈대가 허술하다는 점이다. Harness Engineering은 바로 그 뼈대를 다루는 분야이다.
Agent와 Harness는 무엇이 다른가
많은 개발자가 "Agent 프레임워크"와 "Harness"를 혼동한다. 구분하면 다음과 같다.
- Agent: 판단하는 두뇌. LLM 호출부 + 프롬프트 + 툴 선택 로직
- Harness: 판단을 실행하고 기록하며 복구하는 몸통. DB, 스케줄러, 체크포인트, 재시도 로직
실제로 LangGraph, CrewAI, AutoGPT 등을 뜯어보면 대부분이 harness이다. Agent 부분은 전체 코드의 20%에도 못 미친다. 나머지 80%는 상태 관리, 이벤트 기록, 실패 복구 같은 인프라가 차지한다. 이를 모르고 "LangGraph는 Agent 프레임워크다"라고 뭉뚱그리면 본질을 놓치게 된다.
장시간 실행하면 왜 무너지는가
짧은 태스크는 멀쩡한데 긴 태스크는 왜 자꾸 망가지는가? 원인을 분해하면 네 가지가 도출된다.
1. 컨텍스트 윈도우 폭발
Claude Opus 4.7처럼 100만 토큰급 모델이 등장했더라도 결국 한계는 존재한다. 3시간을 구동하면 로그, 툴 결과, 중간 산출물이 누적되어 앞쪽이 잘려 나간다. 앞쪽이 잘리면 최초 지시사항도 증발한다.
2. 툴 호출 실패 시 재시도 로직 부재
외부 API가 429 에러를 반환하면 Agent는 그저 "실패했다"고 판단하고 다음 단계로 넘어간다. 다만 1분 뒤에 재시도하면 성공할 수 있는 상황임에도 그 로직이 없다. 이런 실패가 누적되면 전체 결과물이 무너진다.
3. 프로세스 사망 시 처음부터 재시작
3시간 구동 중 OOM으로 프로세스가 죽으면 어떻게 될까? 복구 지점이 없다면 처음부터 다시 돌려야 한다. 시간과 토큰 비용이 두 배로 소모된다.
4. 중간 상태 추적 불가
"왜 이러한 결론에 도달했는가"를 확인하고 싶어도 중간 추론 과정이 휘발성이라 사라진다. 디버깅이 사실상 불가능하다.

출처: zilliz.com
환각이 장시간 실행에서 왜 더 심해지는가
환각(hallucination)은 짧은 태스크에서도 발생한다. 다만 장시간 실행에서는 이것이 기하급수적으로 증폭된다. 원리는 다음과 같다.
컨텍스트 앞부분이 잘려 나가며 최초 지시사항이 증발한다. 2시간 전에 "이 파일만 읽고 작업하라"고 지시했더라도 컨텍스트에서 밀려나면 Agent는 이를 기억하지 못한다. 그래서 갑자기 엉뚱한 파일을 읽기 시작한다.
자신이 이전에 지어낸 거짓을 진실로 착각하는 "환각 누적(compound hallucination)" 현상이 발생한다. 1시간 전에 환각으로 "X 함수를 호출했다"고 로그에 남겼다면, 그 로그가 컨텍스트에 남아있는 한 Agent는 이를 사실로 믿고 그 위에 더 쌓아 올린다. 모래성 위에 모래성을 쌓는 격이다.
툴 결과를 제대로 파싱하지 못하면 가짜 결과를 만들어낸다. JSON 파싱 에러가 발생하면 LLM은 "그냥 비슷하게 채워넣자"는 유혹에 빠진다. 이것이 환각의 시작점이다.
해결책은 하나이다. 외부 메모리에 "진실"을 박제해두고, 매 스텝마다 그곳에서 끌어와야 한다. 여기서 DB가 등장한다.
DB는 왜 필수인가 - 상태와 기억의 분리
LLM의 기억은 휘발성이다. 컨텍스트에 존재할 때만 유효하며, 밀려나면 사라진다. 반면 DB는 영속성을 가진다. 한 번 저장하면 프로세스가 죽어도 남는다.
Harness 설계의 첫 번째 원칙은 "LLM에게 기억을 맡기지 말 것"이다. 대신 모든 중요한 상태를 DB에 박제한다. 다만 단순히 로그만 남기는 것이 아니라 구조가 존재한다.
이벤트 소싱 패턴으로 모든 행동 기록
Event Sourcing은 Agent의 모든 행동을 append-only 이벤트로 기록하는 패턴이다. "state를 UPDATE하지 말고, event를 INSERT하라"가 핵심이다. 왜일까? 업데이트하면 이전 상태를 잃어버리지만, 이벤트는 계속 누적되어 언제든 재구성이 가능하기 때문이다.
실전 사례로 LangGraph는 SqliteSaver, PostgresSaver 같은 checkpointer를 제공한다. 이것이 수행하는 작업이 바로 event sourcing이다. 모든 노드 실행 결과를 DB에 기록한다. Claude Code의 conversation transcript 역시 유사한 구조이며, 매 메시지가 append-only로 저장된다.
체크포인팅으로 실패 지점부터 재시작
DB에 상태가 박제되어 있으므로 프로세스가 죽어도 마지막 체크포인트부터 재시작이 가능하다. 이를 durable execution이라 부른다. Temporal.io나 Inngest 같은 워크플로우 엔진이 이 개념을 극한으로 밀어붙인 제품이다.
Temporal의 경우 워크플로우 코드 한 줄 한 줄이 모두 DB에 기록된다. 서버가 폭발해도 다른 서버가 그 상태를 이어받아 계속 실행한다. AI Agent 생태계에서도 이 개념이 표준으로 자리 잡는 중이다.
왜 유독 SQLite가 자주 등장하는가
로컬 Agent 구현을 살펴보면 SQLite가 기본 선택지로 등장하는 경우가 많다. 이유는 세 가지이다.
- 임베디드라 배포가 단순하다. Postgres처럼 별도 서버를 띄울 필요가 없다
- WAL 모드로 동시성도 어느 정도 처리된다. 여러 스레드가 접근해도 안정적이다
- 파일 하나라 백업과 이동이 쉽다. .db 파일만 복사하면 전체 상태 이관이 가능하다
Claude Code 역시 conversation 저장소로 SQLite를 채택한다. 로컬에서 Agent를 구동할 때 SQLite는 사실상 디폴트 선택지이다. 대규모 분산 환경으로 확장할 경우 Postgres나 FoundationDB 같은 것으로 넘어가는 식이다.
YAML은 왜 사용하는가 - 선언적 태스크 정의
DB는 "무엇이 일어났는가"를 기록하는 사실의 저장소이다. 다만 "무엇을 해야 하는가"를 표현하는 도구도 필요하다. 여기서 YAML이 등장한다.
Agent의 행동 계획을 코드가 아닌 데이터로 표현하는 것이 왜 중요한가? 이유는 네 가지이다.
사람이 읽고 수정할 수 있다. 비개발자 PM도 YAML은 읽을 수 있다. 파이썬 코드는 손대기가 꺼려진다.
LLM이 읽고 이해할 수 있다. JSON도 가능하지만 YAML이 더 간결하고 주석도 작성 가능하여 LLM에게 의도를 전달하기 유리하다.
버전 관리가 깔끔하다. Git diff가 보기 좋게 출력된다. 어제와 오늘의 설정 차이가 한눈에 들어온다.
선언적이라 예측 가능하다. "이러한 조건일 때 이것을 수행하라"를 명시하므로 LLM이 임의로 해석할 여지가 줄어든다.
실제 사례를 보면 이 패턴이 곳곳에 존재한다. GitHub Actions 워크플로우, Ansible 플레이북, Kubernetes 매니페스트, Claude Code Skills의 SKILL.md 프론트매터, AutoGPT의 프롬프트 설정. 모두 동일한 철학이다.

왜 JSON이 아니라 YAML인가
JSON 역시 데이터 포맷인데 왜 하필 YAML이 표준으로 자리 잡았는가? 실용적인 이유가 존재한다.
- 주석을 작성할 수 있다. JSON은 공식적으로 주석을 허용하지 않는다. Agent 설정에는 "왜 이 값을 채택했는가"를 남기는 것이 디버깅에 매우 유용하므로 주석이 필수적이다
- 들여쓰기 기반이라 시각적으로 깔끔하다. 중괄호와 대괄호 지옥이 없다
- 다중행 문자열 처리가 편리하다. 프롬프트를 통째로 삽입하기 좋다. JSON에서 긴 프롬프트를 넣으려면 이스케이프 지옥에 빠진다
단점도 있다. 탭과 공백을 혼용하면 파싱이 실패하고, 복잡해지면 오히려 가독성이 떨어진다. 그래서 설정 파일은 YAML로 두되, Agent 간 통신은 JSON Schema로 처리하는 식의 역할 분담이 일반적이다.
DB + YAML 조합이 Harness의 표준이 된 이유
이제 왜 DB와 YAML이 짝을 이루어 등장하는지 감이 올 것이다. 역할이 완벽히 상보적이다.
- YAML = "무엇을 해야 하는가" (What, 의도)
- DB = "무엇을 했는가" (What happened, 사실)
이 둘이 분리되어야 "계획을 변경하더라도 실행 이력은 소실되지 않는다"가 성립한다. YAML 파일을 수정해도 DB의 이전 실행 로그는 그대로 유지된다. 반대로 DB를 복구해도 YAML로 정의된 계획은 보존된다. 서로 다른 차원의 데이터이므로 충돌하지 않는다.
환각 방지에도 이 구조는 유리하다. LLM이 "나는 X를 수행했다"고 거짓을 주장해도 DB에 박제된 이벤트 로그와 대조하면 거짓임을 즉시 식별할 수 있다. YAML에 정의된 "해야 할 일"과 DB에 기록된 "실제로 한 일"을 대조하면 일관성 검증이 자동으로 이루어진다.
실전 설계 팁 몇 가지
이 구조로 Agent를 직접 구축해본 개발자들이 공통적으로 제시하는 조언을 정리한다.
- YAML에는 민감 정보를 절대 넣지 말 것. API 키나 비밀번호 등은 환경변수 참조로만 처리한다. Git에 실수로 푸시되면 재앙이다
- DB 스키마는 처음부터 append-only로 설계한다. UPDATE/DELETE를 쓰기 시작하면 이벤트 소싱 원칙이 깨진다. 상태 변경은 반드시 새 이벤트 추가로 표현한다
- 체크포인트 간격은 5~15분이 적정하다. 너무 잦으면 디스크 IO로 느려지고, 너무 드물면 복구 시 손실이 커진다
- YAML 파싱 실패 시 반드시 fail-fast로 처리한다. 적당히 넘어가면 Agent가 환각으로 빈 값을 채워넣어 더 큰 재앙을 부른다
- DB 쿼리는 반드시 인덱스를 타게 한다. 이벤트가 수십만 건 누적되면 풀스캔으로 인해 멈추는 경우가 있다
환각을 줄이는 Harness 패턴 3가지
Harness 설계에서 환각을 줄이는 실전 패턴 세 가지를 꼽으면 다음과 같다.
1. Structured Output 강제
LLM이 자유롭게 서술하도록 방치하면 환각 확률이 급등한다. 대신 JSON Schema나 Tool use로 출력 구조를 강제해야 한다. "이 필드는 반드시 문자열, 이 필드는 반드시 숫자"라고 못 박으면 거짓을 말할 여지가 줄어든다. Claude API의 tool use나 OpenAI의 function calling이 이를 위한 기능이다.
2. Verifier Agent 패턴
작업 수행 Agent와는 별개로 검증 Agent를 하나 더 구동하는 패턴이다. "이 결과가 정말 옳은가?"를 다른 LLM이 점검한다. 두 Agent가 동일한 환각을 동시에 일으킬 확률은 낮기에 상당 부분이 걸러진다. 연구 용어로는 self-consistency라고도 한다.
3. Ground Truth Lookup
매 스텝마다 DB에 저장된 사실을 재주입하는 패턴이다. 컨텍스트가 아무리 길어지더라도 핵심 진실(최초 지시사항, 제약 조건, 이미 완료된 작업 목록)은 매 턴마다 강제로 다시 주입한다. "스스로 주장하는 것을 믿지 말고, DB에 있는 것을 믿어라"를 LLM에게 학습시키는 셈이다.

출처: examples-of.net
정리하면 다음과 같다
지금까지의 내용을 압축하면 이 정도이다.
- Harness Engineering은 LLM의 바깥 뼈대를 엔지니어링하는 분야이다. 모델 자체는 이미 똑똑하므로 이 뼈대가 안정성을 좌우한다
- 장시간 Agent가 무너지는 이유는 컨텍스트 폭발, 재시도 부재, 복구 불가, 추적 불가의 네 가지이다. 환각은 이 네 가지 위에서 증폭된다
- DB는 진실의 저장소이다. 이벤트 소싱과 체크포인팅으로 LLM의 휘발성 기억을 보완한다
- YAML은 계획의 언어이다. 사람과 LLM 모두가 읽기 좋고 버전 관리도 깔끔하다
- DB + YAML 조합은 "해야 할 일"과 "한 일"을 분리한다. 환각 검증도 여기서 가능해진다
- 환각 방지 3패턴: Structured Output, Verifier Agent, Ground Truth Lookup
당장 무엇부터 시작하면 되는가? 각설하고, 지금 구축 중인 Agent가 있다면 상태를 DB에 박제하는 것부터 시작하라. SQLite로 충분하다. 이벤트 테이블 하나를 만들고 Agent의 모든 행동을 append-only로 기록해보라. 이것만으로도 디버깅 지옥에서 절반은 탈출할 수 있다.
본격적으로 파고들 독자라면 LangGraph의 checkpointing 기능부터 학습하고, 더 진지하게 접근할 경우 Temporal 같은 durable execution 엔진을 검토하면 된다. 직접 구현할 자신이 있다면 Anthropic 엔지니어링 블로그의 "Building effective agents" 글부터 정독할 것을 추천한다.
다음 글에서는 LangGraph로 체크포인팅을 직접 구현하며 실제 3시간짜리 Agent를 구동해보는 실전편을 작성할 예정이다. Harness를 제대로 깔아두면 Agent가 중간에 멈춰도 "왜 아직 실행되고 있지?"라는 반응이 나온다. 그 경험을 한 번 맛보면 이전으로 돌아갈 수 없다.
'AI LLM' 카테고리의 다른 글
| Claude Skills로 API를 구축할 때의 디렉토리 구조 - references/scripts/envs가 정석인 이유 (1) | 2026.04.19 |
|---|---|
| 투박한 AI 디자인 이제 끝인가? 클로드 디자인(Claude Design) 사용 방법 (1) | 2026.04.18 |
| 개발자는 왜 AI가 작성한 코드를 다시 읽지 않는가: 바이브 코딩의 숨겨진 함정 (1) | 2026.04.18 |
| Hook을 사용하여 Claude Skills를 Auto Trigger 구현하기 (0) | 2025.11.13 |
| MCP (Model Context Protocol)을 활용하여 사내 업무 최적화해보기 (0) | 2025.03.16 |
