본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 17. 17:06

코드가 아닌 프로세스를 컴파일하기: 코딩 에이전트를 위한 기계 검증 워크플로

요약

코딩 에이전트의 신뢰성을 높이기 위해 결정론적인 검증기(verifier)를 프로세스에 통합하는 방법을 다룹니다. 단순한 린터를 넘어, 체크들이 서로를 감독하며 워크플로를 컴파일하는 구조적 접근법을 제안합니다.

핵심 포인트

  • LLM의 높은 생성력과 낮은 일관성을 보완하기 위해 결정론적 검증기 배치 필요
  • 제약 조건을 확인하는 비용이 조건을 만족하는 출력을 생성하는 비용보다 저렴함
  • 단순 규칙 집합을 넘어 체크 간 상호 작용이 발생하는 컴파일러적 구조 지향
  • 에이전트의 실패 모드에 맞춘 맞춤형 검증 게이트 구축의 중요성

에이전트가 구축한 프로젝트를 보호하는 결정론적(deterministic) 체크들은 워크플로를 위한 컴파일러이며, 그중 핵심적인 절반은 코드가 아닌 프로세스를 컴파일합니다.

6월 초 어느 화요일 오후, 오전 내내 몇 가지 기록된 컨벤션(convention)들을 위반할 경우 커밋(commit)을 차단하는 스크립트로 변환하는 작업을 마친 후, 나는 또 다른 작업 대신 코딩 에이전트(coding agent)에게 반쯤 형성된 생각을 입력했습니다:

스크립트 게이트(script gates)를 통해 우리가 코딩 에이전트 기반 워크플로를 위한 컴파일러 (compiler)를 구축하고 있는 것 같아

그 시점에 프로젝트에는 약 36개 정도의 이러한 체크들이 있었습니다. 그중 어느 것도 일반적인 의미에서 무언가를 컴파일하지는 않습니다. 그것들은 grep을 수행하고, 파싱(parse)하며, 하나의 아티팩트(artifact)를 다른 것과 diff(차이 비교)하고, 일치해야 하는 두 요소가 일치하지 않을 때 0이 아닌 값으로 종료됩니다. 하지만 그 느낌은 구체적이었고 사라지지 않았기에, 나는 이를 진지하게 다뤄보고 싶었습니다. 이 느낌 중 무엇이 사실이며, 정확히 어느 지점에서 사실이 아니게 되는지 말입니다. 그 한계점이 바로 유용한 부분입니다.

느낌 이면에 숨겨진 비대칭성

에이전트의 첫 번째 반응은 "컴파일러 (compiler)"가 올바른 직관을 표현하기에는 틀린 단어라는 것이었습니다. 정확한 버전은 생성기/검증기 (generator/verifier) 분리이며, 컴파일러는 그 중 하나의 사례일 뿐입니다.

LLM은 처리량은 높지만 일관성은 낮은 생성기(generator)입니다. 그것은 다작하며 진정으로 창의적이지만, 컨텍스트 압박(context pressure) 하에서 47번 규칙에 대해서는 신뢰할 수 없습니다. LLM은 자신이 편집 중인 파일에서 가장 최근에 본 용어를 사용할 것이며, 그 과정에서 유창하고 자신감 있게 행동할 것입니다. 신뢰할 수 없는 생성기를 신뢰할 수 있게 만드는 가장 저렴한 방법은 생성기에게 훈계하는 것이 아닙니다. 그 앞에 결정론적이고 저렴한 _검증기 (verifier)_를 배치하고 비대칭성을 활용하는 것입니다. 제약 조건이 유지되는지 확인하는 것은, 매번 첫 시도에 그 조건을 만족하는 출력을 생성하는 것보다 훨씬 저렴하고 훨씬 더 신뢰할 수 있습니다. 컴파일러는 특정 언어를 중심으로 고정된 그 패턴입니다. 내가 구축하고 있었던 것은 내 생성기의 특정 실패 모드(failure modes)에 맞춘 검증기였습니다.

이러한 비대칭성(asymmetry)이 전혀 새로운 것이 아니라는 점은 즉시 인정해야 합니다. 이는 우리의 툴링(tooling)에서 가장 오래된 아이디어입니다. 타입 체커(Type checkers), 린터(linters), 지속적 통합(continuous integration), 그리고 겸손한 테스트 스위트(test suite)는 모두 특정 속성을 검증하는 것이 그 속성을 보장하는 코드를 생성하는 것보다 비용이 저렴하기 때문에 존재합니다. 컴파일러 비유(compiler metaphor) 또한 새로운 것이 아닙니다. 사람들은 한동안 프롬프트(prompts)와 파이프라인(pipelines)에 대해 이 비유를 사용해 왔습니다. 따라서 만약 이 에세이가 단순히 "체크가 에이전트를 더 신뢰할 수 있게 만든다"는 내용뿐이었다면, 이는 익숙하고 다소 지루한 주장이었을 것입니다. 흥미로운 부분은 더 좁은 범위에 있으며, 컴파일러가 약속하는 것과 이러한 체크들이 실제로 전달하는 것 사이의 간극에 존재합니다.

왜 이것이 단순한 린터의 집합이 아니라 컴파일러처럼 느껴지는가

대부분의 린트 스위트(lint suites)는 한 더미에 불과합니다. 규칙을 축적하고 실행하지만, 그중 어느 것도 다른 규칙에 대해 알지 못합니다. 이것이 컴파일러의 형태를 갖춘 무언가로 변했다는 신호는, 제가 다른 체크들을 감독하는 역할을 하는 체크들을 작성하기 시작했을 때였습니다. 즉, 모든 게이트(gate)를 읽고 각 게이트가 등록되었는지, 컨벤션(convention)에 따라 이름이 지정되었는지, 그리고 커밋 훅(commit hook)에 동일한 방식으로 연결되었는지 확인하는 스크립트와 같은 것입니다. 규칙에 대한 규칙이 기계적으로 강제되는 것입니다.

이러한 메타 계층(meta-layer)은 린트 더미와 컴파일러적 사고를 가진 사람이 정적 의미론(static semantics)이라고 부르는 것 사이의 경계선입니다. 더미는 "오늘 이 특정 트립와이어(tripwires)들이 작동하지 않았다"라고 말합니다. 반면 의미론(semantics)은 "규칙 그 자체가 잘 형성되어 있으므로, 잘못 형성된 규칙의 전체 클래스가 존재할 수 없다"라고 말합니다. 이 경계선을 넘는 것이 바로 워크플로가 체크리스트가 아닌 문법(grammar)을 가진 것처럼 느껴지게 만드는 요소입니다. 또한, 결과적으로 드러났듯이, 이것은 여러분이 가장 의심해야 할 바로 그 느낌이기도 합니다.

비유가 깨지는 세 가지 지점

주의 깊은 독자라면 찬사 섞인 비유를 불신해야 합니다. 따라서 이 비유가 무너지는 지점을 말씀드리겠습니다. 세 가지 파손 지점이 있으며, 각각은 구조를 지탱하는 핵심적인 부분입니다.

이것들은 린터 (linters)이지, 타입 시스템 (type system)이 아닙니다. 컴파일러는 특정 형태의 보증을 제공하는데, 이를 Robin Milner는 _건전성 (soundness)_이라고 명명했습니다. 즉, 프로그램이 타입 체크 (type-check)를 통과하면 특정 범주의 오류는 발생이 불가능해집니다 — "잘 정의된 타입의 프로그램은 잘못될 수 없다 (well-typed programs cannot go wrong)." 반면 저의 체크 방식은 훨씬 더 약한 형태의 보증을 제공합니다. 즉, "이 특정 커밋 (commit)에서 이 특정 트리프와이어 (tripwires)가 작동하지 않았다"는 수준입니다. 철자 금지 목록 (spelling denylist)은 목록에 있는 단어는 잡아내지만, 아무도 목록에 올리지 않은 유의어에는 무지합니다. 응답 형태를 grep으로 찾는 체크는 그 형태가 존재한다는 것을 증명할 뿐, 그 이면의 동작이 올바르다는 것을 증명하지는 않습니다. 제가 신경 쓰는 불변량 (invariant)과 이를 근사하는 grep 사이의 간극이 바로 실제 버그가 계속해서 살아 숨 쉬는 지점이며, 통과된 테스트 스위트 (test suite)는 그 간극을 밝히기보다는 오히려 숨겨버립니다. 저는 이로 인해 발생하는 위험, 즉 통과된 체크가 능동적인 경계심을 잘못된 신뢰로 변질시킨다는 점에 대해 별도의 에세이인 Gates Earned From Failure를 할애하여 다루었습니다. 컴파일러와 관련하여 이 점을 말하자면, "컴파일러"라는 단어가 이러한 체크들이 획득하지 못한 건전성 (soundness) 보증을 몰래 끼워 넣고 있다는 것입니다.

이것은 설계된 것이 아니라 축적된 것이며, 소스 문법 (source grammar)이 존재하지 않습니다. 실제 컴파일러는 언어 정의 (language definition)에서 시작합니다. 누군가가 잘 형성된 프로그램 (well-formed program)이 무엇인지 기록하고, 체커 (checker)가 그 문서를 강제합니다. 저의 "잘 형성된 워크플로 (well-formed workflow)"는 결코 문서화된 적이 없습니다. 그것은 전적으로, 오직 특정 날에 존재하는 체크들의 합집합에 의해 정의될 뿐이며, 그 각각의 체크들은 이미 저를 물었던 하나의 사고 (incident)로부터 반응적으로 탄생했습니다. 따라서 에이전트 (agent)가 준수해야 하는 사실상의 언어 (de-facto language)는 "오늘 통과되는 무엇이든"이며, 그 사이의 간극은 다음 사고가 발생하여 드러나기 전까지는 보이지 않는 상태로 남아 있습니다. 항상 로드되어 있는 지침 파일 (instruction file)이 제가 가진 문법에 가장 가까운 것이지만, 그것은 명세 (specification)가 아닌 산문 형태의 근사치일 뿐입니다.

프로그래머와 언어 설계자는 동일한 에이전트입니다. 이것이 여러분을 가장 걱정하게 만들 요소입니다. 실제 컴파일러는 자신이 판정하는 코드와 적대적으로 독립적(adversarially independent)입니다. 즉, 언어를 설계한 사람들은 여러분의 프로그램을 본 적이 없습니다. 하지만 저의 설정에서는 동일한 에이전트가 결과물(artifact)을 작성하고, 그 결과물의 미래 버전을 판정할 게이트(gate)를 작성합니다. 따라서 게이트는 저자의 사각지대(blind spot)를 충실히 인코딩할 수 있으며, 그 사각지대에 대해서는 영원히 작업이 깨끗하다고 인증해 버릴 수 있습니다. 규율을 통한 강제(Enforcing over discipline)는 명확한 한계가 있는 좋은 규칙입니다. 게이트는 누군가가 이미 기계화(mechanize)하기로 생각한 것만을 포착할 수 있기 때문입니다. Birgitta Böckeler은 테스트에 대해 한 단계 더 높은 차원에서 동일한 관찰을 하며, "에이전트가 테스트까지 생성했을" 때 피드백이 보이는 것보다 훨씬 약해진다는 점을 지적합니다. 생성자가 작성한 검증기(verifier)는 그것이 검증하는 대상과 독립적이지 않습니다.

프로세스를 컴파일하는 절반

여기가 이 에세이를 쓸 가치가 있는 부분입니다. 제가 실제로 수십 개의 체크 항목을 살펴보고 각각 무엇을 검증하는지 물었을 때, 그중 상당수는 코드를 전혀 검사하고 있지 않았습니다. 그것들은 _프로세스(process)_를 검사하고 있었습니다.

어떤 것은 서면 명세(specification)가 필요한 작업이 구현 단계(implementation phase)에서 바로 시작될 수 없도록 강제합니다. 어떤 것은 선언된 의존성(dependency)이 오래된(stale) 작업을 표시합니다. 어떤 것은 수정 사항의 생명주기(lifecycle)를 강제합니다: 제안(proposed), 검토(reviewed), 수락(accepted) 순서로, 건너뛰기 없이 진행되어야 합니다. 이 중 그 어느 것도 프로그램 로직의 한 줄을 읽지 않습니다. 각각은 인간 팀에서는 누구의 컴파일러에도 들어있지 않지만 모두의 머릿속에 들어있는 소프트웨어 개발 생명주기(SDLC) 규율의 한 조각을 인코딩합니다. 우리는 이것을 전문성(professionalism), 혹은 시니어(senior)가 되는 것, 또는 이곳의 방식이 어떻게 돌아가는지 아는 것이라고 부릅니다.

인간 팀은 거의 결코 그 계층을 기계화하지 않으며, 그 이유는 문화적인 것이 아니라 경제적인 것입니다. 사람들은 며칠 또는 몇 주에 걸쳐 컨텍스트 (context)를 유지하고, 판단력을 통해 규칙이 언제 적용되는지 알며, 커밋 (commit)에 남겨진 이름이 책임 (accountability)을 제공합니다. 이 세 가지 요소는 팀이 온보딩 문서 (onboarding doc)에 "우리는 수정 프로세스 (amendment process)를 준수한다"라고 적어두고 준수 여부를 가정할 수 있게 해주는 바로 그 요소들입니다. 이를 강제하기 위한 스크립트를 구축하는 비용은, 대부분 어쨌든 기억하고 있는 소규모 팀에서 이를 통해 방지할 수 있는 드리프트 (drift) 비용보다 더 많이 들 것입니다.

하지만 에이전트 (agent)의 경우, 이 경제적 계산이 급격하게 역전됩니다. 생성기 (generator)는 세션 (session) 간의 연속성이 없으므로 드리프트 (drift) 발생률이 높습니다. 또한 동일한 에이전트가 몇 분 안에 당신을 위한 강제 검사 (enforcing check)를 작성해 줄 것이므로, 기계화 (mechanizing) 비용은 거의 제로에 가깝습니다. 드리프트가 빈번하고 게이트 (gates) 비용이 거의 들지 않을 때, 당신은 소프트웨어 팀의 이전 전체 역사에서 공식화할 가치가 없었던 방법론을 공식화하는 쪽으로 합리적으로 끌리게 됩니다. 이것이 바로 컴파일러 (compiler) 느낌 아래 숨겨진 실제 발견입니다. 제가 제 도메인 어휘를 위해 맞춤법 검사기를 추가했다는 것이 아니라, 에이전트 주도 작업 (agent-directed work)의 경제성이 개발 프로세스 자체를 기계 검증 제약 조건 (machine-checked constraints)으로 컴파일할 가치가 있게끔 조용히 만들어 놓았다는 것입니다.

지시 파일이 더 이상 매뉴얼이 아니게 되는 지점

제가 그 "컴파일러" 메시지를 보낸 바로 그 시간, 저는 이 비유가 실제라면 성립할 수밖에 없는 요청을 뒤이어 보냈습니다:

일부 지시 사항을 은퇴시키거나, 압축하거나, 혹은 (HANDBOOK으로) 옮길 수 있을까요… 에러 (Errors)와 경고 (warnings)가 이미 에러를 간략하게 설명하고 더 자세한 내용이 담긴 HANDBOOK 섹션을 가리키고 있습니다.

일단 검사(check)가 컨벤션(convention)을 소유하게 되면, 모델에게 해당 컨벤션을 기억해달라고 간청하던 상시 로드 파일(always-loaded file) 내의 문단은 불필요한 짐이 됩니다. 기억하는 역할은 게이트(gate)가 수행합니다. 따라서 문장은 삭제하고, 에러 메시지가 실제 검사가 발생했을 때만 읽히는 더 긴 설명으로 향하는 포인터(pointer)를 전달하게 합니다. 해당 세션이 끝날 무렵, 프로젝트는 세 가지 깔끔한 계층으로 정착되었습니다: 지시 사항을 기술하는 상시 로드 파일, 메커니즘을 설명하는 핸드북(handbook), 그리고 이를 강제하는 게이트(gates)입니다. 문장은 더 이상 타입 체커(type-checker)를 중복해서 작성하지 않게 되었습니다.

이것은 컴파일러 아키텍처(compiler architecture)를 문자 그대로 구현한 것이며, 지시 파일(instruction file)의 용도를 재정의합니다. 그것은 에이전트(agent)가 따르도록 신뢰받는 매뉴얼이 아닙니다. 그것은 문법(grammar)에 더 가깝습니다. 즉, 강제 계층(enforcement layer)이 구속력을 갖게 만드는 작고 안정적인 의도 선언입니다. 정확성(Correctness)은 문장에서 검사(checks)로 이동했고, 문장은 더 짧아졌으며 자신의 역할에 대해 더 정직해졌습니다.

어떤 불변량(invariants)이 타입 규칙(type rule)의 자격을 얻는가

이 과정을 통해 얻게 되는 기술은 "더 많은 게이트를 작성하라"는 것이 아닙니다. 검사 제품군(check family)은 계속해서 늘어나기 마련이며, 모든 것을 수용하는 검증기(verifier)는 모든 것을 거부하는 검증기만큼이나 쓸모가 없습니다. 진짜 판단 기준은 어떤 불변량(invariants)이 타입 규칙(type rule)으로 승격될 만큼 충분히 저렴하고, 충분히 건전하며(sound), 오탐(false positives)에 대해 충분히 조용한가, 그리고 어떤 것이 규율(discipline)로 남아 있어야 하는가입니다. 그 판단은 비용 테스트(cost test)이며, 저는 이를 여기서 반복하는 대신 Gates Earned From Failure에서 상세히 다루었습니다: 편차(drift)가 실제라는 증거에 그 편차의 비용을 곱한 값이 검사의 기존 비용보다 클 때 규칙을 구축하십시오.

컴파일러 프레임워크(compiler framing)는 그 테스트에 한 가지 날카로운 경고를 더합니다. 과도하게 엄격한 타이핑(Over-strict typing)은 안전한 기본값이 아니라, 실제적인 실패 모드(failure mode)입니다. 이 모든 것이 구체화되었던 바로 그날 아침, 저는 제가 구축했던 게이트(gate) 하나를 제거했습니다. 그 게이트가 너무 많은 정당한 사례들에 대해 작동하여, 그 소음(noise)을 감수할 가치가 없었기 때문입니다. 컴파일러 용어로 말하자면, 그 게이트는 건전한 프로그램(sound programs)의 컴파일을 거부하고 있었으며, 유효한 코드를 거부하는 타입 시스템(type system)은 유효하지 않은 코드를 허용하는 시스템만큼이나 망가진 것입니다. 거부되었지만 올바른 커밋(commit)은 놓친 버그와 동일한 교훈을 줍니다. 즉, 규칙이 틀렸다는 것입니다. 보정(Calibration)은 양방향으로 이루어집니다.

솔직한 한계

이것은 약 150,000행 규모의 코드베이스를 가진 한 개인의 경험입니다. 저는 도메인 모델(domain model)과 워크플로(workflow)를 모두 소유하고 있으므로, 언어 설계자와 프로그래머가 동일한 에이전트(agent)인 상황은 사고 실험이 아닌 저의 일상적인 현실입니다. 이러한 붕괴는 프로젝트의 가장 날카로운 한계이며, 제가 이를 탈피했다고 말씀드릴 수는 없습니다. 제가 말씀드릴 수 있는 것은, 무엇을 구축할지 의도적으로 결정하는 것만큼이나 무엇이 게이트로 설정되지 _않았는지_를 감사(audit)하려고 노력한다는 점뿐입니다. 왜냐하면 테스트 스위트(suite)는 알려진 함정(tripwires)의 부재를 측정할 뿐, 건전성(soundness)을 측정하지는 않기 때문입니다.

또한 소스 문법(source grammar)도 존재하지 않으며, 이 방식이 건강한 버전으로 문법을 갖게 될 수 있을지도 확신할 수 없습니다. 이 시스템에 대한 솔직한 설명은 "내 워크플로를 위한 컴파일러"가 아니라, "얇은 자가 거버넌스(self-governance) 층이 형성되어 곳곳에서 컴파일러처럼 작동하기 시작한, 검증기(verifiers)들이 점진적으로 쌓여 만들어진 더미"입니다. 이것이 실제 명세(specification)를 가진 무언가로 경화될지, 아니면 잘 관리되는 더미로 남을지는 제가 아직 답하지 못한 질문입니다. 그리고 경제적 논거 전체는 단독 저자가 게이트를 구축하는 바로 그날 자신의 편차(drift)를 마주한다는 점에 달려 있습니다. 팀 단위에서는 컨텍스트 비용(context cost)이 복리로 증가하며, 오탐(false-positive) 분류 작업은 원래의 실패를 전혀 느끼지 못했던 사람들에게 전가됩니다. 저는 단독 작업 사례에 대한 증거를 가지고 있으며, 이러한 형태가 팀으로 옮겨가더라도 유지될 것이라는 의구심을 가지고 있지만, 이것이 반드시 같은 의미는 아닙니다.

이것이 일반화되는 이유

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0