133 lines
12 KiB
Markdown
133 lines
12 KiB
Markdown
# 🛠️ 세션 ID 중복 충돌 해결 종합 설계 및 구현 계획서 (Rev.3 — 단일 격리 디렉터리 통합본)
|
|
|
|
동일 CLI 계열(Claude / Cline)의 서로 다른 역할 에이전트가 같은 workspace에서 동시 구동될 때 대화 세션 ID를 공유하는 결함([Problem_Definition.md](Problem_Definition.md))을 해결하기 위한 최종 종합 설계안 및 구현 계획입니다.
|
|
|
|
기존의 하이브리드 분기(L1/L2 병행) 구조를 걷어내고, **"모든 에이전트의 격리 디렉터리 관리 일원화"**라는 사용자(GM) 피드백을 반영하여 설계를 단순화한 버전입니다. Rev.3에서는 **실측 프로브 결과에 따른 격리 레버 정정 및 auth 시딩 계약**(사용자 승인 조건)을 반영했습니다. 상세 계획의 단일 원본은 [implementation_plan.session_isolation.md](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-session `HOME`이 아닌 **전용 `--data-dir`/`--config` 분리 플래그**를 보유해 시딩 없이 격리 가능합니다.
|
|
|
|
```mermaid
|
|
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, cline `epoch_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 매트릭스 확정 후 진행합니다.
|