체크로 만드세요: 코딩 에이전트의 교훈을 CLAUDE.md 산문이 아닌 게이트로 축적하기
요약
코딩 에이전트의 학습 내용을 CLAUDE.md와 같은 산문 형태의 지침으로 기록하는 대신, 자동화된 체크(gate) 시스템으로 구축해야 한다는 주장입니다. 컨텍스트 압박 상황에서 모델이 규칙을 어기는 문제를 해결하기 위해 스크립트를 통한 강제적 검증의 중요성을 강조합니다.
핵심 포인트
- 산문 형태의 지침은 컨텍스트 압박 시 모델이 무시할 가능성이 높음
- 기록된 규칙과 강제된 규칙 사이의 간극을 메워야 함
- 교훈을 에이전트의 기억이 아닌 자동화된 검증 장치로 전환할 것
- 스테이지된 디프(staged diff) 검사 등 스크립트를 통한 강제적 피드백 루프 구축
컴파운드 엔지니어링 (Compound engineering)은 각 교훈을 에이전트의 산문 (prose)에 기록합니다. 하지만 중요한 것들은 산문이 아닌 체크 (checks)가 되어야 합니다. 산문은 표류하지만, 게이트 (gate)는 표류하지 않기 때문입니다.
컴파운드 엔지니어링 (compound engineering)에 대한 표준 가이드는 그 마지막 단계인, 즉 _컴파운드 (compound)_라고 명명된 단계를 다음과 같은 평이한 지침으로 설명합니다: 작업이 완료되면, "에이전트가 매 세션 시작 시 읽는 파일인 CLAUDE.md에 새로운 패턴을 추가하십시오." 저는 몇 달 동안 그 조언을 따랐습니다. 패턴들은 쌓여갔고, 에이전트는 매 세션 시작 시 그것들을 성실히 다시 읽었지만, 제가 진심으로 신경 썼던 관례는 어쨌든 계속해서 표류했습니다. 규칙은 항상 로드되는 파일 안에 분명히 존재했습니다. 하지만 에이전트의 출력은 컨텍스트 압박 (context pressure) 하에서, 규칙이 지정한 용어 대신 자신이 편집 중인 파일에서 가장 최근에 본 용어를 선택하며 유창하고 자신 있게 규칙으로부터 벗어났습니다. 그것은 결코 반항이 아니었습니다. 모델은 규칙을 어기기로 결정하지 않습니다. 혼잡한 프롬프트 (prompt) 상황에서 모델은 단지 훨씬 이전에 읽은 한 줄보다 눈앞에 있는 토큰 (tokens)에 더 높은 가중치를 둘 뿐입니다.
마침내 효과를 본 해결책은 더 나은 문단이 아니었습니다. 그것은 스테이지된 디프 (staged diff)를 읽고 잘못된 단어가 나타나면 커밋 (commit)을 차단하는 12줄짜리 스크립트였습니다. 프로젝트 전체에 이 스크립트를 처음 실행했을 때, 제가 이미 기록해 두었고 에이전트가 따를 것이라고 믿었던 규칙을 위반한 사례가 737건이나 발견되었습니다. (저는 그 용어 표류에 관한 이야기를 Rails, Not Rules에서 자세히 다루었습니다.) "기록된 것"과 "강제된 것" 사이의 그 간극이 이 에세이의 핵심입니다. 간직할 가치가 있는 교훈은 에이전트가 기억해야 하는 산문에 추가되어서는 안 됩니다. 그것은 다음에 실수가 발생했을 때 자동으로 잡아내는 장치가 되어야 합니다.
루프 (loop)는 맞습니다. 마지막 단계가 틀렸을 뿐입니다.
복합 공학 (Compound engineering)은 루프 (loop)를 올바르게 설정했습니다. 이에 대해 반박하기 전에 그 점은 인정하고 싶습니다. "각 엔지니어링 작업 단위는 후속 작업 단위를 더 어렵게 만드는 것이 아니라 더 쉽게 만들어야 한다"는 전제는 에이전트 (agent)를 지시하기 위한 정확하고 올바른 야망입니다. 계획하고, 작업하고, 검토한 다음, 여러분은 '복합화 (compound)'합니다. 즉, 배운 내용을 다시 피드백하여 다음 실행이 더 똑똑하게 시작되도록 하는 것입니다. 저는 코딩 에이전트와 함께 구축한 약 150,000라인 규모의 코드베이스를 대상으로 매일 이 루프의 버전을 실행하고 있으며, 그 실행 과정을 지켜보며 네 편의 에세이를 발표했습니다.
저의 이견은 좁은 범위이며, 전적으로 마지막 단계에 국한되어 있습니다. 복합 공학의 '복합화 (compound)' 단계는 기억된 산문 (remembered prose)으로 종료되는데, 기억된 산문은 이 루프를 통해 에이전트가 신뢰할 수 없다는 것을 배운 유일한 요소입니다. 하지만 솔직히 말하자면, 동일한 가이드가 '복합화 (compound)'의 바로 마지막 하위 단계에서 스스로의 조언보다 더 날카로운 질문을 던지고 있습니다. 가이드는 "시스템이 [다음번에] 이것을 자동으로 잡아낼 수 있는가"를 확인하라고 말합니다. 그 문장이 바로 제가 하고 싶은 주장인데, 정작 그 주장을 약화시키는 지침 옆에 하나의 열망처럼 놓여 있습니다. 에이전트가 매 세션마다 읽으면서도 여전히 불균일하게 적용하는 파일에 교훈을 추가하지 마세요. 대신, 다음번에 실수를 자동으로 잡아내는 장치를 만드세요.
왜 산문은 패배하고 체크 (check)는 승리하는가
그 이유는 제가 전체 동반 에세이를 통해 다루었던 비대칭성 때문이며, 따라서 한 번만 언급하고 넘어가겠습니다. LLM은 고처리량(high-throughput), 저일관성(low-consistency) 생성기입니다. 즉, 다작하고 진정으로 창의적이지만, 주의를 끌기 위해 항상 로드되어 있는 수백 개의 지침 중 단 한 줄에 불과한 47번 규칙에 대해서는 신뢰할 수 없습니다. 지침 파일(instruction file)에 적힌 한 단락은 그 생성기가 매번 규칙을 기억하고 적용하기를 기대하며 의존합니다. 체크(check)는 그 반대입니다. 체크는 생성기 앞에 위치한 저렴하고 결정론적인 검증기(verifier)이며, 제약 조건이 유지되는지 확인하는 것은 매번 첫 시도에 그 조건을 만족하는 출력을 생성하는 것보다 훨씬 저렴하고 훨씬 더 신뢰할 수 있습니다. 규칙을 산문(prose)으로 작성하는 것은 신뢰할 수 없는 측면에 도박을 거는 것이고, 체크로 작성하는 것은 신뢰할 수 있는 측면에 도박을 거는 것입니다.
신뢰성 문제 위에는 비용의 비대칭성도 쌓여 있는데, 이 부분이 저를 놀라게 했습니다. 약간 중복되는 체크를 유지할 가치가 있는지 검토했을 때, 계산 결과는 한쪽으로 치우쳐 있었습니다. "문서의 한 단락은 모든 세션마다 토큰 비용이 발생하지만, 중복된 게이트(gate)는 실행될 때만 비용이 발생합니다." 에이전트가 실제로 지불하는 비용, 즉 컨텍스트(context) 내의 토큰 비용은 훨씬 더 적습니다. 통과하는 체크는 조용합니다. 에이전트는 이를 보지 못하며 아무런 비용도 지불하지 않습니다. 오직 실패했을 때만 토큰을 소모하며, 이는 다음 시도를 유도하는 단 하나의 메시지를 피드백하기 위해서만 사용됩니다. 항상 로드되는 파일에 있는 산문에는 그러한 오프 스위치(off switch)가 없습니다. 그것은 에이전트가 수행하는 작업과 규칙의 관련성 여부에 관계없이, 영원히 모든 작업에 대해 지불해야 하는 세금이며, 에이전트를 더 똑똑하게 만들기 위해 축적한 산문은 에이전트가 기억해야 할 다른 모든 것들을 서서히 밀어냅니다. 텍스트로서 교훈을 누적하는 것은 단순히 표류하는 것에 그치지 않습니다. 그것은 표류를 유발했던 바로 그 희소한 표면을 비대하게 만듭니다.
루프(loop), 루프라고 명명된 것
만약 체크(check)가 한 단락의 글보다 효과적이라면, 그 관행은 더 이상 "교훈을 축적하는 것"이 아니라 더 구체적인 무언가가 됩니다. 작업 과정에서 드러나는 실패를 관찰하고, 비용 테스트(cost test)를 기준으로 등급을 매기며, 오탐(false-positive)이 발생하지 않도록 만들 수 있는 절반의 과정을 기계화(mechanize)하십시오. 나머지 절반은 의도적으로 규율(discipline)의 영역으로 남겨둡니다. 그 루프(loop)는 제안이 아닙니다. 몇 달 동안 실행했을 때, 동일한 단계의 서로 다른 결과물로서 네 편의 에세이를 만들어낸 실체입니다.
- 표류하던 어휘가 강제된 용어 체크(Rails, Not Rules)가 되었습니다.
- 서로 일치해야 했으나 조용히 일치하지 않았던 두 산출물(artifacts)이 양방향 결합 체크(Couple Both Ways)가 되었습니다.
- "어떤" 교훈이 체크를 얻을 자격이 있는가라는 질문이 비용 테스트(Gates Earned From Failure)가 되었습니다.
- 그리고 이러한 체크의 상당 부분이 코드보다는 프로세스를 제어한다는 깨달음이 제가 방금 인용한 컴파일러 에세이가 되었습니다.
그렇게 읽는다면, 이 네 가지는 별개의 아이디어가 아닙니다. 그것은 하나의 크랭크(crank)를 네 번 돌린 결과입니다. Compound engineering과 저는 이 크랭크에 동의합니다. 다만 우리는 그 끝에서 무엇이 나오는가에 대해서는 의견이 다릅니다. 그들의 버전은 에이전트가 읽을 더 많은 텍스트를 생성하지만, 저의 버전은 텍스트를 불필요하게 만드는 스크립트를 생성합니다.
디프(diff)만 보지 말고 트랜스크립트(transcripts)를 읽으세요
"관찰" 단계에는 그 자체로 한 단락을 할애할 가치가 있는 까다로운 점이 있는데, 왜냐하면 제 방식의 루프가 대부분의 재료를 얻는 곳이 바로 여기이기 때문입니다. Git 히스토리는 무엇이 배포되었는지(what)를 알려줍니다. 하지만 그것은 왜(why)에 대한 기록으로는 형편없으며, 의도적인 작업을 우연한 것처럼 보이게 하거나 우연한 작업을 의도적인 것처럼 보이게 만들 수 있습니다. 세션 트랜스크립트(session transcripts)는 두 번째 요소, 즉 추론 과정, 막다른 길, 그리고 관습이 처음으로 깨진 순간을 기록합니다. 저는 나중에 이를 날짜가 기록된 아카이브처럼 채굴하는데, 이는 트랜스크립트가 불변(immutable)하기 때문에 가능한 일입니다. 당신이 실행에 옮기지 않은 아이디어는 그 안에 사라진 것이 아니라 잠들어 있습니다.
그러한 습관은 또한 저를 정직하게 유지해 줍니다. 이는 제가 창피한 경험을 통해 배운 사실입니다. 이전 에세이를 초안을 작성하던 중, 제가 작성한 연구 노트에서 어떤 깔끔한 문구를 특정 전문가의 말로 인용했습니다. 그 문구는 섹션 전체를 지탱하는 핵심적인 역할을 하고 있었습니다. 출판 전 그녀의 실제 저작물과 대조하여 확인하려 했을 때, 그 문구는 그녀가 쓴 그 어디에서도 나타나지 않았습니다. 제가 지어냈거나 제 노트가 지어낸 것이었으며, 그 산문은 거짓임에도 불구하고 완전히 권위 있게 느껴졌습니다. 실시간 확인(live check)이 저 자신의 자신만만한 기억이 놓친 것을 잡아낸 것입니다. 이것은 용어 게이트(terminology gate)와 동일한 교훈이며, 저 자신에게 적용된 것입니다. 즉, 지속 가능하고 재실행 가능한 검증(re-runnable verification)은 유효하지만, 기억된 버전은 기억하는 사람이 저일지라도 표류(drift)한다는 것입니다.
상태(state)가 아닌 규칙(rule)을 인코딩하세요
교훈이 체크(check)가 되어야 한다는 점을 인정한다면, 살아남는 체크를 만드는 데에는 기술(craft)이 필요합니다. 이에 대해 제가 가진 가장 날카로운 프레임워크는, 에이전트에게 DNA가 신체를 인코딩하는 방식에서 소프트웨어가 빌려올 수 있는 것이 무엇인지 반쯤 진지하게 물었던 세션에서 나왔습니다: "DNA는 이러한 신체 구축 지침을 매우 간결하면서도 효과적인 방식으로 표현해 냅니다. 예를 들어, 각 세포는 혈관으로부터 5개 세포 이내에 위치합니다." 유용한 답변은 압축(compression)의 반대였습니다. 에이전트가 도출한 규칙은 "열거된 현재 상태(enumerated current state)가 아니라, 생성 규칙(generative rule)과 불변량(invariant)을 재도출하는 게이트(gate)를 저장하라"는 것이었습니다.
DNA는 결코 "모든 세포는 혈관으로부터 5개 세포 이내에 위치한다"와 같은 정보를 저장하지 않습니다. 그것은 창발적 속성 (emergent property)입니다. DNA는 대략 "굶주린 세포가 자신을 향해 혈관이 자라나도록 하는 신호를 방출한다"라는 국소적 규칙 (local rule)을 인코딩하며, 그러면 전역적 속성 (global property)은 자연스럽게 도출됩니다. 소프트웨어에서의 비유는 명확합니다. 지침 파일 (instruction file)이 세상의 현재 상태, 즉 수치, 구성 요소 목록, 유효한 목적지 집합 등을 열거하는 모든 곳은, 세상이 변하고 아무도 그 산문을 업데이트하지 않는 순간 부패하게 될 지점입니다. 생성 규칙 (generating rule)을 인코딩하고 체크 (check)가 필요할 때마다 사실을 재생성하게 하십시오. 그러면 동기화해야 할 것이 아무것도 남지 않습니다. 이와 관련된 움직임은 제가 프로모터 패턴 (promoter pattern)이라고 생각하는 것입니다: "게놈 (genome)을 표현하지 말고, 신호가 도착했을 때 유전자 (gene)를 전사 (transcribe)하라." 이는 실무적으로 매 세션마다 문서 전체에 비용을 지불하는 대신, 작업이 필요할 때만 관련 섹션 하나를 로드하는 인덱스를 의미합니다. 저만 이런 결론에 도달한 것이 아닙니다. Birgitta Böckeler는 항상 켜져 있는 컨텍스트 (context)에서 벗어나, "LLM이 관련이 있다고 판단할 때만 에이전트가 해당 스킬 폴더에 있는 모든 것을 로드하는" 스킬 (skills) 중심으로의 동일한 변화를 설명합니다. Thoughtworks의 최신 Technology Radar 또한 같은 이유로 "에이전트 스킬 (Agent Skills)"을 피드포워드 제어 (feedforward controls) 항목 아래에 분류하고 있습니다.
빌려온 본능 중에는 거부해야 할 것도 하나 있습니다. DNA는 세포당 수리 감독관이 없기 때문에 점 돌연변이 (point mutations)에 강하며, 따라서 중복되고 퇴화된 인코딩 (degenerate encodings)을 허용합니다. 하지만 통제된 에이전트 시스템은 그 반대를 원합니다. 당신에게는 수리 에이전트 (repair agent)가 있으므로, '실패 시 폐쇄 (fail-closed)' 방식의 취약함 (brittleness)을 원해야 합니다. 즉, 아슬아슬한 오류가 발생했을 때 조용히 계속 작동하게 두는 것이 아니라, 빌드를 요란하게 깨뜨려야 합니다. DNA의 결함 허용 (fault tolerance) 방식을 빌려오는 것은 단지 드리프트 (drift)를 초록색 체크 표시 뒤로 숨기는 것에 불과합니다. 체크를 취약하게 유지하십시오.
제가 의도적으로 자동화하지 않은 절반
교훈을 기계화하는 것에 대한 종합적인 통찰은, 그것이 거부했던 자리를 당신에게 돌려줍니다. 실패가 인지되고 몇몇 사례가 체크(check)로 승격되는 단계는 자동화된 것도 아니고, 깔끔한 검토 과정(review pass)도 아닙니다. 세션 종료 시점에 기록을 읽고 게이트(gate)를 제안하는 배치 작업(batch job) 같은 것은 없습니다. 판단은 작업이 진행되는 동안 주고받는 대화 속에서 실시간으로 이루어집니다. 보통 "이것을 체크로 만들어야 할까요?"라고 물어야 하는 사람은 바로 저입니다. 왜냐하면 에이전트는 스스로 그 질문을 신뢰성 있게 표면화하지 않기 때문입니다. 제 경험상, 그것은 모델이 도달하도록 튜닝(tuned)된 목표가 아닙니다. 저는 모든 트랜스크립트(transcript)를 읽고 모든 후보 체크를 제안하는 스캐너를 통해 그 앞단(front)을 자동화하는 방안을 검토했으나, 거절했습니다. 그것은 판단의 비중이 너무 높고 노이즈가 많을 것이며, 노이즈가 많은 제안 스트림은 무시하게 되기 마련입니다. 코드를 지배하는 비용 테스트(cost test)는 도구(tooling)에도 적용됩니다. 여기서의 실수는 저렴하고 회복 가능하므로, 정직한 결정은 인지하는 과정을 실시간 토론에 맡기고 도구를 만들지 않는 것입니다. "이 부분은 자동화하지 않기로 결정했습니다"라고 말하는 것은, 나머지 논거들이 증명해내야 할 안티 하이프(anti-hype, 과장 방지)적인 태도입니다.
세션 중간에 제가 배워야 했던 것과 동일한 규율의 더 조용한 버전이 있습니다. 질문을 한 번에 해결하기 위해 래퍼(wrapper)를 구축한 후, 저는 "확실히 하기 위해" 수동으로 내부 로직을 재감사(re-audit)하려는 저 자신을 발견했습니다. 그때 받은 교정은 명확했습니다. "그 단계들을 다시 오케스트레이션(re-orchestrate)하지 말고, 내부를 사전 감사(pre-audit)하지 마십시오. 실행기(runner)가 실제로 고장 났다면 요란하게 검증하는 것은 런타임(runtime)에서 실패할 뿐입니다. 선제적인 그레핑(pre-emptive grepping)은 스크립트가 해결하기 위해 존재하는 내용을 단지 재도출할 뿐입니다." 만약 체크를 신뢰한다면, 신뢰하십시오. 수동으로 다시 확인하는 것은 체크가 제거하기 위해 구축된 바로 그 비용을 그대로 지불하는 꼴입니다.
제가 발을 딛고 있는 곳, 그리고 제가 주장하지 않는 것
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기