컨텍스트·프롬프트 조립 — 횡단분석
컨텍스트·프롬프트 조립 — 횡단분석
한 줄 요약
무상태(stateless) LLM에게 일을 시키기 직전, 프로젝트 규칙·환경·도구목록을 “무엇을 / 어떤 순서로 / 어떤 형식으로” 쌓아 올리는가 — 7개 하네스가 같은 문제를 푸는 방식이 파일 / 코드 fragment / 런타임 훅 / 불변 스펙으로 갈린다.
왜 배우나: 내 스택(Claude Code + Codex + OMC)의
CLAUDE.md·AGENTS.md를 “모델이 실제로 따르는 컨텍스트”로 설계하려면, 각 방식의 강제력과 비용을 알아야 헛수고(드리프트·토큰 폭발)를 줄인다.
그림
flowchart TD
A["무상태 LLM<br/>(빈 컨텍스트로 시작)"] --> B{"무엇으로 채울까?"}
B -->|사람이 쓰는 마크다운| C["파일파<br/>Claude Code · 내 패턴"]
B -->|"코드 타입·마커"| D["코드 fragment파<br/>Codex"]
B -->|매 프롬프트 한 줄| E["런타임 문장파<br/>fable-ish"]
B -->|"사전 합의·불변 계약"| F["불변 스펙파<br/>ouroboros"]
B -->|여러 벤더 통합| G["멀티벤더파<br/>gajae-code"]
C --> H["계층 병합<br/>(넓은 범위 먼저 → 가까운 범위 나중)"]
D --> H
G --> H
H --> I["예산·게이트로 잘라냄<br/>(글자수·줄수·deadline)"]
E --> I
F --> I
I --> J["모델에게 전달<br/>system / user / additionalContext"]
J --> K["윈도우 한계 도달 시<br/>압축·요약 후 재주입"]
K --> A
쉽게 풀기
LLM은 매번 기억상실증에 걸린 신입사원이라고 생각하면 쉽다. 어제 무슨 일을 했는지, 회사 규칙이 뭔지 하나도 기억하지 못한 채 매일 아침 출근한다. 그래서 일을 시키기 전에 “업무 매뉴얼”을 다시 손에 쥐여줘야 한다. 이 매뉴얼을 만드는 작업이 바로 컨텍스트 조립이다.
문제는, 매뉴얼을 통째로 다 읽히면 너무 길어서(토큰 폭발) 정작 오늘 할 일을 들을 자리가 없어진다. 그래서 7개 하네스는 각자 다른 전략을 쓴다.
- 파일파 (Claude Code, 내 패턴) — 매뉴얼을
CLAUDE.md라는 종이 파일로 책상 위에 둔다. 사람이 직접 펜으로 고칠 수 있어 투명하다. 단점은 신입이 “이건 참고사항이지 꼭 지킬 명령은 아니네”라고 흘려들을 수 있다는 것(강제력 약함). - 코드 fragment파 (Codex) — 매뉴얼 각 문단에 **형광펜 표식(마커)**을 붙여 둔다. 나중에 “환경 정보 문단만 새것으로 갈아끼우기”가 정밀하게 된다. 대신 문단 추가하려면 인쇄소(컴파일)를 거쳐야 해서 사람이 즉석에서 못 고친다.
- 런타임 문장파 (fable-ish) — 매뉴얼 자체를 안 만든다. 대신 일을 시킬 때마다 귓속말로 “지금은 위험하니 조심해” 한 줄만 속삭인다. 오염이 전혀 없고 항상 현재 상황을 반영하지만, 영구 규칙을 담을 그릇이 없다.
- 불변 스펙파 (ouroboros) — 일을 시작하기 전에 면접(인터뷰)을 봐서 목표·제약·합격기준을 합의하고 도장을 찍어(frozen) 버린다. 요구사항이 도중에 흔들리는 일을 원천 차단하지만, 매번 면접이 필요해 무겁다.
- 멀티벤더파 (gajae-code) — 신입이 여러 회사(Claude·Gemini·Codex·Cursor) 매뉴얼을 동시에 봐야 할 때, 이를 하나의 통합 매뉴얼로 합쳐 준다. 가까운 폴더의 규칙이 더 위로 오게 정렬한다.
핵심 비유 하나만 기억하자: 본문은 책장(디스크)에 꽂아두고, 책상(컨텍스트)에는 “몇 번 책장 몇 번째 책” 메모(포인터)만 올린다. 그래야 책상이 안 넘친다.
핵심 정리
| 프레임워크 | 컨텍스트의 형태 | 주입 위치(강제력) |
|---|---|---|
| Claude Code | CLAUDE.md 4계층 + @import + MEMORY.md 인덱스 | system 뒤 user/context(조언, 강제 X) |
| Codex | Prompt 구조체 + 마커 달린 fragment | Responses API instructions 필드 |
| OMC | CLAUDE.md 내 OMC:START/END 마커 블록 + 런타임 키워드 | CC 자동로드 + 훅 additionalContext |
| gajae-code | .md 정적 임포트 + ContextFile(멀티벤더) | system prompt 블록 배열 |
| ouroboros | Seed(Pydantic frozen=True) 불변 계약 | 실행 프롬프트에 계약 섹션 렌더 |
| fable-ish | 영구 파일 없음, context_for_mode() 한 줄 | 소프트=additionalContext / 하드=block |
| 내 패턴 | CLAUDE.md→@AGENTS.md 정본 단일화 | system 영역 텍스트 연결 |
[!note] 생명주기·트리거 (언제 조립되나)
- 세션 시작 1회: Claude Code(이후 증분 누적), gajae-code(
buildSystemPrompt()6단계Promise.all+withDeadline(5s)), 내 패턴- 턴 시작: Codex
build_initial_context()(윈도우 초과 시compact.rs요약)- 이벤트별: OMC·fable-ish가
UserPromptSubmit/PostToolUse(실패 시)/Stop(차단)에서 주입·차단- 게이트 통과 후: ouroboros는
interview → 모호도≤0.2 게이트 → seed(굳힘) → run(렌더)순서
[!note] 모두가 수렴하는 공통 패턴 (왜 비슷해지나) 근본 제약(무상태·유한 윈도우·강제력 없는 user 메시지)이 같아서 설계가 수렴한다.
- 무상태 전제 → 매 세션 재주입 (압축돼도 파일/마커로 다시 끌어옴)
- 본문 인라인 회피 → 포인터/경로/디스크립터 (인라인하는 건 작은 ouroboros Seed뿐)
- 계층 병합 = 덮어쓰기 아닌 연결, 가까울수록 우세 (넓은 범위 먼저, 구체적 범위 나중)
- 예산·게이팅으로 보호 (MEMORY 200줄/25KB, 스킬 1000/3000자, deadline 5s, project_doc truncate)
additionalContextJSON 채널 — CC 호스트 셋(OMC·fable-ish·내 패턴)의 사실상 주입 ABI
[!note] 분기점 — 누가 왜 다르게 했나 (트레이드오프)
- 파일 vs 코드 vs 문장 vs 스펙: 파일파는 편집성·투명성을 얻고 강제력을 잃음 / 코드파(Codex)는 윈도우 관리 정밀성을 얻고 운영 유연성을 잃음 / 문장파(fable-ish)는 오염 제로를 얻고 영속 규칙 그릇을 잃음 / 스펙파(ouroboros)는 드리프트 차단을 얻고 무거움을 떠안음
- 강제력의 위치: CC·내 패턴은 컨텍스트(조언)와 강제(hook/권한)를 분리 / OMC는 결합 / fable-ish는 소프트·하드 채널을 깔끔히 이분 / ouroboros는 계약 자체에 내장
- 멀티벤더: gajae만 6종 벤더를 통합, 내 패턴은 “CLAUDE.md=AGENTS.md 정본 공유”로 2벤더를 단일화
- 키워드 트리거: OMC만 본격 구현(대가로 방대한 sanitize 로직)
[!note] 베스트 — 내 스택(Claude+Codex+OMC)에 차용할 구체안
- [Codex→내 CLAUDE.md] 마커 기반 식별: 가변 섹션을
<project_env>...</project_env>XML 마커로 감싸 스크립트로 핀포인트 갱신 (자유본문은 압축 시 통째로 날아감)- [gajae→내 조립] 인라인 금지 + deadline: 프롬프트는
.md분리·with{type:"text"}임베드, 수집에withDeadline(5s)+fallback으로 D드라이브 느린 I/O 방어- [OMC→fable-ish] 소프트/하드 2단 게이트: akh2 게이트를 “소프트 경고(
additionalContext)→하드 block(커밋 시)“으로 나눠 모델이 일찍 자가교정 +MAX_STOP_BLOCKS=2무한루프 차단- [ouroboros→대형작업 한정] 모호도 게이트: 전면 도입은 무거우니 ppt-forge/akh2 신규 대량작업 진입점에만 “goal/constraints 못박고 모호도≤0.2” 적용
- [내 패턴 강화] 정본 단일화 + 디스크립터 예산:
@AGENTS.md단일화 유지 + MEMORY 샤드는 score 상위 5개만 주입한 줄 결론: 정본은 파일로(투명), 가변 섹션은 마커로(정밀), 주입은 경로+예산으로(토큰), 강제는 소프트→하드 2단으로(자가교정), 대형작업만 불변 스펙 게이트로(드리프트 차단).
실제 예시
// gajae-code: packages/coding-agent/src/system-prompt.ts
// .md 템플릿을 코드에 인라인하지 않고 정적 임포트 + 블록 배열로 조립
import projectRules from "./rules.md" with { type: "text" }; // 인라인 금지 규약
// depth 내림차순 정렬 = "deeper overrides higher"의 물리 구현
const blocks: string[] = [systemBlock, ...contextFiles
.sort((a, b) => b.depth - a.depth) // 가까운(deeper) 파일이 뒤 = prominent
.map((f) => f.content)];
// withDeadline(5s): 수집 실패가 세션 시작을 죽이지 않게 하는 정책 (속도 아님)
const prompt = await withDeadline(buildSystemPrompt(blocks), 5000, fallbackPrompt);
# ouroboros: src/ouroboros/core/seed.py — frozen 불변 계약
from pydantic import BaseModel
class Seed(BaseModel):
model_config = {"frozen": True} # goal/constraints/acceptance 불변, ontology만 진화
goal: str
constraints: list[str]
acceptance_criteria: list[str]
# render_seed_contract_for_execution()는 실제로 아래 섹션을 렌더(seed_contract_prompt.py:142)
# ## Goal · Task Type · Constraints · Brownfield · Ontology Lens
# ## Evaluation Principles · Exit Conditions
# (※ Acceptance Criteria / Auto Recursion Guard는 별도 함수 — 교차검증 참고)
// OMC / fable-ish: CC 플러그인의 사실상 주입 ABI
// 훅 stdout으로 이 봉투를 내보내야 유효 (message가 아니라 additionalContext)
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "이 frontmatter 계약 위반 위험 — 관계 10종 확인 필요"
}
}
// 하드 차단은 별도: { "decision": "block", "reason": "..." } (MAX 2회로 교착 방지)
요약 & 셀프체크
- 컨텍스트 조립 = 무상태 LLM에 매번 규칙·환경·도구를 다시 먹이는 작업이며, 본문은 디스크·포인터만 책상에 올리는 게 공통 생존 전략이다.
- 7개는 파일 / 코드 fragment / 런타임 문장 / 불변 스펙 / 멀티벤더로 갈리며, 핵심 차이는 “형식”보다 **강제력(모델이 무시 가능한가, 런타임이 막을 수 있는가)**과 **라이프사이클(정적 prefix vs 턴별 vs 이벤트별 vs 게이트)**이다.
- 내 스택에는 마커 식별·deadline 방어·소프트/하드 2단 게이트·대형작업 한정 모호도 게이트를 차용할 수 있다.
셀프체크
- 같은 디렉토리에
CLAUDE.md(또는AGENTS.md)가 여러 계층에 있을 때, 누가 우세한가? (덮어쓰기인가 연결인가?) CLAUDE.md에 “반드시 X 하라”고 적으면 모델이 그것을 강제로 따르는가? 강제하려면 어디에 둬야 하나?- fable-ish의 소프트(
additionalContext)와 하드(decision:block) 채널의 차이는 무엇이고, 왜 둘 다 필요한가?
연결
근거 — 참조한 기능노트 및 소스 경로
- CC_20_prompt-assembly — CLAUDE.md 4계층·@import 4홉·MEMORY 200줄·압축 생존표·output style
- CX_20_prompt-and-context-assembly —
Prompt/BaseInstructions/ContextualUserFragment·마커·build_initial_context·AGENTS.md walk-up - OMC_20_prompt-assembly-claudemd — OMC:START/END 마커·키워드맵·skill-injector 예산·additionalContext 봉투
- GJ_20_context-and-prompt-assembly —
.md정적임포트·ContextFile depth·멀티벤더 priority·pruning 히스테리시스 - OB_20_spec-engine-seed-and-double-diamond —
Seed(frozen)·모호도 게이트0.2·render_seed_contract_for_execution - FB_30_context-injection-via-additionalContext —
context_for_mode·소프트/하드 채널·Stop block·agents.md→docs - MINE_20_prompt-assembly-claudemd —
@AGENTS.md정본단일화·3꼴(import/XML헌법/SSoT)·plugin 합성 - 원문아카이브:
/mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/memory.md,context-window.md,output-styles.md,cli-reference.md - 소스:
/home/seunghyeong/harness-work/codex/codex-rs/core/src/session/mod.rs:2871(build_initial_context),context-fragments/src/fragment.rs;/home/seunghyeong/harness-work/gajae-code/packages/coding-agent/src/system-prompt.ts;/home/seunghyeong/harness-work/oh-my-claudecode/scripts/keyword-detector.mjs,skill-injector.mjs;/home/seunghyeong/harness-work/ouroboros/src/ouroboros/core/seed.py;/home/seunghyeong/harness-work/fable-ish/scripts/classify_task.py - 내 정본:
/home/seunghyeong/projects/conclave/{CLAUDE.md,AGENTS.md},/mnt/d/akh2/CLAUDE.md,/home/seunghyeong/.claude/plugins/marketplaces/omc/CLAUDE.md
[!tip] Codex 교차검증 (gpt-5.5가 harness-work 소스 직접 대조) 큰 계보 축은 맞지만, 비교축을
파일/코드/훅에서 **권한 계층 / 라이프사이클 / 강제력 / 실패 정책**으로 재정렬해야 정확하다는 결론.
- 사실 보정: ① ouroboros
render_seed_contract_for_execution()는Goal·Task Type·Constraints·Brownfield·Ontology Lens·Evaluation Principles·Exit Conditions만 렌더,Acceptance Criteria·Auto Recursion Guard는 별도 함수(seed_contract_prompt.py:142,:7). ② CodexAGENTS.override.md는 합치는 게 아니라 첫 매칭만 선택·break→ 같은 위치 기본 파일을 shadow(agents_md.rs:255). ③ “모든 조각이 고유 마커”는 틀림 — unmarked fragment 존재(fragment.rs:37). ④ gajae “블록별 메시지”는 절반만 맞음 — OpenAI Responses 경로에선instructions하나로 join(system-prompt.ts:365,openai-responses.ts:487). ⑤ OMC “본문 인라인 금지”는 runtime descriptor에만 맞고 정적CLAUDE.md엔 크게 인라인. ⑥CLAUDE.md는 “system prompt”가 아니라 호스트가 자동 로드하는 메모리/컨텍스트. ⑦ fable-ish는 영구 instruction 파일만 안 만들 뿐 ledger/state 파일은 사용(ledger.py:175).- 빠진 차이(최대 누락): Authority ladder —
system/instructions·developer·user memory·hook additionalContext·hook block·tool permission은 강제력이 다르다. host-owned(Codex·gajae) vs **runtime-owned(OMC·fable-ish·내 패턴, base system prompt 못 가짐)**가 차용 가능성을 가른다. 라이프사이클(정적 prefix/턴별/이벤트별/게이트)이파일 vs 코드보다 중요. gajae timeout은 “빠른 조립”이 아니라 “수집 실패가 세션을 안 죽이는 정책”.- 해석 보정: “포인터로 수렴”은 과함 → 실제는 “큰 본문은 정적 prefix/파일, 동적 선택만 descriptor”. “불변 계약이 강제력”도 과함 → frozen은 데이터 불변일 뿐, 강제력은 grading·pipeline gate·acceptance tracking에서 나옴(degraded seed·partial product 경로 존재). “병합=concat”도 부정확 → 우선순위+dedupe+일부 shadow.
- 차용 현실성: 마커는 현실적이나 자동 교체/삭제는 안 됨(가독성만). 네이티브
CLAUDE.md로딩엔 timeout 못 검 → hook 캐시/요약/상한으로. soft/hard gate는 매우 현실적이나 max block·fail-open 없으면 교착. ambiguity gate는 대형작업만, deadline 시 degraded 허용 명시 필요. MEMORY 샤드는 자동 RAG처럼 제어 불가 → 별도 hook이 top-N 선택.