실행하세요, 읽지 말고: 코딩 에이전트는 체크 스크립트를 실행하는 대신 예측합니다
요약
코딩 에이전트가 체크 스크립트를 직접 실행하는 대신 코드를 읽고 결과를 예측하려는 경향이 비용과 정확도 측면에서 안티 패턴임을 지적합니다. 에이전트가 구현부를 분석하여 동작을 추론하게 만드는 대신, 명확한 지침과 실행을 통해 결정론적인 결과를 얻어야 함을 강조합니다.
핵심 포인트
- 에이전트가 스크립트를 실행하는 대신 코드를 읽고 결과를 예측하는 것은 비용 효율적이지 않음
- 코드 분석을 통한 결과 예측은 모델이 잘못된 추론을 하게 만드는 안티 패턴임
- 에이전트가 게이트의 작동 여부를 추론하고 있다면 이미 설계 의도에서 벗어난 것임
- 체크 스크립트는 에이전트에게 단순한 인프라가 아닌 소비해야 할 텍스트로 인식됨
당신의 체크 스크립트에 대한 읽기 권한을 가진 코딩 에이전트는 스크립트를 실행하는 대신 이를 예측할 것입니다. 예측은 비용이 더 많이 들고 정답을 틀릴 수 있습니다.
내가 디렉팅하던 한 세션 앞에 작은 결정 사항이 놓여 있었습니다. 위임된 세션이 특정 결정 형태를 따르도록 유도하는 선택적 가드(guard)를 켤 것인지 여부였습니다. 이 가드는 비활성 상태로 배포됩니다. 소유 브리프(brief)에는 단 한 문장으로 명시되어 있습니다: 이를 켜는 것은 사용자의 자체 등록 사항이며, 그렇지 않으면 이 기능은 비활성 상태의 텍스트(inert prose)이며, 이는 이 저장소의 명시된 기본값이라는 내용입니다.
세션은 그 문장을 읽지 않았습니다. 해당 섹션을 가져오려는 첫 번째 시도가 빈 값으로 돌아오자, 더 나은 헤딩(heading)으로 쿼리를 재시도하는 대신 가드의 구현부를 열어 28줄 전체를 읽었습니다. 그리고 해당 기능이 일반적인 경로(common path)에서 깨끗하게 종료되며 활성화해도 무해하다는 점을 스스로 확인했습니다. 그러고 나서 가드를 관리형 설정 파일(governed configuration file)에 연결했습니다. 그 과정에서 세션은 자신의 추론 과정에 해당 인터페이스를 통제하는 두 가지 체크에 대한 예측을 기록했습니다: 해당 체크들은 추적되는 설정 파일을 관리하지만, 이것은 추적되지 않으므로 아무것도 실행되지 않을 것이라는 내용이었습니다.
나는 그 수정을 포착하고 한 가지 질문을 던졌습니다:
이 프로젝트의 목표는 간결한 지침을 제공하고, 게이트(gates)가 이를 저렴하게 검증하면서 실패 시 추가 컨텍스트를 제공하는 것입니다. 게이트가 실행되는 것을 방지하기 위해 게이트 코드를 분석하는 것은 비용이 많이 드는 안티 패턴(anti-pattern)입니다. 당신의 settings.local.json 수정은 안티 패턴이 아니었나요?
그 후 실제로 체크가 실행되었습니다. 수정 사항이 적용된 상태와 제거된 상태 모두 결과는 '그린(green)'이었습니다. 배터리(battery) 내의 어떤 체크도 이 실수를 잡아낼 수 없었을 것입니다. 왜냐하면 권고용 가드는 구조적으로 무해하기 때문입니다. 이 문제에 대한 유일한 권위는 브리프였고, 브리프는 세션이 소스 코드에 대한 고고학적 조사(archaeology)를 수행하는 대신 건너뛰어 버린 유일하고 저렴한 정보원이었습니다.
그것이 바로 한 에피소드에 담긴 실패의 전부입니다. 세션은 구현 내용을 읽기 위해 토큰을 지불했고, 답변을 생성했지만, 그 답변은 메커니즘 자체가 결코 포착하지 못했을 방식으로 틀렸습니다. 해당 세션의 사후 분석(post-mortem) 한 줄은 제가 본 어떤 세션의 규칙 중 가장 날카로운 진술입니다:
나 자신을 잡아낼 수 있는 신호: 만약 내가 게이트(gate)가 작동할지 여부에 대해 추론하고 있다면, 나는 이미 궤도(rails)를 벗어난 것이다.
강제 스크립트(enforcement script)는 불활성 인프라가 아니다
체크(check)에 대한 반사적인 모델은 그것이 그저 그 자리에 놓여 작동하거나 작동하지 않는 것이라고 생각하는 것입니다. 그것은 배관(plumbing)과 같습니다. 아무도 배관을 유지하는 데 비용이 얼마나 드는지 생각하지 않습니다.
하지만 에이전트가 읽을 수 있는 저장소(repository) 내의 체크 스크립트는 해당 에이전트에게 단순한 배관이 아닙니다. 그것은 텍스트이며, 텍스트는 에이전트가 가장 잘 소비하는 대상입니다. "이 작업을 수행하고 체크 결과가 무엇인지 확인하라"는 상황에 직면했을 때, 모델은 항상 손에 닿는 곳에 더 저렴해 보이는 옵션을 가집니다. 바로 체크를 읽고 그것이 무엇이라고 말할지 알아내는 것입니다. 그 옵션이 더 저렴해 보이는 이유는 읽는 것이 모델이 하루 종일 하는 일이기 때문입니다. 하지만 그것은 더 저렴하지 않습니다. 체크가 결정론적(deterministically)으로 수행할 작업을 추론하는 데 토큰을 낭비하게 만들며, 모델이 동작을 관찰하는 대신 구현(implementation)으로부터 동작을 추론하기 때문에 더 나쁜 답변을 생성하게 됩니다.
저는 이것이 단순한 실수로 취급하기를 그만둘 만큼 충분히 많은 횟수를 목격했습니다.
이것은 반복되며, 산문(prose)도 이를 막지 못한다
설정(config) 수정 6일 전, 다른 세션은 위임된 단계(delegated stage)를 발송할지 여부를 결정해야 했습니다. 예산 가드(budget guard)는 모든 위임 호출 시 사전 도구 훅(pre-tool hook)으로 실행되며, 이는 제가 이전에 작성한 바와 같이 동일한 가로채기 지점(interception point)이며, 시도하는 순간 권위 있게 규칙을 적용합니다. 해당 세션은 발송을 시도하고 가드의 답변을 기다리는 대신, 가드의 판결을 미리 계산하기 위해 대기 루프(waiting loop)를 구축했습니다. 그리고 소유 문서에는 해당 판결이 절대 차단하지 않는다고 명시되어 있음에도 불구하고, 권고(advisory) 판결을 차단(blocking)으로 읽었습니다. 저의 반응은 다음과 같습니다:
과도하게 조심스러운 태도에 대한 당신의 위 지적에 대해 - 이것이 바로 불필요한 토큰 소비의 정확한 원인입니다. 예측하려고 토큰을 태우는 대신, 직접 액션을 시도해보고 차단되는지 확인해야 합니다. 저는 이 점이 지침에 명확히 명시되어 있다고 생각했습니다.
세션은 이에 동의했으며, '항상 시도하는 것이 승리한다'는 구조적 이유를 다음과 같이 인정했습니다:
실패 시 차단되는 가드(fail-closed guard)가 바로 예측을 불필요하게 만드는 요소입니다. ... 시도하는 것이 사전 계산(pre-computing)보다 엄격하게 더 저렴하며, 저의 폴링 루프(polling loop)가 얻을 수 있었던 유일한 정보는 훅(hook)이 어차피 저에게 전달했을 정보뿐이었습니다.
두 가지 비용이 존재하며, 두 번째는 사람들이 놓치는 부분입니다. 첫 번째는 공짜인 무언가를 계산하는 루프에 소모되는 토큰 비용입니다. 두 번째는 계산된 답변이 틀렸다는 점입니다. 문서 어디에도 판결이 차단(blocks)을 유발한다고 명시되어 있지 않았으며, 세션이 스스로 주의 사항을 만들어낸 것입니다. 예측은 동일한 결과에 도달하는 더 느린 경로가 아닙니다. 그것은 비싼 비용을 치르고 도달한, 더 신뢰할 수 없는 다른 결과입니다.
여기에는 제가 이전에 작성했던 내용과 유사한 가족적 유사성이 있습니다: 에이전트가 자신의 작업이 통과되었다고 보고하는 것은 하나의 주장이며, 믿기보다는 검증을 원하는 것입니다. 이는 다른 대상을 향한 동일한 불신입니다. 그 경우 의심스러운 산출물은 에이전트가 무엇을 했는지에 대한 설명입니다. 여기서는 메커니즘이 무엇이라고 말했을지에 대한 에이전트의 모델입니다.
제가 프롬프트 형태의 해결책을 찾는 것을 멈추게 된 것은 다음 대화 때문이었습니다. 세션은 이를 기록하고 수정하겠다고 제안했습니다. 저는 이것이 처음이 아니라는 점을 지적했고, 세션은 제가 설명한 것보다 더 나은 방식으로 그 점을 인정했습니다:
"기록 및 수정되었습니다"라는 말이 가치 없다는 당신의 말이 맞습니다. — 그것은 이 세션과 함께 사라질 것이기 때문입니다.
매 세션마다 다시 적용해야 하는 지침이, 더 저렴해 보이고 언제든 사용 가능한 가독성 있는 대안에 맞서 경쟁하고 있다면, 그것은 제안이 아니라 유혹과 경쟁하고 있는 것입니다. 그것은 가이드레일 (rail)로서 기능하지 못합니다. 저는 이전에 이에 대해 길게 논증한 바 있으며, 여기서 명백한 귀결 이상의 논쟁을 다시 하지는 않겠습니다. 즉, 산문 (prose)이 도메인 어휘를 구속하지 못한다면, 이것 또한 구속하지 못한다는 것입니다.
전환점: 설계 속성으로서의 불투명성 (opacity)
그렇다면 불편한 선택지가 남습니다. 만약 소스 (source)가 예측을 유도하는 원인이라면, 소스를 제거하십시오.
이 문제는 쉘 (shell) 기반의 체크 배터리 (check battery)를 컴파일된 바이너리 (compiled binary)로 옮길지 여부를 검토하던 아주 평범한 평가 과정 중에 자연스럽게 떠올랐습니다. 논의된 이유들은 지루할 정도로 뻔한 것들이었습니다. Linux를 넘어선 이식성 (portability), 독립적인 릴리스 주기를 가진 독립 유틸리티들의 툴체인 (toolchain), 린터 (linter) 대신 실제 컴파일러를 사용하는 것 등입니다. 그러다 마지막에 이것이 나열되었습니다:
덤으로 덧붙이자면, 우리는 코딩 에이전트 (coding agents)가 체크 스크립트 (check scripts)를 읽고 실행 결과(execution results)를 예측하는 대신, 해당 게이트 (gate)가 확인하는 라이프사이클 액션 (lifecycle actions)을 수행하고 실패한 게이트가 수정 지침을 제공하도록 유도하려고 노력해 왔습니다. 하지만 스크립트 소스 코드 (script source code)의 존재가 정기적으로 문제를 일으키며 불필요한 토큰 (token) 낭비를 초래하고 있습니다.
일주일간의 측정 결과, 순위는 뒤집혔습니다. 목록에 있는 다른 모든 이점은 평범한 엔지니어링의 영역입니다. 하지만 이 이점은 체크 (check)의 성격 자체를 변화시킵니다:
에이전트는 바이너리의 소스를 읽을 수 없습니다. 고객 프로젝트에서 게이트는 컴파일된 상태로 배포되므로, 에이전트는 구현 내용을 조사하여 결과가 어떻게 나올지 추론할 수 없으며 반드시 실행해야만 합니다. 이는 오라클 우선 (oracle-first) 방식을 에이전트가 조용히 우회할 수 있는 교리적 문구가 아닌, 구조적으로 (structurally) 강제되도록 만듭니다.
일반적으로 진술하자면: 체크(check)가 설계된 출력은 그 자체의 판정(verdict)과 수정 텍스트입니다. 출처는 인터페이스가 아니라 구현(implementation)입니다. 구현을 읽고 인터페이스를 예측하는 소비자(consumer)는 잘못된 표면(surface)을 소비하고 있는 것이며, 이 표면을 사용 불가능하게 만드는 것은 체크가 약하다는 고백이라기보다는 정당한 설계 선택입니다.
이것은 엔지니어링 본능에 반하는 부분이며, 그래야 합니다. 읽을 수 있는 도구(readable tooling)는 좋습니다. 감사 가능한 도구(auditable tooling)는 더 좋습니다. 이 둘 중 어느 것도 논쟁의 대상이 아닙니다. 주장은 더 좁으며, 오직 한 가지 특정 소비자에게 관한 것입니다: 눈앞에 놓인 모든 것을 읽게 될 확률적 텍스트 생성기입니다. 그 리더에게 있어 읽을 수 있는 구현은 설계된 출력과 경쟁하는 채널이며, 외관상의 비용(apparent cost)에서는 승리하지만 정확도(accuracy)에서는 패배합니다.
경계: 구현은 숨기고, 개요(brief)는 절대 숨기지 마라
명백한 과잉 보정(over-correction)은 모든 것을 숨기는 것이고, 저는 바로 그것을 제안했습니다. 같은 세션에서 제가 제시한 제안은 명세서 문서와 소스 코드 접근을 모두 차단하는 것이었습니다. 세션은 그 절반을 거부했고, 이 거부가 이 기록들에서 가장 유용한 부분입니다:
SPEC과 소스 접근을 차단하면 전체 방법론이 의존하는 것을 망가뜨릴 것입니다—명세서 우선(spec-over-precedent), 즉 “소유 문서가 진실이다.” 문서 접근을 제거하면 저는 과도하게 생각하는 것조차 멈추지 않고, 이전 사례에서 추측하기 시작할 것이며, 이것이 이 세션 초반에 당신이 지적했던 실패입니다.
맞습니다. 그리고 그것은 논거가 실제로 필요로 하는 선을 그립니다. 세 가지 표면, 세 가지 다른 답변입니다:
| 표면 (Surface) | 정의 | 에이전트가 읽어야 하는가? |
|---|---|---|
| 브리프 (The brief) | 메커니즘의 _목적_이 무엇인지, 그리고 당신이 그것을 건드려야 하는지 여부 | 예 — 최대한 읽기 쉬워야 함. 이것은 그라운드 트루스 (Ground truth)임. |
| ... |
브리프를 제거한다면 당신은 에이전트를 고친 것이 아니라, 에이전트가 생성하는 실패의 종류를 바꾼 것에 불과합니다. 에이전트는 체크를 예측하는 것을 멈추고, 마지막으로 유사했던 변경 사항이 어떤 모습이었는지 모방하기 시작할 것입니다. 이는 비용은 많이 들지만 눈에 보이는 실패를, 비용은 적게 들지만 눈에 보이지 않는 실패로 맞바꾸는 것입니다. 가드 (guard)의 소스 코드를 잘못 읽었던 바로 그 세션은, 자체 사후 분석 (post-mortem)을 작성할 때는 이 구분을 정확하게 해냈습니다. 소스는 어떤 사물이 무엇을 하는지 알려주고, 브리프는 그것이 무엇을 위한 것인지 알려주며, 오직 두 번째만이 당신이 그것을 건드려야 하는지 여부에 답해줍니다.
이것이 아닌 것
세 명의 이웃이 충분히 가까이 앉아 있어, 논거가 그들에게 고개를 끄덕이는 대신 직접 마주해야만 합니다.
커크호프스의 원칙 (Kerckhoffs's principle)이 실제적인 반론입니다. 암호 시스템은 키를 제외한 모든 것이 공개되더라도 안전해야 하며, 섀넌 (Shannon)의 버전은 적이 당신의 설계를 즉시 완전히 파악할 것이라고 가정하라고 말합니다. 은닉을 통한 보안 (Security through obscurity)은 실패하며, 그 이유는 타당합니다. 저는 이 중 어느 것도 부정하지 않습니다. 왜냐하면 저는 다른 질문에 답하고 있기 때문입니다. 커크호프스는 적대적인 독자가 당신의 시스템을 깨뜨릴 수 있는지를 묻습니다. 이 논의는 협력적인 독자의 읽기가 당신에게 어떤 비용을 발생시키는지를 묻습니다. 에이전트는 체크를 공격하는 것이 아닙니다. 에이전트는 이용 가능한 경로 중 가장 도움이 될 것 같은 경로를 택하고 있는데, 그것이 공교롭게도 가장 비용이 많이 드는 경로일 뿐입니다. 그리고 누구도 읽지 않아야 그 정확성이 유지되는 체크라면, 당신이 이 논의의 어느 쪽을 취하든 그것은 나쁜 체크일 것입니다. 여기서 불투명성 (Opacity)은 적에 맞서 강점을 사는 것이 아닙니다. 그것은 협력자로부터 유혹적인 잘못된 경로를 제거하는 것입니다. 그리고 이것은 얕은 종류의 불투명성입니다. 이 논거에서 소스를 세상으로부터 비밀로 유지해달라고 요구하는 것은 아무것도 없으며, 단지 에이전트가 이미 읽고 있는 워킹 트리 (working tree)에 소스가 놓여 있는 것을 멈춰달라고 요구할 뿐입니다.
Goodhart의 법칙은 인접하지만 같은 실패는 아닙니다. Marilyn Strathern이 표현한
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기