Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-b78af5ad.md
T

9.0 KiB

Issue #64 리뷰 — agy·codex·hermes DB 읽기 전용 열기 (job b78af5ad)

  • 리뷰어: planner-reviewer-claude-01
  • 검증 경로: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1
  • identity: HEAD b0eccc530149bc6e9211727e19921665c7b80492, tree f5a412a3ee96465db7c4b3223d2d76e95716ddfa 가 일치한다. 작업 트리는 clean 이고 .mam 은 없다.
  • 범위: base 3ce5d5f(v4.9.7)..head 1커밋. 판정은 base..head 전체에 대한 것이다.
  • 참고 자료: creator(agy) 리포트 .mam/jobs/c721e4f5/agy-reports/report-final.md
  • 환경: macOS, Python 3.9.6, sqlite 3.51.0. 실제 에이전트 홈은 ls·lsof·ps 로만 봤고, sqlite 로 연 적은 없다.

결론

이슈 범위는 빠짐없이 바뀌었다. 조회 전용 열기는 모두 _ro_connect 로 바뀌었고, 쓰기와 MAM 자신의 DB 는 그대로다. 새 테스트는 base 에서 실패하고 head 에서 통과한다. 원인도 직접 재현해 확인했다. 차단 결함은 없다. 다만 새 런타임 테스트의 reconcile 단계가 실제로는 drift C 를 실행하지 않는다(N1). 또 기존부터 있던 workspace 간 오고정 문제를 새 테스트가 기대값으로 고정해 버렸다(N2). 둘 다 테스트를 보강하라는 권고다.

1. 원인 확인 — 직접 재현함

creator 의 결론: reconcile.sh drift C(agy, :786-839)는 고정되지 않은(unpinned) 실행 중 agy 행이 있으면 호스트 공용 conversations/*.db 를 전부 훑는다. 각 DB 에 verify_session_uuid 를 부르고, 이어서 AgyAgentAdapter.verify_artifact 가 sqlite3.connect(path) 로 연다. last_conversations.json 이 그 대화를 다른 workspace 의 것으로 적어 두어도 cache_match=False 는 _sibling_claimed_uuids 만 보고 거르지 않는다(agy.py:60-72). 그러면 macOS persistent WAL 때문에 -wal·-shm 이 남는다.

재현 방법:

  • git archive 사본에 임시 테스트를 넣었다(mam_sandbox, 임시 HOME, mock herdr).
  • 사이드카 없는 WAL agy DB 하나를 만들고, last_conversations.json 은 그 대화를 다른 workspace ws_b 에 매핑했다.
  • ws 의 고정되지 않은 running 행을 mock herdr 에 살아 있는 pane 으로 등록했다.
  • reconcile.sh --once --emit-diff --dry-run 을 실행했다.
사본 DB 디렉터리 결과
base <uuid>.db, .db-shm, .db-wal — 사이드카가 생긴다
head <uuid>.db 만 — 바뀌지 않는다

이로써 v4.9.7 리뷰 때 #55 가드가 다른 workspace 의 agy DB 옆에서 잡은 사이드카는 MAM 의 reconcile drift C(status·create 가 부르는 경로 포함)가 만든 것으로 설명된다. 다만 그때 그 시각에 고정되지 않은 agy 행이 실제로 있었는지는 기록이 없어 확인하지 못했다.

2. 바뀐 곳 확인

  • 공용 helper. _ro_connect 가 base.py:111-135 로 옮겨졌다. 본문은 그대로이고, 임시 복사본 이름만 os.path.basename(path) 를 쓴다.
    • opencode 는 base 의 것을 import 한다(opencode._ro_connect is base._ro_connect, 테스트로 고정).
    • opencode 에서 쓰지 않게 된 quote·tempfile import 는 지워졌다. shutil·sqlite3 는 아직 쓰고 있다(which, purge).
  • db_rows_naming 기본 연결. 기본값이 _ro_connect 가 됐다(base.py:145). 그래서 codex·opencode 의 residual 확인도 clean WAL DB 를 읽는다. base 에서는 mode=ro 가 Mac 에서 unable to open 으로 실패해 빈 결과를 냈다.
  • 조회 전용 열기를 바꾼 곳:
    • agy verify_artifact(agy.py:74)
    • codex _find_rollout_via_db(codex.py:80), discover(:195)
    • hermes verify_artifact(hermes.py:51), discover(:100)
    • status.sh hermes·codex 두 모드(:90-91, :131-132, :290-291, :331-332)
    • reconcile.sh hermes drift(:865), codex drift(:1048)
  • 남은 sqlite3.connect(grep 으로 전수 확인). 모두 대상이 아니라는 판단이 맞다.
    • 의도된 쓰기: codex·hermes·opencode purge 의 DELETE
    • MAM 자신의 DB: lib.sh:1599,2068, atomic_yaml.py:61, state.py:17, verify_session.py:41(agent-sessions.db), loop/runner.py:416
  • import 정리. agy 에서 sqlite3 import 를 지웠고 다른 사용처는 없다. status.sh 의 opencode 블록은 이제 lib_py.agents.base 에서 import 한다.

3. 테스트 실행

조건: env -i, 임시 HOME, XDG_DATA_HOME=<scratchpad>, basetemp 는 scratchpad, -p no:cacheprovider, PYTHONDONTWRITEBYTECODE=1.

새 모듈 tests/test_issue64_ro_connect.py

  • head: 43 passed.
  • base 사본에 이 파일만 넣으면: 23 failed, 20 passed. creator 의 수치와 같다.
  • base 에서 실패한 것은 모든 clean·walonly 경우, 정적 검사, workspace 간 테스트다. 실패 메시지는 사이드카 -shm·-wal 이 생겼다는 것이다.

관련 13개 모듈

  • 대상: issue64, issue60_linux_fixes, issue60_opencode_v2, a4, issue10_13_purge, issue31, orc_onboard, tier1, tier2, uuid_target, issue55, issue57, issue21_worktree_prereqs.
  • 결과: 461 passed, 1 skipped, 실패 0, 15분. skip 은 PATH 에 opencode 가 없는 실바이너리 계약 테스트다. #55 가드 오탐은 없었다.

뮤테이션 (git archive 사본, issue64 모듈)

뮤테이션 결과
agy verify_artifact 를 sqlite3.connect 로 5 failed(workspace 간 테스트 포함)
db_rows_naming 기본값을 옛 mode=ro URI 로 4 failed
hermes discover 를 sqlite3.connect 로 2 failed
reconcile hermes drift 를 sqlite3.connect 로 1 failed — 정적 검사만 잡음
reconcile codex drift 를 sqlite3.connect 로 1 failed — 정적 검사만 잡음

린트

  • git diff --check 는 깨끗하다.
  • 바뀐 Python 6개 파일이 3.9 로 파싱된다.
  • status.sh·reconcile.sh 는 /bin/bash -n(3.2)을 통과한다.

비차단

  • N1. 런타임 테스트의 reconcile 단계는 drift C 를 실행하지 않는다.
    • 위치: test_status_and_reconcile_runtime_leave_sidecars_alone(:241-...).
    • 원인: 세 행을 mock herdr 에 등록하지 않는다. 그래서 drift A 가 먼저 herdr gone … Marked terminated 로 처리하고 drift C 를 건너뛴다. base 에서 같은 상황을 실행해 drift 결과로 확인했다.
    • 결과: reconcile 의 hermes·codex 변경은 정적 문자열 검사로만 고정된다(위 뮤테이션 2개).
    • 권고: 이슈 64 요구는 "실제 실행 또는 최소한 정적 검사"라 충족은 된다. 그래도 mock herdr state 에 세 세션을 running 으로 넣으면 실제 drift C 경로가 검증된다(내 재현처럼 state["agents"][name] = {...}). status 단계는 실제로 열린다. base 에서 hermes state.db 사이드카로 실패한다.
  • N2. 기존 결함: drift C(agy)가 다른 workspace 의 대화를 고정한다.
    • 내 재현에서 last_conversations.json 이 ws_b 의 것으로 적은 대화가 epoch 뒤 유일한 후보였다. 그러자 base·head 모두 ws 행에 updated conversation id: <uuid> 로 고정했다(dry-run 기준).
    • 이 PR 이 만든 문제는 아니고 #64 범위 밖이다. 다만 새 테스트 test_unpinned_agy_reconcile_…(:288)가 verify_session_uuid(ws_a, …, uuid_b) is True 를 단언해 이 동작을 기대값으로 고정한다.
    • 권고: 이 단언은 빼고 디렉터리 목록만 본다. 오고정은 별도 이슈로 다룬다. 예를 들어 cache_match 가 아니면서 lc 가 그 uuid 를 다른 cwd 에 매핑하면 거절한다.
  • N3. _ro_connect 의 immutable 경쟁 창(ms 수준)은 #60 에서 남긴 것이 공용 helper 를 통해 agy·codex·hermes 로도 넓어졌다. 세 에이전트의 실제 DB 는 모두 WAL 형태다(ls: hermes·codex 는 사이드카 쌍이 있고, agy 대화 72개 중 7개에 사이드카가 있음). 실행 중에는 사이드카가 있으므로 mode=ro 갈래를 탄다.

관찰·공개

  • 실제 codex DB 의 사이드카. 관련 모듈 실행 전후로 ~/.codex/ 목록을 비교했더니 state_5.sqlite-shm·-wal 이 다시 나타나 있었다(mtime 23:29/23:35, 지금은 보유 프로세스 없음).
    • 이 두 파일은 잡 시작 직후(23:04)에도 있었다가 사라졌던 것이다.
    • 같은 시간대에 실제 ~/.codex 를 쓰는 codex 프로세스들이 떠 있다: codex resume(이 루프의 리뷰어), app-server daemon, IDE 확장.
    • 내 실행은 env -i·임시 HOME 이었고, head 코드는 사이드카를 만들지 않으며, #55 가드도 아무것도 잡지 않았다. 그래서 테스트 산출물이 아니라 실제 codex 활동으로 본다. 판정 근거로는 쓰지 않았다.
  • 검증 트리에 임시 파일이 들어갔다 지워짐. 재현 중 내 셸 명령 실수로 임시 테스트 파일(tests/test_zz_review_agy.py)이 검증 트리에 몇 초간 복사됐다. 그 파일로 아무것도 실행하지 않았고 즉시 지웠다. 이후 git status --porcelain --ignored 는 비어 있고 HEAD·tree 는 그대로다. 모든 재현은 scratchpad 사본에서 했다.

[VERDICT: PASS]