Claude Code 좋은 프롬프트는 내부 구조에서 나온다

--continue 옵션을 못 주면 지금까지 나눈 대화를 잃어버릴까 봐 불안해하는 사람이 많다. 세션이 끊기면 작업 맥락이 통째로 사라진다고 믿기 때문이다. 결론부터 말하면 그 불안은 대부분 근거가 없다. Claude Code를 실행할 때마다 모델은 사실상 새로 태어나며, “대화가 이어진다”는 감각은 착시에 가깝다. 이 사실 하나를 받아들이면 좋은 프롬프트가 무엇인지가 분명해진다. 이 글은 코딩 에이전트의 내부 동작을 따라가면서, 거기서 자연스럽게 도출되는 프롬프트 작성 원칙을 정리한다

Claude Code는 한 턴을 이렇게 처리한다

코딩 에이전트의 한 턴은 단순한 루프다. 사용자가 입력을 주면 에이전트는 어떤 도구를 쓸지 먼저 판단하고, 그 도구를 실행할 권한이 있는지 확인한 뒤 실행한다. 실행 결과를 LLM에 전달하고, LLM의 판단을 받아 화면에 렌더링한 다음, 다시 사용자 입력을 기다린다

사용자 입력
  → 도구 선택
  → 권한 확인
  → 도구 실행
  → 결과를 LLM에 전달
  → LLM 판단
  → 화면 렌더링
  → (다시) 사용자 입력

여기서 두 가지가 중요하다. 첫째, 도구 실행은 그 자체로 위험한 행위다. 악성 코드를 돌리거나 중요 파일을 지울 수 있으므로 위험성 판단 로직이 별도로 작동하고, 위험하다고 보이면 즉시 차단한다. 둘째, 단순한 파일 검색이나 내용 조회는 LLM을 거치지 않고 로컬에서 처리한다. grep, glob 같은 도구를 로컬에서 돌리면 토큰을 쓰지 않기 때문이다.

에이전트의 성능이 도구에 크게 좌우되는 이유가 여기 있다. 파일 입출력, 셸, 검색, 웹 접근 같은 내장 도구가 에이전트의 손발이다. 이 손발을 외부 애플리케이션까지 확장하는 표준이 MCP(Model Context Protocol)다. 스킬과 플러그인도 결국 이 도구 계층의 연장선이다

시작할 때 무엇을 읽는가

에이전트가 켜질 때 가장 먼저 하는 일은 컨텍스트 수집이다. 현재 시각, Git 브랜치, 최근 커밋 내역, 사용자 이름 같은 정보를 모아 “지금 어떤 상태에서 무슨 작업을 하던 중인가”를 파악한다. 그다음 CLAUDE.md를 로딩한다. CLAUDE.md는 세션 시작마다 자동으로 읽히는 프로젝트 지침 파일이다

여기서 자주 오해받는 지점이 --continue(또는 -c) 옵션이다. 그냥 실행한 경우와 --continue로 실행한 경우를 비교하면, 실제로 달라지는 것은 단 하나다

항목그냥 실행–continue 실행
시스템 프롬프트로딩로딩
CLAUDE.md로딩로딩
스킬 · MCP로딩로딩
이전 메시지 배열없음로딩

차이는 이전 대화 기록을 메시지 배열로 다시 불러오느냐 마느냐뿐이다. 그래서 --continue는 흔히 생각하는 “세션 복원” 도구가 아니라 단기 작업 재개를 위한 편의 기능에 가깝다. 대화를 잃을까 봐 전전긍긍할 만큼 결정적인 기능이 아니라는 뜻이다

LLM에는 ‘대화’라는 개념이 없다

이 대목이 핵심이다. LLM API 호출은 본질적으로 독립적이다. 호출과 호출 사이에 어떤 상태도 보존되지 않는다. 한 세션에서 대화가 길어진다는 것은, 매 턴마다 그동안의 전체 히스토리를 통째로 다시 보낸다는 뜻이다. 그래서 대화가 길어질수록 모델이 읽어야 할 정보가 늘고, 응답은 느려지며, 작업과 무관한 옛 내용이 노이즈로 작용한다

조금 다르게 말하면 이렇다. 한 세션의 열 번째 턴은, --continue로 같은 입력을 첫 턴에 넣은 것과 모델 입장에서는 거의 같은 사건이다. 모델은 매번 같은 자료를 읽고 같은 역할을 연기하는, 매번 새로운 인스턴스에 가깝다. 우리가 느끼는 일관성은 모델이 기억하기 때문이 아니라, 매번 같은 입력을 다시 받기 때문에 생기는 착시다

이 사실의 실용적 결론은 분명하다. 결정과 맥락은 대화 히스토리가 아니라 파일에 존재해야 한다

그래서 좋은 프롬프트는 ‘환경 설계’다

에이전트가 매번 새로 깨어난다면, 던져야 할 질문은 “대화를 어떻게 잘 압축할까”가 아니라 “깨어난 직후 무엇을 발견하게 할까”다. 새 아르바이트생이 출근했을 때 빠르게 일을 시작하도록 인수인계 문서를 잘 갖춰 두는 것과 같다

Anthropic도 같은 방향을 권한다. 컨텍스트 한계에 다다른 대화를 요약해 이어가는 compaction보다, 깨끗한 새 컨텍스트로 시작하되 진행 상황 파일과 Git 히스토리로 상태를 재발견하게 하는 방식을 고려하라고 안내한다. 최신 모델은 파일 시스템에서 현재 상태를 스스로 파악하는 데 효과적이기 때문이다. 그래서 핵심 인프라는 다음 셋으로 모인다

  • CLAUDE.md: 프로젝트를 관통하는 영속적 지침. 세션마다 자동 로딩된다.
  • 디렉터리 구조와 docs: 모델이 참조할 수 있도록 정리된 명세와 설계 문서.
  • Git: 마지막 작업이 무엇이었는지 재구성하는 근거.

특히 Git은 사실상 필수에 가깝다. 같은 프롬프트라도 Git으로 커밋·브랜치를 관리하는 환경이, 그렇지 않은 환경보다 에이전트의 상황 인지 능력이 높다. 에이전트는 “마지막에 무엇을 했지”를 커밋 로그와 diff에서 찾기 때문이다. 사람이 코드를 직접 수정했다면, 반드시 상세한 메시지와 함께 commit한 뒤 세션을 여는 편이 유리하다. 그래야 에이전트가 변경 사실과 그 의도를 인식할 수 있다

도구가 바닐라 설정으로도 잘 작동하도록 설계되어 있다는 점도 기억할 만하다. 거창한 하니스(harness)를 직접 짜는 데 시간을 쏟기보다, 첫 설계와 문서를 명확히 하는 쪽이 같은 시간 대비 더 큰 효과를 낸다

이름이 에이전트의 판단을 좌우한다

에이전트는 디렉터리 이름과 파일 이름을 근거로 기능을 추측하고, 열어볼 대상을 고른다. 이름이 모호하거나 잘못 지어지면 오인이 시작되고, 그 오류가 누적되면 걷잡을 수 없어진다. 좋은 모델은 네이밍을 잘하므로, 코딩 컨벤션 정도만 합의해도 파일명과 디렉터리명은 알아서 일관되게 정리되는 경우가 많다

검색을 시킬 때 의도를 정확히 전달하는 것도 같은 맥락이다. 찾으려는 문자열을 따옴표로 명확히 감싸면, 공백이 포함된 구문까지 정확히 검색하게 되어 추측에 의한 오류를 줄인다

# 모호한 지시
세션 관련 코드 좀 봐줘

# 명확한 지시
"loadSessionList" 문자열을 코드 전체에서 검색해서 호출 관계를 분석해줘

부정문의 역설 – ‘하지 마’는 약하게 작동한다

프롬프트에서 부정문은 의외로 약하게 표상된다. “X를 하지 마”라는 지시는 모델의 어텐션에서 오히려 ‘X’를 강하게 활성화시키는 역작용을 낸다. “코끼리를 생각하지 마세요”라는 말을 들으면 머릿속에 코끼리가 더 또렷해지는 것과 같다. 성능이 낮은 모델일수록 금지했던 것을 거꾸로 다시 제안하는 현상이 관찰된다.

그래서 배제는 부정문이 아니라 긍정문으로, 그리고 가능하면 파일로 표현해야 한다.

# CLAUDE.md

## Do Not
- 인증에 자체 구현 세션 스토어를 사용하지 않는다 → JWT 기반으로 통일한다
  - 이유: 멀티 서버 환경에서 세션 동기화 비용이 크고, 이미 게이트웨이가 JWT를 검증한다

여기서 결정적인 부분은 ‘이유’를 함께 남기는 것이다. 거부(negative space), 즉 “무엇을 검토했지만 채택하지 않았는가”는 대화 요약 과정에서 가장 먼저 사라진다. compaction은 결과 중심으로 요약하기 때문에 “B를 사용함”은 살아남아도 “A를 검토했으나 이런 이유로 제외함”은 버려질 가능성이 높다. 근거가 사라지면 에이전트는 거부됐던 선택지를 멀쩡한 대안으로 다시 들고 온다. 게다가 모델은 구현된 코드에 대해 사후적으로 그럴듯한 정당화를 만들어내는 경향이 있어서, 같은 코드베이스에서 매번 다른 이유를 붙이기도 한다.

정리하면, 무언가를 거부할 때는 그 결정과 이유를 CLAUDE.md의 금지 섹션, 설계 결정 문서(ADR), 커밋 메시지, 코드 주석 중 적절한 곳에 명시적으로 남겨야 한다. 그래야 거부했던 패턴이 다음 세션에서 부활하지 않는다

코딩 에이전트의 세 가지 고질병과 처방

에이전트를 오래 쓰다 보면 반복적으로 부딪히는 문제가 있다. 크게 셋으로 묶을 수 있다

첫째, 잘못된 가정이다. 모호한 지점을 사용자에게 확인하지 않고 스스로 해석한 뒤 폭주하듯 진행한다. 처방은 모호함을 0으로 만드는 것이다. 행동을 지시하기 전에, 모호한 부분이 남아 있으면 작업을 시작하기 전에 되물어 달라고 요청한다. 추측하지 말고 코드를 직접 분석한 뒤 결과를 보고하게 하고, 선택지가 여럿이면 트레이드오프를 정리해 제시하게 한다

이 에러의 원인을 추측하지 말고, 관련 코드를 직접 분석해서 원인과
해결 방안을 보고해줘. 방안이 여러 개면 장단점을 정리해줘.
내 승인 없이 코드를 수정하지는 말아줘.

둘째, 코드 부풀리기다. 간단한 작업에도 불필요한 추상화와 모듈을 늘려 가독성을 떨어뜨린다. 처방은 최소 변경 원칙이다. 꼭 필요한 코드만 외과 수술하듯 바꾸라고 요구하면 눈에 띄게 달라진다. “이 작업은 50줄을 넘지 않을 것 같은데, 넘을 것 같으면 이유를 먼저 알려줘” 같은 기준을 함께 주면 효과가 크다

셋째, 멋대로 수정이다. 특정 함수만 고치라고 했는데 주변 코드까지 건드리고 주석을 지우기도 한다. 처방은 경계를 명시하는 것이다. “이 함수만 고치고 나머지는 건드리지 마라. 개선할 부분이 보여도 수정하지 말고 알려만 달라”를 버그 수정 프롬프트에 항상 붙인다

이 모든 처방의 바탕에는 성공 기준 정의가 있다. 무엇이 성공이고 무엇이 실패인지, 어떤 방식으로 판정하는지를 명확히 적어 두면 에이전트는 그 기준을 향해 스스로 반복한다. 테스트 코드를 함께 요구하는 TDD 방식이 잘 맞는 이유다

좋은 프롬프트에는 최소한의 CS 지식이 필요하다

“로그인 기능 만들어줘”는 빈틈이 많은 프롬프트다. 지식 기반 인증인지, 비밀번호는 단방향 해시로 저장할지, 소셜 로그인을 포함할지, 인증 이후 서버 간 접근은 어떻게 처리할지가 모두 비어 있다. 이 빈틈을 “알아서 결정해”로 넘기면 좋은 결과가 나오기 어렵다. 좋은 프롬프트는 에이전트가 스스로 판단하도록 떠넘기는 것이 아니라, 판단에 필요한 맥락을 충분히 제공하고 모호한 지점만 되묻게 만드는 것이다

세션 기반 로그인을 만들려고 한다. 스택은 Spring Boot + PostgreSQL이고
사용자 규모는 초기 1만 명 수준이다. 비밀번호는 단방향 해시로 저장한다.
세션 방식과 JWT 방식의 트레이드오프를 정리해주고,
결정에 필요한 정보가 더 있으면 먼저 물어봐줘.

그래서 결국 보안, 데이터베이스, 네트워크 같은 CS 기초가 프롬프트 품질을 끌어올리는 토대가 된다. 에이전트와 공유하는 용어집을 따로 문서화하는 것도 도움이 된다. 같은 UI 요소를 누구는 “네모”, 모델은 “카드”라고 부르면 의사소통이 어긋난다. 용어는 가능하면 모델 쪽에 맞춰 통일하는 편이 실용적이다

정리

Claude Code는 매 실행마다 새로 깨어나는 에이전트이고, 대화의 연속성은 착시다. 그러므로 좋은 프롬프트의 본질은 긴 대화를 잘 유지하는 기술이 아니라, 새로 깨어난 에이전트가 파일 시스템에서 올바른 상태를 곧바로 발견하도록 환경을 설계하는 일이다. 맥락과 결정은 대화가 아니라 CLAUDE.md, 문서, 그리고 Git에 남겨라. 한 줄로 요약하면, 대화를 압축하지 말고 환경을 설계하라

출처 – 인프런 강의 중 [Claude Code로 만드는 1인 개발자 자동화 시스템 – Sidabari 프로젝트]

참고자료