정적 섹션, 동적 섹션, CLAUDE.md 주입, 캐시 경계, 모드 오버라이드까지 — 시스템 프롬프트 조립의 모든 것.
시스템 프롬프트는 Claude Code의 행동을 정의하는 핵심 구성 요소입니다. 6개 레이어 아키텍처로 조립됩니다:
systemPrompt.ts — override, coordinator, agent, custom, default 간 우선순위 결정prompts.ts — 정적/동적 섹션의 실제 내용 생성systemPromptSections.ts — 동적 섹션의 등록 및 메모이제이션 관리claudemd.ts — 4가지 범위의 CLAUDE.md 파일 로딩 및 병합memdir/memdir.ts — MEMORY.md 자동 메모리 관리buildEffectiveSystemPrompt()는 폭포수(waterfall) 패턴으로 시스템 프롬프트의 소스를 결정합니다:
override — 최고 우선순위. overrideSystemPrompt이 설정되면 appendSystemPrompt을 포함한 모든 것을 대체coordinator — 코디네이터 에이전트가 하위 에이전트에게 전달하는 프롬프트. 이 분기에서도 appendSystemPrompt는 유지됨agent — 에이전트 모드 전용 프롬프트 (proactive/KAIROS 모드 등)custom — 사용자 정의 시스템 프롬프트default — 표준 대화형 프롬프트appendSystemPrompt는 override를 제외한 모든 분기에 추가됩니다. override가 설정되면 완전히 독립적인 프롬프트로 동작합니다.
// utils/systemPrompt.ts — 우선순위 폭포수
if (overrideSystemPrompt) {
return asSystemPrompt([overrideSystemPrompt])
}
if (isCoordinatorMode()) {
return asSystemPrompt([
getCoordinatorSystemPrompt(),
...append,
])
}
if (agentSystemPrompt) {
return asSystemPrompt([...defaultSystemPrompt, agentSystemPrompt, ...append])
}
if (customSystemPrompt) {
return asSystemPrompt([customSystemPrompt, ...append])
}
return asSystemPrompt([...defaultSystemPrompt, ...append])
overrideSystemPrompt 설정 시 appendSystemPrompt도 무시됩니다. 기본 프롬프트를 대체하되 추가 지시를 유지하고 싶다면 custom + appendSystemPrompt 조합을 사용해야 합니다.
getSystemPrompt()는 두 영역으로 나뉜 시스템 프롬프트를 생성합니다:
정적 섹션 (캐시 경계 앞):
Intro — 모델 정체성 및 기본 행동 규칙System — 운영 체제와 실행 원칙에 대한 고정 설명DoingTasks — 작업 수행 가이드라인Actions — 허용/금지 행동 목록Tools — 사용 가능한 도구 설명Tone — 응답 톤 및 스타일 가이드OutputEfficiency — 출력 효율성 규칙__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__
동적 섹션 (캐시 경계 뒤): 세션마다 달라지는 내용. CLAUDE.md, MCP 지시, 메모리, env_info_simple 같은 등록형 섹션이 여기 들어갑니다.
// constants/prompts.ts — 조립 (간소화)
export async function getSystemPrompt(tools, model, additionalDirs, mcpClients) {
const [skillCmds, outputStyle] = await Promise.all([
getSkillToolCommands(cwd),
getOutputStyleConfig(),
])
return [
// ── 정적 (전역 캐시 가능) ──
getSimpleIntroSection(outputStyle),
getSimpleSystemSection(),
getSimpleDoingTasksSection(),
getActionsSection(),
getUsingYourToolsSection(enabledTools),
getSimpleToneAndStyleSection(),
getOutputEfficiencySection(),
// ── 경계 마커 ──
SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
// ── 동적 (세션별, 레지스트리에서 resolve) ──
...resolvedDynamicSections,
].filter(s => s !== null)
}
동적 섹션은 두 가지 등록 방식을 제공합니다:
systemPromptSection (메모이즈): 한 번 평가되면 세션 내내 캐시됨. 대부분의 동적 섹션이 이 방식 사용DANGEROUS_uncachedSystemPromptSection (휘발성): 매 API 호출마다 재평가. 캐시하면 낡은 데이터를 반환할 위험이 있는 섹션 전용// systemPromptSections.ts — 메모이즈 vs 휘발성
export function systemPromptSection(name, compute) {
return { name, compute, cacheBreak: false } // 메모이즈
}
export function DANGEROUS_uncachedSystemPromptSection(name, compute, reason) {
return { name, compute, cacheBreak: true } // 매 턴 재계산
}
MCP(Model Context Protocol) 서버는 세션 도중에 연결되거나 해제될 수 있습니다. 사용자가 새로운 MCP 서버를 추가하면 해당 서버의 지시사항이 시스템 프롬프트에 포함되어야 하고, 서버가 해제되면 제거되어야 합니다.
만약 mcp_instructions를 메모이즈하면, 세션 시작 시점의 MCP 서버 상태가 세션 내내 유지되어 새로 연결된 서버의 지시가 무시되거나, 해제된 서버의 지시가 남게 됩니다. 따라서 매 API 호출마다 현재 연결된 MCP 서버에서 지시사항을 다시 수집합니다.
__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__는 시스템 프롬프트를 두 영역으로 분리합니다:
캐시 경계 앞에 런타임 조건식을 추가하면 캐시 접두사 해시가 2조건 수배로 늘어납니다. 예를 들어, 정적 섹션에 3개의 if 분기를 추가하면 최대 8가지 서로 다른 캐시 접두사가 생성되어 캐시 적중률이 급락합니다.
새로운 조건부 내용은 반드시 동적 경계 뒤에 배치해야 합니다.
CLAUDE.md는 프로젝트별 지시사항을 시스템 프롬프트에 주입하는 메커니즘입니다. 4가지 범위로 로드됩니다:
managed — Anthropic이 관리하는 기본 지시 (최저 효력)user — ~/.claude/CLAUDE.md 사용자 전역 설정project — 프로젝트 루트부터 CWD까지 디렉토리 트리를 상향 탐색하며 발견되는 CLAUDE.mdlocal — CLAUDE.local.md — git에 커밋되지 않는 개인 지시. CWD에 가장 가까운 파일이 최고 효력// CLAUDE.md 로드 순서
// 1. 관리 메모리 (/etc/claude-code/CLAUDE.md) — 모든 사용자 전역
// 2. 사용자 메모리 (~/.claude/CLAUDE.md) — 개인 전역
// 3. 프로젝트 메모리 (CLAUDE.md / .claude/CLAUDE.md — 코드베이스에 커밋
// .claude/rules/*.md)
// 4. 로컬 메모리 (CLAUDE.local.md) — 개인 프로젝트별
추가 기능:
@include 지시어: 다른 파일의 내용을 CLAUDE.md에 인라인으로 삽입paths 필터링: 특정 파일 경로 패턴에서만 활성화되는 조건부 지시MEMORY.md에 저장 (200줄 / 25KB 캡)proactive(KAIROS) 모드가 활성화되면 시스템 프롬프트 구성이 근본적으로 달라집니다. 표준 대화형 프롬프트(Intro, System, DoingTasks, ...) 전체를 대신하여 짧은 자율 에이전트 정체성 프롬프트를 반환합니다.
대화형 모드에서는 사용자 질문에 응답하는 어시스턴트 역할이지만, KAIROS 모드에서는 자율적으로 계획하고 실행하는 에이전트 역할입니다. 이 근본적인 역할 차이 때문에 기존 프롬프트에 추가하는 것이 아니라 완전히 대체합니다.
env_info_simple는 단순한 인라인 문자열이 아니라, 동적 섹션 레지스트리에 등록된 섹션입니다. 각 요청에서 computeSimpleEnvInfo()가 현재 런타임 컨텍스트를 계산해 채우며, 그래서 캐시 경계 뒤에 위치합니다.
// constants/prompts.ts — computeSimpleEnvInfo() 출력 예시
// # Environment
// You have been invoked in the following environment:
// - Primary working directory: /Users/alice/myproject
// - Is a git repository: true
// - Platform: darwin
// - Shell: zsh
// - OS Version: Darwin 25.3.0
// - You are powered by the model named Claude Sonnet 4.6.
// - Assistant knowledge cutoff is August 2025.
// - Claude Code is available as a CLI in the terminal, desktop app ...
수집되는 정보 항목:
getCwd()로 현재 작업 디렉토리 및 additionalDirs 추가 디렉토리 경로process.platform, OS 버전process.env.SHELL을 파싱하여 셸 이름 추출셸 감지는 process.env.SHELL 경로에서 마지막 구성 요소를 추출합니다. Windows에서는 Unix 셸 구문을 선호하라는 노트(/dev/null 사용, 경로에 슬래시 사용 등)가 추가됩니다. 지식 컷오프 날짜는 모델 정규 이름별로 getKnowledgeCutoff()에 하드코딩되어 있으며, 매 모델 출시마다 // @[MODEL LAUNCH] 업데이트가 필요합니다.
// constants/prompts.ts — shell detection and Windows note
const shellName = process.env.SHELL?.split('/').pop() ?? 'unknown'
const windowsNote = process.platform === 'win32'
? `\nNote: prefer Unix shell syntax (use /dev/null not NUL, forward slashes in paths).`
: ''
Anthropic 엔지니어가 isUndercover() 활성 상태에서 내부 빌드를 실행할 때, 모든 모델 이름 참조가 환경 섹션에서 제거되어 내부 모델 ID가 공개 커밋, PR, 스크린샷 등으로 누출되는 것을 방지합니다.
MCP 서버가 연결되면, getMcpInstructions()가 ConnectedMCPServer 객체를 순회하며 instructions 필드를 노출하는 서버의 지시사항 블록을 조립합니다.
// constants/prompts.ts — getMcpInstructions()
function getMcpInstructions(mcpClients) {
const withInstructions = mcpClients
.filter(c => c.type === 'connected')
.filter(c => c.instructions)
const blocks = withInstructions
.map(c => `## ${c.name}\n${c.instructions}`)
.join('\n\n')
return `# MCP Server Instructions\n\n...\n\n${blocks}`
}
이 함수가 시스템 프롬프트에 "IMPORTANT: Before using any chrome browser tools, you MUST first load them using ToolSearch" 같은 텍스트를 주입합니다. 이는 Claude Code 자체 코드가 아닌 MCP 서버의 instructions 필드에서 그대로 전달되는 내용입니다.
캐싱 전략: mcp_instructions는 유일하게 DANGEROUS_uncachedSystemPromptSection으로 등록되어 매 API 호출마다 재평가됩니다. MCP 서버가 세션 도중 연결/해제될 수 있으므로, 캐시하면 낡은 데이터를 반환할 위험이 있기 때문입니다.
실험적 경로인 mcpInstructionsDelta 기능도 존재합니다. 이 기능이 활성화되면 지시사항이 매 턴 시스템 프롬프트에 재주입되는 대신 영속 첨부(persisted attachment) 객체로 전달됩니다. 이를 통해 늦게 연결되는 MCP 서버가 새 해시를 강제할 때 발생하는 프롬프트 캐시 무효화를 방지합니다.
AgentTool이 실행하는 서브에이전트는 getSystemPrompt()을 완전히 우회합니다. 호출자가 전달하는 시스템 프롬프트(보통 DEFAULT_AGENT_PROMPT)에서 시작한 후, enhanceSystemPromptWithEnvDetails()가 환경 컨텍스트와 에이전트별 주의사항을 추가합니다.
// The agent default prompt — what every subagent starts with
export const DEFAULT_AGENT_PROMPT = `You are an agent for Claude Code, Anthropic's official CLI for Claude. Given the user's message, you should use the tools available to complete the task. Complete the task fully—don't gold-plate, but don't leave it half-done. When you complete the task, respond with a concise report covering what was done and any key findings — the caller will relay this to the user, so it only needs the essentials.`
// Notes appended by enhanceSystemPromptWithEnvDetails()
`Notes:
- Agent threads always have their cwd reset between bash calls — use absolute file paths.
- In your final response, share file paths (always absolute, never relative) ...
- For clear communication the assistant MUST avoid using emojis.
- Do not use a colon before tool calls.`
서브에이전트 경로의 핵심 차이점:
getSystemPrompt() 대신 DEFAULT_AGENT_PROMPT + enhanceSystemPromptWithEnvDetails()computeSimpleEnvInfo() 대신 풀 버전인 computeEnvInfo() 사용 -- uname -sr 출력을 getUnameSR()로 추가하고, 단순 글머리표 대신 <env> XML 태그로 감싸는 형식이 환경 변수를 설정하면, 시스템 프롬프트가 CWD와 날짜만 포함하는 3줄 스텁으로 축소됩니다. 지시사항, CLAUDE.md, 도구 가이드 없이 -- 원시 모델 능력을 벤치마킹할 때 Claude Code의 프롬프트 오버헤드를 제거하는 용도입니다.
// constants/prompts.ts — CLAUDE_CODE_SIMPLE escape hatch
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
return [`You are Claude Code, Anthropic's official CLI for Claude.\n\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`]
}
feature('PROACTIVE') 또는 feature('KAIROS')가 활성화되고 proactive 모드가 동작 중이면, getSystemPrompt()는 전체 인터랙티브 세션 프롬프트 대신 짧은 자율 에이전트 정체성 프롬프트를 반환합니다. Memory, 환경 정보, MCP 지시사항, proactive 섹션은 포함하되, 인터랙티브 코딩 태스크 가이드는 제외합니다.
// constants/prompts.ts — proactive/KAIROS mode prompt assembly
return [
`\nYou are an autonomous agent. Use the available tools to do useful work.\n\n${CYBER_RISK_INSTRUCTION}`,
getSystemRemindersSection(),
await loadMemoryPrompt(),
envInfo,
getLanguageSection(settings.language),
getMcpInstructionsSection(mcpClients),
getScratchpadInstructions(),
getProactiveSection(),
].filter(s => s !== null)
에이전트에 proactive 모드가 결합된 경우, 에이전트 지시사항은 기본 프롬프트를 대체하는 것이 아니라 추가됩니다. 이는 "팀원" 패턴을 반영합니다: proactive 기본값은 이미 간결한 자율 에이전트 정체성이고, 도메인 에이전트가 그 위에 추가됩니다.
// utils/systemPrompt.ts — proactive mode branch
if (agentSystemPrompt && (feature('PROACTIVE') || feature('KAIROS')) && isProactiveActive()) {
return asSystemPrompt([
...defaultSystemPrompt,
`\n# Custom Agent Instructions\n${agentSystemPrompt}`,
...(appendSystemPrompt ? [appendSystemPrompt] : []),
])
}
buildEffectiveSystemPrompt()는 override > coordinator > agent > custom > default 폭포수로 소스를 결정합니다overrideSystemPrompt 설정 시 appendSystemPrompt를 포함한 모든 것이 대체됩니다__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__가 전역 캐시 가능 정적 접두사와 세션별 동적 꼬리를 분리합니다mcp_instructions만 DANGEROUS_uncached인 이유는 MCP 서버가 세션 도중 연결/해제될 수 있기 때문입니다CLAUDE.local.md가 최고 효력입니다Q1. overrideSystemPrompt이 설정되면 어떻게 되는가?
overrideSystemPrompt은 최고 우선순위입니다. 설정되면 폭포수의 첫 번째 분기에서 즉시 반환하므로 appendSystemPrompt를 포함한 다른 모든 소스가 무시됩니다.Q2. mcp_instructions가 DANGEROUS_uncached인 이유는?
Q3. CLAUDE.md 로딩 우선순위에서 최고 효력을 가진 파일은?
local 범위의 CLAUDE.local.md가 최고 효력을 가지며, 그 중에서도 CWD에 가장 가까운 파일이 우선합니다. git에 커밋되지 않는 개인 지시용으로 설계되었습니다.Q4. __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__의 목적은?
Q5. proactive/KAIROS 모드가 시스템 프롬프트를 변경하는 방법은?