release(v4.14.0): multi-agent-mux-reset skill purges and recreates resident agents; registrations refuse during a reset; lsof holders in purge hints; codex purges its capability dir (Issue #78) - archive the review reports, bump the lockstep version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
b49314c3d0
commit
d400add841
24 files changed
+491
-32
No files matched your search
@@ -0,0 +1,72 @@
|
||||
# PR #82 (Issue #78) 리뷰
|
||||
|
||||
검증 경로 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T184637Z-6f4228/3`. 테스트 뒤 HEAD `b49314c3d039027d3f9478cacecfda587fdc8abc`, tree `d49ff60bbf89437e997f7da21e2fae8df570456b`, porcelain 0. base `a3364e82e1f9e9c6ac504eef377e8dbefba02b8d` 의 조상이다. 스냅샷에 `.venv` 가 없어 제어 평면 Python 3.9.6 으로, 작업 디렉터리는 스냅샷으로 실행했다. 실제 작업 공간에서 `reset.sh`, `stop_session.sh --purge-conversation`, `--dry-run` 은 실행하지 않았다. 체크아웃 모니터(pid 3714, `reconcile.sh --subscribe --idle-timeout 0`)는 그대로 두고 신호를 보내지 않았다. 제품 파일은 수정하지 않았다.
|
||||
|
||||
이번 스냅샷의 추가분 `b49314c` 은 이전 head `568385c` 이후 6개 파일(+213/−74)이다. 두 작업 공간이 같은 잡 디렉터리를 동시에 reset 할 때 나중 fence 가 앞 마커를 지우던 겹침, 그리고 아직 없는 행 레지스트리를 fence 하지 않던 구멍을 막는지 코드를 확인했다. 브리프의 diff 는 잘려 있어 스냅샷을 직접 보았다. 브리프의 줄 수 812/286 은 현재 `lib_py/reset.py` 934줄, `reset.sh` 349줄과 다르다.
|
||||
|
||||
## 확인한 것
|
||||
|
||||
### 다른 살아있는 reset 의 펜스는 덮어쓰지 않는다
|
||||
|
||||
`fence_registries` (`reset.py:492-519`) 는 `jobs_dirs` 가 주는 디렉터리마다 `registry_lock` 안에서 `reset_marker` 를 본다. 상태가 `live` 또는 `unknown` 이고 pid 가 이 reset 이 아니면 `FenceBusy` 를 내고 그 파일은 그대로 둔다 (`reset.py:510-514`). 없거나 `stale` 이거나 pid 가 자기 것이면 자기 마커로 바꾼다 (`:515-518`).
|
||||
|
||||
`reset_marker` (`registry.py:97-116`):
|
||||
|
||||
- 파일이 없으면 `absent`. `ps` 를 부르지 않는다 (`:105-106`).
|
||||
- 읽기 실패는 `unknown` (`:107-108`).
|
||||
- pid 가 숫자가 아니면 `stale` (`:109-111`).
|
||||
- 이 프로세스의 lstart 를 얻지 못하면 `unknown` (`:112-113`).
|
||||
- 홀더가 죽었거나 lstart 가 다르면 `stale` (`:114-116`).
|
||||
- 맞으면 `live` (`:117`).
|
||||
|
||||
`reset.sh` 는 `fenced <경로>` 만 정리 목록에 넣고, `refused ` 줄은 거절 이유로 찍은 뒤 exit 1 한다 (`reset.sh:152-163`). 정리는 pid 가 이 셸일 때만 마커를 지운다 (`reset.sh:120-121`). 다른 reset 의 마커는 목록에 없고, 지워지지도 않는다. fence 는 spec 쓰기와 purge 보다 앞이다 (`reset.sh:150-168`). 거절된 reset 은 대화를 지우지 않는다.
|
||||
|
||||
### 아직 없는 행 레지스트리도 막는다
|
||||
|
||||
`jobs_dirs` (`update.py:150-163`) 는 `.mam/jobs`, `MAM_JOBS_DIR`, 행의 `control_plane.mam_jobs_dir` 를 디렉터리가 아직 없어도 물리 경로당 한 번 돌려준다. 앞의 둘은 모든 등록을 거부하고, 행이 가리키는 다른 제어 평면은 이 레지스트리의 세션만 거부한다. fence 는 그 경로를 만든 다음 마커를 쓴다 (`reset.py:507-518`). `open_jobs` 는 같은 목록을 쓰되, 디렉터리가 아니면 건너뛴다 (`update.py:174-176`). 잡 목록은 `*.json` 만 보므로 마커 파일은 잡이 아니다 (`update.py:116-120`).
|
||||
|
||||
등록 거부는 그대로다. 마커가 없고 stale 이면 통과한다. `sessions` 에 없는 세션은 통과한다. `MAM_RESET_OWNER_PID` 가 마커 pid 이면 통과한다. `unknown` 과 `live` 는 거부한다 (`registry.py:120-135`). 호출은 레지스트리 잠금 안이다 (`registry.py:219-222`). 마커가 없을 때 일반 등록의 추가 비용은 그 파일을 한 번 열다 실패하는 것이다. `ps` 는 마커가 있을 때만 돈다.
|
||||
|
||||
### R2 와 이전 안전장치는 유지된다
|
||||
|
||||
post-discovered purge 는 `target_id_owned` 가 `verify_session_uuid(..., mode="revalidate")` 를 통과할 때만 한다 (`reset.py:522-537`). codex `verify_artifact` 는 rollout cwd 가 행의 폴더와 같을 때만 참이다 (`adapters/codex.py:99-130`). create 는 codex source 를 `pending-discovery` 로 둔다 (`create_session.sh:620-625`).
|
||||
|
||||
| SKILL.md | 내용 | 코드 |
|
||||
|---|---|---|
|
||||
| :26 | 공유 레지스트리가 다른 작업 공간의 살아있는 reset 에 막혀 있으면 아무것도 바꾸지 않고 거부한다. 그 마커를 바꾸거나 지우지 않는다 | `reset.py:510-514`, `reset.sh:120-121`, `:160-163` |
|
||||
| :26 | 아직 없는 행 레지스트리도 만들어 막는다. 다른 제어 평면은 이 작업 공간의 세션만 | `update.py:150-163`, `reset.py:507-518` |
|
||||
| :26 | 어떤 경로로 열어도 거부. 예외는 reset 자신의 create 온보딩 | 마커가 디렉터리 안에 있음. create 의 `MAM_RESET_OWNER_PID=$$` |
|
||||
| :47 | 죽인 reset 의 마커는 홀더가 죽으면 무시 | `registry.py:114-116`. fence 는 그 stale 마커를 자기 것으로 바꾼다 (`reset.py:510-518`) |
|
||||
|
||||
제안 1–6, 대상 선택, spec, 잠금 순서, lsof, codex capability, 패키징은 이번 여섯 파일 diff 가 지우지 않았고, 같은 테스트 묶음이 다시 통과했다. submit 이 `.mam/loop-guard-active` 자체를 보지 않는 것은 리뷰 루프에 대한 기존 한계다. reset 은 작업 공간 마커와 레지스트리 마커로 등록을 거부한다.
|
||||
|
||||
## 실행한 테스트
|
||||
|
||||
스냅샷에 `.venv` 가 없다. cwd 는 검증 경로, 인터프리터는 `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python` (3.9.6). `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID`, `HERDR_SESSION_NAME`, `HERDR_SESSION`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `MAM_JOBS_DIR`, `DELEGATE_JOB_LOGS_DIR` 는 해제했다. `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, `--basetemp=/tmp/mam-i78-30c01a28` (`mam-review` 와 `shim` 없음), `-q --tb=line`.
|
||||
|
||||
파일: `tests/test_issue78_reset.py`, `tests/test_issue78_hints_codex.py`, `tests/test_issue42_skill_blocks.py`, `tests/test_issue10_13_purge.py`, `tests/test_issue31_purge_residual.py`, `tests/test_issue63_same_cwd_ids.py`.
|
||||
|
||||
결과: **201 passed, 1 skipped in 994.63s**. 로그의 종료 줄은 `EXIT:0`. 세션 가드 오류는 없었다. 건너뛴 항목은 `tests/test_issue42_skill_blocks.py:263-266` 이다. 스냅샷에 `.claude/skills` 가 없다.
|
||||
|
||||
이번 커밋의 테스트도 통과했다. `test_registry_cli_is_refused_after_the_target_recheck` (`:1793`, canonical/alias/custom/row-absent), `test_fence_waits_for_a_registration_and_covers_every_registry_read` (`:1920`, 없는 디렉터리와 stale 마커), `test_other_reset_cannot_take_a_live_fence` (`:2031`, 대상 있음/없음), `test_post_discovered_id_is_purged_only_when_it_verifies_for_its_folder` (`:1984`, own/foreign).
|
||||
|
||||
전체 스위트, `tests/test_issue34_job_tree.py`, `tests/test_issue50_dispatch_herdr.py` 는 돌리지 않았다. 테스트 뒤에 이 basetemp 의 pytest 프로세스는 남지 않았다. 테스트 뒤 HEAD·tree·porcelain 은 위와 같다.
|
||||
|
||||
## 발견 사항
|
||||
|
||||
차단: 없음. 한 reset 은 다른 살아있는 reset 의 마커를 바꾸거나 지우지 않고, 그 경우 purge 전에 거부한다. 아직 없는 행 레지스트리는 만들어 막는다. 마커가 없을 때 일반 등록은 `ps` 를 부르지 않는다.
|
||||
|
||||
비차단:
|
||||
|
||||
- 죽은 reset 의 stale 마커는 다음 fence 가 자기 마커로 바꾼다 (`reset.py:510-518`, 테스트 `:1936-1937`). SKILL.md:26 의 "다른 reset 의 마커를 바꾸지 않는다" 는 바로 앞의 살아있는 공유 펜스에 대한 문장이고, 죽은 홀더는 SKILL.md:47 과 같이 무시된다.
|
||||
- `ps` 가 이 프로세스의 lstart 를 주지 못하면 마커는 `unknown` 이다. 등록은 거부되고 (`registry.py:131-133`), 다른 reset 도 그 펜스를 가져가지 않는다 (`reset.py:511`).
|
||||
- fence 가 만들었어도 종료 시 지우는 것은 `.reset-in-progress` 뿐이다 (`reset.sh:120-121`). 행이 가리키던 빈 디렉터리와 `.lock` 은 남는다.
|
||||
- `MAM_RESET_OWNER_PID` 는 herdr `new-session` 의 pane env 로 넘어가지 않는다. create 가 herdr 서버를 새로 띄우면 `lib.sh:276-290` 의 deny 목록에 이 변수가 없다. 기본 상주 경로는 `new-session` 이다.
|
||||
- 원격 `handle_dispatch` 는 reset 거부로 `mirror_job` 이 `None` 이면 0 으로 돌아간다 (`remote.py:307-310`).
|
||||
- `--dry-run` reset 의 파이썬은 `_delegate_py_bin` 과 다를 수 있다 (`reset.sh:51-58`). `update.sh` 의 거절 문구는 reset 이 loop 마커를 잡고 있어도 review loop 라고 말한다. 줄 수는 934/349 이다.
|
||||
|
||||
## 사용자 확인 필요
|
||||
|
||||
create 와 resume 은 `mam_reset_admit` 을 부르지 않는다. 결정 변경은 필요하지 않다. reset 중에 새로 만든 행은 spec 에 없고, spec 에 있는 행은 purge 전에 agent·서버·열린 잡·ID 소유를 다시 본다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user