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

16 KiB

A-4 BaseAgentAdapter — 미해결 현황 분석 · 해결 계획 · 교차 리뷰 (Rev.2)

  • job_id: f2bd7e13 (Rev.1 = 5af41284)
  • 역할: Planner
  • 반영한 이의제기: 50fdb719 (agy, herdr:agy-creator-01) — [VERDICT: PASS WITH CHALLENGE]
  • 기준 커밋: e10db89 (HEAD)
  • 실측 하네스:
    • .mam/jobs/5af41284/claude-reports/proposed/probe_a4_status.sh (Rev.1, 유효)
    • .mam/jobs/f2bd7e13/claude-reports/proposed/probe_direct_call_contract.sh (Rev.2 신규, 실행·검증 완료)
  • 회귀 기준선: tier1 + sanity + workspace_scope + uuid_target = 46 passed
  • 저장소 변경: 없음

0. Rev.1 대비 변경 요약

agy 의 3개 주장은 메커니즘이 전부 사실이다. 다만 그중 둘은 영향 범위 서술이 실측과 다르며, 그 차이가 우선순위를 바꾼다.

이의제기 항목 메커니즘 영향 서술 Rev.2 반영
§1 HOME_DIR 폴백 → Silent Fail-Close SUSTAINED (재현) 정정: 오늘은 도달 불가 — 어댑터가 활성화시키는 잠복 결함 N0 신설, Phase 1 선행 조건
§2.1 load_state_json 서브셸 포크 SUSTAINED (70ms 실측) OVERRULED: watchdog 매 주기가 아니라 status.sh 1회 N7 신설, P3
§2.2 python -m 브리지 오버헤드 SUSTAINED (실측 완료) 절대값 정정 (25ms → 15.0/18.7ms) 계약 유지, §6.4 개방 항목 종결

추가 발견 (agy 가 짚지 않은 것): lib_py 세 모듈의 HOME_DIR 해석 방식이 서로 다르다. 한쪽은 조용히 실패하고 다른 쪽은 시끄럽게 실패한다. 따라서 수정은 "verify_session.py 한 줄 고치기"가 아니라 계약 통일이어야 한다(§1.3).

Rev.1 의 결론(A-4 착수, lib_py/agents/ 채택, G1 선행)은 바뀌지 않는다.


1. 이의제기 판정

1.1 Primary — HOME_DIR 미주입 시 Silent Fail-Close: SUSTAINED

재현했다. HOME_DIR 없이 직접 호출하면:

HOME_DIR present : False
home             : ''
agy  경로        : /.gemini/antigravity-cli/conversations
cline 경로       : /.cline/data/sessions
verify(agy)      : False

verify_session.py:84home = home_dir or os.environ.get("HOME_DIR", "") 이므로 home 이 빈 문자열이 되고, 모든 아티팩트 경로가 사용자 홈이 아니라 시스템 루트에 조립된다. os.path.exists() 가 전부 False 를 반환하니 어떤 세션도 검증을 통과하지 못한다. agy 의 서술 그대로다.

인용 정정: agy 는 line 110 이라 했으나 HEAD 기준 line 84 다. 코드는 인용된 것과 동일하다.

1.2 정정 — 이 결함은 오늘 도달 가능하지 않다

agy 는 이를 P0 로 제시했다. 현 호출자를 전수 확인한 결과 오늘은 트리거가 없다:

(a) lib.sh 의 HOME_DIR 주입 지점       : env_python(1030) · atomic_dump_yaml(1092) 둘 다 주입
(b) 파이썬에서 직접 import 하는 저장소 코드 : (없음)
(c) 테스트의 호출 방식                  : 전부 bash -c "source lib.sh && …" 경유

lib_py 를 파이썬에서 직접 import 하는 코드는 lib_py 자기 자신(workspace_uuid.pyverify_session.py)뿐이고, 그 경로는 이미 env_python 안에서 실행되므로 HOME_DIR 이 있다. 테스트도 전부 셸을 경유한다.

따라서 정확한 성격은 이렇다: 오늘은 무해하지만, 내 Rev.1 Phase 1(N4/N5, lib_py/agents/ 어댑터)이 착수되는 순간 활성화되는 잠복 결함이다. 어댑터는 정의상 파이썬에서 직접 호출되기 때문이다.

이 구분이 중요한 이유는 우선순위 때문이다. Rev.1 의 G1(파사드 빈 문자열)은 부분 배포만으로 오늘 발생한다. 이 건은 내가 코드를 추가해야 발생한다. 둘을 같은 P0 로 묶으면 순서를 정할 수 없다. 그래서 이 건은 P0 가 아니라 Phase 1 의 선행 조건(N0) 으로 배치한다 — 실질적으로는 "어댑터보다 먼저 해야 한다"는 같은 결론이지만, G1 보다 뒤라는 점이 분명해진다.

1.3 추가 발견 — 세 모듈의 실패 방식이 다르다

agy 는 verify_session.py 만 지적했다. 세 모듈을 전부 보면 계약이 하나가 아니다:

verify_session.py   get() → 조용한 기본값        84:  home = home_dir or os.environ.get("HOME_DIR", "")
workspace_uuid.py   environ[] → KeyError (시끄러움) 21:  home = os.environ['HOME_DIR']
atomic_yaml.py      HOME_DIR 미사용

workspace_uuid.py 는 이미 올바르게 행동한다 — 계약이 깨지면 KeyError 로 즉시 죽는다. verify_session.py 만 조용히 잘못된 답을 낸다.

그러므로 수정 방향은 agy 가 제안한 "verify_session.py 한 줄 교체"보다 넓어야 한다: lib_py 전체에 하나의 HOME_DIR 해석 계약을 두고 세 모듈이 그것을 쓰게 한다.

agy 가 제안한 폴백 체인 자체는 채택한다:

home = home_dir or os.environ.get("HOME_DIR") or os.environ.get("HOME") or os.path.expanduser("~")

$HOME 으로 떨어지는 것이 격리 계약을 해치지 않는지 확인했다 — lib.sh:41HOME_DIR="${HOME_DIR:-$HOME}" 이므로 둘은 기본적으로 같은 값이고, 격리는 HOME_DIR 이 아니라 iso_root 로 수행된다(f"{iso_root or home}/…"). 따라서 폴백이 다른 워크스페이스의 저장소를 읽게 만들지 않는다.

다만 마지막에 빈 값이 남으면 예외를 던져야 한다. expanduser("~") 까지 실패하는 환경(HOME 없는 컨테이너)에서 다시 "" 로 떨어지면 같은 결함이 재발한다.

1.4 §2.1 서브셸 포크 — 메커니즘 SUSTAINED, 영향 OVERRULED

코드는 agy 가 말한 자리에 있고, 비용도 실측했다:

reconcile.sh:350  script = f"source '{lib_sh}' && load_state_json"
포크 비용         n=5  median=70 ms  (min 68 / max 74)

그러나 이 포크는 watchdog 경로에서 일어나지 않는다. 해당 블록은 이런 가드 안에 있다:

try:
    d
except NameError:
    subprocess

그리고 atomic_dump_yamld미리 정의한다(lib_py/atomic_yaml.py:97/101/103). reconcile.sh:861-865` 의 분기를 보면:

DRY_RUN=1 → env_python        → d 없음  → bash 포크 (70ms)
DRY_RUN=0 → atomic_dump_yaml  → d 있음  → 포크 없음

그리고 실제 소비자는:

create_session.sh:389   reconcile.sh --once        (쓰기 → 포크 없음)
lib.sh:1395             reconcile.sh --subscribe   (쓰기 → 포크 없음)
status.sh:19            reconcile.sh --dry-run     (읽기 → 포크 발생)

즉 백그라운드 watchdog 은 포크하지 않는다. 포크가 일어나는 유일한 소비자는 status.sh — 사용자가 직접 실행하는 상태 조회 명령이다.

agy 의 서술("모니터 스위프 시 … 매 주기 발생 … 백그라운드 watchdog의 응답 지연과 CPU 자원 낭비")은 성립하지 않는다. 실제 성격은 대화형 status.sh 1회당 70ms 이다. 고칠 가치는 있으나(대화형 명령에서 70ms 는 체감된다) P3 이며, A-4 의 선행 조건이 아니다.

1.5 §2.2 브리지 오버헤드 — SUSTAINED, 절대값 정정

Rev.1 §6.4 에서 "재측정하지 않았다"고 남긴 개방 항목을 이번에 닫았다:

python -m (venv)    : 15.0 ms
python -m (system)  : 18.7 ms
bash case (기준선)  :  2.8 ms

설계 문서의 22.8ms 와 agy 의 25ms 는 이 환경에서 다소 비관적이다(15.0/18.7ms). 그러나 **bash case 대비 5.46.7배**라는 관계는 그대로이므로, "스크립트당 1회 호출 후 eval" 계약은 변경 없이 유효하다. 분기마다 호출하면 안 된다는 결론이 절대값이 아니라 배수에서 나오기 때문이다.


2. 현황 (Rev.1 에서 변경 없음 — 요약)

e10db89 이후 상태와 미해결 3건(G1/G2/G3)은 이의제기의 영향을 받지 않았다. 전문은 5af41284 보고서를 참조하고 결론만 옮긴다.

  • G1 (P0): VERIFY_SESSION_PYTHON 파사드가 파일 유실 시 조용히 빈 문자열 → reconcile.shNameError 로 죽음. 오늘 도달 가능.
  • G2: 팬아웃 39 → 44 (+5). 증가분이 전부 정상적인 결함 수정(F4/F6)에서 나왔다 — 추상화 없이 고칠수록 사본이 는다.
  • G3: A-4 M0 체크리스트가 mam_agents 가 아니라 lib_py 에 대해 이미 충족. lib_py/agents/ 채택 권고, CI py_compile 재귀 교정 필수.

신규 (이의제기 반영):

  • G4: lib_pyHOME_DIR 해석 계약이 모듈마다 다르고, verify_session.py 는 조용히 실패한다. Phase 1 선행 조건.
  • G5: status.sh --dry-run 경로에 70ms 서브셸 포크. P3.

3. 실행 계획 (Rev.2)

Phase 0 — 즉시 (A-4 와 독립)

N1. 파사드 폴백을 시끄럽게 — G1, P0. 오늘 도달 가능한 유일한 건. N2. reconcile.sh 를 import 로 전환98393a97 D2b-③ 잔여. 완료 시 파사드 삭제 가능(N1 무의미화). 착수 전 §4.3 전수 조사 필요. N3. CI py_compile 재귀 교정 — G3. 어댑터 도입 전에 해야 신규 파일이 처음부터 검사된다.

Phase 1 — A-4 M0/M1

N0. lib_py HOME_DIR 해석 계약 통일 — G4, ← 이의제기 §1 반영, 신규. N4 의 선행 조건. lib_py/paths.py(또는 verify_session.py 내 공용 헬퍼)에 단일 해석 함수를 두고 세 모듈이 그것만 쓰게 한다:

def resolve_home(home_dir=None):
    h = home_dir or os.environ.get("HOME_DIR") or os.environ.get("HOME") or os.path.expanduser("~")
    if not h or h == "/":
        raise ValueError("HOME_DIR unresolvable — refusing to build paths from the filesystem root")
    return h

workspace_uuid.py:21os.environ['HOME_DIR'] 도 이 함수로 교체한다 — 지금은 우연히 올바르게 시끄럽지만, 계약이 두 벌인 상태를 남기지 않는다. 회귀 테스트: env -u HOME_DIRverify_session_uuid 를 직접 호출해 False 가 아니라 정상 동작하는지, 그리고 HOME/HOME_DIR 둘 다 없을 때 ValueError 로 죽는지. 현재 이 경로를 검증하는 테스트는 0개다(§4.2).

N4. lib_py/agents/ 골격base.py · registry.py · __main__.py · adapters/{claude,agy,hermes,cline}.py. mam_agents/ 신설안 폐기. 셸 브리지 계약을 명시한다(§1.5 실측 근거): 스크립트 진입 시 eval "$(python -m lib_py.agents facts <agent>)" 1회. 루프 안 호출 금지. 15.0ms × 분기 수는 bash case 2.8ms 대비 즉시 손해다.

N5. M1 own_key / agent_of_row 이관a4589a4b W7/W8d 흡수. 우선순위 ①명시인자 →②레지스트리 row →③이름 접미사 →④pane.cmd →⑤실패는 실패. reconcile.sh 입양 루프에는 ④ 를 적용하지 않는다(3aee63cf §1.2 실측 반증).

Phase 2 — A-4 M2~M7

설계 문서 단계를 따르되 M2 는 내용이 이미 landing 되었으므로 이관만 수행. M4 는 hermes DB 스키마 실측 후 착수(§4.1).

Phase 3 — 공백 보충 및 성능

N6. 입양 소유권 마커3aee63cf W8a/W8b. A-4 에 대응 항목 없음. N7. load_state_json 파이썬 경로 제공 — G5, ← 이의제기 §2.1 반영, 신규. P3. lib_py/state.pyload_state_json() 을 두고 reconcile.sh 의 dry-run 폴백이 bash -c 대신 이를 직접 부르게 한다. 대상은 status.sh 체감 지연 70ms 이며, watchdog 성능과는 무관하다(§1.4). 이는 6481e5b4 의 "소형 블록 4개는 옮기지 않는다" 결정에 대한 부분 예외다 — load_state_json(39줄)은 그 결정 당시 "정적 검사 가치가 낮다"는 이유로 제외했으나, 여기서는 성능이라는 다른 근거가 생겼다. 근거가 바뀌었으므로 결정도 바뀐다.


4. 검증

팬아웃 게이트 (Rev.1 에서 유지):

현재 44 → M1 이후 ≤38 → M2 이후 ≤30 → 최종 상한 약 15

각 단계마다 probe_a4_status.sh 재실행, S1 단조 감소 확인. 회귀는 46 passed 기준선.

신규 게이트:

  • N0: env -u HOME_DIR 직접 호출이 정상 동작 + HOME/HOME_DIR 부재 시 ValueError. probe_direct_call_contract.sh D1/D3 이 그대로 게이트다.
  • N1/N2: lib_py/verify_session.py 를 지운 사본에서 reconcile.sh --once0 아닌 종료코드. lib.sh 단위 테스트로는 잡히지 않는다.
  • N4: 브리지 호출 횟수가 스크립트당 1회인지 — python -m lib_py.agents 호출을 세는 정적 검사.
  • N7: status.sh 실행 시간 전후 비교(현재 포크분 70ms).

5. 리스크 및 미측정 항목

5.1 hermes 미설치 — 변화 없음. lib_py/verify_session.pySELECT cwd FROM sessions WHERE id=?sessions 테이블에 cwd 컬럼이 있다는 추론에 근거하며 실행 확인되지 않았다. 컬럼이 없으면 sqlite3.OperationalErrorexcept Exception: return False 에 삼켜져 모든 hermes 세션이 조용히 검증 실패한다 — G4 와 정확히 같은 종류의 침묵이다. M4 전에 확인할 것.

5.2 직접 호출 경로를 검증하는 테스트가 0개다. §1.2 에서 확인했듯 모든 테스트가 셸을 경유한다. 그래서 G4 가 머지될 때까지 아무도 몰랐다. N0 는 수정과 함께 직접 호출 테스트를 반드시 추가해야 한다 — 그러지 않으면 어댑터 도입 후 같은 종류의 결함이 또 잠복한다.

5.3 N2 착수 전 reconcile.shlib.sh 의존 전수 조사가 여전히 미이행이다. 98393a97 §6.1 → 5af41284 §6.3 → 지금까지 세 번째로 남기는 권고다. MAM_VERIFY_PY 는 전수 grep 했으나 다른 셸 변수·함수 의존은 조사하지 않았다. 이번 이의제기(§2.1)가 정확히 그 미조사 영역에서 나왔다 — 조사했다면 내가 먼저 찾았을 것이다.

5.4 §1.5 의 브리지 측정은 최소 스텁 기준이다. 실제 어댑터는 registry.py + 4개 어댑터 모듈을 import 하므로 15.0ms 보다 느려진다. N4 착수 시 실제 패키지로 재측정할 것. 배수가 커지면 eval 1회 계약이 더 중요해질 뿐 방향은 같다.

5.5 G2 지표는 독립 구현이다. A-4 문서의 34 와 이 보고서의 44 를 직접 빼서 쓰면 안 된다. delta(+5)가 근거다.

5.6 lib_py/agents/ 채택은 되돌리기 쉽다. mam_agents/ 로 되돌리는 비용은 remove.sh 1줄 + CI 1줄. 이 결정에서 막히지 말 것.


6. 판정

이의제기 3건의 메커니즘을 전부 확인해 수용했고, 그중 2건의 영향 서술을 실측으로 정정했다. 정정이 우선순위를 바꾼다 — HOME_DIR 건은 P0 가 아니라 Phase 1 선행 조건이고, 서브셸 포크는 watchdog 병목이 아니라 status.sh 의 P3 개선이다. 그리고 agy 가 짚지 않은 모듈 간 계약 불일치(G4) 를 추가로 발견해 수정 범위를 넓혔다.

[ADJUDICATION: SUSTAINED]50fdb719 §1 (HOME_DIR 폴백), §2.1 (서브셸 포크 존재), §2.2 (브리지 오버헤드) [ADJUDICATION: OVERRULED]50fdb719 §2.1 의 영향 서술 (watchdog 매 주기 병목) — 쓰기 경로는 포크하지 않음을 실측

결론: A-4 착수 유지. 순서는 N1(P0) → N2 → N3 → N0(신규 선행) → N4 → N5 → M2~M7 → N6/N7.

[VERDICT: PASS]

(이 토큰은 이 계획서 산출물의 완성도를 뜻한다. 감사 대상 코드에 대한 판정이 아니다 — 감사 결과는 P0 1건(G1) 미해결 + 잠복 1건(G4) 이다.)