12 KiB
🛠️ 세션 ID 중복 충돌 해결 종합 설계 및 구현 계획서 (Rev.3 — 단일 격리 디렉터리 통합본)
동일 CLI 계열(Claude / Cline)의 서로 다른 역할 에이전트가 같은 workspace에서 동시 구동될 때 대화 세션 ID를 공유하는 결함(Problem_Definition.md)을 해결하기 위한 최종 종합 설계안 및 구현 계획입니다.
기존의 하이브리드 분기(L1/L2 병행) 구조를 걷어내고, **"모든 에이전트의 격리 디렉터리 관리 일원화"**라는 사용자(GM) 피드백을 반영하여 설계를 단순화한 버전입니다. Rev.3에서는 실측 프로브 결과에 따른 격리 레버 정정 및 auth 시딩 계약(사용자 승인 조건)을 반영했습니다. 상세 계획의 단일 원본은 implementation_plan.session_isolation.md (Rev.3)입니다.
1. 배경 및 문제 정의
동일한 워크스페이스에서 다중 역할 에이전트(예: planner와 reviewer-a, developer와 reviewer-b)가 기동될 때, 동일한 대화 UUID 또는 Cline 대화 ID를 공유함으로써 컨텍스트가 오염되고, 히스토리 쓰기 경합으로 인한 락 충돌 및 데이터 유실이 발생하는 문제가 발생했습니다.
1.1 근본 원인 분석
- C1. 생성 시 고유 ID 미지정: 세션 생성 시점에 특정 세션 ID 인자를 지정하지 않아 CLI 기본 동작인 "최근 대화 상속"이 실행됨.
- C2. 워크스페이스 단위 키잉: 대화 세션 매핑이 역할(role) 차원 없이 단순히
workspace_root경로만을 키로 삼아 이뤄짐. - C3. mtime 기반 추측 바인딩 (Tier-2): resolver가 워크스페이스 내에서 최종 수정 시간(mtime)이 가장 최신인 세션을 바인딩하여, 다른 역할의 에이전트가 동일한 UUID를 상속받게 됨.
2. 개정된 설계 방향: "단일 격리 디렉터리" 일원화 (Rev.3 정정 반영)
각 에이전트의 ID 파라미터 지원 여부에 따라 구현을 분기하는 대신, 모든 에이전트에 대해 균일하게 격리 디렉터리 주입 방식으로 일원화하여 아키텍처의 복잡도를 제거합니다.
⚠️ Rev.3 정정 (실측 근거): 이전 판의 "Claude =
CLAUDE_PROJECT_DIR격리"는 동작하지 않는 설계였습니다.CLAUDE_PROJECT_DIR은 MAM resolver의 읽기 전용 변수(lib.sh:23,477)로, claude CLI가 대화를 쓰는 위치를 바꾸지 못합니다. claude의 쓰기 위치 이동은CLAUDE_CONFIG_DIR(~/.claude지붕 전체 이동)로만 가능하며, 이 경우 auth/설정 시딩이 필수입니다(§2.3). 또한 cline은 실측 결과 per-sessionHOME이 아닌 전용--data-dir/--config분리 플래그를 보유해 시딩 없이 격리 가능합니다.
graph TD
A[세션 생성] --> U[uuidgen: isolation-UUID 발급]
U --> B[.mam/agent_homes/<uuid>/ 생성]
B -->|Claude| B1[CLAUDE_CONFIG_DIR 주입 + auth/설정 심링크 시딩]
B -->|Cline| B2[--data-dir 격리 + --config 공유 auth]
B -->|Agy / Hermes| B3[Phase 0 프로브: 전용 레버 or HOME+시딩 폴백]
A --> C[Defense-in-depth: R1/R2 resolver 불변식]
B1 & B2 & B3 --> E[isolation 블록 세션 row 영속화 → resume/resolve/stop 재적용]
2.1 일원화 설계의 핵심
- 격리 식별자 ≠ 대화 식별자: MAM이
uuidgen으로 발급하는 isolation-UUID는 디렉터리 식별자입니다. CLI는 자기 방식대로 대화 ID를 mint하되(claude UUID, clineepoch_rand등) 자기만의 격리 디렉터리 안에서 하게 됩니다 — cline의 비-UUID 포맷 문제가 원천 소멸합니다. - 작업 디렉터리(Cwd) 공유: 에이전트들의 작업 디렉터리는 기존과 동일하게 같은 프로젝트 루트를 바라봅니다(
-c "$WORKSPACE"불변). 협업 소스코드 컨텍스트는 완벽히 일치하며, 격리는 대화 상태 저장소의 위치만 이동합니다. - 격리 루트:
<workspace>/.mam/agent_homes/<isolation-uuid>/—.gitignore(.mam/) 자동 커버, remove/stop 청소 계약에 자연 포함. - 스키마 영속화: 세션 row에
isolation: {uuid, root, lever, seeded[]}블록을atomic_dump_yaml로 영속화(DB+YAML). resume/resolve/stop은 이 블록만 읽는 단일 디스패치 함수를 경유 — agent별 레버 차이는 함수 내부에 캡슐화되어 관리 모델은 완전 통일됩니다. - 근본 원인 해소 기전: 신규 격리 디렉터리엔 상속할 최근 대화가 없음(C1 무해화) / UUID 네이밍으로 cwd-key 충돌 소멸(C2) / 디렉터리당 대화 1개 → resolver 항상 유일 후보(C3 소멸).
2.2 agent별 격리 레버 매핑 — ✅ Phase 0 실측 확정 (2026-07-10)
| Agent | Spawn 레버 | 시딩 (전부 심링크) | 격리 내 대화 경로 | 실증 |
|---|---|---|---|---|
| claude | CLAUDE_CONFIG_DIR=<root> env |
.credentials.json, settings.json, plugins/ |
<root>/projects/<key>/<uuid>.jsonl |
✅ 실호출 PASS |
| cline | --data-dir <root> 플래그 |
settings/* + globalState.json (⚠️ --config 공유만으론 auth 미공유 — 실측 반증, 시딩 필수) |
<root>/sessions/<id>/<id>.json (⚠️ 실HOME data/sessions/와 레이아웃 상이) |
✅ 실호출 PASS |
| agy | HOME=<root> env |
~/.gemini/ auth 3종(oauth_creds.json, google_accounts.json, antigravity-oauth-token) + 메타(installation_id/settings.json/state.json) |
<root>/.gemini/antigravity-cli/conversations/<uuid>.db |
✅ auth 검증 PASS (시딩 전 실패→후 성공) |
| hermes | HOME=<root> env |
~/.hermes/{auth.json, config.yaml, .env} |
<root>/.hermes/state.db |
✅ 읽기 격리 PASS ("No sessions found" + fresh state.db) |
2.3 시딩 계약 (claude 및 HOME 폴백 agent) — 사용자 승인 조건
- 심링크 강제, 복사 금지: 복사 시 토큰 갱신이 격리 사본으로 발산해 원본과 어긋남. 심링크는 갱신이 원본 단일 파일에 수렴(현행 다중 인스턴스 동작과 동일 의미론).
- claude 시딩 목록(초안):
.credentials.json,settings.json,plugins/— Phase 0에서 최종 확정, row의seeded[]에 기록. - stop 퍼지 시 심링크 원본 무손상 검증 포함.
2.4 R1/R2 resolver 불변식 (2중 안전 장치)
- R1: resolver(
find_workspace_uuid등)가 최신 파일을 스캔해오기 전, 현재 실행 중인 다른 세션들이 소유한*_own대화 ID 집합을 후보군에서 제외(claimed-set filtering)합니다. - R2:
atomic_dump시점에 새로 등록하려는 세션 ID가 이미 실행 중인 다른 세션의 ID와 중복될 경우SystemExit에러로 강제 진입 차단합니다.
2.5 정리(Cleanup) 계약 (RC-2)
- 세션 정지(
stop_session.sh) 시isolation.root를 일괄 청소(rm -rf)하는 단순·명확한 클린업 규칙..mam/하위 배치로remove.sh전체 청소도 자동 커버.
2.6 Non-Goals
- 에이전트 CLI(
claude,cline,agy,hermes) 자체 바이너리/코드를 수정하지 않습니다. 구동 시 외부 환경변수·플래그 주입 인터페이스만 사용합니다. - 기존 단일 에이전트 동작 구조의 하위 호환성은 완벽하게 보존합니다.
3. 단계별 구현 계획 (Roadmap)
대전제: Phase 0 검증 게이트 통과 전에는 격리 주입(Phase 2 이후) 코드 구현에 착수하지 않습니다.
Phase 0 (격리 레버 실측 및 검증 — G2 재조준)
▼
Phase 1 (R1/R2 공통 불변식 필터 — Phase 0 산출물 비의존, 병렬 선행 가능)
▼
Phase 2 (격리 프로비저닝·시딩·주입 및 isolation 스키마 영속화 구현)
▼
Phase 3 (stop_session.sh 클린업 계약 연동 구현)
▼
Phase 4 (통합 및 회귀 검증)
Phase 0 — 격리 레버 실측 (구현 전 필수 실측)
- G2-claude:
CLAUDE_CONFIG_DIR=<격리경로>기동 시 (a) 대화 jsonl이<격리경로>/projects/<key>/에 생성 (b).credentials.json심링크만으로 로그인 유지 (c) settings/plugins 심링크로 행동 드리프트 없음. - G2-cline:
--data-dir <격리경로> --config ~/.cline/data/settings기동 시 (a) 세션이 격리경로에 생성 (b) 공유 auth 정상 동작. - G2-agy / G2-hermes: 전용 레버(project/home env) 실측, 부재 시
HOME오버라이드+auth 시딩 유효성. - DoD:
{agent × (레버, 시딩 목록, 대화 저장 실경로)}매트릭스 확정 → §2.2 갱신.
Phase 1 — 공통 불변식 구현 (전략 무관, 선행 가능)
- 의존성 참고: Phase 1은 격리 프로브 결과에 의존하지 않고 공통
lib.sh에만 적용되므로, Phase 0 완료 여부와 무관하게 병렬로 또는 선제적으로 구현할 수 있습니다. - T1. claimed-set 필터 구현:
lib.sh(find_workspace_uuid계열) 탐색 로직 수정. 후보군 중 다른 실행 중인 row의*_own을 필터링 아웃. - T2. 생성-시 중복 assertion: 세션 등록/덤프 로직 부근에서 중복 assert — 중복 ID 충돌 시 즉시 거부.
- DoD: 동일 대화 ID 강제 주입으로 세션 2개 생성 시도 시, 두 번째 생성 요청이 거부됨을 증명.
Phase 2 — 격리 프로비저닝 및 영속화 구현 (전체 에이전트 적용)
- T3. 격리 프로비저닝: create 시 isolation-UUID 발급 →
.mam/agent_homes/<uuid>/생성 → agent별 심링크 시딩(§2.3). - T4. spawn 주입: 디스패치 함수가 agent별 레버(§2.2:
CLAUDE_CONFIG_DIR/--data-dir/ 프로브 결과)로 격리 루트 주입. - T5. 스키마 영속화:
isolation블록을 row에 저장하고 resume/resolve 시 재소싱 적용 — 저장+재적용은 원자적 세트. - DoD: Claude / Cline 세션 각각 2개 동시 생성 시 대화 파일 물리 분리·캐시 비공유, resume 시 올바른 복원 확인.
Phase 3 — stop 정리 계약 확장
- T6. stop 정리 계약 확장:
stop_session.sh가isolation.root를 자동 퍼지(rm -rf), 심링크 원본 무손상 검증 포함. - DoD: stop 후 격리 디렉터리 잔존 0, 실HOME auth/설정 원본 무손상, 디스크 누수 없음.
Phase 4 — 통합 및 회귀 검증
- V1. 다중 기동 테스트: planner(claude), reviewer-a(claude), developer(cline), reviewer-b(cline) 4개 동시 기동 시 4개 세션 ID 모두 유일성 확보 검증.
- V2. 동시 쓰기 경합: 4개 에이전트 동시 동작 시 락 경합/데이터 유실 현상 재현 불가 확인.
- V3. 단일 세션 회귀 검증: 격리 정책 적용 후 기존 단일 에이전트 구동 환경에서 문제없이 정상 동작함을 검증.
4. 리스크 및 검수 포인트 (Reviewer 관점, Rev.3 갱신)
| ID | 리스크 | 완화책 |
|---|---|---|
| RK1 | 격리 경로 주입 후 resolve/resume 시 전역 기본 경로 스캔으로 회귀 | isolation 블록 저장과 resume 시 재적용을 원자적 세트로 묶고 단일 디스패치 함수 강제 |
| RK2 | 시딩 드리프트: CLI 업데이트로 신규 파일 등장 시 심링크 목록 누락 → 격리 인스턴스 행동 이상 | seeded[]를 row에 기록, Phase 0 매트릭스에 시딩 파일 목록 명세 |
| RK3 | 토큰 갱신 발산: auth 파일을 복사 시딩하면 격리 사본의 토큰 갱신이 원본과 어긋남 | 심링크 강제 — 갱신이 원본 단일 파일에 수렴 |
| RK4 | 비정상 종료 시 격리 디렉터리 미정리로 인한 디스크 누수 | .mam/ 하위 배치(remove.sh 커버) + stop 퍼지 + 고아 agent_homes/* GC(선택) |
| RK5 | 단일 에이전트 워크플로우 기존 사용자의 하위 호환성 회귀 | 격리 활성화를 세션 다중성 조건 혹은 명시적 플래그(--isolate-strict)로 제어, V3 회귀 게이트 |
5. 의사결정 및 다음 단계
- ✅ 모든 에이전트를 격리 디렉터리 방식으로 일원화하는 방향 최종 승인 (트레이드오프 — claude
--session-id실측 확정 레버의 미채택 — 인지 후 결정). - ✅ 승인 조건 반영: (1) Phase 0에 claude
CLAUDE_CONFIG_DIR+심링크 로그인 유지 프로브 포함, (2) 시딩 계약(§2.3) 명문화. - ⏭️ 승인에 따라 Phase 0 (격리 레버 실측) 및 Phase 1 (공통 불변식 필터) 태스크에 착수합니다. Phase 2 이후 구현은 Phase 0 매트릭스 확정 후 진행합니다.