내 패턴 · 상태·메모리 영속성 (다층 메모리와 세션 상태)
내 패턴 · 상태·메모리 영속성 (다층 메모리와 세션 상태)
한 줄 요약
컨텍스트 윈도우 밖 “디스크 메모리”를 세션·프로젝트·사용자 3단 서랍으로 나눠 자동으로 읽고 쓰는 시스템. → AI가 매번 기억상실에 걸리지 않고, 잊으면 안 되는 것을 다음에 깨어날 때 다시 읽도록 만들기 위해 배운다.
그림
flowchart TD
A[세션 시작] --> B["SessionStart 훅: 작업폴더 cwd와 세션 ID 읽기"]
B --> C{"경로 해석기: OMC_STATE_DIR 우선 → 워크스페이스 마커 → git 루트 → 현재폴더"}
C --> D["PID 생존확인: 죽은 소유자면 상태 서랍 회수"]
C --> E["사용자 MEMORY.md와 샤드 → CLAUDE.md로 자동 주입"]
C --> F["notepad 우선순위 메모 → notepad-priority 블록으로 주입"]
D --> G[모델 컨텍스트 구성]
E --> G
F --> G
G --> H["도구 호출: 호출 전후 훅이 세션 상태 읽기·쓰기"]
H --> I["세션 종료: sessions/아이디.json 레코드 기록"]
I --> J["remember 태그 → 7일/영구 메모로 영속"]
쉽게 풀기
AI는 대화 한 번이 끝나면(컨텍스트 윈도우가 닫히면) 방금 한 말을 모두 잊습니다. 사람으로 치면 매번 기억상실에 걸린 채 출근하는 직원과 같습니다. 어제 무슨 일을 했는지, 사장님 취향이 뭔지, 지금 작업이 어디까지 진행됐는지 전부 백지 상태로 출근하는 셈이죠.
이 패턴은 그 직원에게 “잊으면 안 되는 것은 노트에 적어두고, 다음에 출근하면 그 노트부터 다시 읽게 하는” 장치입니다. 핵심은 노트를 한 권으로 뭉치지 않고 세 개의 서랍으로 나눈 것입니다.
- ① 세션 서랍 (오늘 이 작업 메모): 지금 이 프로젝트의 지금 세션에서만 쓰는 임시 작업 노트. 이를테면 “방금 어떤 도구를 쓰다 에러가 났다”, “이 알림은 이미 한 번 띄웠으니 또 띄우지 마라” 같은 그날치 메모입니다.
- ② 프로젝트 서랍 (이 프로젝트 공용 규칙): 이 프로젝트를 여는 사람이라면 누구나·언제나 공유하는 규칙. “이 프로젝트 비밀번호는 0000”, “여기는 절대 이렇게 하면 안 된다” 같은 절대 잊으면 안 되는 약속입니다.
- ③ 사용자 서랍 (나에 대한 영구 기억): 어느 프로젝트를 열든 따라오는 “사용자가 누구인가”에 대한 기억. 직업, 진행 중인 프로젝트 목록, 자주 빠지는 함정 같은 것이죠.
서랍마다 형식·수명·읽혀주는 방식이 다릅니다. 그리고 서랍을 정확히 어느 위치에서 열어야 하는지 자동으로 찾아주는 “주소 해석기”(resolveSessionStatePaths)가 있습니다. 이 해석기는 정해진 우선순위로 위치를 찾습니다. 환경변수로 지정한 곳이 1순위, 그다음 워크스페이스 표시 파일, 그다음 git 저장소 루트, 마지막이 현재 폴더입니다.
한 가지 더 똑똑한 장치가 있습니다. 만약 어떤 직원이 노트를 펴놓은 채 갑자기 퇴사해버리면(프로세스가 죽으면) 그 노트는 영영 잠겨 아무도 못 쓰게 될 수 있습니다. 그래서 **“이 노트 주인이 아직 살아있나?”**를 확인하는 장치(process.kill(pid, 0)로 생존만 살짝 확인)가 있어서, 주인이 죽은 노트는 새 직원이 이어받아 쓸 수 있게 합니다.
핵심 정리
세 서랍은 저장 위치·형식·수명·읽혀주는 방식이 모두 다릅니다. 표는 슬림하게 정리하고, 세부 스키마는 콜아웃으로 분산합니다.
| 계층(서랍) | 저장 위치 | 수명 |
|---|---|---|
| 사용자 전역 자동메모리 | ~/.claude/.../memory/MEMORY.md + 샤드 | 영구(세션·프로젝트 간 지속) |
| 프로젝트 세션 레코드 | <proj>/.omc/sessions/<id>.json | 종료 시 기록·보존 |
| 프로젝트 세션 상태 | <proj>/.omc/state/sessions/<id>/*.json | 세션 스코프 |
| 프로젝트 공유 메모리 | <proj>/.omc/project-memory.json, notepad.md | 프로젝트 영구 |
| 위키 KB | <proj>/.omc/wiki/*.md | 영구(git-ignored) |
| 훅 영속 노트 | notepad 우선순위/작업 | 7일 또는 영구 |
[!note] AI에 읽혀주는 방식(주입 시점이 서랍마다 다름)
- 사용자 메모리(영구):
MEMORY.md인덱스 + 샤드가CLAUDE.md메커니즘으로 세션 시작 시 시스템 프롬프트에 자동 주입. 모델은 이를 “전역 사실(사용자가 누구·어떤 프로젝트가 있는지)“로 소비. 인덱스는 1줄 요약만 두고 본문은 샤드로 분리 → 토큰 절약 + 필요할 때만 샤드 열람.- 프로젝트 메모리/노트패드: SessionStart 훅이 현재폴더를 읽어 프로젝트를 감지하고, notepad 우선순위 메모를
<notepad-priority>블록으로 주입. 모델은 이를 “이 프로젝트에서 절대 잊으면 안 되는 규칙”으로 소비.- 세션 상태(JSON): 매 도구 호출 전후(PreToolUse/PostToolUse) 훅이 read/write 경로를 얻어 상태 갱신. 모델이 직접 읽는 게 아니라 훅이 모델 동작을 조정(같은 알림 재출력 억제, 직전 도구 에러 복기 등).
- 훅 영속 노트 수명: 모델이
<remember>(7일)/<remember priority>(영구) 태그를 쓰면 notepad에 기록 → 다음 session-start에서 우선순위대로 재주입.
[!note] 사용자 메모리 샤드 frontmatter 스키마 (실측)
필드 타입 필수 설명 namestring O 샤드 식별자(파일명과 대응) descriptionstring O 1줄 요약(인덱스에 표시) metadata.node_typeenum O 실측값 memorymetadata.typeenum O 실측값 projectmetadata.originSessionIduuid - 이 메모리를 만든 세션 ID(출처 추적)
[!note] 세션 종료 레코드 스키마
.omc/sessions/<id>.json(실측)
필드 타입 필수 설명 session_iduuid O 세션 고유 ID(파일명과 동일) ended_atISO8601 O 세션 종료 시각(UTC) reasonstring O 종료 사유(실측 "other")agents_spawnednumber O 스폰된 서브에이전트 수 agents_completednumber O 완료된 서브에이전트 수 modes_usedstring[] O 사용된 실행 모드 배열(ralph/ultrawork 등)
실제 예시
세션이 끝나면 “오늘 무슨 일이 있었나”를 한 줄 레코드로 남깁니다.
// /home/seunghyeong/projects/conclave/.omc/sessions/19e65e29-02c0-4a43-adfe-b53daaa7ce45.json
{
"session_id": "19e65e29-02c0-4a43-adfe-b53daaa7ce45",
"ended_at": "2026-06-13T06:19:44.700Z",
"reason": "other",
"agents_spawned": 0,
"agents_completed": 0,
"modes_used": []
}
세션 도중에는 작업 진행 상황(예: 서브에이전트 추적)을 상태 파일에 계속 갱신합니다.
// /home/seunghyeong/projects/conclave/.omc/state/sessions/19e65e29-.../subagent-tracking-state.json
{
"agents": [],
"total_spawned": 0,
"total_completed": 0,
"total_failed": 0,
"last_updated": "2026-06-13T06:06:32.846Z"
}
서랍 위치를 찾아주는 “주소 해석기”입니다. 읽기 경로와 쓰기 경로를 일부러 분리해서, 읽을 땐 세션 파일 → 옛날 파일 순으로 찾고, 쓸 땐 항상 세션 폴더에 씁니다. 둘을 헷갈리지 않게 타입(브랜드)으로 구분합니다.
// /home/seunghyeong/.claude/plugins/marketplaces/omc/src/lib/worktree-paths.ts
// 해석 순서: OMC_STATE_DIR > .omc-workspace 마커 > git > cwd
export const WORKSPACE_MARKER = '.omc-workspace';
export type ReadPath = string & { readonly __brand: 'ReadPath' };
export type WritePath = string & { readonly __brand: 'WritePath' };
export function resolveSessionStatePaths(stateName, sessionId, directory) {
// sessionId 없으면 legacy 단일 파일을 read/write 모두로 브랜딩
// effectiveRead: 세션 스코프 파일을 먼저 탐침, 없으면 legacy로 폴백
const effectiveRead = (existsSync(sessionScoped) ? sessionScoped : legacy) as ReadPath;
return {
effectiveRead, // 읽기 전용 브랜드
effectiveWrite: sessionScoped as WritePath, // 쓰기는 항상 세션 스코프
};
}
노트 주인(프로세스)이 살아있는지 확인해서, 죽은 주인의 잠긴 상태를 새 세션이 이어받게 합니다.
// /home/seunghyeong/.claude/plugins/marketplaces/omc/templates/hooks/session-start.mjs
function isOwnerProcessAlive(state) {
const pid = state && typeof state.owner_pid === 'number' ? state.owner_pid : null;
if (pid === null || pid <= 0) return true; // 미상 → 하위호환: 살아있다고 가정
if (pid === process.pid) return true;
try {
process.kill(pid, 0); // 시그널 0 = 죽었는지 탐침만(영향 없음)
return true;
} catch (e) {
// ESRCH = 그런 프로세스 없음 → 소유자 사망, 상태 회수 가능
// EPERM = 다른 유저 소유 → 판단 불가, 살아있다고 가정
}
}
// 소유자 PID가 죽었으면 그 상태파일은 고아 → restore 억제하지 않고 새 세션이 차지
직접 만들 때 쓰는 최소 템플릿입니다.
// .omc/sessions/<session-id>.json — 세션 종료 레코드(최소형)
{
"session_id": "00000000-0000-0000-0000-000000000000",
"ended_at": "2026-01-01T00:00:00.000Z",
"reason": "other",
"agents_spawned": 0,
"agents_completed": 0,
"modes_used": []
}
<!-- ~/.../memory/MEMORY.md — 사용자 전역 인덱스(샤드 1줄 요약만) -->
# Memory Index
- [프로젝트명](shard.md) — 경로, 한줄핵심, 함정 주의
<!-- ~/.../memory/shard.md — 샤드 본문 -->
---
name: project-x
description: "한 줄 설명"
metadata:
node_type: memory
type: project
originSessionId: <세션UUID>
---
본문(결정사항, 비번 위치, 함정, 검증완료 항목...)
직접 만들 때 체크리스트:
- 3스코프 분리: 세션(
.omc/state/sessions/<id>/) vs 프로젝트(.omc/project-memory.json,notepad.md) vs 사용자(~/.../memory/) - 경로 해석 우선순위 고정:
OMC_STATE_DIR>.omc-workspace마커 > git 루트 > cwd - read와 write 경로 분리(read는 세션→legacy 폴백, write는 항상 세션 스코프) — 브랜드 타입으로 혼동 방지
- PID-aware liveness:
process.kill(pid,0)로 죽은 소유자 상태 회수, 미상 PID는 살아있다고 가정(하위호환) - 인덱스는 1줄 요약, 본문은 샤드로 → 자동주입 토큰 절약
- 영속 수명 구분: 작업노트 7일 / 핵심 규칙 영구
- 위키는 임베딩 없이 keyword+tag 매칭, git-ignore 기본
요약 & 셀프체크
3줄 요약:
- AI는 세션이 끝나면 기억을 잃으므로, 디스크에 적어두고 다음 세션 시작 때 다시 읽혀주는 영속 메모리가 필요하다.
- 메모리는 세션(임시 작업) · 프로젝트(공용 규칙) · 사용자(나에 대한 영구 기억) 3단 서랍으로 나뉘며, 각각 형식·수명·주입 시점이 다르다.
- 주소 해석기가 정해진 우선순위로 서랍 위치를 찾고, read/write 경로를 분리하며, 죽은 소유자의 상태는 PID 생존확인으로 회수한다.
스스로 답해보기:
- 세 서랍(세션/프로젝트/사용자) 중 “비밀번호는 0000”은 어디에 적어야 하고, 그 이유는?
- 읽기 경로와 쓰기 경로를 굳이 분리한 이유는 무엇인가? (힌트: legacy 폴백 vs 항상 세션 스코프)
- 프로세스가 죽은 채 남긴 상태 파일을 새 세션이 어떻게 안전하게 이어받는가?
연결
MINE_개요 · _분석축_루브릭 · MINE_30_hooks-lifecycle · MINE_20_prompt-assembly-claudemd
근거 파일
/home/seunghyeong/.claude/projects/-home-seunghyeong/memory/MEMORY.md(인덱스 실측)/home/seunghyeong/.claude/projects/-home-seunghyeong/memory/conclave-debate-site.md(샤드 frontmatter 실측)/home/seunghyeong/projects/conclave/.omc/sessions/19e65e29-02c0-4a43-adfe-b53daaa7ce45.json(세션 레코드)/home/seunghyeong/projects/conclave/.omc/state/sessions/19e65e29-.../{subagent-tracking,last-tool-error,pre-tool-advisory-throttle}-state.json(세션 상태 3종 실측)/home/seunghyeong/.claude/plugins/marketplaces/omc/scripts/project-memory-session.mjs(SessionStart 주입 훅)/home/seunghyeong/.claude/plugins/marketplaces/omc/templates/hooks/lib/state-root.mjs(경로 해석 위임)/home/seunghyeong/.claude/plugins/marketplaces/omc/templates/hooks/session-start.mjs(PID liveness, Priority 주입)/home/seunghyeong/.claude/plugins/marketplaces/omc/src/lib/worktree-paths.ts(resolveSessionStatePaths, ReadPath/WritePath 브랜드)/home/seunghyeong/.claude/plugins/marketplaces/omc/skills/wiki/SKILL.md(위키 KB 모델)/home/seunghyeong/.claude/settings.json(enabledPlugins/model 실측)
[!tip] Codex 교차검증 보존 원본 노트에는 별도의 Codex 교차검증 섹션이 존재하지 않았다. 향후 교차검증 결과가 추가되면 이 콜아웃에 누적 보존한다.