OMC (oh-my-claudecode) · 상태·메모리 영속성 (state / notepad / project-memory / wiki 4계층)
한 줄 요약
OMC는 프로젝트 폴더 안의 .omc/ 디렉터리를 AI의 “외장 기억장치”로 삼아, 세션이 끝나도 살아남는 4종류의 기억을 디스크에 파일로 적어 둔다.
왜 배우나: AI는 대화창을 닫으면 기억을 잃는데, 어떤 기억을 어디에·어떻게·얼마나 오래 남길지 설계할 줄 알아야 “세션을 넘어 일하는” 에이전트를 만들 수 있기 때문이다.
그림
flowchart TD SS["새 세션 시작 (SessionStart 훅)"] -->|"cwd, session_id 전달"| PM["project-memory-session.mjs"] PM -->|"등록 register"| CC["컨텍스트 수집기 contextCollector"] CC -->|"지시사항 자동 주입 (critical/note)"| MODEL["('Claude의 머릿속 (컨텍스트)')"] MODEL -->|"진행상태 읽기 state_read"| ST["state — 모드 진행 상태"] MODEL -->|"작업메모 읽기 notepad_read"| NP["notepad — 포스트잇 메모"] MODEL -->|"지식검색 wiki_query"| WK["wiki — 누적 지식베이스"] MODEL -->|"안전하게 기록 (임시파일→이름변경)"| DISK["('.omc/ 폴더에 파일로 저장')"] DISK -.->|"다음 세션이 다시 읽음"| SS
핵심 흐름: project-memory만 세션이 시작될 때 자동으로 머릿속에 들어가고, 나머지 셋은 AI가 필요할 때 도구를 호출해서 꺼내 본다. 쓰기는 전부 안전하게 이뤄져 도중에 끊겨도 파일이 깨지지 않는다.
쉽게 풀기
AI 비서를 “매일 아침 기억을 통째로 잃어버리는 천재 직원”이라고 상상해 보자. 어제 무슨 일을 어디까지 했는지, 이 회사의 규칙이 뭔지를 매번 다시 알려줘야 한다. OMC는 그 직원 책상에 4종류의 메모 도구를 놓아둔다. 종류가 나뉜 이유는, 기억마다 “얼마나 오래 남겨야 하는지”와 “언제 다시 봐야 하는지”가 다르기 때문이다.
1) state — 작업 진행표 (지금 몇 번째 반복 중?)
자동실행 모드(autopilot, ralph 등)가 “지금 3번째 반복인데 최대 10번까지야”처럼 진행 상태를 적어 두는 칸이다. 마라톤 선수의 랩 카운터와 같다. 여러 세션이 동시에 돌아도 섞이지 않도록 세션마다 별도 폴더(sessions/{세션ID}/)에 보관한다.
2) notepad — 책상 위 포스트잇 사람이 모니터에 붙여 두는 포스트잇과 똑같다. 단 칸이 셋으로 나뉘어 있다.
- Priority(우선순위): “이건 매번 꼭 보고 시작해” — 항상 로드되므로 짧게(500자 미만).
- Working(단기 작업메모): 오늘 작업하며 끄적인 메모 — 7일 지나면 자동으로 떼어진다.
- MANUAL(수동): 사람이 직접 적은 내용 — 절대 자동으로 안 지운다.
3) project-memory — 신입 안내서 “우리 회사는 무슨 기술을 쓰고, 빌드는 이렇게 하고, 이런 규칙을 지켜라”를 담은 안내서다. 가장 큰 차이는 세션이 시작되면 자동으로 AI 머릿속에 꽂힌다는 점. 그래서 대화가 길어져 압축(compaction)되어 중간 내용이 날아가도, 핵심 지시사항은 살아남는다.
4) wiki — 사내 위키
세션을 넘어 계속 쌓이는 지식 백과다. 페이지(slug.md) + 자동 목차(index.md) + 작업 로그(log.md) 구조로, 새 발견을 차곡차곡 적립한다. 검색은 키워드·태그 매칭 방식이라(벡터 임베딩 아님) AI가 “관련 페이지 원본”만 받아 직접 읽고 인용한다.
네 계층의 공통 안전장치
넷 다
.omc/폴더에 저장되고, 모두 동일한 worktree 경계 검증(worktree-paths.ts)을 거친다. 즉 프로젝트 폴더 밖으로는 절대 파일을 쓰지 못하게 막는다. “메모가 엉뚱한 데 떨어지지 않게 하는 울타리”라고 보면 된다.
핵심 정리
네 계층을 한눈에:
| 계층 | 비유 | 어디에 저장 |
|---|---|---|
| state | 작업 진행표·랩카운터 | .omc/state/sessions/{세션ID}/{모드}-state.json |
| notepad | 책상 포스트잇 | .omc/notepad.md (단일 파일) |
| project-memory | 신입 안내서 | .omc/project-memory.json |
| wiki | 사내 위키 | .omc/wiki/{slug}.md |
계층별 “언제 읽히나 / 얼마나 오래 사나”가 다르다 — 이게 핵심:
- state: AI가 직접 읽기보다 자동실행 루프가 “몇 회차냐, 멈춰야 하냐”를 판단하는 제어 신호로 쓴다.
- notepad-Priority: 세션마다 항상 로드 (영구·짧게).
- notepad-Working: 7일 후 자동 prune.
- notepad-MANUAL: 영구 (절대 자동삭제 안 함).
- project-memory: SessionStart에서 자동 주입 → 압축돼도 지시 생존.
- wiki: AI가
wiki_query로 필요할 때 검색 → 원본만 받아 직접 인용.
입력 한도(비대화 방지)
Priority ≤2000자(권장 <500), Working/MANUAL ≤4000자, 그 외 커스텀 페이로드도 크기 검증(
validatePayload)을 거친다. wiki 태그는 ≤20개. 메모가 무한정 커지지 않게 하는 안전선이다.
저장 루트는 어떻게 정해지나
getOmcRoot가 다음 순서로.omc/의 위치를 결정한다: ① 환경변수OMC_STATE_DIR→ ②.omc-workspace마커 → ③ git worktree 루트 → ④ 현재 작업 폴더(cwd).
실제 예시
① state가 디스크에 적는 실제 구조 — 어떤 상태든 _meta(누가·언제·어느 세션이 썼는지)가 자동으로 붙는다.
// /home/seunghyeong/harness-work/oh-my-claudecode/src/tools/state-tools.ts (line 716-726)
const stateWithMeta = {
...builtState,
_meta: {
mode,
sessionId: sessionId || null,
updatedAt: new Date().toISOString(),
updatedBy: 'state_write_tool'
}
};
atomicWriteJsonSync(statePath, stateWithMeta);
// statePath 예: .omc/state/sessions/{sessionId}/ralph-state.json② notepad.md 초기 골격 — 한 파일 안에 3섹션이 주석과 함께 들어간다.
// /home/seunghyeong/harness-work/oh-my-claudecode/src/hooks/notepad/index.ts (line 141-153)
// notepad.md 초기 템플릿 (3섹션 골격)
const content = `# Notepad
<!-- Auto-managed by OMC. Manual edits preserved in MANUAL section. -->
${PRIORITY_HEADER}
<!-- ALWAYS loaded. Keep under 500 chars. Critical discoveries only. -->
${WORKING_MEMORY_HEADER}
<!-- Session notes. Auto-pruned after 7 days. -->
${MANUAL_HEADER}
<!-- User content. Never auto-pruned. -->
`;③ wiki 작업 로그(log.md)에 append되는 한 줄 — 위키는 절대 덮어쓰지 않고 이어 붙인다.
// /home/seunghyeong/harness-work/oh-my-claudecode/src/hooks/wiki/storage.ts (line 329-340)
const logLine = `## [${entry.timestamp}] ${entry.operation}\n` +
`- **Pages:** ${entry.pagesAffected.join(', ') || 'none'}\n` +
`- **Summary:** ${entry.summary}\n\n`;
let existing = existsSync(logPath) ? readFileSync(logPath, 'utf-8') : '# Wiki Log\n\n';
atomicWriteFileSync(logPath, existing + logLine);④ 직접 “세션 넘는 메모” 계층을 만든다면 — 경계검증 → 세션격리 → 원자적 쓰기 순서가 골격이다.
// 1) 공유 경계검증 — 프로젝트 밖 쓰기 차단 (worktree-paths 패턴)
function validateWorkingDirectory(dir?: string): string {
const trusted = getWorktreeRoot(process.cwd()) || process.cwd();
if (!dir) return trusted;
const resolved = realpathSync(resolve(dir));
const rel = relative(realpathSync(trusted), resolved);
if (rel.startsWith('..') || isAbsolute(rel)) throw new Error('outside worktree');
return trusted; // 항상 루트만 반환 (.omc 가 하위폴더에 안 생기게)
}
// 2) 세션별 경로 (state 패턴)
const statePath = `${root}/.omc/state/sessions/${sessionId}/${mode}-state.json`;
validateSessionId(sessionId); // .. / \ 금지, 영숫자+하이픈, ≤256자
// 3) 원자적 쓰기 + _meta 부착
atomicWriteJsonSync(statePath, {
active: true, iteration: 1,
_meta: { mode, sessionId, updatedAt: new Date().toISOString(), updatedBy: 'my_tool' },
});
// 4) SessionStart 훅에서 자동주입 (project-memory 패턴)
// 훅이 stdin JSON({cwd, session_id})을 읽어 register → contextCollector직접 만들 때 체크리스트
- 저장 루트는
OMC_STATE_DIR > .omc-workspace > git루트 > cwd순으로 해석한다.- 모든 쓰기는 atomic(임시파일→rename). 직접
writeFileSync로 본파일에 쓰지 않는다.- 세션ID는 path traversal 검증(
../\금지) 후에만 경로에 합친다.- 세션별 폴더로 격리하고, session_id 없는 레거시 경로 쓰기엔 경고를 띄운다.
- 수명 정책을 계층별로 분리: 항상로드(priority) / N일 후 prune(working) / 영구(manual).
- compaction을 견뎌야 하는 지시는 SessionStart 자동주입 경로에 태운다.
- 페이로드 크기 제한(
validatePayload)으로 비대화 방지.- wiki류 누적 지식은 slug+자동 index.md+append-only log.md 3종 세트로 운영하고 lint로 orphan/stale/broken-ref를 점검.
요약 & 셀프체크
3줄 요약
- OMC는
.omc/폴더를 외장 기억장치로 써서 state(진행표) / notepad(포스트잇) / project-memory(신입 안내서) / wiki(사내 위키) 4계층을 디스크에 남긴다. - 계층마다 “언제 읽히나”와 “얼마나 오래 사나”가 다르다 — project-memory만 세션 시작 시 자동 주입되고, 나머지는 AI가 도구로 꺼내 본다.
- 모든 쓰기는 원자적(임시파일→rename)이고 worktree 경계 안에서만 일어나, 동시 세션·중단에도 파일이 깨지거나 새지 않는다.
스스로 답해 보기
- 대화가 압축돼도 지시사항이 살아남게 하려면 4계층 중 어디에, 어떤 우선순위로 넣어야 할까?
- notepad의 Working과 MANUAL 칸의 결정적 차이는 무엇이고, 왜 그렇게 나눴을까?
- wiki 검색이 “벡터 임베딩 없이 키워드·태그 매칭”이라는 사실은, AI가 결과를 쓰는 방식에 어떤 제약을 줄까?
연결
OMC_개요 · OMC_10_entrypoint-hooks-loop (SessionStart 훅 진입) · OMC_60_guardrails-permissions (worktree 경계검증 공유) · _분석축_루브릭
Codex 교차검증 (원문 분석 보존)
본 노트의 사실관계는 다음 근거 파일에 기반한다(원문 분석 시 교차 확인됨):
src/tools/state-tools.ts— state 5도구,atomicWriteJsonSync, cancel-signal(writeSessionCancelSignal, TTL 30s)src/tools/notepad-tools.ts— notepad 6도구, SECTION_NAMES, 입력 한도src/tools/memory-tools.ts— project-memory 4도구, ProjectMemory/UserDirectivesrc/tools/wiki-tools.ts— wiki 7도구, WIKI_CATEGORIES, frontmatterscripts/project-memory-session.mjs— SessionStart 자동주입src/lib/worktree-paths.ts— OmcPaths, resolveStatePath/resolveSessionStatePath, validateWorkingDirectory(OrLinkedWorktree), validateSessionIdsrc/hooks/notepad/index.ts— PRIORITY/WORKING/MANUAL 헤더, 템플릿, DEFAULT_CONFIG(workingMemoryDays=7)src/hooks/project-memory/{types,storage,index,formatter}.ts— 스키마(version 1.0.0), saveProjectMemory/atomicWriteJson, registerProjectMemoryContext, directive critical/note 포맷src/hooks/wiki/{storage,types}.ts— WIKI_DIR, index.md/log.md/slug, titleToSlug, appendLog, WikiPageFrontmatterCLAUDE.md— worktree_paths 절(경로 일람·해석 순서)핵심 검증 포인트: ① state는 모델 소비보다 루프 제어 신호 ② project-memory만 SessionStart 자동주입 ③ wiki는 벡터 임베딩 없이 키워드+태그 매칭(원본 매치만 반환, 인용·합성은 모델 몫).