경계 마커
core/agent/system_prompt.py:35에 정의되어 있습니다. v0.93부터 9개 prompt 파일의 16개 marker가 XML 래퍼로 전환됩니다. 경계 자체는 동일 sentinel 문자열이지만, marker가 노출되는 영역은 <dynamic_context> XML 안으로 캡슐화됩니다.
PROMPT_CACHE_BOUNDARY = "__GEODE_PROMPT_CACHE_BOUNDARY__" # v0.93+ XML 래퍼 (system prompt 안에서) ... STATIC 영역 ... __GEODE_PROMPT_CACHE_BOUNDARY__ <dynamic_context> ... DYNAMIC 영역 ... </dynamic_context>
build_system_prompt()이 이 marker를 두 섹션 사이에 삽입합니다. audit-mode는 dynamic 블록을 strip합니다 (System Prompt Modes 참조).
- STATIC (마커 이전). router 템플릿,
GEODE.md의 아이덴티티. 턴 간 안정적. - DYNAMIC (마커 이후). 현재 날짜, 모델 카드, 프로젝트 메모리 (G2-G4), 사용자 컨텍스트. 매 턴 변경됨.
어댑터의 분할 방식
core/llm/providers/anthropic.py:476-495의 agentic 어댑터.
from core.agent.system_prompt import PROMPT_CACHE_BOUNDARY
if PROMPT_CACHE_BOUNDARY in system:
static_part, dynamic_part = system.split(PROMPT_CACHE_BOUNDARY, 1)
sys_blocks = [
{"type": "text", "text": static_part.rstrip(),
"cache_control": {"type": "ephemeral"}},
{"type": "text", "text": dynamic_part.lstrip()},
]
else:
sys_blocks = [
{"type": "text", "text": system,
"cache_control": {"type": "ephemeral"}},
]STATIC 블록은 캐시 마커를 받지만 DYNAMIC 블록은 받지 않습니다. Anthropic은 STATIC 프리픽스를 서버 측에서 캐싱하며, 5분 TTL 안의 후속 턴은 DYNAMIC 접미부와 새 user 메시지에 대해서만 과금됩니다.
Non-agentic 호출 지점
core/llm/router.py의 네 호출 지점 (라인 481, 582, 749, 901)은 더 단순한 헬퍼 system_with_cache(system)를 써서 system prompt 전체를 단일 ephemeral 블록으로 감쌉니다. 이들은 STATIC/DYNAMIC 구조화 system prompt를 조립하지 않는 non-agentic LLM 호출 (단발 prompt, 평가 호출) 입니다.
캐시되는 것, 캐시되지 않는 것
| 대상 | 캐시 여부 |
|---|---|
| Anthropic system prompt . STATIC 섹션 | Yes (ephemeral) |
| Anthropic system prompt . DYNAMIC 섹션 | No (매 턴 변경) |
| Anthropic non-agentic system prompt (전체) | Yes (단일 ephemeral 블록) |
Anthropic messages 배열 | Yes. 최근 3개 non-system 메시지 (PR #864, 헬퍼 anthropic.py:175, 호출 :501) |
| OpenAI / Codex system prompt | GEODE 측 명시 wiring 없음 (OpenAI가 서버 측에서 자동 캐싱) |
| GLM system prompt | 프로바이더 관리 |
messages history 캐싱 (PR #864)
Anthropic은 요청당 최대 4개의 캐시 breakpoint를 허용합니다. 어댑터는 messages.create 직전에 apply_messages_cache_control(messages)를 적용해 최근 3개 non-system 메시지의 마지막 content 블록에 cache_control: ephemeral을 붙입니다. 위의 system 블록과 합치면 4개 슬롯을 모두 채워 롤링 히스토리가 캐시됩니다.
# core/llm/providers/anthropic.py:175 (helper) and :501 (call site)
cached_messages = apply_messages_cache_control(messages)
create_kwargs = {
"system": sys_blocks,
"messages": cached_messages,
...
}헬퍼는 비-변형 (얕은 복사된 새 리스트 반환) 이며, str과 list[block] content를 모두 처리하고 빈 리스트 content는 조용히 건너뜁니다. 19개 케이스가 tests/test_anthropic_messages_cache.py에서 검증됩니다.
캐시 무효화
캐시 키는 캐시된 블록의 바이트 단위 콘텐츠와 모델 ID입니다. STATIC 섹션의 어떤 변경 (예. 업데이트된 GEODE.md 아이덴티티, 새 도메인 예제 목록, router 템플릿 수정) 도 캐시를 무효화하며, 후속 턴이 다시 캐시에 적중하기 전에 한 번의 전체 요청 비용을 지불해야 합니다.
이것이 prompt drift 감지 (Prompt Hashing) 와 prompt 캐싱이 설계상 긴밀하게 결합된 이유입니다. 조용한 prompt 변경은 조용한 캐시 무효화로 이어지며, 해시 잠금장치는 그런 변경을 의식적인 단계로 강제합니다.