Files
multi-agent-mux/implementation_plan.session_isolation.md
T

142 lines
11 KiB
Markdown

# Implementation Plan — 세션 ID 중복 충돌 해소 / 역할별 세션 격리 (Rev.3)
- **작성자**: Planner Agent
- **날짜**: 2026-07-10 (Rev.2 → Rev.3 개정)
- **상태**: Draft (Phase 0 검증 게이트 대기 — 구현 미착수)
- **관련 자료**: [Problem_Definition.md](Problem_Definition.md), [session_isolation_discussion.md](session_isolation_discussion.md)
- **확정된 방향 (Rev.3)**: **all-L2 단일화** — 전 에이전트 isolation-UUID 격리 디렉터리 + 세션 row 영속화 + R1 claimed-set 불변식
---
## 0. Rev.2 → Rev.3 변경 이력 (Decision Log)
| # | 결정 | 사유 |
|---|------|------|
| D1 | **L1(생성-시 대화 ID 인자 주입) 전략 폐기** | 관리 모델 통일 우선. claude `--session-id`는 실측 확정된 레버지만, agent별 L1/L2 분기 유지 비용보다 단일 격리 메커니즘의 일관성을 우선함 (트레이드오프 인지 후 사용자 확정) |
| D2 | **격리 식별자 = MAM 발급 isolation-UUID** (CLI 대화 ID와 분리) | cline 등 비-UUID 자체 ID 포맷 문제 원천 소멸 — CLI가 자기 방식대로 대화 ID를 mint하되 **자기만의 격리 디렉터리 안에서** 하게 함 |
| D3 | **격리 정보를 세션 row(`isolation` 블록)로 영속화** | resume/resolve/stop이 단일 디스패치로 재적용 → 주입/해석 불일치(RK2) 차단 |
| D4 | **claude 격리 레버 = `CLAUDE_CONFIG_DIR` + auth 시딩** | 실측: `~/.claude/` 한 지붕 아래 `.credentials.json`+`settings`+`plugins`+`projects/` 동거 확인. 대화만 옮기는 좁은 레버 부재 → 지붕 이동 + 시딩 계약 필수. ⚠️ 기존 문서의 `CLAUDE_PROJECT_DIR`**MAM resolver 읽기 전용 변수**(lib.sh:23,477)로 CLI **쓰기** 위치를 바꾸지 못함 — Rev.2의 해당 서술 정정 |
| D5 | **cline 격리 레버 = `--data-dir` + `--config` 분리 조합** | 실측: `cline --data-dir <path>`("Use isolated local state", default `~/.cline`) + `--config <path>`(설정/auth, default `~/.cline/data/settings`) 별도 플래그 확인 → 대화만 격리·auth 공유 가능, 시딩 불필요 예상 |
---
## 1. 배경 및 문제 정의
동일 CLI 계열(Claude/Cline)의 서로 다른 역할 에이전트가 같은 workspace에서 동시 구동될 때
**동일한 대화 세션 ID를 공유**하는 결함 (`Problem_Definition.md`).
### 1.1 근본 원인 (코드 근거) 및 all-L2가 끊는 방식
| # | 원인 | 근거 | all-L2 해소 기전 |
|---|------|------|------------------|
| C1 | spawn 시 세션 ID 미지정 → CLI "cwd 최근 대화 상속" | `create_session.sh:125/:135` | 신규 격리 디렉터리엔 상속할 최근 대화가 **없음** → 각자 fresh 시작 |
| C2 | 저장소가 workspace(cwd) 단위 키잉, role 차원 없음 | `lib.sh:480`, `:615` | 격리 디렉터리가 **UUID 네이밍** → cwd 파생 key 충돌 원천 소멸 |
| C3 | resolver Tier-2가 mtime 최신 파일 반환 → 동일 UUID 해석 | `lib.sh:564` | 격리 디렉터리 안엔 대화가 **하나뿐** → disk-scan 항상 유일 후보, mtime 경합 소멸 |
### 1.2 불변 전제 (변경 금지)
- **cwd 공유**: 모든 에이전트는 `-c "$WORKSPACE"`로 동일 workspace에서 기동 (협업 전제). 격리는 **대화 상태 저장소의 위치만** env/플래그로 옮기며 cwd는 절대 건드리지 않음.
- CLI(claude/cline/agy/hermes) 자체 미수정 — 인자/환경변수 인터페이스만 사용 (Non-Goal).
- 기존 단일-에이전트 워크플로우 회귀 0.
---
## 2. 설계 (all-L2 단일 격리 메커니즘)
### 2.1 격리 디렉터리
- 생성 시 `uuidgen`으로 **isolation-UUID** 발급.
- 격리 루트: `<workspace>/.mam/agent_homes/<isolation-uuid>/`
- `.mam/` 하위 → `.gitignore:11` 자동 커버, `remove.sh`/stop 청소 계약에 자연 포함.
### 2.2 세션 row 스키마 확장 (`isolation` 블록)
```yaml
tmux_sessions:
- name: <session_name>
role: <role>
isolation:
uuid: <isolation-uuid>
root: .mam/agent_homes/<isolation-uuid>
lever: claude_config_dir | cline_data_dir | home | <agy/hermes 프로브 결과>
seeded: [".credentials.json", "settings.json", "plugins"] # 심링크 목록 (해당 시)
```
- `atomic_dump_yaml` 경유로 DB+YAML 동시 기록 (P1 상시 미러 수정본 전제).
- resume/resolve/stop은 이 블록만 읽는 **단일 디스패치 함수**로 재적용 — agent별 레버 차이는 이 함수 내부에 캡슐화.
### 2.3 agent별 레버 매핑 — ✅ Phase 0 실측 확정 매트릭스 (2026-07-10)
| Agent | Spawn 레버 | 시딩 목록 (전부 **심링크**) | 격리 내 대화 저장 실경로 | 실증 |
|---|---|---|---|---|
| claude | `CLAUDE_CONFIG_DIR=<root>` env | `.credentials.json`, `settings.json`, `plugins/` | `<root>/projects/<key>/<uuid>.jsonl` | ✅ 실호출 — jsonl 격리 생성, 로그인 유지, 실HOME 무변화(27→27) |
| cline | `--data-dir <root>` 플래그 | `settings/*`(providers.json 등 5종), `globalState.json` | `<root>/sessions/<id>/<id>.json` ⚠️ | ✅ 실호출 — 세션 격리 생성, 실HOME 무변화(13→13) |
| agy | `HOME=<root>` env | `~/.gemini/{oauth_creds.json, google_accounts.json, installation_id, settings.json, state.json}` + `antigravity-cli/{antigravity-oauth-token, installation_id, settings.json}` | `<root>/.gemini/antigravity-cli/conversations/<uuid>.db` | ✅ auth 검증(`agy models` — 시딩 전 실패/후 성공), fresh conversations/ 확인 |
| hermes | `HOME=<root>` env | `~/.hermes/{auth.json, config.yaml, .env}` | `<root>/.hermes/state.db` (sessions 테이블) | ✅ 읽기 격리 검증 — 격리 HOME "No sessions found" + fresh state.db, 실HOME 세션 비노출 |
**Phase 0 실측 정정·주의사항**:
1. ⚠️ **cline `--config` 가정 반증**: `--config ~/.cline/data/settings` 공유 지정만으로는 auth가 공유되지 않음(`Unauthorized`) — cline이 data-dir 안에 자체 settings를 생성. → **cline도 시딩 필수** (Rev.3 본문 "시딩 불필요 예상" 정정).
2. ⚠️ **cline 격리 레이아웃 상이**: 격리 시 `<root>/sessions/`(실HOME은 `~/.cline/data/sessions/`) — resolver 재적용(T5)에서 **lever별 경로 템플릿 분기** 필요.
3. hermes `config.yaml`에 실HOME 절대경로(런타임 `hermes-agent`) 내장 — 런타임은 읽기 공유라 무해하나, 격리 범위가 state/세션에 한정됨을 기록.
4. agy는 첫 실행 시 격리 HOME에 디렉터리 구조를 자동 부트스트랩(fresh `conversations/` 포함).
- **resolver 연동**: 디스패치 함수가 row의 `isolation`을 읽어 `HOME_DIR`/`CLAUDE_PROJECT_DIR`(MAM 읽기 변수, `lib.sh:22-23`)를 격리 루트 기준으로 export한 뒤 `find_workspace_uuid`/resume 호출.
### 2.4 공통 불변식 (전략 무관 유지)
- **R1. claimed-set 필터**: Tier-2 반환 전, 같은 workspace의 다른 running row 소유 `*_own` 집합 제외 (`lib.sh:540-575`). 격리가 부분 실패해도 이중 배정 구조적 차단.
- **R2. 생성-시 유일성 assert**: 새 `*_own`이 기존 running `*_own`과 충돌 시 `SystemExit` (`lib.sh:377` 근처).
---
## 3. 단계별 태스크
### Phase 0 — 검증 게이트 (**구현 전 필수·차단**, Rev.3 재조준: 구 G1 삭제)
- **G2-claude**: `CLAUDE_CONFIG_DIR=<격리경로>` 기동 시 (a) 대화 jsonl이 `<격리경로>/projects/<key>/`에 생성되는가 (b) `.credentials.json` **심링크만으로 로그인 유지**되는가 (c) settings/plugins 심링크로 행동 드리프트 없는가.
- **G2-cline**: `--data-dir <격리경로> --config ~/.cline/data/settings` 기동 시 (a) 세션이 `<격리경로>/data/sessions/`(또는 상응 경로)에 격리 생성되는가 (b) auth/providers가 공유 config에서 정상 동작하는가.
- **G2-agy**: `--new-project` per-role 부여 시 대화 격리 여부, 또는 데이터 경로 env 존재 여부. 부재 시 `HOME` 오버라이드+auth 시딩 유효성.
- **G2-hermes**: home 이동 env/플래그 실측. 부재 시 `HOME` 오버라이드+auth 시딩 유효성.
- **DoD**: `{agent × (레버, 시딩 목록, 대화 저장 실경로)}` 매트릭스 확정 → §2.3 갱신.
### Phase 1 — 공통 불변식 (Phase 0과 병렬 착수 가능)
- **T1. claimed-set 필터** / **T2. 생성-시 유일성 assert** (§2.4).
- **DoD**: 동일 ID 강제 주입 2개 create → 두 번째 거부 (단위 재현).
### Phase 2 — all-L2 격리 구현 (구 Phase 2/3 통합)
- **T3. 격리 디렉터리 프로비저닝**: create 시 isolation-UUID 발급 → `.mam/agent_homes/<uuid>/` 생성 → agent별 시딩(§2.3, 심링크) 수행.
- **T4. spawn 주입**: 디스패치 함수가 agent별 레버(env/플래그)로 격리 루트 주입.
- **T5. 스키마 영속화 + 재적용**: `isolation` 블록 atomic_dump 기록, resume/resolve가 동일 디스패치로 재소싱 (**저장+재적용은 원자적 세트** — RK2).
- **T6. stop 청소 계약**: `stop_session.sh``isolation.root`를 퍼지(`rm -rf`), 심링크 대상 원본은 보존 확인.
- **DoD**: 동일 workspace, 같은 CLI 2개(다른 role) 동시 생성 → 대화 파일 물리 분리, `*_own` 상이, 교차 write 0.
### Phase 3 — 통합 및 회귀 검증
- **V1**: planner/reviewer-a(claude) + developer/reviewer-b(cline) 4개 동시 기동 → 4개 대화 ID 전부 유일.
- **V2**: 동시 write 락 충돌·컨텍스트 오염 재현 불가.
- **V3**: 각 role resume이 자기 대화만 복원 (isolation 재적용 경유).
- **V4**: 단일-에이전트 기존 워크플로우 회귀 0 (격리 미사용 경로 불변).
- **V5**: stop 후 격리 저장소 잔존 0 + 실HOME auth/설정 원본 무손상.
---
## 4. 리스크 및 검수 포인트 (Rev.3 갱신)
| ID | 리스크 | 완화 |
|----|--------|------|
| RK1 | 격리 실패/부분 적용 시 이중 배정 | R1 claimed-set 필터가 최후 방어선 (무조건 유지) |
| RK2 | spawn 주입 경로와 resolve/resume 스캔 경로 불일치 → 전역 회귀 | T5 "저장+재적용" 원자적 세트, 단일 디스패치 함수 강제 |
| RK3 | **시딩 드리프트**: CLI 업데이트로 신규 파일 등장 시 심링크 목록 누락 → 행동 이상 | `seeded` 목록을 row에 기록, Phase 0 매트릭스에 파일 목록 명세, 온보딩 문서화 |
| RK4 | 토큰 갱신 발산 (복사 시딩 시) | **심링크 강제** — 갱신이 원본 단일 파일에 수렴 (현행 다중 인스턴스 동작과 동일) |
| RK5 | 비정상 종료 시 격리 디렉터리 누수 | `.mam/` 하위 배치로 remove.sh 전체 청소 커버 + T6 stop 퍼지 + (선택) create 시 고아 `agent_homes/*` GC |
| RK6 | 단일-에이전트 기존 사용자 회귀 | 격리 발동을 명시 플래그/다중성 조건으로 제어 (`--isolate` 등, Phase 0 후 확정), V4 회귀 게이트 |
---
## 5. 승인 및 다음 단계
- 본 계획(Rev.3)은 **Phase 0 게이트 통과 전 코드 구현 착수 금지**를 대전제로 유지한다.
- 다음 액션: **Phase 0 (G2-claude / G2-cline / G2-agy / G2-hermes) 프로브 실행** — 스크래치 워크스페이스에서 실측 후 §2.3 매트릭스 확정.