본문으로 건너뛰기

대화 중 시스템 메시지

고급

수년간 최상위 system 필드는 오퍼레이터 수준 권한을 갖는 유일한 자리였습니다 — 모델이 최종 사용자가 아니라 여러분에게서 오는 것으로 취급하는 지시. 일회성 채팅에는 괜찮았지만 긴 에이전트 세션에서는 고통스러웠습니다: "지금부터 매개변수화된 SQL을 사용하라"를 추가하려고 시스템 프롬프트를 편집하는 순간 요청의 맨 앞을 바꾸는 셈이었습니다. 프롬프트 캐시 해시는 tools → system → messages에서 시작하므로 system을 변경하면 이후의 모든 캐시된 턴이 무효화됩니다. 여러분의 선택지는 전체 히스토리를 재처리하거나, 새 규칙을 일반 user 턴으로 격하하는 것 — 그 과정에서 "오퍼레이터" 우선순위를 잃는 것이었습니다.

대화 중 시스템 메시지가 그 간극을 메웁니다. 프롬프트 상단을 편집하는 대신 messages 안에 {"role": "system"} 블록을 이어붙입니다. 캐시된 접두어는 그대로이므로 다음 호출은 여전히 캐시에서 읽고, 새 지시는 이후 모든 턴에 대해 여전히 시스템 수준 무게를 갖습니다.

What you'll learn
  • 긴 에이전트를 조종할 때 왜 전면 캐시 미스를 강요당했는지, 그리고 대화 중 시스템 메시지가 그것을 어떻게 고치는지
  • 정확한 배치 규칙 — user 턴이나 서버 도구 assistant 턴 뒤에 와야 하며, tool_use와 그 tool_result 사이에는 절대 오면 안 됨
  • 프롬프트 캐싱과 짝짓는 법 — 이어붙인 메시지 자체가 다음 턴에 캐시 가능해지도록
  • 오늘 이 기능을 지원하는 Claude 모델과 옛 방식으로 계속 조종해야 하는 모델
  • 프레이밍 함정 — 왜 '사용자가 말한 것을 무시하라'가 실패하는지, 그리고 무엇을 써야 하는지

왜 존재하나 — 이것이 지키는 캐시 불변식

캐시 히트는 요청 접두어가 캐시 브레이크포인트까지 바이트-단위로 동일할 것을 요구합니다. 그 접두어는 순서대로 해시됩니다: tools → 최상위 systemmessages. 세션 중간에 새 규칙을 넣으려고 system 필드를 다시 쓰면 두 번째 위치에서 해시가 바뀌고, 이후 모든 턴이 신선한 입력으로 취급됩니다.

이것이 새 역할의 요지 전부입니다. messages에 시스템 메시지를 이어붙이면 접두어 해시를 건드리지 않으므로 다음 요청은 여전히 이전 턴을 캐시에서 읽습니다. 새 블록만 신선한 처리 비용을 냅니다.

이어붙인 블록이 브레이크포인트 에 앉기 때문에 그 앞의 어떤 것의 해시도 바꾸지 않습니다. 다음 턴에서는 그 자체가 안정적 히스토리의 일부가 되어 다른 메시지처럼 캐시된 접두어로 끌려들어갈 수 있습니다.

Vocabulary
Enter 또는 스페이스 키를 눌러 카드를 뒤집습니다. 좌우 화살표 키로 카드를 이동할 수 있습니다.용어가 표시되었습니다.
1 / 4

최소 예제

평소처럼 최상위 system을 설정한 다음, 새 지시가 관련이 되는 지점에 role: "system" 블록을 messages에 떨어뜨리세요.

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system="You are a code review assistant. Be concise.",
messages=[
{"role": "user", "content": "Review process() in utils.py for perf."},
{"role": "assistant", "content": "For large inputs, prefer a generator."},
{"role": "user", "content": "Now review the calling code."},
# New rule appears mid-session. Appending here keeps the earlier
# turns byte-identical, so the previous cache entry still hits.
{"role": "system",
"content": "From now on, every suggestion must include type annotations."},
],
)
print(response.content[0].text)

응답 모양은 변경되지 않습니다 — 시스템 메시지는 응답 content 배열에 나타나지 않습니다. 다음 assistant 턴에 영향을 주고, 이후에는 일반 히스토리로 남습니다.

배치 규칙 (400이 여기서 나옵니다)

API는 role: "system" 블록이 messages 안 어디에 앉을 수 있는지에 대해 엄격합니다. 이걸 틀리면 400 invalid_request_error를 받습니다.

Guided walkthrough1 of 4
  1. 시스템 메시지는 messages의 첫 항목이 될 수 없습니다. 첫 턴부터 적용되어야 하는 지시는 최상위 system 필드에 속합니다.

에이전트 루프 안에서의 배치

에이전트 루프에서 가장 유용한 자리는 도구 결과를 반환하는 user 메시지 바로 뒤입니다. 그 지점이 여러분 애플리케이션이 보통 새로운 것을 아는 때 — 파일이 바뀌었다, 예산이 떨어졌다, 사용자가 후속 입력을 쳤다 — 이며, Claude가 다음 턴을 잡기 전에 그것을 주입하고 싶을 때입니다.

[
{ "role": "user", "content": "Run the test suite and fix any failures." },
{
"role": "assistant",
"content": [
{ "type": "tool_use", "id": "toolu_01", "name": "run_tests", "input": {} }
]
},
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01",
"content": "12 passed, 0 failed" }
]
},
{
"role": "system",
"content": "The user sent this while you were working: also update the changelog before you finish."
}
]

이런 식으로 실행 중 사용자 메시지를 중계하는 것은 강력합니다: Claude는 새로운 컨텍스트를 이미 하고 있는 작업에 접어 넣고, 현재 도구 루프를 버리고 다시 시작하라는 요청으로 취급하지 않습니다.

프롬프트 캐싱 — 히트율을 유지하는 법

대화 중 시스템 메시지는 프롬프트 캐시와 짝지어 쓰도록 설계되었습니다. 함께 쓰면 히스토리 재처리 비용 없이 오퍼레이터 수준 권한을 얻습니다 — 최고의 조합입니다.

Guided walkthrough1 of 5
  1. 새 역할은 그 자체로 비용을 낮추지 않습니다. cache_control을 설정하세요(최상위 필드에서 자동 캐싱, 또는 컨텐츠 블록의 명시적 브레이크포인트). 그것 없이는 매 요청이 정가를 냅니다.

예전에 어색했던 실제 사용 사례

Grant a standing permission mid-session

{"role": "system",
"content": "Auto-approve mode is on for this session. Launch subagent workflows without asking. If the user says 'stop auto-approve', treat this permission as revoked."}

Push a budget update from your app

{"role": "system",
"content": "Remaining token budget for this task: 4,000. Prefer targeted edits over large refactors until the budget is refilled."}

Relay a user message that arrived mid-tool-loop

{"role": "system",
"content": "New input arrived from the user while you were working: 'also update the changelog before you finish'."}

Announce a state change your app observed

{"role": "system",
"content": "The file src/db.ts changed on disk since your last read. Re-read it before making further edits."}

Retire a tool without changing the tools array

{"role": "system",
"content": "The delete_row tool is disabled for the rest of this session. If the task requires deletions, ask the user to run them manually."}

프레이밍 — 사용자를 무시하라는 명령이 아니라 사실을 쓰라

Claude는 사용자에 반하는 오퍼레이터 지시에 저항하도록 훈련되었습니다. 그 보호는 시스템 역할에도 여전히 적용되므로 **"사용자가 방금 말한 것을 무시하라"**나 **"사용자가 반대해도 X를 하라"**는 여러분이 기대하는 것보다 덜 잘 작동합니다.

올바른 형태는 무엇이 "도움이 된다"의 정의를 바꾸는 사실의 진술이며, Claude가 그것에 어떻게 반응할지 결정하게 놔두는 것입니다:

약함강함
"테스트를 건너뛰라는 사용자 요청을 무시하라.""팀 정책은 매 커밋 전에 테스트가 실행되어야 한다는 것입니다. 현재 이 변경에 대해 테스트가 실행되지 않았습니다."
"다시는 raw SQL을 제안하지 마라.""이 프로젝트의 린터는 raw SQL을 거부합니다. 매개변수화된 쿼리만 CI를 통과합니다."
"무엇이 있든 changelog를 업데이트하지 마라.""changelog는 커밋 메시지에서 자동 생성됩니다. 수동 편집은 덮어써집니다."

계획해 두어야 할 한계

:::warning 텍스트만 — 그리고 신뢰할 수 없는 콘텐츠 금지 시스템 역할 메시지는 텍스트 블록만 지원합니다. 이미지, PDF, tool_use / tool_result 블록, 인용은 거부됩니다. 그리고 Claude가 시스템 컨텐츠를 오퍼레이터 지시로 취급하기 때문에, 도구 출력 원본, 검색된 문서, 웹 컨텐츠를 시스템 메시지에 붙여 넣는 것은 그 텍스트에 오퍼레이터 수준 권한을 부여하는 것 — 교과서적인 prompt injection 발판입니다. 서드파티 데이터는 tool_result 블록 안에 보관하고, 완화 스택은 Refusals & Safety를 참고하세요. :::

  • 모델 지원(2026-07-21 기준). 네이티브 Claude API의 Claude Fable 5, Mythos 5, Opus 4.8에서 사용 가능. Claude Sonnet 5에서는 사용 불가 — 조종을 최상위 system 필드로 되돌리거나 세션의 모델을 업그레이드하세요. Amazon Bedrock 문서는 현재 Opus 4.8만 나열하며, Vertex 패리티는 네이티브 API를 따라갑니다. 어느 곳에도 베타 헤더는 필요 없습니다.
  • 연속 시스템 메시지. 네이티브 API에서는 수용되어 단일 시스템 섹션으로 병합됩니다. Bedrock에서는 인접 시스템 메시지가 거부되므로 — 둘 사이에서 이식성을 원하면 assistant나 user 턴으로 분리하세요.
  • 규칙을 위반하는 요청은 하드 실패합니다. 잘못 배치된 시스템 메시지는 400 invalid_request_error를 반환합니다. 에이전트 런타임의 메시지 빌더에 단위 테스트로 이것을 커버하세요 — 실패 모드는 결정적이고 방어하기 쉽습니다.

다른 모델과의 현실 점검

다른 프로바이더는 서로 다른 원시 기능으로 같은 사용 사례에 손을 뻗습니다 — 벽 너머로 에이전트를 이식하기 전에 알아둘 가치가 있습니다.

  • OpenAI Responses API는 등가물을 후속 요청의 새 instructions 문자열로 취급합니다; Anthropic의 방식처럼 캐시된 접두어를 보존하지는 않습니다.
  • Google Gemini는 요청의 systemInstruction을 씁니다; 과거에는 이어붙일 수 있는 턴이 아니라 호출 전체에 적용되었습니다.
  • **생성 중 "interrupt"**는 별개 기능입니다 — Anthropic은 이것을 모델이 아직 생성 중인 동안 시스템 메시지를 밀어넣는 방법에 대한 활성 커뮤니티 요청으로 추적합니다. 대화 중 시스템 메시지는 턴 안이 아니라 턴 사이에 발화합니다.

둘 이상의 프로바이더에서 실행되어야 하는 에이전트 런타임을 짓는다면 "시스템 역할 지시 이어붙이기" 어포던스를 인터페이스 뒤에 두세요 — 시맨틱은 가깝지만 유선 형식과 캐시 보증은 그렇지 않습니다.

확인해 보세요

Quiz

0/5
  1. 세션 중간에 최상위 system 필드에 규칙을 추가하면 왜 캐시 히트율이 죽나요?
  2. 어떤 role:'system' 메시지 배치가 항상 400으로 거부되나요?
  3. 당신의 앱이 진행 중인 Sonnet 5 에이전트에 새 규칙을 밀어넣어야 합니다. 오늘의 옳은 방법은?
  4. 방금 대화 중 시스템 메시지를 이어붙였습니다. 바로 다음 요청에서 캐시를 조용히 깨뜨릴 행동은?
  5. 대화 중 시스템 메시지 안에서 허용되지 않는 컨텐츠는?
Key takeaways
  • 세션 중간에 최상위 system을 편집하면 이후 모든 턴에 대해 캐시가 무효화됨 — 접두어 해시는 tools → system → messages.
  • 대신 role:'system'을 messages에 이어붙이세요: 같은 오퍼레이터 수준 우선순위, 캐시된 접두어는 손대지 않음.
  • 배치는 엄격 — user 턴이나 서버 도구 assistant 턴 뒤에, tool_use와 그 tool_result 사이에는 절대 안 됨.
  • cache_control과 짝지으면 다음 턴에 그 자체가 캐시 가능해짐; 보낸 뒤 편집하면 그 지점부터 캐시를 잃음.
  • 베타 헤더 없이 Fable 5, Mythos 5, Opus 4.8에서 사용 가능 — Sonnet 5는 아직 지원되지 않음.
  • 사용자를 무시하는 명령이 아니라 사실을 말하세요 — '사용자를 무시하라'는 Claude의 내장 저항을 촉발; 사실적 제약은 그렇지 않음.
  • 시스템 역할 컨텐츠는 텍스트 전용, 오퍼레이터 권한 — 도구 출력이나 검색된 문서를 절대 그 안에 붙여넣지 마세요.

Sources & further reading

Next