이 장면은 보자마자 알아볼 수 있어야 하니, 여기서부터 시작하겠습니다. 게임을 다 만들었는데 재시작하면 점수가 0으로 돌아갑니다. 그래서 에이전트에게 묻습니다:
재시작하면 왜 점수가 사라지나요?차분하고 매끄러운 답이 돌아옵니다:
프로젝트에는 이미 점수 저장 기능이 있는데, 기록이 어디에서도 켜져 있지 않을 뿐입니다.
설정 하나만 바꾸면 재시작 후에도 결과가 유지됩니다.확인하러 가 봅니다. 프로젝트에는 그런 설정도, 점수 저장 기능도 없고 처음부터 없었습니다. 코드 한 줄, 흔적 하나 없습니다. 답변은 여러분의 코드를 분석한 것처럼 읽혔지만, 실제로는 이런 프로젝트가 보통 어떻게 만들어지는지에 대한 추측이었습니다.
그런데 그 전에 머뭇거림도, 단서도 없었습니다. 톤은 앞서 정확했던 모든 답변과 똑같습니다. 도구가 고장 난 것도 아니고, 모델이 어제보다 나빠진 것도 아닙니다.
원인은 답변이 만들어지는 방식에 있습니다. 모델은 저장소에서 완성된 사실을 꺼내 오지 않습니다. 여러분의 텍스트에 이어질 확률이 높은 내용을 한 조각씩 골라 나갈 뿐입니다. 그런데 확률이 높은 것과 정확한 것은 서로 다릅니다. 모델이 본 적 없는 여러분의 프로젝트 이야기라면 더욱 그렇습니다. 지어냈지만 그럴듯한 세부 내용을 환각이라고 부릅니다.

아래에서는 함정이 어디에 있는지, 실수는 왜 늘 차분한 톤으로 나오는지, 그리고 이런 경우 대부분을 막아 주는 세 가지 검증을 살펴봅니다.
모델이 여러분의 요청에서 보는 것
여기에는 가장 단순한 함정이 있습니다. 모델이 여러분의 프로젝트를 들여다보고 있는 것처럼 느껴진다는 점입니다. 실제로 모델은 자신에게 전달된 것만 보고, 전달되는 것은 다음과 같습니다:
- 내장된 지식. 학습 과정에서 모델의 파라미터에 자리 잡은 패턴입니다. 사실과 출처가 적힌 카드가 아니라 숫자이기 때문에, 잘 아는 사실도 모델이 부정확하게 재현할 수 있습니다. 게다가 시점의 경계가 있습니다. 그 이후에 나온 라이브러리라면, 모델은 누군가 보여 준 만큼만 압니다.
- 현재 대화. 여러분의 요청, 대화 기록, 에이전트가 읽은 파일, 에이전트가 실행한 명령의 출력입니다.
- 애플리케이션 메모리. 별도 파일에 저장되어 있다가 다음 세션을 시작할 때 대화에 들어가는 내용입니다. Claude Code에서는 여러분이 직접 쓰는
CLAUDE.md와, 에이전트가 스스로 메모를 남기는 auto memory가 이 역할을 합니다. - 외부 소스. 문서, 검색, 데이터베이스, 프로젝트 파일 등 학습 데이터에 없었거나 그 이후에 바뀌었을 수 있는 모든 것입니다.
뒤의 세 가지는 모두 같은 경로로 들어옵니다. 답변에 영향을 주려면 현재 대화 안에 들어와 있어야 합니다. 모델은 스스로 프로젝트를 들여다보지 않습니다.
여기서 한 번쯤 생각을 바꿔 둘 만한 결론이 나옵니다. 아무도 열지 않은 파일은 모델에게 존재하지 않습니다. 그 내용이 대화에 들어오기 전까지 "우리 프로젝트에서 인증은 어디서 처리하나요?"라는 질문은 "이런 프로젝트에서는 그런 파일을 보통 뭐라고 부르나요?"로 읽는 편이 정직합니다. 어느 쪽이든 답은 받게 되고, 겉보기에는 똑같습니다.
그 옆에는 더 작지만 속 쓰린 함정도 있습니다. 한 번 읽은 파일은 읽은 시점의 모습 그대로 대화에 남습니다. 여러분이 에디터에서 한 줄을 고쳐도 대화 기록에는 예전 버전이 남아 있습니다. Claude Code가 파일이 바뀌었다고 표시해 주기는 하지만, 에이전트가 그 파일을 다시 읽을지는 모델이 판단합니다. 대화 도중에 직접 고쳤다면 다시 읽어 달라고 분명하게 요청하는 편이 확실합니다.
Claude Code는 먼저 프로젝트를 읽고 명령을 실행한 다음에 답합니다. 답변에는 보통 어떤 파일을 열었고 무엇을 실행했는지가 보입니다. 에이전트가 아무것도 읽기 전에 여러분의 코드에 대한 답이 나왔다면, 지금 보고 있는 것은 여러분의 코드 분석이 아니라 전형적인 프로젝트에 대한 추측입니다.
표현이 결과를 바꾸는 이유
대화에 들어가는 모든 것 중에서 여러분이 직접 쓰는 것은 요청 하나뿐입니다. 그래서 요청 속 사소한 부분이 결과를 좌우합니다. 단어 몇 개의 순서를 바꾸거나 조건 하나를 덧붙이면 답변이 다른 방향으로 흘러갑니다. 도구의 변덕이 아닙니다.
여기서 헷갈리기 쉬우니 따로 짚어 두겠습니다. 같은 요청을 두 번 보내도 답은 달라집니다. 답변에 항상 확률이 가장 높은 이어짐이 쓰이는 것은 아니고, 생성에는 확률이 조금 낮은 선택지를 얼마나 쉽게 고를지 정하는 설정이 있기 때문입니다. 그러면 이런 의문이 생깁니다. 어차피 답이 흔들린다면 내가 쓴 단어가 무슨 상관일까? 차이는 무엇이 흔들리느냐에 있습니다. 같은 요청 사이의 편차는 같은 선택을 둘러싼 노이즈입니다. 문장은 달라져도 방향은 그대로입니다. 반면 요청에 쓴 단어는 방향 자체를 옮기고, 그것도 매번 같은 쪽으로 옮깁니다.
가장 효과가 없는 것은 모호한 요청입니다. 모호한 요청은 모델에게 선택을 맡기고, 모델은 전반적으로 가장 확률이 높은 것, 즉 지금까지 본 모든 것의 평균을 고릅니다. 간단한 "텍스트를 줄여 주세요"를 예로 들어 봅시다. 얼마나 줄일까요? 몇 퍼센트, 3분의 1, 절반? 무엇은 빼도 되고 무엇은 건드리면 안 될까요? 요청에는 이 질문들 중 어느 것에 대한 답도 없으니 답은 모델이 정하게 되고, 형식적으로는 모델이 옳습니다. 반면 "설명을 30% 줄여 주세요"나 "설명을 300자로 줄여 주세요"는 여러분이 정할 부분에서 모델에게 선택의 여지를 남기지 않습니다.
같은 일을 부탁하는 두 요청을 비교하면 잘 보입니다. "정렬을 고쳐 주세요"는 모든 것을 열어 둡니다. 어느 목록인지, 어느 필드 기준인지, 무엇을 고장으로 볼지. 반면 두 번째 표현은 열어 두는 것이 거의 없습니다:
주문 목록에서 날짜가 오래된 것부터 최신순으로 정렬되어 있는데 반대여야 합니다.
먼저 정렬 순서가 정의된 파일과 줄을 표시해 주고 그 다음에 수정 방법을 제안해 주세요.두 번째 요청은 첫 번째가 추측을 요구하던 만큼만 더 깁니다. 그리고 검증의 절반을 대신해 줍니다. 여러분이 묻기도 전에 에이전트가 코드의 해당 위치를 가져오니까요.
결론은 간단합니다. 요청에 자유가 적을수록 답변에 추측도 적습니다. 마법 주문 이야기가 아니라 평범한 이야기입니다. 틀을 여러분이 정하든지, 아니면 누군가 대신 정하게 됩니다.
자신 있는 톤은 사실이 아닙니다
이제 가장 중요한 함정입니다. 모델에게는 "이건 확실히 안다"는 억양과 "이건 방금 지어냈다"는 억양이 따로 없습니다. 두 문장 모두 같은 방식으로 만들어지고 똑같이 차분하게 나옵니다. 둘 사이에 관련이 전혀 없는 것은 아니지만 쓸모는 없습니다. 모델의 자신감은 늘 부풀려져 있어서, 톤만으로는 확인된 내용과 지어낸 내용을 구별할 수 없습니다.
일반 텍스트에서는 이상한 주장이 눈에 걸려서 저절로 멈칫하게 됩니다. 코드에서는 이 안전장치가 작동하지 않습니다. 이름은 그럴듯하고 구문은 익숙하며, 라이브러리를 속속들이 외우고 있지 않다면 진짜 메서드와 비슷하게 짜 맞춘 메서드를 눈으로는 구별할 수 없습니다. 그래서 실수는 읽을 때보다 실행할 때 드러나는 경우가 더 많습니다.
게다가 이런 이름은 처음부터 완전히 지어낸 경우가 드뭅니다. 생성된 코드에서 존재하지 않는 라이브러리 참조를 찾아본 한 분석에서는, 그런 이름의 3분의 1 이상이 실제 이름 두 개를 이어 붙인 것이었습니다. 그럴듯하게 느껴지는 이유도 여기에 있습니다. 이름의 각 부분은 어디선가 본 적이 있는데, 그 조합으로 본 적이 없을 뿐입니다.
무엇을 찾아야 하는지 알면 한 번에 알아볼 수 있는 네 가지 경우입니다:
| 어떻게 보이나 | 무슨 일이 있었나 | 어떻게 잡아내나 |
|---|---|---|
| 명령에 없는 플래그를 제시함 | 모델이 익숙한 형태대로 명령줄을 조립해서 플래그가 진짜처럼 보임 | 명령의 도움말이나 문서 |
| 프로젝트에 없는 파일, 함수, config 필드를 언급함 | 이런 프로젝트에 흔한 이름이지만 아무도 프로젝트를 읽지 않음 | 프로젝트 검색, 해당 줄을 보여 달라고 요청 |
| 여러분의 프로젝트 구조를 자신 있게 설명함 | 대화에 프로젝트 파일이 하나도 없어서 전형적인 프로젝트를 설명함 | 답하기 전에 어떤 파일을 열었는지 묻기 |
| "완료"라고 했지만 명령은 실행되지 않음 | 보고가 실행 결과가 아니라 작업 설명을 바탕으로 만들어짐 | 직접 실행하고 출력 확인 |
네 가지의 공통점은 하나입니다. 답변은 작업의 결과처럼 들리지만, 그 뒤에 실제 작업은 없었습니다.
마지막 경우는 따로 살펴볼 필요가 있습니다. 속임수처럼 보이지만 속임수는 없기 때문입니다. 에이전트는 작업이 끝난 것처럼 보일 때 멈춥니다. 실행해 볼 수단이 없다면 "끝난 것처럼 보인다"가 유일한 신호이고, 그때 검증 루프 역할은 여러분이 맡게 됩니다.
그 대가는 측정할 수 있습니다. 에이전트 로그를 분석한 한 연구에서는 거짓 "완료"가 전체 실패의 거의 절반을 차지했습니다. 반면 에이전트 자신의 보고와 별개로 상태를 확인한 경우에는 이런 사례가 거의 사라졌습니다. 다음 섹션의 두 번째 검증이 노리는 것도 이것입니다. 보고 대신 직접 눈으로 확인할 수 있는 것을 가져다 놓는 것이죠.
그렇다면 모델은 왜 모른다고 인정하기보다 그럴듯하게 채워 넣는 쪽을 택할까요? 원인은 모델을 학습시키고 평가해 온 방식에 있습니다. 객관식 시험을 떠올려 보세요. 빈칸으로 내면 확실히 0점이고, 아무거나 찍으면 점수를 받을 가능성이라도 있습니다. 이런 체계에서는 침묵이 언제나 손해이고, 조금이라도 불확실하면 일단 답하는 것이 합리적입니다. 사람은 나중에 실제 업무를 하면서 다시 배웁니다. 틀린 답이 솔직한 "모르겠습니다"보다 더 비싸다는 것을요. 모델에게는 그런 재학습이 없습니다.
확신이 없을 때 솔직하게 알려 달라고 모델에게 부탁하는 것은 도움이 됩니다. 하지만 그 말도 같은 메커니즘으로 만들어진 텍스트입니다. 보조 신호로는 쓸 만해도 검증을 대신하지는 못합니다.
도구가 맹점을 줄입니다
여기서는 대화할 때 흔히 한데 뭉뚱그리는 두 단어를 구분해 두면 좋습니다. 모델이 할 수 있는 일은 딱 하나입니다. 대화를 받아서 그 뒤에 이어질 내용을 내놓는 것입니다. 에이전트는 모델을 감싸는 프로그램으로, 도구를 가지고 있습니다. 파일 읽기, 명령 실행, 프로젝트 수정 같은 것들입니다.
이제 오류가 정확히 어디서 생기는지를 좌우하는 부분입니다. 도구를 고르는 것은 프로그램이 아닙니다. 모델이 답변 안에서 어떤 파일을 열어 달라거나 어떤 명령을 실행해 달라고 요청하면, 프로그램은 그 요청을 확인하고 실행한 뒤 결과를 대화로 돌려줍니다. 결정은 모델이 하고, 실행은 프로그램이 합니다. 그러니 결정 자체가 빗나갈 수도 있습니다. 엉뚱한 파일, 엉뚱한 명령, 엉뚱한 테스트처럼요.
그래도 차이는 큽니다. 파일을 열기 전에는 그 파일에 대한 답이 비슷한 프로젝트들에 대한 기억으로 만들어집니다. 파일을 읽고 테스트를 돌리는 순간 대화에 사실이 생깁니다. 여기 내용이 있고, 여기 출력이 있습니다. 이건 더 이상 기억이 아니라 검증이고, 그 뒤의 답변은 품질이 눈에 띄게 달라집니다.
그래서 대화 첫머리에 한 줄만 적어 주면 되는 방법이 나옵니다:
먼저 프로젝트에서 이것이 어디에 있는지 찾아서 보여 주고 그 다음에 설명해 주세요.맹점은 좁아지지만 사라지지는 않습니다. 에이전트는 필요한 파일 대신 옆 파일을 열 수도 있고, 명령 출력을 제멋대로 해석할 수도 있고, 진짜 원인에 닿기 전에 처음 떠오른 그럴듯한 설명에서 멈출 수도 있습니다. 전부 제가 직접, 그것도 여러 번 본 일입니다. 그래서 테스트 출력, 문서 내용, 파일 내용 같은 외부 신호가 근거 없이 내놓은 가장 자신 있는 추론보다 더 무겁습니다.
1분이면 되는 세 가지 검증
다행히 이런 실수는 거의 힘들이지 않고 잡아낼 수 있습니다. 모든 것은 갈림길 하나로 정리됩니다.
여기서 볼 것은 글이 얼마나 설득력 있느냐가 아니라, 답변에 붙잡을 만한 근거가 있느냐입니다. 구체적인 파일, 구체적인 줄, 명령과 그 출력 같은 것이죠. 근거가 없다면 눈앞에 있는 것은 아직 결과가 아니라 가설입니다. 많은 답변이 그렇게 시작하고, 문제는 그다음에 여러분이 무엇을 하느냐입니다.
이제 세 가지 검증입니다. 각각 1분도 걸리지 않고, 셋이 한꺼번에 필요한 경우는 드뭅니다.
- 위치를 보여 달라고 요청합니다. "더 자세히 설명해 주세요"가 아니라 "파일, 줄, 해당 코드 조각을 보여 주세요"라고 합니다. 그런 위치가 없다면 바로 드러납니다.
- 결과를 직접 열어 보거나 실행합니다. 브라우저에서 페이지를, 터미널에서 명령을, 프로젝트에서 테스트를 돌려 봅니다. 작업에 대한 보고와 작업 자체는 같은 것이 아닙니다.
- 도구에 대한 주장은 도구 자체로 확인합니다. 명령에는 내장 도움말이 있고, 라이브러리에는 문서가 있습니다. 언급된 플래그나 config 키는 거기에 있거나 없거나 둘 중 하나이고, 30초면 확인됩니다. 라이브러리 메서드는 좀 더 신중해야 합니다. 문서에 모든 것이 실리지는 않으므로, 문서에 없다는 것은 다시 물어볼 이유이지 최종 판정이 아닙니다.
답변과 입씨름하는 대신 근거를 직접 요청하세요:
이것이 설명되어 있는 파일과 줄을 보여 주세요.
프로젝트에 그런 위치가 없으면 이것이 추측이라고 직접 말해 주세요.늘 가까이 두고 쓸 만한 두 줄입니다. 첫 줄은 에이전트가 비슷한 프로젝트에 대한 기억으로 답하는 대신 프로젝트 안을 들여다보게 합니다. 두 번째 줄은 에이전트에게 빠져나갈 길을 줍니다. 그런 위치가 없다고 말하는 편이 답을 그럴듯하게 지어내는 것보다 쉬워집니다.
"완료" 보고에도 같은 요청이 통합니다. 대상만 실행으로 바뀝니다. "실행한 명령과 그 출력을 전부 보여 주세요."
그렇다고 보장이 되는 것은 아닙니다. "이것은 추측입니다"라는 문장도 다른 모든 것과 같은 메커니즘으로 만들어집니다. 빗나갈 가능성은 줄여 주지만 검증을 대신하지는 않습니다.
오해가 남지 않도록 한 가지만 덧붙이겠습니다. 검증한다는 것은 도구를 믿지 않는다는 뜻이 아닙니다. 여러분도 직접 작성한 diff를 커밋 전에 다시 읽습니다. 여기서도 마찬가지이고, 이유가 조금 더 무거울 뿐입니다.
Claude Code 작업에서 이것이 의미하는 것
키보드 앞에서 바로 적용할 수 있는 결론은 이렇습니다:
- 파일, 줄, 명령 출력에 대한 참조가 있는 답변은 몇 초 만에 확인할 수 있습니다. 참조가 없는 답변은 확인할 방법이 없어서, 다시 읽고 수긍하는 것밖에 할 수 없습니다.
- 질문하기 전에 에이전트가 필요한 것을 먼저 읽게 하세요. 단계 하나를 더하는 것만으로 한 종류의 실수가 통째로 사라집니다.
- 자신감으로 판단해서는 안 됩니다. 말 뒤에 검증이 있든 추측이 있든 자신감은 늘 부풀려져 있습니다.
- "맞는 것처럼 들린다"와 "맞게 작동한다"는 다른 이야기이고, 둘을 구별해 주는 것은 실행뿐입니다.
- 실수의 대가가 클수록 테스트, 문서, 실제 파일 같은 외부의 무언가가 대화에 더 일찍 등장해야 합니다. 작은 수정이라면 diff를 한번 훑어보는 것으로 충분합니다.
- 에이전트가 틀렸다면 평가가 아니라 사실로 알려 주세요. "프로젝트에 그런 함수는 없습니다. 폴더에 있는 건 이것입니다"는 작업을 앞으로 나아가게 하지만, "또 다 지어냈네"는 아무 데로도 나아가게 하지 않습니다. 그리고 기억해 두세요. 여러분이 자신 있게 반박하면 에이전트는 자기가 옳았을 때조차 따지기보다 맞춰 줄 가능성이 큽니다. 에이전트가 동의했다고 해서 여러분이 옳다는 뜻은 아닙니다.
가끔 자신 있게 빗나가는 메커니즘이라도, 여전히 아주 쓸모 있는 도구입니다. 그 한계를 알아 두는 이유는 딱 하나, 놀라느라 시간을 쓰지 않고 곧장 검증으로 넘어가기 위해서입니다.
GO TO FULL VERSION