지식위키

Codex · 프롬프트·컨텍스트 조립 (base 프롬프트 + AGENTS.md + 환경/스킬/권한 fragment)

Codex · 프롬프트·컨텍스트 조립 (base 프롬프트 + AGENTS.md + 환경/스킬/권한 fragment)

한 줄 요약

Codex가 모델에 보내는 한 번의 요청은 고정 base 프롬프트(취업규칙) 위에 “지금 이 순간의 사실” 조각(포스트잇)을 정해진 순서로 쌓은 한 덩어리다. 모델이 왜 특정 행동을 하는지(권한·위치·프로젝트 규칙)는 모두 이 조립 단계에서 결정된다. 여기를 알면 Codex의 판단 근거를 통제할 수 있다.

그림

flowchart TD
  A[턴 시작] --> B[build_initial_context 호출]
  B --> C1["base 프롬프트 선택<br/>설정 오버라이드 > 히스토리 > 모델 기본"]
  B --> D1["권한·스킬·플러그인·성격 조각<br/>→ developer 묶음"]
  B --> D2["AGENTS.md 찾아 연결 + 환경 정보<br/>→ user 묶음"]
  D1 --> E[developer 메시지 1개]
  D2 --> F[user 메시지 1개]
  C1 --> G[Prompt.base_instructions 칸]
  E --> H[Prompt.input 칸]
  F --> H
  G --> I[Responses API 요청 발송]
  H --> I
  I --> J[모델 응답]
  J --> K{"대화창이 한계를 넘었나?"}
  K -- 예 --> L[compact 가 요약으로 압축]
  K -- 아니오 --> A

쉽게 풀기

1단계 — base = “전 직원 취업규칙”. 요청의 맨 밑판은 base instructions(시스템 프롬프트)다. “너는 Codex다, 이렇게 행동하라”는 고정 헌법이며 모델마다 prompt.md 한 장으로 정해진다. 매번 새로 쓰지 않고 늘 같은 판이 깔린다.

2단계 — 그 위에 “오늘 책상 포스트잇”을 붙인다. 취업규칙만으로는 지금 상황을 모른다. 그래서 상황 조각(fragment)을 얹는다 — 어느 폴더(cwd)·셸·날짜·권한·쓸 수 있는 스킬/플러그인, 그리고 프로젝트 AGENTS.md 규칙. 각각이 포스트잇 한 장이다.

3단계 — 포스트잇마다 “이름표”와 “붙일 자리”. 각 조각은 고유 마커(예: <environment_context>…</environment_context>)와 역할(role) 을 갖는다. 정책·도구 안내는 developer, “지금의 사실”은 user다. 이름표가 있어야 대화가 길어졌을 때 Codex가 옛 조각을 찾아 갈아끼울 수 있다.

4단계 — 같은 역할끼리 한 봉투로. developer 조각은 모여 developer 메시지 1개, user 조각은 user 메시지 1개가 된다. base는 별도 칸(base_instructions)에 담긴다. 이 셋이 합쳐져 한 요청이 된다.

아래 그림이 이 4단계의 흐름이다.

flowchart LR
  P1["② 상황 조각들<br/>(포스트잇)"] --> R{③ role 분류}
  R -->|developer| DV["권한·스킬·플러그인·성격"]
  R -->|user| US["AGENTS.md·환경"]
  DV --> M1["④ developer 메시지 1개"]
  US --> M2["④ user 메시지 1개"]
  B1["① base 취업규칙"] --> BK["base_instructions 칸"]
  M1 --> REQ[한 번의 모델 요청]
  M2 --> REQ
  BK --> REQ

[!note] 핵심 비유 base = 회사 취업규칙(전 직원 동일). fragment = 그날 책상 포스트잇(오늘 위치·열쇠 권한·쓸 도구). 마커 = 포스트잇 제목(떼고 새로 붙일 때 찾는 용도).

핵심 정리

조립에 쓰이는 세 가지 그릇과, 조각이 지켜야 할 공통 계약은 다음과 같다.

그릇담는 것요청의 어디로
base_instructions시스템 프롬프트 전문(고정 헌법)instructions 필드
developer 묶음권한·스킬·플러그인·성격 등 정책/능력input의 developer 메시지
user 묶음AGENTS.md·환경 등 “지금의 사실”input의 user 메시지

조각의 공통 계약(ContextualUserFragment 트레잇): role()=들어갈 역할(developer/user), markers()/type_markers()=시작·끝 이름표(식별·교체용), body()=이름표 사이 본문, render()=이름표+본문을 구분자 없이 이어붙임.

[!note]- 펼쳐보기: base 프롬프트 선택 우선순위 + 주요 fragment 목록 base 선택 우선순위 (core/src/session/mod.rs:570~584)

  • config.base_instructions 오버라이드가 있으면 그것
  • ② 세션 재개 시 히스토리의 session_meta.base_instructions
  • ③ 둘 다 없으면 현재 모델의 기본 base

모델별 base는 models.json의 각 모델 base_instructions 필드에 인라인 텍스트로 박혀 있다. core/gpt-5.2-codex_prompt.mdprompt.md사람용 소스 사본일 뿐, 어떤 .rs/.tomlinclude_str!로 직접 가져가지 않는다(grep 확인).

주요 fragment 한눈에 보기

  • UserInstructions(=AGENTS.md) — role user, 마커 # AGENTS.md instructions … </INSTRUCTIONS>
  • EnvironmentContext — role user, <environment_context>…</environment_context> (cwd·shell·date·timezone·network·filesystem·subagents)
  • AvailableSkillsInstructions — role developer, <skills_instructions>…
  • PermissionsInstructions — role developer, <permissions instructions>… (샌드박스·승인정책·writable roots·denied reads)
  • AvailablePluginsInstructions / AppsInstructions — role developer, 플러그인·MCP 커넥터 목록
  • PersonalitySpec / CollaborationMode / TokenBudgetContext — role developer, 성격·협업모드·컨텍스트 메타

AGENTS.md는 cwd에서 프로젝트 루트까지 거슬러 올라가며 경로상의 파일을 순서대로 모아 연결한다. 그 발견 규칙을 그림으로 보면 다음과 같다.

flowchart TD
  S[cwd에서 시작] --> U{"루트 마커<br/>.git 도달?"}
  U -- 아니오 --> UP[상위 폴더로 이동] --> U
  U -- 예 --> ROOT[프로젝트 루트 확정]
  ROOT --> COL["루트→cwd 경로의<br/>모든 AGENTS.md 순서대로 수집"]
  COL --> JOIN["첫 doc 앞에 --- project-doc --- 삽입"]
  JOIN --> BUD{"project_doc_max_bytes<br/>예산 초과?"}
  BUD -- 예 --> TR[초과분 truncate]
  BUD -- 아니오 --> OK[연결 완료]
  TR --> OK

[!note]- 펼쳐보기: AGENTS.md 발견 규칙 전문 (core/src/agents_md.rs)

  • project_root_markers(기본 .git)를 만날 때까지 cwd에서 위로 올라가 루트를 정한다
  • 루트→cwd 경로상의 모든 AGENTS.md(+AGENTS.override.md+fallback 파일명)를 순서대로 모아 연결한다. 루트 위로는 안 올라간다
  • 첫 project doc 앞에 \n\n--- project-doc ---\n\n 구분자를 넣는다
  • project_doc_max_bytes 예산을 넘으면 잘라낸다(truncate)

실제 예시

Prompt 구조체가 한 턴 요청의 페이로드다. baseBaseInstructions { text: String }(protocol/src/models.rs)로 감싸지며 기본값은 BASE_INSTRUCTIONS_DEFAULT = include_str!("prompts/base_instructions/default.md").

[!note]- 펼쳐보기: Prompt 구조체 필드 + fragment 렌더 코드 전문 Prompt 구조체 (codex-rs/core/src/client_common.rs)

// input: 대화 입력 아이템(base 위에 쌓인 fragment + 히스토리) — 조립된 컨텍스트가 여기로
// tools: 사용 가능한 툴(내장 + MCP 서버 툴)
// parallel_tool_calls: 병렬 툴콜 허용 여부
// base_instructions: 시스템 프롬프트 본문 → Responses API의 instructions 필드로 전달
// personality(opt) / output_schema(opt) / output_schema_strict(기본 true)

AGENTS.md 조각의 마커·body (core/src/context/user_instructions.rs)

impl ContextualUserFragment for UserInstructions {
    fn role(&self) -> &'static str { "user" }
    fn markers(&self) -> (&'static str, &'static str) { Self::type_markers() }
    fn type_markers() -> (&'static str, &'static str) {
        ("# AGENTS.md instructions", "</INSTRUCTIONS>")
    }
    fn body(&self) -> String {
        let directory = self.directory.as_ref()
            .map(|directory| format!(" for {directory}"))
            .unwrap_or_default();
        format!("{directory}\n\n<INSTRUCTIONS>\n{}\n", self.text)
    }
}

렌더 결과(개념):

# AGENTS.md instructions for /repo

<INSTRUCTIONS>
(AGENTS.md 내용)
</INSTRUCTIONS>

EnvironmentContext body 렌더링 (core/src/context/environment_context.rs)

fn body(&self) -> String {
    let mut lines = Vec::new();
    lines.push(format!("  <cwd>{}</cwd>", environment.cwd.to_string_lossy()));
    lines.push(format!("  <shell>{}</shell>", environment.shell));
    if let Some(current_date) = &self.current_date {
        lines.push(format!("  <current_date>{current_date}</current_date>"));
    }
    if let Some(timezone) = &self.timezone {
        lines.push(format!("  <timezone>{timezone}</timezone>"));
    }
    match &self.network { Some(network) => lines.push(format!("  {}", network.render())), None => {} }
    if let Some(filesystem) = &self.filesystem { lines.push(format!("  {}", filesystem.render())); }
    if let Some(subagents) = &self.subagents {
        lines.push("  <subagents>".to_string());
        lines.extend(subagents.lines().map(|line| format!("    {line}")));
        lines.push("  </subagents>".to_string());
    }
    format!("\n{}\n", lines.join("\n"))
}

렌더 결과(개념):

<environment_context>
  <cwd>/home/user/repo</cwd>
  <shell>bash</shell>
  <current_date>2026-06-15</current_date>
  <timezone>Asia/Seoul</timezone>
  <network enabled="true"><allowed>example.com</allowed></network>
  <filesystem><workspace_roots><root>/repo</root></workspace_roots>
    <permission_profile type="managed"><file_system type="restricted" /></permission_profile></filesystem>
</environment_context>

조립 순서 — build_initial_context

build_initial_context는 3개 버킷에 각 fragment를 render() 해서 push한 뒤 합친다. 버킷에서 메시지가 되는 흐름은 다음과 같다.

flowchart TD
  subgraph DEV[developer_sections 순서]
    d1[model-switch] --> d2[Permissions] --> d3[developer_instructions]
    d3 --> d4[Collaboration] --> d5[realtime] --> d6[Personality]
    d6 --> d7[Apps] --> d8[Skills] --> d9[Plugins]
    d9 --> d10[확장 contributor] --> d11[TokenBudget]
  end
  subgraph USR[contextual_user_sections 순서]
    u1[확장 contributor] --> u2["UserInstructions=AGENTS.md"] --> u3["EnvironmentContext+subagents"]
  end
  DEV --> BD["build_developer_update_item<br/>role=developer 메시지 1개"]
  USR --> BU["build_contextual_user_message<br/>role=user 메시지 1개"]
  BD --> NOTE["섹션은 구분자 join이 아니라<br/>섹션마다 별도 ContentItem::InputText"]
  BU --> NOTE

separate_developer_sections는 별도 developer 메시지로 분리되는 항목들이다. (session/mod.rs:2871, context_manager/updates.rs:191)

새 fragment 직접 추가하기

// my_crate/src/context/my_context.rs
use codex_context_fragments::ContextualUserFragment;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyContext { pub note: String }

impl ContextualUserFragment for MyContext {
    fn role(&self) -> &'static str { "developer" }            // 1) 역할
    fn markers(&self) -> (&'static str, &'static str) { Self::type_markers() }
    fn type_markers() -> (&'static str, &'static str) {
        ("<my_context>", "</my_context>")                     // 2) 고유 마커
    }
    fn body(&self) -> String {                                 // 3) 본문(개행 직접 포함)
        format!("\n  {}\n", self.note)
    }
}
// build_initial_context 에서 등록
if turn_context.config.include_my_context {
    developer_sections.push(MyContext { note: "...".into() }.render());
}

[!warning] 추가할 때 체크리스트

  • roledeveloper(정책/능력) 또는 user(AGENTS.md·환경 같은 사실)로 맞게 골랐는가
  • type_markers()가 다른 조각과 충돌하지 않는 고유 태그인가
  • body()가 마커와 붙어도 안 깨지게 자기 줄바꿈을 직접 넣었는가 (render는 구분자 안 넣음)
  • base 변경은 config.base_instructions 오버라이드나 models.json 모델 항목 수정(둘 다 instructions 필드로 감)
  • AGENTS.md를 키우면 project_doc_max_bytes 한도 확인(초과 시 truncate)

요약 & 셀프체크

3줄 요약:

  1. 한 번의 모델 요청 = 고정 base + 상황 조각(fragment)을 정해진 순서로 쌓은 것.
  2. 각 조각은 고유 마커와 역할(developer/user)을 갖고, 같은 역할끼리 한 메시지로 묶여 Prompt.input에, base는 base_instructions 칸에 따로 들어간다.
  3. 마커가 붙는 이유는 대화가 길어졌을 때 옛 조각을 찾아 교체·압축(compact)하기 위해서다.

스스로 답해보기:

  • AGENTS.md 조각과 환경 정보 조각은 왜 둘 다 user이고, 권한·스킬 조각은 왜 developer인가?
  • 같은 모델에서 base 프롬프트를 바꾸려면 어디를 건드려야 하나? (우선순위 3단계 중 어느 것?)
  • 조각마다 마커가 없다면 대화가 길어졌을 때 무엇을 못 하게 되는가?

연결

CX_개요 · _분석축_루브릭

[!tip]- 펼쳐보기: 분석 근거 (소스 교차확인) 본 노트의 사실은 아래 소스를 직접 확인해 작성했다. 특히 “모델별 base는 models.json 인라인 텍스트이며 prompt.md는 사람용 사본일 뿐, 어떤 코드도 include_str!로 직접 가져가지 않는다”는 점은 grep으로 교차검증했다.

  • …/core/src/client_common.rsPrompt 구조체
  • …/protocol/src/models.rsBaseInstructions, BASE_INSTRUCTIONS_DEFAULT
  • …/context-fragments/src/fragment.rsContextualUserFragment 트레잇, render()/matches_text()
  • …/core/src/context/mod.rs — fragment 모듈 등록부
  • …/core/src/context/user_instructions.rs — AGENTS.md 마커
  • …/core/src/context/environment_context.rs — cwd/shell/date/network/filesystem/subagents 렌더
  • …/core/src/context/available_skills_instructions.rs — skills 조각(role=developer)
  • …/core/src/context/permissions_instructions.rs + …/prompts/src/permissions_instructions.rs — permissions 조각
  • …/core/src/agents_md.rs — AGENTS.md 발견·연결·예산
  • …/core/src/session/mod.rs:2871build_initial_context
  • …/core/src/context_manager/updates.rs:183build_developer_update_item/build_contextual_user_message
  • …/core/src/context_manager/mod.rs — history/normalize 윈도우 관리
  • …/core/src/compact.rs — 한계 초과 요약 압축(SUMMARIZATION_PROMPT)
  • …/protocol/src/protocol.rs:94~105 — 마커 상수(ENVIRONMENT_CONTEXT_OPEN_TAG 등)
  • …/models-manager/src/model_info.rs + models.json + prompt.md — 모델별 base 출처
  • …/core/gpt-5.2-codex_prompt.md, gpt_5_codex_prompt.md — base 사람용 소스 사본