Files
multi-agent-mux/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-64990742.md
T

28 KiB

B-1 구현 계획서 Rev.2find_workspace_uuid tier-3 신원 캐시 복구

  • Job: 662f07ba (Planner) — 원안 64990742 Rev.1 을 Creator 이의제기 2b9e9b67(agy) 반영하여 개정
  • 대상: IMPROVEMENTS.md §2 / B-1
  • 기준 커밋: b6c41e6 (작업 트리 clean)
  • 작성 원칙: 이의제기의 전제와 처방을 각각 따로 실행하여 판정한다. 상대 코드를 그대로 돌려 보지 않고 논평하지 않는다.

0. 이의제기 판정 요약 (Adjudication)

대상 판정 근거 (실측)
전제 — "d가 truthy인데 agent_identities가 없고, YAML에는 남아 있는 상태에서 폴백이 영구 차단된다" 기각 (Refuted) load_state_json과 폴백은 같은 base 경로 · 같은 DB-우선 우선순위로 읽는다. 6가지 디스크 상태 전수 측정 결과 네 변형이 전 셀 동일
처방if isinstance(d, dict) and d.get('agent_identities'): 로 바꾸면 하위호환 신원이 "완벽히 복원"된다 기각 (Refuted) 제안 코드를 원문 그대로 실행했다. 문제로 지목된 S2·S6 셀에서 여전히 miss. 폴백에는 진입하지만(probe=1) 폴백 역시 DB를 먼저 읽어 YAML에 도달하지 못한다
처방의 경로 폴백os.environ.get('YAML_PATH','') → 없으면 <ws>/.mam/... 추측 거부 (Rejected) 본 계획서가 제거하려는 D1(경로 추측)을 그대로 재도입한다. 직전 잡 4dbf4feb${1:-$PWD}와 동일한 패턴
조건 변경 자체 채택 (Adopted — 단, 근거는 다름) 하위호환 복구 목적으로는 무의미하나, load_state_json의 반환 형태 변경에 대한 내성으로는 유효. 재조회 비용 실측 0.063 ms → 성능을 반대 논거로 쓰지 않는다
원안의 나머지 (D1·D3·D4·D5, V-1…V-6, 커밋 분할) 유지 이의 없음

요약: agy가 지적한 셀(S2/S6)은 agy의 코드로도 고쳐지지 않는다. 그 셀을 실제로 고치려면 DB(권위)를 제치고 YAML(미러)을 읽어야 하는데, 이는 atomic_dump_yaml의 설계와 정면으로 충돌한다. 따라서 Rev.2는 조건 변경은 받되(근거 교체), 경로 추측은 거부하고, S2/S6를 "의도된 동작"으로 테스트에 못박는다(V-8).


1. 전제 검증 — d와 폴백은 같은 것을 읽는다

load_state_json(lib.sh:510-545)의 소스 결정 로직:

yaml_path = os.environ['YAML_PATH']
db_path   = os.path.splitext(yaml_path)[0] + '.db'
if os.path.exists(db_path):        d = json.loads(<state row>) ; d['herdr_sessions'] = <sessions table>
elif os.path.exists(yaml_path):    d = yaml.safe_load(f)

Rev.1 폴백과 완전히 동일한 base 경로, 동일한 DB-우선 우선순위다. 따라서 d가 신원을 못 가졌다 ⟹ 폴백도 같은 자리에서 같은 것을 읽어 못 가진다. 이의제기가 상정한 "d에는 없고 디스크에는 있는" 상태가 성립하려면 두 읽기의 소스가 갈려야 하는데 갈릴 수 없다.

1-1. 6가지 디스크 상태 전수 측정

tier-1(관계없는 세션 1행)·tier-2(last_conversations.json 부재)를 굶겨 tier-3만 남긴 상태에서 네 변형을 각각 실행:

상태 디스크 배치 HEAD Rev.1 if d: agy 제안 Rev.2
S1 DB=신원 있음, YAML=신원 있음 HIT HIT HIT HIT
S2 DB=신원 없음, YAML=신원 있음이의제기 시나리오 miss miss miss miss
S3 YAML만 존재, 신원 있음 HIT HIT HIT HIT
S4 YAML만 존재, 신원 없음 miss miss miss miss
S5 DB만 존재, 신원 없음 miss miss miss miss
S6 DB에 state 행 없음 + sessions 행 존재, YAML=신원 있음 miss miss miss miss

네 변형이 6/6 셀에서 동일하다. 조건 변경은 해석 결과를 단 한 셀도 바꾸지 못한다.

S3가 HIT인 점에 주목할 것 — "YAML에만 신원이 있는" 정상 상태는 load_state_jsonelif 분기로 YAML 전체를 싣기 때문에 d가 이미 신원을 갖는다. 이의제기가 걱정한 하위호환 경로는 Rev.1에서 이미 살아 있었다.


2. 처방 검증 — 제안 코드를 그대로 실행했다

이의제기 §3의 코드를 한 글자도 바꾸지 않고(폴백 진입 여부를 세는 PROBE 한 줄만 추가) 위 매트릭스에 투입했다. S2 결과:

S2   agy=miss   probes=1

probes=1폴백에 진입했다는 뜻이다. 그런데도 miss인 이유는 폴백의 첫 분기가 if os.path.exists(db_path): 이기 때문이다. DB가 존재하므로 YAML elif는 평가조차 되지 않고, DB의 state 행에는 신원이 없으므로 ai = {}로 끝난다. 폴백이 열렸을 뿐 도달하지 못한다.

S6도 같다 — DB는 존재하고 state 행만 없어 row = Noneai = {}.

이의제기 §2-2의 결론 "tier-3 캐시 조회를 100% 영구 차단"Rev.1이 아니라 두 변형 모두에 해당하며, 제안된 수정은 그 차단을 해제하지 못한다. 전제와 처방이 함께 성립하지 않는다.

2-1. S2/S6를 진짜로 고치려면 무엇이 필요한가 (그리고 왜 하지 않는가)

DB에 없고 YAML에만 있는 신원을 살리려면 DB 조회가 비었을 때 YAML로 넘어가는 union 방식이어야 한다 (if ... elif ...가 아니라 if not ai and os.path.exists(yaml_path): ...).

그렇게 하지 않는다. atomic_dump_yaml(lib.sh:682-920)은 SQLite BEGIN IMMEDIATE 트랜잭션을 권위로 삼고 YAML은 그 뒤에 temp+rename으로 쓰는 미러다. DB에서 사라진 신원을 YAML에서 되살리면:

  • stop_session.sh --purge-conversation이 DB에서 지운 신원이 YAML 미러를 통해 부활한다. (stop_session.sh:333-348이 지우는 대상이 바로 이 캐시다.)
  • 두 파일이 갈린 상태 = 이미 손상된 상태이며, 손상된 미러를 권위보다 우선시키는 복구는 P0-C 워크스페이스 격리 보증(다른 워크스페이스 UUID 절대 반환 금지) 을 무너뜨릴 수 있다.

따라서 S2/S6의 miss는 버그가 아니라 의도된 동작이며, Rev.2는 이를 V-8로 못박아 후대의 "친절한 복구" 시도를 차단한다.


3. 처방의 경로 폴백 거부

yaml_path = os.environ.get('YAML_PATH', '')
if not yaml_path:
    yaml_path = os.path.join(ws, ".mam", "agent-sessions.yaml")   # ← D1 재도입

YAML_PATHenv_python(lib.sh:661-662)이 무조건 설정한다. 이 블록은 env_python을 통해서만 실행되므로 이 가드는 도달 불가능한 죽은 코드다. 그리고 만에 하나 도달한다면(= 이 블록이 env_python 밖에서 실행되는 미래) 그때야말로 워크스페이스 기준 추측이 가장 위험하다 — AGENT_SESSIONS_YAML이 재정의된 배치에서 조용히 엉뚱한 파일을 읽는다. 이는 본 계획서가 제거하려는 D1 그 자체이며, ea36e81if 'mam_dir' in locals()구조가 동일한 죽은 가드다.

대신 채택하는 하드닝: 경로 해석을 try 안쪽으로 옮긴다. os.environ['YAML_PATH']를 그대로 쓰되(추측 없음), 만약 미설정이면 KeyErrorexcept에 잡혀 WARN 한 줄과 ai={}로 수렴하여 rc=0 계약이 유지된다. Rev.1은 이 줄이 try 밖에 있어 KeyErrorrc=1이 될 수 있었다 — 이의제기가 간접적으로 드러낸 실질적 개선점이며, Rev.2에 반영했다.

(이 경로는 공개 인터페이스로는 재현 불가하다. env_python이 항상 변수를 넣기 때문이다. 따라서 테스트를 붙이지 않고 구조적 방어로만 남긴다 — 검증 불가한 것을 검증했다고 적지 않는다.)


4. 조건 변경 채택 — 근거를 교체하여

해석 결과가 6/6 동일하므로 하위호환 복구를 근거로 한 채택은 불가하다. 그럼에도 조건 자체는 채택한다. 근거는 하나뿐이다:

if d:는 "load_state_json이 병합 상태에 agent_identities를 실어 준다"는 암묵적 결합에 의존한다. 이 저장소는 이미 state 블롭에서 sessions 테이블을 분리한 전례가 있고(현재 라이브 DB의 state.data 최상위 키는 ['snapshot'] 뿐이다), 같은 방식으로 신원이 분리되면 if d:조용히 폴백을 건너뛴다.

비용은 실측했다: 재조회 1회 = sqlite3.connect + SELECT = 0.063 ms/call (200회 평균, 로컬 SSD). tier-3은 tier-1·tier-2가 모두 실패했을 때만 도달하는 경로이므로 성능은 반대 논거가 되지 못한다. 성능을 이유로 거부하지 않는다는 점을 명시한다.

다만 조건 변경만으로는 위 결합을 감지하지 못하고 은폐할 뿐이므로(폴백이 조용히 대신 일한다), V-7 결합 테스트를 함께 넣는다. 런타임 우회 + CI 감지 두 겹이 되어야 실제 방어가 된다.

N-1과의 상호작용(중요): 저장소 어디에도 agent_identities 쓰기 코드가 없으므로 (§5 참조) 실사용에서 d.get('agent_identities')는 사실상 항상 falsy다. 즉 조건 변경 후 폴백이 사실상 상시 경로가 된다. 이는 무해하지만, "1차 경로는 재조회 없음"이라던 Rev.1의 설계 근거 ②는 더 이상 성립하지 않는다. D2의 의의는 "재조회 제거"가 아니라 "경로 추측 제거" 로 축소 기술한다.


5. HEAD 실측 (Rev.1에서 유지)

5-1. 현재 코드 (.agents/skills/lib.sh:1266-1282)

ai = {}
db_path = f"{mam_dir}/agent-sessions.db" if 'mam_dir' in locals() else os.path.join(ws, ".mam", "agent-sessions.db")
yaml_path = f"{mam_dir}/agent-sessions.yaml" if 'mam_dir' in locals() else os.path.join(ws, ".mam", "agent-sessions.yaml")
try:
    import yaml
    if os.path.exists(db_path):
        ...
    elif os.path.exists(yaml_path):
        with open(yaml_path) as f:
            d = yaml.safe_load(f) or {}     # ← 상태 딕셔너리 d 를 덮어씀
        ai = d.get('agent_identities', {})
except Exception as e:
    print(f"WARN: tier-3 identity lookup failed: {e}", file=sys.stderr)

git blame: 1267·1268·1270·1281·1282 행이 ea36e81(2026-08-05), 나머지는 2026-06-21 원본. B-1 원문의 NameErrorea36e81에서 이미 제거되었다 — 전제는 낡았고, 증상 대신 경로 추측이 남았다.

5-2. 잔존 결함 재현 매트릭스

# 조건 HEAD Rev.2
P1 상태 파일이 <ws>/.mam/에 있음 (기본 배치) <uuid> <uuid>
P2 AGENT_SESSIONS_YAML<ws>/.mam/ "" 무음 <uuid>
P3 .db 존재 + PyYAML 없음 "" <uuid>
P4 agent_identities 없음 "" ""
P5 project_cwd 불일치 "" ""
P7 agent_identities가 dict 아님 Traceback, rc=1 "", rc=0
P8 hermes 신원이 conversation_id "" <uuid>

6. 결함 목록

D1 — 상태 파일 경로를 추측한다 (핵심)

env_pythonYAML_PATH로 권위 경로를 넘겨주고(lib.sh:661-662), 같은 파일의 다른 두 블록은 이를 지킨다(load_state_json L512, atomic_dump_yaml L746). tier-3만 os.path.join(ws, ".mam", ...)를 직접 조립한다. AGENT_SESSIONS_YAMLlib.sh:26에서 재정의 가능하고 해석 대상 워크스페이스는 WORKSPACE_ROOT와 같을 의무가 없다 → 갈리는 순간 무음 실패(P2). if 'mam_dir' in locals() 가드는 mam_dir 정의가 0건이므로 항상 False인 죽은 코드다.

D2 — 이미 로드한 상태를 다시 읽는다 (Rev.2에서 축소 기술)

find_workspace_uuid는 진입 시 MAM_STATE_JSON으로 병합 상태를 받아 d에 담는다(L1130-1133). tier-3의 두 번째 읽기는 이 중복이 D1·D3·D4를 낳은 구조적 원인이다. 단, §4에 따라 Rev.2는 재조회 자체를 제거하지 않는다 — 경로 추측만 제거한다.

D3 — import yaml이 SQLite 분기까지 죽인다

try 첫 줄의 import yaml 때문에 PyYAML 부재 시 YAML이 필요 없는 .db 분기까지 무력화된다(P3). 도달 가능성: deploy/install.sh:86시스템 python3의 PyYAML을 하드 게이트하지만 _delegate_py_bin(L1416-1421)은 $VIRTUAL_ENV를 최우선 선택한다. 무관한 venv 활성 상태에서 게이트를 우회한다 — stub 없이 실제 bare venv로 실측:

picked python: .../barevenv/bin/python
HEAD out=<Traceback ... ModuleNotFoundError: No module named 'yaml'|WARN: tier-3 identity lookup failed: ...|>

D4 — d 섀도잉 + rc=0 계약 위반

YAML 분기의 d = yaml.safe_load(f)가 tier-1/2의 상태 딕셔너리를 덮어쓴다(현재는 이후 미사용이라 무해하나, 한 줄만 추가돼도 즉시 오동작하는 함정). 더 심각한 것은 ai.get(agent)(L1284)가 try 이라 비-매핑 입력 시 AttributeErrorrc=1이 된다는 점이다(P7). 함수 주석의 "Always exits 0"(L1106) 위반이며, resolve_session_id.sh:44set -euo pipefail 아래 마지막 명령이라 그대로 스크립트 종료 코드가 된다(stop_session.sh:140,159|| true로 방어됨).

D5 — hermes/cline 폴백이 잘못된 딕셔너리를 본다

cand = ai_agent.get('session_id') or ai.get('conversation_id')   # L1295, L1299

ai{agent: {...}} 맵이므로 ai.get('conversation_id')항상 None. cline은 tier-2 디스크 스캔에 가려지지만, hermes는 tier-2가 WHERE cwd=?로 조회하므로 hermes DB의 cwd가 다르면 tier-2가 실패하고 tier-3만 남는다 → 폴백이 죽는다(P8).


7. 인접 발견 (B-1 범위 밖 — 별도 등재)

N-1 — agent_identities 쓰기 코드가 저장소에 0건

전수 조사: lib.sh:1275,1280(읽기), stop_session.sh:334(기존 항목 비우기), reconcile.sh:690(진단 읽기). 생성·갱신 0건, git log -S"d['agent_identities']"도 공집합. 라이브 워크스페이스의 state.data 최상위 키는 ['snapshot']뿐이고 agent_identitiesnull이다. → B-1을 완벽히 고쳐도 현행 코드로 생성된 워크스페이스의 복원 건수는 0이다. tier-3은 (a) 마이그레이션된 YAML, (b) 수동 편집 상태를 위한 하위호환 읽기 경로로만 의미를 갖는다. §4에서 본 것처럼 이 사실은 조건 변경의 실효(폴백이 상시 경로가 됨)에도 직결된다.

N-2 — load_state_json의 PyYAML 하드 의존

lib.sh:511이 모듈 최상단에서 yaml을 import 하여, .db만으로 충분한 경우에도 PyYAML 부재 시 traceback과 함께 d={}가 되어 tier-1·tier-2가 함께 붕괴한다(P3 stderr에서 관측).


8. 왜 기존 55개 테스트가 이를 잡지 못했는가

tests/conftest.py::mam_sandboxAGENT_SESSIONS_YAML = tmp_path/.mam/agent-sessions.yaml, WORKSPACE_ROOT = tmp_path로 두고, 테스트는 find_workspace_uuid {tmp_path} ...로 호출한다. 따라서 추측 경로 <ws>/.mam/agent-sessions.yaml과 권위 경로가 모든 테스트에서 문자 단위로 동일하다. 하네스가 구조적으로 D1을 볼 수 없다. → 신규 테스트는 **워크스페이스 디렉터리와 상태 파일 디렉터리를 분리한 split_sandbox**를 쓴다.


9. 구현 계획

원자적·이등분 가능 커밋 4개. F1↔F2↔F3 사이 순서 제약 없음(각 테스트가 서로 독립적으로 red).

F1 — tier-3 상태 소스 교정 (D1·D3·D4 해소, D2 축소) — Rev.2 개정

lib.sh:1266-1282 전체를 아래로 치환한다. (프로토타입 검증 완료)

ai = d.get('agent_identities') if isinstance(d, dict) else None
if not isinstance(ai, dict) or not ai:
    ai = {}
    try:
        yaml_path = os.environ['YAML_PATH']
        db_path = os.path.splitext(yaml_path)[0] + '.db'
        if os.path.exists(db_path):
            conn = sqlite3.connect(db_path, timeout=60.0)
            row = conn.execute('SELECT data FROM state WHERE id=1').fetchone()
            if row:
                ai = json.loads(row[0]).get('agent_identities') or {}
            conn.close()
        elif os.path.exists(yaml_path):
            import yaml
            with open(yaml_path) as f:
                _ydoc = yaml.safe_load(f) or {}
            ai = _ydoc.get('agent_identities') or {}
    except Exception as e:
        print(f"WARN: tier-3 identity lookup failed: {e}", file=sys.stderr)
if not isinstance(ai, dict):
    ai = {}

Rev.1 → Rev.2 변경 2건:

# 변경 이유
if d:d에서 신원을 실제로 확보했는지로 분기 이의제기 채택. 단 근거는 하위호환이 아니라 load_state_json 반환 형태 변경 내성(§4). V-7과 한 쌍으로만 유효
yaml_path/db_path 해석을 try 안쪽으로 이동 YAML_PATH 미설정 시 KeyErrorrc=1이 되는 잔여 계약 위반 차단(§3)

거부한 변경 1건: os.environ.get('YAML_PATH','')<ws>/.mam/... 추측 폴백. D1 재도입(§3).

설계 근거:

  1. 1차 소스는 이미 로드된 d — 정상 경로에서 재조회 없음.
  2. 폴백은 YAML_PATH 기반, 추측 없음 — L512/L746과 동일 규약. .db 파생도 동일.
  3. import yaml은 YAML 분기 안 — SQLite 분기가 PyYAML에 인질 잡히지 않는다.
  4. _ydoc + 이중 isinstance 가드 — 섀도잉 제거, rc=0 계약 복원.

F2 — hermes/cline 폴백 키 수정 (D5)

-        cand = ai_agent.get('session_id') or ai.get('conversation_id')
+        cand = ai_agent.get('session_id') or ai_agent.get('conversation_id')

lib.sh:1295, lib.sh:1299 두 곳. replace_all 금지 — 두 줄이 완전히 동일하므로 문맥(elif agent == 'hermes': / 'cline')을 포함해 개별 치환할 것.

F3 — 회귀 테스트 신설

신규 파일 tests/test_b1_tier3_identity.py (V-1…V-8). 전용 split_sandbox 픽스처를 파일 내부에 둔다 — conftest.py::mam_sandbox를 고치면 기존 55개 테스트의 전제가 흔들리므로 공용 픽스처는 건드리지 않는다.

F4 — 주석·문서 정합

  • lib.sh:1105 tier-3 설명에 상태 소스가 d(1차)와 $YAML_PATH(폴백)임을 명시.
  • DB가 권위, YAML은 미러이며 tier-3은 미러를 권위보다 앞세우지 않는다는 한 줄을 추가(§2-1).
  • IMPROVEMENTS.md B-1 항목 교체 + B-10/B-11 신규 등재(§11).

10. 테스트 계획 (red-before-green 실측 완료)

ID 테스트명 검증 대상 HEAD Rev.2
V-1 test_b1_tier3_honours_agent_sessions_yaml_path D1 — 워크스페이스≠상태파일 디렉터리 FAIL PASS
V-2 test_b1_tier3_db_branch_survives_missing_pyyaml D3 — PyYAML 부재 시 SQLite 분기 생존 FAIL PASS
V-3 test_b1_tier3_corrupt_identities_still_exits_zero D4 — rc=0 계약 FAIL PASS
V-4 test_b1_tier3_hermes_conversation_id_fallback D5 — hermes 폴백 키 FAIL PASS
V-5 test_b1_tier3_refuses_foreign_workspace_identity P0-C 격리 유지 (가드) PASS PASS
V-6 test_b1_tier3_absent_identities_is_silent 무신원 시 무음·무예외 (가드) PASS PASS
V-7 test_b1_load_state_json_surfaces_agent_identities 결합 고정load_state_json이 신원을 d에 실어야 함 PASS PASS
V-8 test_b1_tier3_does_not_read_yaml_mirror_behind_the_db 동작 고정 — stale DB를 YAML 미러로 되살리지 않음 PASS PASS

V-7·V-8은 이의제기 대응으로 신설했다. 둘 다 HEAD에서도 green인 고정(pinning) 테스트다 — red를 만드는 것이 목적이 아니라, §1·§2에서 측정으로 확인한 사실을 코드로 못박아 다음 리팩터에서 조용히 깨지는 것을 막는 것이 목적이다. V-7이 깨지면 §4의 결합이 끊어진 것이고, V-8이 깨지면 누군가 미러를 권위보다 앞세운 것이다.

4개 변형 전수 실행 결과:

HEAD   4 failed, 4 passed in 1.23s
REV1   8 passed in 1.18s      (if d:)
AGY    8 passed in 1.19s      (이의제기 제안 원문)
REV2   8 passed in 1.20s      (채택안)

세 수정안이 동일하게 8 passed라는 사실 자체가 §1의 결론을 다시 확인해 준다 — 조건 변경은 관측 가능한 동작을 바꾸지 않는다.

핵심 픽스처(구현자는 이 구조를 그대로 쓸 것):

@pytest.fixture
def split_sandbox(tmp_path):
    """워크스페이스와 상태 파일을 서로 다른 디렉터리에 둔다 — mam_sandbox 의 우연을 깬다."""
    shutil.copytree(os.path.join(REPO, ".agents", "skills"), tmp_path / ".agents" / "skills")
    ws = tmp_path / "ws";           (ws / ".mam").mkdir(parents=True)
    state_dir = tmp_path / "state"; state_dir.mkdir()
    home = tmp_path / "home";       (home / ".claude" / "projects").mkdir(parents=True)
    return {"root": tmp_path, "ws": ws, "yaml": state_dir / "agent-sessions.yaml",
            "home": home, "lib": tmp_path / ".agents" / "skills" / "lib.sh"}

V-2의 PyYAML 부재는 스텁 모듈(PYTHONPATH=<tmp>/noyamlyaml/__init__.py에서 raise ImportError)로 결정적으로 재현한다 — 실제 venv 생성 없이 CI에서 빠르고 안정적이다. 검증에 사용한 전체 테스트 파일(8케이스)은 저장소 밖 스크래치패드에 그대로 있다: /private/tmp/claude-501/.../scratchpad/b1/test_b1_tier3_identity.py


11. 검증 게이트

게이트 명령 통과 기준 실측
G-A bash -n .agents/skills/lib.sh 구문 통과 통과
G-B pytest tests/test_b1_tier3_identity.py -q (수정 ) 4 failed, 4 passed 확인
G-C pytest tests/test_b1_tier3_identity.py -q (수정 ) 8 passed 확인
G-D pytest tests/test_tier1_unit.py tests/test_tier2_component.py -q 55 passed (회귀 0) Rev.2 패치 클론에서 55 passed / 459.81s
G-E pytest tests/test_tier2_component.py -k "find_workspace_uuid or resume or stop" 10 passed 확인 (13.3s)
  • G-D는 MQTT 왕복 때문에 ~8분 소요된다. 백그라운드 실행 후 회수할 것.
  • tests/test_sanity.pyHEAD 이전부터 45초 이상 행(hang) 하는 기존 문제로 본 작업 범위 밖이다 (baseline 아카이브 대조 확인 완료). 게이트에 포함하지 않는다.

12. IMPROVEMENTS.md 갱신 문안

### **B-1: `find_workspace_uuid` tier-3 신원 캐시 해석 오류** — ✅ 해소 (F1/F2)
- tier-3이 상태 파일 경로를 `<workspace>/.mam/` 로 추측하던 문제를 제거하고, 이미 로드된 병합 상태(`d`)를
  1차 소스로, `$YAML_PATH`(권위 경로)를 폴백으로 사용하도록 교정.
- PyYAML 부재 시 SQLite 분기까지 무력화되던 `import yaml` 위치, 상태 딕셔너리 `d` 섀도잉,
  비정상 `agent_identities` 입력 시 `rc=1`(계약 위반), hermes/cline 폴백 키 오류를 함께 수정.
- **DB가 권위, YAML은 미러**이며 tier-3은 미러를 권위보다 앞세우지 않는다(V-8로 고정).
- 회귀 테스트 `tests/test_b1_tier3_identity.py` (V-1…V-8) 신설.

### **B-10: `agent_identities` 쓰기 경로 부재**
- 저장소 전체에 `agent_identities` 를 생성·갱신하는 코드가 **0건**이며, 읽기(lib.sh tier-3,
  reconcile.sh 진단)와 삭제(stop_session.sh --purge-conversation)만 존재한다. 현행 코드로 생성된
  워크스페이스에서 tier-3은 구조적으로 빈 값이며, 하위 호환 읽기 경로로만 기능한다.
- 결정 필요: (a) 세션 캡처 시 기록하는 쓰기 경로 신설 / (b) 하위 호환 전용으로 명시하고 문서화.

### **B-11: `load_state_json` 의 PyYAML 하드 의존**
- `lib.sh:511` 이 모듈 최상단에서 `yaml` 을 import 하여, `.db` 만으로 충분한 경우에도 PyYAML 부재 시
  traceback 과 함께 상태가 `{}` 로 붕괴되어 tier-1·tier-2 가 동시에 무력화된다.
- `_delegate_py_bin``$VIRTUAL_ENV` 를 최우선 선택하므로 `deploy/install.sh` 의 시스템 python3
  PyYAML 게이트를 우회하는 경로가 실재한다(실측).

총 추적 건수 18 → 19 (B-1 해소, B-10·B-11 신규).


13. 리스크

ID 리스크 평가 완화
RK-A 폴백 진입 조건 변경으로 tier-3이 느려진다 무시 가능 — 재조회 0.063 ms/call, tier-1·2 실패 시에만 도달 실측 기재. 성능을 논거로 쓰지 않음
RK-B (개정) d에 신원이 있는지로 분기하면 폴백이 상시 경로가 된다(N-1 때문) — 동작은 동일하나 "재조회 없음" 설계 근거가 소멸 D2를 "경로 추측 제거"로 축소 기술. V-7이 결합 파손을 CI에서 검출
RK-C isinstance 가드가 정상 입력을 거른다 없음 — dict일 때 무동작 V-5·V-6
RK-D F2 치환 시 동일 문자열 2줄을 replace_all로 뭉갠다 문맥 포함 개별 치환 강제
RK-E 수정 후에도 실사용 복원이 늘지 않아 "미해결"로 오판 높음 N-1(B-10) 동반 보고. 효과 범위를 하위호환 상태를 가진 워크스페이스로 한정해 커밋 메시지에 명시
RK-F (신규) 후대에 "S2/S6도 살리자"며 YAML 미러 union 폴백을 추가한다 --purge-conversation으로 지운 신원 부활, P0-C 격리 훼손 V-8이 즉시 red. §2-1 근거를 F4 주석으로 코드 옆에 남긴다
RK-G (신규) YAML_PATH 미설정 하드닝(변경 ②)이 공개 인터페이스로 검증 불가 낮음 테스트를 만들지 않고 구조적 방어로만 남긴다고 명시. 검증하지 않은 것을 검증했다고 적지 않음

14. 작업 순서 체크리스트 (Creator용)

  1. tests/test_b1_tier3_identity.py 추가 → G-B(4 failed, 4 passed) 확인. red 미확인 시 진행 금지.
  2. F1 적용(lib.sh:1266-1282 치환) → G-A, G-C.
  3. F2 적용(lib.sh:1295,1299 개별 치환) → G-C 재확인.
  4. G-E → G-D(백그라운드)로 회귀 0 확인.
  5. F4 주석/문서 → IMPROVEMENTS.md §12 문안 반영.
  6. 커밋 분할:
    • fix(lib): resolve tier-3 identity source from loaded state and $YAML_PATH (B-1)
    • fix(lib): read hermes,cline tier-3 fallback from the agent entry
    • test(lib): add B-1 tier-3 regression suite (V-1..V-8)
    • docs(improvements): close B-1, open B-10,B-11

15. 경계 선언

본 문서는 설계·리뷰 산출물이며 저장소 코드는 한 줄도 수정하지 않았다. 이의제기 코드·Rev.1·Rev.2 세 변형과 8개 테스트는 전부 스크래치패드의 git archive HEAD 클론 안에서만 실행했고, 작업 트리는 호출 시점과 동일하게 clean이다(b6c41e6). herdr 명령 실행 없음, 라이브 세션 미접촉.

MULTI_AGENT_RULES.md §1에 따라 구현은 Creator, 커밋은 GM 소관이다. 차단 항목 없음.

Creator께: 이의제기의 전제와 처방은 측정으로 기각되었으나, 그 과정에서 잔여 계약 위반(변경 ②) 을 발견했고 V-7·V-8 두 개의 고정 테스트를 얻었습니다. 지적해 주신 결합(if d:의 암묵적 의존)은 실재하며, Rev.2는 이를 런타임 우회 + CI 검출 두 겹으로 막습니다.

[VERDICT: PASS] [AGREEMENT: REACHED]