OMC (oh-my-claudecode) · 컨텍스트/프롬프트 조립 (CLAUDE.md 주입 + 키워드 감지 + 스킬 인젝션)
OMC (oh-my-claudecode) · 컨텍스트/프롬프트 조립 (CLAUDE.md 주입 + 키워드 감지 + 스킬 인젝션)
한 줄 요약
OMC는 Claude가 보는 텍스트를 파일(CLAUDE.md)·입력 가로채기(키워드 훅)·세션 복원 세 군데에서 조립해, 백지 상태로 시작하는 모델에게 “무엇을 알고 시작할지”를 심는다. 왜 배우나: 키워드 한 단어로 모드가 켜지는 마법의 정체가 이 “프롬프트 조립”이며, 직접 하네스를 만들 때 가장 먼저 베껴야 할 핵심이다.
그림
flowchart TD
A["설치 1회: omc-setup"] --> B["CLAUDE.md에<br/>OMC:START~END 마커 블록 끼우기"]
B -. "세션마다 CC가 자동 로드" .-> M["모델 시스템 프롬프트<br/>(항상 켜진 운영 매뉴얼)"]
S["세션 시작 SessionStart"] --> S1["상태 복원<br/>(ralph·ultrawork·메모리·notepad)"]
S1 --> AC["additionalContext<br/>(추가 주입 텍스트)"]
U["사용자 입력 UserPromptSubmit"] --> K["키워드 탐지기"]
K --> K1["청소: 코드·인용·옛 로그 제거"]
K1 --> K2{"매직 키워드?"}
K2 -- "예" --> K3["모드 상태파일 쓰기<br/>+ MAGIC KEYWORD 안내문"]
K2 -- "아니오" --> K4["그냥 통과"]
U --> J["스킬 주입기"]
J --> J1["트리거 매칭 + 점수"]
J1 --> J2["학습된 스킬 요약 블록"]
K3 --> AC
J2 --> AC
AC --> M
M --> R["모델 응답:<br/>모드/스킬 실행"]
쉽게 풀기
Claude 본체는 기억이 없는 천재 가정교사다. 매번 새로 출근해 책상 위 종이만 읽고 일을 시작하며, 어제 한 일도 집에 있는 도구도 스스로는 모른다. OMC는 그 가정교사가 출근하기 전에 책상에 종이를 미리 깔아두는 비서다. 종이를 까는 자리가 세 군데다.
flowchart LR
subgraph 정적["1. 벽 안내문 (설치 1회)"]
P1["CLAUDE.md 마커 블록"]
end
subgraph 동적["2. 현관 쪽지 (매 입력)"]
P2["키워드 탐지 훅"]
P3["스킬 주입기"]
end
subgraph 복원["3. 어제 일지 (세션 시작)"]
P4["SessionStart 복원"]
end
정적 --> 모델["기억 없는 천재 모델"]
동적 --> 모델
복원 --> 모델
-
벽에 붙인 상시 안내문 (CLAUDE.md 마커 블록) — 설치 때 딱 한 번,
CLAUDE.md에<!-- OMC:START --> … <!-- OMC:END -->주석 테두리 블록을 끼운다. 안에는 직원(에이전트)·연장(도구)·위임 규칙·모델 라우팅이 적혀 있다. Claude Code가 세션마다 CLAUDE.md를 자동 로드하므로 이 안내문은 항상 걸려 있는 운영 매뉴얼이 된다. 따로 주입 코드를 돌릴 필요 없이 파일에 박아두면 끝. -
현관에서 쪽지 끼우기 (키워드 훅, 매 입력) — 사용자가 문장을 칠 때마다
UserPromptSubmit훅이 먼저 가로챈다.ralph·autopilot·ultrawork·ccg같은 매직 키워드가 보이면 “이 모드를 시작하라”는 쪽지를 끼워준다(keyword-detector.mjs). 동시에 입력과 관련 있는 “학습된 스킬” 요약도 함께 끼운다(skill-injector.mjs). -
출근 직후 어제 일지 복원 (SessionStart) — 새 세션이 시작되면 프로젝트 메모리·위키·notepad 우선순위 메모·중단됐던 모드 상태(ralph 루프 등)를 다시 책상에 올린다(session-start.mjs / wiki-session-start.mjs).
핵심 비유: 모델은 추론기, OMC는 그 앞에 종이를 까는 담당자. 마법 같은 자동화는 전부 “어떤 종이를 깔았느냐”의 결과다.
[!note] 가로챈 쪽지를 모델이 왜 따를까? 훅이 끼운 텍스트는 모델에게 “시스템이 준 추가 지시문”으로 보인다. 사용자 글인지 비서 쪽지인지 구분 못 하고 한 덩어리로 읽기 때문에, 키워드 한 단어가 모드를 켠다.
핵심 정리
조립이 일어나는 세 자리
| 자리 | 시점 | 누가 |
|---|---|---|
| CLAUDE.md 마커 블록 | 설치 1회 (정적) | installer |
| 키워드·스킬 쪽지 | 매 입력 (동적) | keyword-detector / skill-injector |
| 세션 복원 | 세션 시작마다 | session-start / wiki-session-start |
마커 블록 안의 주요 섹션
| 섹션 태그 | 한 줄 역할 |
|---|---|
OMC:VERSION | 설치 버전(드리프트 감지) |
<operating_principles> | 위임·증거우선·최소경로 원칙 |
<delegation_rules> | 위임 vs 직접 판단 |
<model_routing> | haiku·sonnet·opus 배분 |
<agent_catalog> | 에이전트 + 기본 모델 |
<tools> / <skills> | 도구 목록 / 키워드→스킬 매핑 |
[!note]- 펼쳐보기: 선택 섹션(필수 아님)
<hooks_and_context>— system-reminder 패턴 + 킬스위치DISABLE_OMC/OMC_SKIP_HOOKS설명<commit_protocol>— 커밋 트레일러 규약<worktree_paths>—.omc/상태 경로 해석 있으면 좋지만 없어도 동작한다.
키워드 → 모드 매핑 (요지)
상태파일까지 만드는 스킬형과 텍스트만 끼우는 모드형으로 갈린다.
flowchart TD
IN["입력 키워드"] --> Q{"sanitize 후<br/>매칭 + 정보성 질문?"}
Q -- "정보성('ralph가 뭐야?')" --> SKIP["모드 안 켬"]
Q -- "스킬형" --> SF["상태파일 + 안내문<br/>ralph·autopilot·ultrawork·ultragoal·ralplan"]
Q -- "모드형" --> MF["텍스트만<br/>ccg·deep-interview·tdd·review·wiki…"]
SF -. "ralph는 동반" .-> UW["ultrawork 상태파일도 생성"]
-
ralph/don't stop/until done/랄프(로렌 제외) → ralph (상태파일 , ultrawork 동반) -
autopilot/full auto/오토파일럿→ autopilot (상태파일 ) -
ultrawork/ulw/uw→ ultrawork (상태파일 ) -
ultragoal/ralplan→ 각각 상태파일 (명시 호출 의도 요구) -
ccg/deep interview/tdd/code review/ultrathink/wiki등 → 텍스트 모드만 (상태파일 )
[!note]- 펼쳐보기: 추측 아닌 실코드 안전장치
- team은 자동감지에서 제거 — 워커가 “team”을 보고 또 team을 스폰하는 무한 루프 방지.
/team으로만 명시 호출.- 정보성 질문(“ralph가 뭐야?“)은
isInformationalKeywordContext로 걸러 모드를 안 켠다.- 과거 훅 출력(
[RALPH LOOP - ITERATION N])을 붙여넣어도stripSystemEchoes로 제거 → 자기강화 루프 차단.Ralph Lauren은(랄프)(?!로렌)부정전방탐색으로 제외.
스킬 인젝션 예산 (토큰 폭발 방지)
| 제한 | 값 | 의미 |
|---|---|---|
| 디스크립터 1개 | 최대 1000자 | 본문은 디스크, 요약만 |
| 전체 컨텍스트 | 최대 3000자 | 한 입력 총량 |
| 세션당 개수 | 최대 5개 | 초과분은 미주입 |
| 재주입 방지 | 세션 1시간 TTL | dedup |
실제 예시
A. 설치 시 박히는 CLAUDE.md 마커 블록
<!-- OMC:START -->
<!-- OMC:VERSION:4.9.1 -->
# oh-my-claudecode - Intelligent Multi-Agent Orchestration
...
<skills>
Keyword triggers: "autopilot"→autopilot, "ralph"→ralph, "ulw"→ultrawork, ...
</skills>
<!-- OMC:END -->
[!note]- 펼쳐보기: 마커 블록 전문 발췌 + 설치 래퍼 코드
<!-- /home/seunghyeong/harness-work/oh-my-claudecode/CLAUDE.md --> <!-- OMC:START --> <!-- OMC:VERSION:4.9.1 --> # oh-my-claudecode - Intelligent Multi-Agent Orchestration ... <agent_catalog> Prefix: `oh-my-claudecode:`. See `agents/*.md` for full prompts. explore (haiku), analyst (opus), planner (opus), architect (opus), debugger (sonnet), executor (sonnet), verifier (sonnet), tracer (sonnet), security-reviewer (sonnet), code-reviewer (opus), test-engineer (sonnet), designer (sonnet), writer (haiku), ... </agent_catalog> <skills> Keyword triggers: "autopilot"→autopilot, "ralph"→ralph, "ulw"→ultrawork, "ccg"→ccg, "ralplan"→ralplan, "deep interview"→deep-interview, ... </skills> <!-- OMC:END -->설치 코드 — 기존 블록만 교체하고 마커 밖 사용자 본문은 보존:
// src/installer/index.ts const START_MARKER = '<!-- OMC:START -->'; const END_MARKER = '<!-- OMC:END -->'; const versionMarker = version ? `<!-- OMC:VERSION:${version} -->\n` : ''; // 기존 사용자 본문이 없을 때: return `${START_MARKER}\n${versionMarker}${cleanOmcContent}\n${END_MARKER}\n`; // 사용자 본문이 있을 때는 마커 블록 + 빈 줄 + USER_CUSTOMIZATIONS + 사용자 본문 보존
B. 키워드 매칭 후 끼워지는 안내문 (본문이 아니라 “경로 + 안내”만)
핵심은 스킬 본문을 인라인하지 않고 경로만 가리킨다는 점이다. 흐름은 아래와 같다.
sequenceDiagram
participant U as 사용자
participant H as keyword-detector
participant CC as Claude Code
participant M as 모델
U->>H: 입력 ("ralph 시작")
H->>H: sanitize → 키워드 매칭
H->>H: 모드 상태파일 atomic write
H-->>CC: additionalContext (경로+안내, 본문 X)
CC->>M: 원문 + 주입텍스트 병합
M->>M: SKILL.md 읽어 워크플로 시작
[!note]- 펼쳐보기: 실제 MAGIC KEYWORD 안내문 출력
// scripts/keyword-detector.mjs — createSkillInvocation() `[MAGIC KEYWORD: ${skillName.toUpperCase()}] Skill routing detected: ${skillName} Preferred invocation: /oh-my-claudecode:${skillName} Read fallback: open ${skillPath} and follow its SKILL.md instructions. User request (compact echo; original prompt remains authoritative): ${compactHookText(originalPrompt)} IMPORTANT: Start the ${skillName} workflow immediately. ...`
C. 학습된 스킬 frontmatter + 주입 형식
# skills/wiki/SKILL.md
name: wiki
triggers: ["wiki", "wiki this", "wiki add", "wiki lint", "wiki query"]
[!note]- 펼쳐보기: formatSkillsMessage 주입 형식 (본문은 디스크, 요약만)
<mnemosyne> ## Relevant Learned Skills Compact descriptors only; full learned skill bodies stay on disk to avoid prompt bloat. ### <name> (<scope>) <skill-metadata>{"path":"...","triggers":[...],"score":10,"scope":"project"}</skill-metadata> Summary: ... Load instructions: if this skill is needed, read <path> and follow the full instructions there. </mnemosyne>
D. 훅이 stdout으로 돌려주는 JSON (모델이 소비하는 핵심)
{ "continue": true,
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "<주입할 텍스트>"
} }
[!warning]
message가 아니라 반드시hookSpecificOutput.additionalContext여야 모델이 받는다(코드 주석에 명시). Claude Code가 이 값을 모델 컨텍스트에 추가한다.
E. 직접 만들 때 최소 키워드 훅
// myharness/scripts/keyword-hook.mjs
#!/usr/bin/env node
import { readFileSync } from 'fs';
const input = readFileSync(0, 'utf-8'); // stdin
let data = {}; try { data = JSON.parse(input); } catch {}
const prompt = (data.prompt || '').toLowerCase();
// echo/코드블록 제거(false positive 방지)
const clean = prompt.replace(/```[\s\S]*?```/g, '').replace(/`[^`]+`/g, '');
const MAP = [
[/\b(loop|don't stop|until done)\b/, 'ralph'],
[/\b(auto|autopilot)\b/, 'autopilot'],
];
let hit = null;
for (const [re, name] of MAP) if (re.test(clean)) { hit = name; break; }
if (!hit) { console.log(JSON.stringify({ continue: true, suppressOutput: true })); process.exit(0); }
console.log(JSON.stringify({
continue: true,
hookSpecificOutput: {
hookEventName: 'UserPromptSubmit',
additionalContext:
`[MAGIC KEYWORD: ${hit.toUpperCase()}]
Preferred invocation: /myharness:${hit}
Read fallback: skills/${hit}/SKILL.md
IMPORTANT: start the ${hit} workflow immediately.`
}
}));
[!tip] 모드 상태파일이라는 부수효과 키워드가 매칭되면 텍스트만 넣는 게 아니라
<omcRoot>/state/sessions/<sid>/<mode>-state.json을 atomic write 한다. 예: ralph는{active, iteration, max_iterations:100, prompt, session_id, linked_ultrawork:true, awaiting_confirmation:true}. 이 파일을 Stop 훅(persistent-mode.mjs)이 읽어 루프 강제에, SessionStart가 읽어 모드 복원에 쓴다. 그래서 ralph 키워드는 ultrawork 상태파일도 같이 만든다.
직접 만들 때 체크리스트
- 마커 래퍼는 START/END + VERSION 3줄, 교체 시 사용자 본문 보존(
^START\r?\n[\s\S]*?^END). - 키워드 훅은 stdin JSON 읽고
hookSpecificOutput.additionalContext로만 반환(message아님). - 정보성 질문/코드블록/과거 echo는 sanitize 후 매칭(자기강화 루프 방지).
- 자동 스폰 위험 키워드(team류)는 자동감지에서 빼고 슬래시 전용으로.
- 스킬 본문 인라인 금지, “경로 + 요약”만(세션당 개수/문자 예산 둘 것).
- 모드 상태는 session-scoped JSON atomic write, SessionStart에서 복원.
- 훅 실패는 항상
{continue:true}fail-open. - 킬스위치 env(
DISABLE_OMC,OMC_SKIP_HOOKS=keyword-detector) 존중.
요약 & 셀프체크
3줄 요약
- 모델은 백지 천재, OMC는 그 앞에 종이를 까는 비서 — CLAUDE.md(상시)·키워드 훅(매 입력)·세션 복원(시작) 세 자리에서 텍스트를 조립한다.
- CLAUDE.md 마커 블록은 자동 로드되고, 키워드·스킬은 훅이
additionalContext로 끼우되 본문이 아니라 “경로 + 요약”만 넣어 토큰을 아낀다. - 무한 루프·자기강화·오탐을 막는 안전장치(team 제외, 정보성 질문 필터, echo 제거, 예산 제한)가 핵심 노하우다.
스스로 답해보기
- Q1. 키워드 한 단어가 모드를 켤 수 있는 이유는? (힌트: 모델이 사용자 글과 훅 쪽지를 어떻게 구분하나)
- Q2. 스킬 본문을 통째로 안 넣고 “경로 + 요약”만 넣는 이유 두 가지는?
- Q3. “ralph”는 상태파일을 만드는데 “tdd”는 안 만든다. 왜 차이가 날까?
연결
OMC_개요 · OMC_10_entrypoint-hooks-loop · _분석축_루브릭
근거 파일
[!note]- 펼쳐보기: 근거 파일 전체 목록
CLAUDE.md— 마커 블록 실물(VERSION 4.9.1, 모든 섹션 태그)scripts/keyword-detector.mjs— 키워드→모드 매핑, sanitize, 상태파일 생성, MAGIC KEYWORD 출력scripts/skill-injector.mjs— frontmatter triggers 매칭, mnemosyne 디스크립터, 예산/dedupscripts/session-start.mjs— SessionStart 컨텍스트 조립(상태복원/project-memory/notepad/예산)scripts/wiki-session-start.mjs— 위키 컨텍스트 주입scripts/pre-compact.mjs— 압축 처리(dist/hooks/pre-compact 위임)src/agents/prompt-helpers.ts— 외부모델 위임 시 system_prompt/파일컨텍스트 조립·sanitizesrc/installer/index.ts— START/END/VERSION 마커 래퍼 생성(L1804~1860)hooks/hooks.json— 훅 이벤트 배선(UserPromptSubmit/SessionStart/PreCompact 순서)skills/wiki/SKILL.md— 스킬 frontmatter(triggers) 실측.mcp.json— MCP 서버t등록(bridge/mcp-server.cjs) (경로 prefix:/home/seunghyeong/harness-work/oh-my-claudecode/)
[!tip] Codex 교차검증 별도 Codex 교차검증 기록은 없다. 핵심 사실(마커 블록 자동 로드,
additionalContext필드 필수, team 자동감지 제외, 스킬 예산 제한, ralph→ultrawork 동반 상태파일)은 모두 위 근거 파일의 실코드/실측에 근거하며 추측으로 변경하지 않았다. 추후 교차검증 결과는 이 콜아웃에 누적한다.