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) 리뷰 - 새 루프 3라운드 (b49314c) - planner-reviewer-claude-01
|
||||||
|
|
||||||
|
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T184637Z-6f4228/3`
|
||||||
|
- HEAD `b49314c3d039027d3f9478cacecfda587fdc8abc`, tree `d49ff60bbf89437e997f7da21e2fae8df570456b`, base `a3364e8`.
|
||||||
|
- 리뷰 시작과 끝에서 확인한 것: HEAD·tree 가 [리뷰 identity] 와 같고, `git status --porcelain --ignored` 가 비어 있고, `.mam` 이 없다.
|
||||||
|
- 브리프는 이전 라운드(4b82e70c)와 identity(head·tree·검증 경로)와 커밋 수만 다르다. diff 는 잘려 있어 스냅샷을 직접 봤다.
|
||||||
|
- 2라운드(568385c, 내가 PASS) 뒤 새 커밋은 `b49314c` 하나다. 6개 파일, +213/-74.
|
||||||
|
- `lib_py/delegation/registry.py`, `lib_py/reset.py`, `lib_py/update.py`, `SKILL.md`, `reset.sh`, `tests/test_issue78_reset.py`
|
||||||
|
- 커밋 메시지에 따르면 codex 4라운드의 R1(다른 reset 의 fence 를 덮어씀, 내 N8 과 같은 지점)과 R2(없는 행 레지스트리를 fence 하지 않음)를 고친 것이다.
|
||||||
|
- 그 밖의 파일은 바뀌지 않았다. 이전 리포트(57b76c9a, ee7ae43d, c15c85f2, 4b82e70c)의 확인이 그대로 유효하다.
|
||||||
|
|
||||||
|
## 이번 변경
|
||||||
|
|
||||||
|
| 확인 | 근거(파일:줄) | 결과 |
|
||||||
|
|---|---|---|
|
||||||
|
| R1: 다른 live reset 의 fence 를 덮어쓰지 않는가 | `reset.py:492-519` `fence_registries`: registry lock 안에서 `reset_marker(d)` 를 읽고(`registry.py:97-117`), `live`·`unknown` 이면서 pid 가 자기 것이 아니면 `FenceBusy` 를 던진다(`reset.py:510-514`). `stale` 마커만 교체한다. | 덮어쓰지 않는다. 확인과 쓰기가 같은 lock 안이라 두 reset 이 동시에 같은 레지스트리를 잡을 수 없다. 2라운드에서 내가 재현한 N8(B 가 A 의 마커를 덮어쓰고 지움)은 B 가 거부되는 것으로 막힌다. |
|
||||||
|
| 거부 시 "nothing was changed" 가 맞는가 | `reset.sh:150-164`: `fenced <path>` 줄은 `FENCED` 에 넣고, `refused <msg>` 줄은 메시지로 쓴 뒤 exit 1. EXIT trap(`reset.sh:118-125`)이 `FENCED` 중 pid 가 `$$` 인 마커만 지운다. | 거부 전에 fence 한 자기 레지스트리는 trap 이 지운다. 다른 reset 의 마커는 pid 가 달라 남는다. 새 테스트가 B 의 loop marker·reset marker 도 남지 않음을 본다. |
|
||||||
|
| 지울 때 경합 | `reset.sh:118-125` 의 확인과 `rm` 은 lock 밖이다. | 내 마커가 live 인 동안 다른 fence 는 그것을 교체하지 못하므로(R1), 확인과 rm 사이에 남의 마커로 바뀌지 않는다. 문제없다. |
|
||||||
|
| 교착·양쪽 거부 | 디렉터리마다 짧게 lock 을 잡는다. | A 가 D1→D2, B 가 D2→D1 순서로 잡으면 둘 다 FenceBusy 로 거부될 수 있다. 교착은 없고 둘 다 아무것도 바꾸지 않은 채 끝나므로 다시 실행하면 된다. 비차단. |
|
||||||
|
| `reset_refusal` 리팩터 | `registry.py:120-135` | 판정이 2라운드와 같다. absent·stale 이면 통과하고, sessions 필터, owner pid, unknown 이면 거부, live 이면 거부. 바뀐 것은 owner 확인이 ps 뒤로 간 것뿐이다. reset 자신의 create 에서만 ps 를 두 번 더 부른다. 마커가 없는 일반 등록은 여전히 ps 를 부르지 않는다. |
|
||||||
|
| R2: 없는 레지스트리도 fence 하는가 | `update.py:150-163` `jobs_dirs` 가 존재 여부와 무관하게 (dir, addressed_only) 를 realpath 로 한 번씩 낸다. `reset.py:507-508` 이 각 dir 을 `os.makedirs` 한 뒤 fence 한다. | 등록도 `registry_lock` 이 `mkdir(parents=True)` 로 디렉터리를 만든다(`mqtt_common.py:359`). 그러므로 없는 레지스트리를 만들고 fence 하는 것이 맞다. 만들 수 없으면 OSError → `refused ...` → reset 거부(fail-closed). |
|
||||||
|
| `open_jobs` 동작이 바뀌었나 | `update.py:166-185`: `jobs_dirs` 로 열거한 뒤 `_is_jobs_dir` 가 False 면 건너뛴다. | 이전과 같은 디렉터리 집합·순서·중복 제거다(전에는 `_is_jobs_dir` 를 중복 제거 전에 봤지만, 없는 경로끼리 realpath 가 같아도 결과는 같다). `update.sh` 의 precheck 도 이 함수를 쓰므로 `test_issue72_update.py` 를 함께 돌렸다(아래, 통과). |
|
||||||
|
| SKILL.md 대조 | `SKILL.md:26`의 "created when absent", "다른 작업 공간의 live reset 이 이미 fence 한 공유 레지스트리면 아무것도 바꾸지 않고 거부", "다른 reset 의 마커를 교체·삭제하지 않음" | `reset.py:507-519`, `reset.sh:118-125,150-164` 와 맞다. SKILL.md 에만 있고 코드로 강제되지 않는 규칙은 새로 생기지 않았다. |
|
||||||
|
|
||||||
|
## 실행한 테스트 (이 Mac, Python 3.9.6, /bin/bash 3.2)
|
||||||
|
명령: `env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR PYTHONDONTWRITEBYTECODE=1 <본 체크아웃 .venv/bin/python> -m pytest -p no:cacheprovider -q --basetemp=$TMPDIR/j18bt.*/…`
|
||||||
|
- 리뷰 트리에는 .venv 가 없어 본 체크아웃의 .venv 인터프리터를 썼다. `&` 없이 띄웠다(SIGINT 상속 문제 회피).
|
||||||
|
- 처음에 파일 이름을 잘못 넣어(`test_issue55_update.py`, 없는 파일) A 묶음이 0건으로 끝났다. 올바른 `test_issue72_update.py` 로 다시 돌린 결과가 아래다. 다시 돌린 A 는 B 와 일부 시간이 겹쳤지만 모두 통과했다.
|
||||||
|
|
||||||
|
| 대상 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| A: `test_issue78_reset.py` + `test_issue78_hints_codex.py` + `test_issue42_skill_blocks.py` + `test_issue72_update.py` (시그널 테스트, 새 `[row-absent]`, `test_other_reset_cannot_take_a_live_fence[False/True]` 포함) | **134 passed, 1 skipped** (819 s) |
|
||||||
|
| B: delegation 8개 파일 (`test_issue5_phase1_delegation.py` `test_issue12_registry_secrets.py` `test_issue14_delivery_ack.py` `test_issue18_liveness_contract.py` `test_issue34_job_tree.py` `test_issue50_onboard.py` `test_tier1_unit.py` `test_issue6_fail_closed.py`) | **249 passed** (635 s) |
|
||||||
|
|
||||||
|
`git archive` 사본 mutation (모두 대상 테스트가 실패한다):
|
||||||
|
|
||||||
|
| mutation | 실패한 테스트 |
|
||||||
|
|---|---|
|
||||||
|
| m1 fence 의 FenceBusy 검사 제거(남의 live 마커를 덮어씀) | `test_other_reset_cannot_take_a_live_fence[False]`, `[True]` (2 failed) |
|
||||||
|
| m3 없는 행 레지스트리를 건너뜀 | `test_registry_cli_is_refused_after_the_target_recheck[row-absent]`, `test_fence_waits_for_a_registration_and_covers_every_registry_read` (2 failed) |
|
||||||
|
| m4 stale 마커도 busy 로 봄 | `test_fence_waits_…` (1 failed, stale 교체 검증) |
|
||||||
|
| m5 reset.sh 가 `fenced ` 접두어를 벗기지 않음(종료 시 마커가 안 지워짐) | `test_registry_cli_is_refused_after_the_target_recheck[canonical]` (1 failed) |
|
||||||
|
|
||||||
|
추가 probe (임시 디렉터리, `lib_py.reset fence` 직접 실행):
|
||||||
|
- terminated 행의 `control_plane.mam_jobs_dir` 가 지워진(쓸 수 있는) 경로를 가리키면: rc 0. 그 경로(`gone-ws/.mam/jobs`, 부모 포함)가 **새로 만들어지고**, reset 이 끝난 뒤에도 빈 디렉터리로 남는다.
|
||||||
|
- 만들 수 없는 경로(`/nonexistent-root/...`)면: rc 1, `refused the job registries cannot be fenced against new registrations ([Errno 30] Read-only file system: '/nonexistent-root')`. 먼저 fence 한 자기 `.mam/jobs` 는 출력되어 trap 이 지운다.
|
||||||
|
|
||||||
|
프로세스: 테스트 앞뒤로 `reconcile.sh`(본 체크아웃 모니터 3714 하나), mock herdr, 구독자 목록이 같다. 바뀐 것은 오케스트레이터의 실제 `herdr agent wait planner-reviewer-claude-01`(70490 → 82377)뿐이며 테스트와 무관하다. 내 probe 의 holder 는 스크립트 안에서 정리했다.
|
||||||
|
|
||||||
|
## 발견 사항
|
||||||
|
|
||||||
|
### 차단
|
||||||
|
없음.
|
||||||
|
|
||||||
|
### 비차단
|
||||||
|
- **N11. fence 가 모든 행(stopped·terminated 포함)의 `control_plane.mam_jobs_dir` 를 만들고, 만들 수 없으면 reset 전체를 거부한다.**
|
||||||
|
- `jobs_dirs`(`update.py:150-163`)는 상태와 무관하게 모든 행을 본다. `fence_registries`(`reset.py:508`)는 각 경로를 `makedirs` 한다.
|
||||||
|
- 지워진 작업 공간을 가리키는 오래된 행이 있으면, 그 디렉터리 트리가 다시 생기고 남는다(probe 로 확인).
|
||||||
|
- 만들 수 없는 경로를 가리키는 행이 하나라도 있으면 reset 이 거부된다. 메시지가 어느 행 때문인지 말하지 않는다. 사용자가 그 행을 고치거나 지워야 한다.
|
||||||
|
- 안전한 쪽(fail-closed)이라 차단으로 보지 않는다. 다만 이 디렉터리에 등록할 수 없는 경우(만들 수 없음)는 등록도 생길 수 없으므로, 거부 대신 건너뛰어도 안전하다. 또 다른 control plane 레지스트리는 대상 행(running·control root)의 것만 fence 해도 대상 보호에는 충분하다.
|
||||||
|
- **N12. 두 reset 이 서로 다른 순서로 공유 레지스트리를 잡으면 둘 다 거부될 수 있다.** 교착은 없고, 둘 다 아무것도 바꾸지 않는다. 다시 실행하면 된다.
|
||||||
|
- 2라운드의 N8 은 해소됐다. N9(sessions 필터의 `herdr:` 접두어 정확 비교)는 그대로다. N10(죽은 reset 의 마커)은 이제 어느 작업 공간의 다음 fence 든 stale 마커를 교체하므로 범위가 줄었다.
|
||||||
|
- 이전 리포트의 N1, N2, N4, N5, N6 은 그대로다.
|
||||||
|
- 코드 양: reset.py 934줄, reset.sh 349줄. 이번 변경은 `jobs_dirs` 추출로 `open_jobs` 와 fence 의 열거를 하나로 합쳐, 오히려 중복을 줄였다. 불필요한 코드는 찾지 못했다.
|
||||||
|
|
||||||
|
## 사용자 확인 필요
|
||||||
|
- N11: fence 와 open-job 검사가 stopped·terminated 행의 control plane 레지스트리까지 보고 만드는 것이 의도인지. 아니면 만들 수 없는 경로는 건너뛰거나 대상 행으로 좁힐지.
|
||||||
|
- 수동 stop 이 pre-assigned 행의 id 를 폴더 전체에서 추정하는 동작(이전 리포트 3번)을 별도 이슈로 다룰지.
|
||||||
|
- N6: agy 에도 폴더 소유 확인이 필요한지.
|
||||||
|
- 병합 뒤 main 에 `.claude/skills/multi-agent-mux-reset` 링크를 손으로 만들어야 한다.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# PR #82 / Issue #78 — b49314c 리뷰: PASS
|
||||||
|
|
||||||
|
잡 `035792f2` 리뷰 결과다. 이전 리뷰의 **공유 live fence 덮어쓰기(R1)**와 **미생성 control_plane registry 누락(R2)**는 모두 해소됐다. 원래 실패를 일으켰던 공개 등록 CLI 탐침으로 확인했고, 이번 검토에서 차단 결함을 찾지 못했다.
|
||||||
|
|
||||||
|
## Identity·검토 범위
|
||||||
|
|
||||||
|
- Loop `loop-20261029T184637Z-6f4228`, round 3.
|
||||||
|
- Base `a3364e82e1f9e9c6ac504eef377e8dbefba02b8d`.
|
||||||
|
- Head `b49314c3d039027d3f9478cacecfda587fdc8abc`.
|
||||||
|
- Tree `d49ff60bbf89437e997f7da21e2fae8df570456b`.
|
||||||
|
- 검증 경로 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T184637Z-6f4228/3`.
|
||||||
|
- 기준은 읽기 전용 `tea issues 78 --comments`, `tea pulls 82`, `INSTRUCTION.md`이다. 누적 22파일과 직전 `568385c..HEAD`의 6파일 변경을 실제 snapshot에서 확인했다. 이전에 대조한 누적 reset/adapter/배포 코드와 이번 변경을 함께 검토했으며, truncated summary만으로 판단하지 않았다.
|
||||||
|
- 파일:줄은 검증 경로 기준이다. creator 작업 트리에서 실행·수정하거나 커밋/브랜치 변경을 하지 않았다.
|
||||||
|
|
||||||
|
## 이전 차단 사항의 판정
|
||||||
|
|
||||||
|
### R1 — 공유 live fence 덮어쓰기: 해소
|
||||||
|
|
||||||
|
- `.agents/skills/lib_py/delegation/registry.py:97`의 reset_marker가 absent/stale/live/unknown 판정을 공유한다.
|
||||||
|
- `.agents/skills/lib_py/reset.py:507`–`:518`은 각 registry_lock 안에서 기존 marker를 확인한다. 다른 PID의 live 또는 unknown owner이면 FenceBusy를 발생시키고 기존 marker를 교체하지 않는다.
|
||||||
|
- `reset.sh:154`–`:163`은 fence가 성공한 경로와 거부 사유를 구분한다. EXIT cleanup(:117–124)은 이미 얻은 자신의 marker만 해제한다.
|
||||||
|
- 체크인 테스트 `test_other_reset_cannot_take_a_live_fence[False/True]`는 B에 대상이 없는 경우/있는 경우를 모두 검증한다. A의 fence bytes 유지, B의 행·대화 보존, B marker cleanup, B 종료 후 외부 등록 거부 및 A 정상 완료를 확인한다.
|
||||||
|
|
||||||
|
**별도 재현:** [probe_shared_reset.py](evidence/probe_shared_reset.py), [shared.log](evidence/shared.log).
|
||||||
|
|
||||||
|
임시 A의 stop을 마지막 job 검사 뒤 gate에서 대기시킨 채, 같은 MAM_JOBS_DIR를 쓰는 독립된 임시 B에서 원본 reset.sh --yes를 실행했다. B rc=1, `fenced by a reset of A` 사유, 공유 marker bytes 동일, A는 계속 살아 있다. 이어 외부 public registry.py register도 rc=1로 거부돼 잡이 생기지 않았고 A는 rc=0으로 정상 완료했다. 이전 탐침에서 B가 rc0으로 marker를 제거하던 동작이 사라졌다.
|
||||||
|
|
||||||
|
추가로 ps 판정 불가를 주입한 탐침에서 신규 register/API와 새 fence 획득 모두 거부됐고 기존 foreign marker bytes가 보존됐다([unknown.log](evidence/unknown.log)). stale marker 교체 및 registry lock barrier는 체크인 테스트로 확인했다.
|
||||||
|
|
||||||
|
### R2 — 아직 없는 행 지정 registry가 fence에서 빠짐: 해소
|
||||||
|
|
||||||
|
- `.agents/skills/lib_py/update.py:150`–`:163`의 jobs_dirs는 존재 여부와 무관하게 canonical·MAM_JOBS_DIR·행의 control_plane.mam_jobs_dir를 realpath 기준으로 중복 제거하고 addressed_only 범위를 반환한다.
|
||||||
|
- `reset.py:507`–`:509`가 같은 jobs_dirs를 사용해 없는 디렉터리도 만들고 lock/fence를 획득한다.
|
||||||
|
- `update.py:166`–`:184`의 open_jobs도 같은 열거 함수를 사용한다. 읽기 검사는 없는 디렉터리를 계속 건너뛰고, 기존 unreadable-directory/record 거부 및 타 제어 평면의 addressed-only 의미를 보존한다.
|
||||||
|
|
||||||
|
**별도 재현:** [probe_absent_row_registry.py](evidence/probe_absent_row_registry.py), [absent.log](evidence/absent.log).
|
||||||
|
|
||||||
|
reset 시작 전에 target 행에 아직 없는 `<임시 WS>/elsewhere/jobs`를 control_plane.mam_jobs_dir로 기록하고, MAM_JOBS_DIR는 canonical 그대로 뒀다. 마지막 target 검사 뒤 같은 public CLI로 그 경로에 등록을 시도했다. 이번에는 rc=1, `a reset of ... is in progress`로 거부돼 새 잡이 없고 reset은 rc=0으로 끝났다. 중간 행 변경이나 raw 잡 JSON 쓰기를 사용하지 않았다.
|
||||||
|
|
||||||
|
체크인 `test_registry_cli_is_refused_after_the_target_recheck[row-absent]`와 fence의 absent 경로 생성·stale 교체·realpath dedupe 테스트도 통과했다. update의 open/terminal/dry-run, unreadable jobs directory, 타 control-plane 대상 필터 회귀 3건도 통과했다.
|
||||||
|
|
||||||
|
위 두 탐침의 `old_transcript_exists=false`는 외부 등록이 거부된 뒤 사용자가 승인한 정상 reset이 완료된 결과다. pending 잡을 허용한 상태로 대화를 지우던 이전 실패와 구분된다.
|
||||||
|
|
||||||
|
### 이전 라운드 보호의 유지
|
||||||
|
|
||||||
|
- canonical/alias/custom 공개 등록 거부는 체크인 매개변수 테스트로 재검증했다.
|
||||||
|
- `reset.py:522`의 target_id_owned는 post-discovered ID의 cwd 소유권을 기존 verify_session_uuid(revalidate)로 확인한다. `reset.sh:283`에서 false이면 purge 대신 skip한다.
|
||||||
|
- 이전 foreign Codex 탐침 재실행 결과는 **A 검증 false / B 검증 true / 외부 파일 보존 / reset rc4 / purge 명령 없음**이다([foreign.log](evidence/foreign.log)). own/foreign 체크인 대조도 통과했다.
|
||||||
|
- strict spec 필드·타입·agent·workspace·중복 이름 검사, row agent/server 비교, pre-assigned ID 없는 행 거부, captured old ID 저장, created/done 미재purge, 시그널·중간 실패·rc4·rc5 재실행 보호가 유지됐다.
|
||||||
|
|
||||||
|
## 제안 1–6 및 SKILL.md 주장 대조
|
||||||
|
|
||||||
|
| 기준/문서 줄 | 실행 코드·테스트 근거 | 판정 |
|
||||||
|
|---|---|---|
|
||||||
|
| 제안1, SKILL:3·23 — root running, worktree/non-running/orchestrator 제외, metadata 유지 | reset.py:105–178 select_targets; reset.sh:306–317 create 인자; 선택/realpath/제외 행/2-target 정상 E2E | 충족 |
|
||||||
|
| 제안2, SKILL:25 — 열린 잡·loop·update·자신·invalid agent/role·ID/spec 사전 거부 | reset.py:424–473; reset.sh:38–40·98–174; 각 거부·동시 admission 테스트 | 충족 |
|
||||||
|
| 제안3, SKILL:17–18·24 — parser·확인·dry-run 무변경 | reset.sh:27–79·177–203; 비TTY rc3, dry-run .mam 파일무변경 | 충족. TTY 해석은 아래 사용자 확인 사항 |
|
||||||
|
| 제안4, SKILL:27 — 첫 stop 전에 원자 spec, 재개 | reset.py:183–272 mkstemp/fsync/replace·strict schema; :275–353 record_old_id/classify_entry; reset.sh:203·228–230·337 | 충족. 5-state, 같은 ID resume, 중단 후 재실행 |
|
||||||
|
| 제안4, SKILL:28 — 순차 stop → recorded/owned ID → purge → create/onboard | reset.sh:217–323, reset.py:522–537; 기존 stop/create 재사용 | 충족, 병렬 create 없음 |
|
||||||
|
| 제안5, SKILL:29 — changed/open job/no ID/unverified/in-use skip, 외부 holder 미종료 | reset.sh:233–302, reset.py SKIP_RESULTS; skip·파일/행 보존·rerun 테스트 | 충족 |
|
||||||
|
| 제안6, SKILL:33–39 — 결과 표와 exit0/1/2/3/4/5, INT/TERM/HUP | reset.py:599–683 build_table, reset.sh trap:126–134·결과:325–349 | 충족. 원본 Mac signal 테스트 사용 |
|
||||||
|
| SKILL:26·47 — update admission → loop/reset marker → release; run_loop/update/다른 reset 거부; configured registry/alias·owner onboarding·stale 표식 | reset.sh:98–174·317; registry.py:97–135·232 이후; reset.py:492–519; canonical/alias/custom/absent·shared live fence·barrier·owner/stale | 충족. 이번 R1/R2 해소 |
|
||||||
|
| 함께 고칠 것, SKILL:44 — lsof PID/명령줄·기존 ps 안내·서비스 제외·외부 미종료 | agents/base.py:89–115·385–403, agy in_use_paths, opencode _is_shared_service/_holder_counts | 충족. lsof 부재·timeout·holder·서비스 제외, 실제 Mac lsof 탐침 |
|
||||||
|
| 함께 고칠 것 — Codex capability UUID leaf만 삭제 | adapters/codex.py:134–179, plausible ID/leaf/symlink 처리, delete 성공·실패 및 타 UUID 보존 | 충족 |
|
||||||
|
| SKILL:43 — 비가역·먼저 dry-run | 도움말/동의 및 dry-run 분리 | 운영 권고. 과거 dry-run 실행 이력을 강제하는 규칙은 아님 |
|
||||||
|
| SKILL:45 — Grok shared memory 보존 | 기존 Grok의 세션별 제거 유지, purge/adapter 회귀 | 충족. 메모리 삭제 옵션 추가 없음 |
|
||||||
|
| SKILL:46 — AGY edit hook | hooks/loop_delegation_guard.sh:51과 reset.sh:110이 같은 loop marker 사용 | 코드 일치 |
|
||||||
|
| SKILL:48 — 같은 종류 post-discovered 순차 ID 대기 | create의 기존 MAM_ID_ORDER_WAIT_S/adapter ordering, #63 회귀 | 코드 일치 |
|
||||||
|
|
||||||
|
명령 예시, 옵션/동작 표, exit 표, safety notes를 코드와 대조했다. 순서·사전 거부·원자 저장·잠금·정리는 스크립트에 있고 테스트로 고정된다. 이번 검토에서 SKILL에만 있고 실행 코드에 빠진 기계적 규칙을 찾지 못했다. 수기 잡 파일 쓰기는 등록 프로토콜 밖이므로 문서대로 target 재검사 때 이미 보이는 기록만 검출한다. 이 문구를 임의 파일 쓰기의 원자적 admission 보장으로 확대하지 않았다.
|
||||||
|
|
||||||
|
## 일반 registration·remote·update 영향과 단순화
|
||||||
|
|
||||||
|
- marker 없는 registry에서 register_job 10회 + remote mirror/replay 2회 동안 subprocess.run을 금지한 탐침이 통과했다. **일반 경로의 ps/subprocess 호출은 0회**다([fast.log](evidence/fast.log)).
|
||||||
|
- live fence에서 remote.mirror_job은 ResetInProgressError를 처리해 None을 반환하고 잡 JSON/brief 디렉터리를 만들지 않는다. 기존 registry stdout/CLI/import/환경 계약과 remote finished/newer attempt 재전송도 통과했다.
|
||||||
|
- 기존과 달리 live marker의 owner 예외·타 session 범위 판단 전에 reset_marker가 liveness를 조회한다. 따라서 marker가 존재하는 registry에서는 최대 두 ps 호출이 있을 수 있다. reset이 없는 보통 등록의 비용 증가와 구분해야 하며 차단 사유는 아니다.
|
||||||
|
- addressed-only 표식이 있는 타 control-plane registry에서 covered session은 거부하고 unrelated session은 등록되는 별도 탐침도 통과했다([unknown.log](evidence/unknown.log)).
|
||||||
|
- 이전 위험한 중복이었던 registry 경로 열거가 jobs_dirs 한 함수로 합쳐졌다. reset은 없는 경로에 보호를 설치하고 update는 읽기만 하는 차이는 각 호출부에 남아 적절하다. 순서/필터를 바꾸는 불필요한 재설계는 아니다.
|
||||||
|
- reset.py 934줄/reset.sh 349줄이라는 양 자체를 결함으로 세지 않았다. 기존 stop/create·verify_session_uuid·open_jobs 재사용이 유지된다. reset.py:54–100의 read-only state 로더는 실제 SQLite -shm까지 바꾸지 않는 dry-run 요구 때문에 복사본을 읽는다. 환경 JSON만 읽는 update._state(:375)로 단순 치환할 수 없으며 추가 추상화는 승인 조건이 아니다.
|
||||||
|
|
||||||
|
## 패키징·버전
|
||||||
|
|
||||||
|
install.sh의 일반 .agents 복사(:367)와 reset 도움말(:673), remove.sh:96 fallback, gitea-ci.yml:45 ShellCheck 대상, 두 README:60, code-block inventory 및 10-skill layout을 확인했다. detached snapshot에 실제 .claude/skills가 없어 관련 1건은 skipped이며 합성 layout 검사는 통과했다. SKILL version과 lib.sh MAM_VERSION은 4.13.0으로 일치한다. test_version_consistency 목록과 VERSIONS 매트릭스는 브리프대로 릴리스 때 오케스트레이터가 넣을 사항으로 확인했고 누락 결함으로 세지 않았다.
|
||||||
|
|
||||||
|
## Mac 실행·정적 검사
|
||||||
|
|
||||||
|
Darwin arm64, /bin/bash 3.2.57, 지정 제어 루트 venv Python 3.9.6. 검증 tree에는 venv가 없어 브리프 상단의 명시적 인터프리터를 썼다. 모든 pytest cwd는 검증 경로다. 전체 스위트는 실행하지 않았다.
|
||||||
|
|
||||||
|
공통 명령:
|
||||||
|
```bash
|
||||||
|
env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID \
|
||||||
|
-u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION \
|
||||||
|
-u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR \
|
||||||
|
-u DELEGATE_JOB_LOGS_DIR -u AGENT_SESSIONS_YAML \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python \
|
||||||
|
-m pytest -p no:cacheprovider --basetemp=<TMPDIR/아래 이름> -q <선택>
|
||||||
|
```
|
||||||
|
|
||||||
|
| basetemp 이름 (TMPDIR 아래) | 선택 파일/옵션 | 결과 |
|
||||||
|
|---|---|---|
|
||||||
|
| mam-eval-035792f2-main | tests/test_issue78_reset.py tests/test_issue78_hints_codex.py tests/test_issue42_skill_blocks.py | **84 passed, 1 skipped, 1 session-teardown error / 700.03s**. [main.log](evidence/main.log) |
|
||||||
|
| mam-eval-035792f2-purge | tests/test_issue10_13_purge.py tests/test_issue31_purge_residual.py tests/test_a4_adapter_contract.py tests/test_issue63_same_cwd_ids.py | **150 passed / 282.73s**. [purge.log](evidence/purge.log) |
|
||||||
|
| mam-eval-035792f2-contracts | 아래 9 node, 등록 node 양 mode 포함 | **10 passed / 16.38s**. [contracts.log](evidence/contracts.log) |
|
||||||
|
| mam-eval-035792f2-shared | -s -p probe_shared_reset tests/test_issue78_reset.py | **1 passed / 33.96s**. [shared.log](evidence/shared.log) |
|
||||||
|
| mam-eval-035792f2-absent | -s -p probe_absent_row_registry tests/test_issue78_reset.py | **1 passed / 32.64s**. [absent.log](evidence/absent.log) |
|
||||||
|
| mam-eval-035792f2-foreign | -s -p probe_foreign_id tests/test_issue78_reset.py | **1 passed / 20.60s**. [foreign.log](evidence/foreign.log) |
|
||||||
|
| mam-eval-035792f2-fast | -s -p probe_registry_paths tests/test_issue78_reset.py | **2 passed / 0.19s**. [fast.log](evidence/fast.log) |
|
||||||
|
| mam-eval-035792f2-native | -s -p probe_native_holders tests/test_issue78_reset.py | **2 passed / 0.28s**. [native.log](evidence/native.log) |
|
||||||
|
| mam-eval-035792f2-unknown | -s -p probe_unknown_fence tests/test_issue78_reset.py | **2 passed / 0.16s**. [unknown.log](evidence/unknown.log) |
|
||||||
|
|
||||||
|
일반 선택 회귀는 **244 passed, 1 skipped, 1 session-teardown error**, 별도 외부 탐침은 **9 passed**다. 같은 사례를 반복 실행해 합산하지 않았다.
|
||||||
|
|
||||||
|
종료 오류 분석용 재실행(중복 합산 안 함):
|
||||||
|
|
||||||
|
- `mam-eval-035792f2-layout-rerun`: `tests/test_issue42_skill_blocks.py::test_claude_skills_layouts[broken_entry-False]` — **1 passed / 0.04s**.
|
||||||
|
- `mam-eval-035792f2-agy-rerun`: `tests/test_issue78_hints_codex.py::test_in_use_hints_list_lsof_holders[agy]`, `tests/test_issue78_reset.py::test_post_discovered_without_recorded_id_8b_and_exit_5` — **2 passed / 64.39s**.
|
||||||
|
|
||||||
|
|
||||||
|
contracts의 node 목록:
|
||||||
|
```text
|
||||||
|
tests/test_issue5_phase1_delegation.py::test_c05_submit_job_stdout_is_exactly_the_job_id
|
||||||
|
tests/test_issue5_phase1_delegation.py::test_registry_cli_contract_snapshot
|
||||||
|
tests/test_issue5_phase1_delegation.py::test_delegation_package_imports_no_sibling_lib_py_modules
|
||||||
|
tests/test_issue5_phase1_delegation.py::test_importing_delegation_does_not_mutate_environment
|
||||||
|
tests/test_issue18_liveness_contract.py::test_remote_runner_ignores_a_resent_dispatch_of_a_finished_attempt
|
||||||
|
tests/test_issue18_liveness_contract.py::test_remote_runner_never_rolls_a_newer_attempt_back
|
||||||
|
tests/test_issue72_update.py::test_open_jobs_refuse_and_terminal_or_dry_run_do_not
|
||||||
|
tests/test_issue72_update.py::test_a_jobs_directory_that_cannot_be_listed_refuses
|
||||||
|
tests/test_issue72_update.py::test_other_control_planes_count_only_jobs_addressed_to_this_registry
|
||||||
|
```
|
||||||
|
|
||||||
|
외부 플러그인은 명령 앞에 `PYTHONPATH=<evidence 절대 디렉터리>`를 더하고 `-s -p <probe 모듈> tests/test_issue78_reset.py`로 선택한다. probe 파일은 collection을 해당 탐침으로 바꾸며 snapshot의 fixtures/helpers를 그대로 쓴다. shared 탐침은 이전 실패 때의 B rc0 기대만 이번 보호 정책인 rc1/동일 marker bytes 기대로 바꿨고, 외부 register·실제 reset 동작은 유지했다. 제품 코드/체크인 테스트는 수정하지 않았다.
|
||||||
|
|
||||||
|
변경 Python 11파일 ast.parse(feature_version=(3,9)), lib.sh/reset.sh/delegate wrapper 각각 /bin/bash -n, git diff --check가 통과했다([static.json](evidence/static.json)). 새 reset 경로에 mapfile/declare -A/flock(1)/setsid(1)/proc, GNU 전용 sed -i/stat -c/readlink -f 의존을 추가하지 않았다. 빈 배열·trap은 native 동작 테스트로 확인했다. 실제 Mac lsof가 자신이 만든 holder PID를 찾았고, timeout 주입 시 10초 상한과 빈 추가 힌트를 확인했다. ShellCheck는 설치되어 있지 않아 실행하지 않았다.
|
||||||
|
|
||||||
|
## 세션 종료 guard 오류와 판정 한계
|
||||||
|
|
||||||
|
main 실행은 기능 단언 **84 passed, 1 skipped**였지만 세션 종료의 기존 agent_home_leak_guard가 **1 error**를 냈다. 따라서 이 명령의 exit code는 **1**이며, 오류 없는 전체 스위트 통과라고 보고하지 않는다([main.log](evidence/main.log)).
|
||||||
|
|
||||||
|
감시기는 실제 AGY 홈에서 `637fea1e-9288-4a72-857e-7d23e04bb12d.db-wal`과 `.db-shm`의 새 디렉터리 항목을 발견했다. 기존 conftest.py:1582–1598도 병행 실제 agent가 새 파일을 만들면 false positive가 가능하다고 설명한다. 오류는 마지막 레이아웃 사례에 붙었지만 그 사례는 tmp_path의 symlink layout만 만든다.
|
||||||
|
|
||||||
|
- DB를 SQLite로 열거나 수정하지 않고 lsof의 PID와 ps의 실행 파일·경과 시간만 확인했다. 보유자는 실제 `agy` PID **66608**(약 11시간 실행), **91891**(약 34분 실행)으로, 11분 40초의 main 실행보다 먼저 시작한 외부 프로세스다([ambient-holder.json](evidence/ambient-holder.json)).
|
||||||
|
- main basetemp 안에는 해당 UUID의 artifact가 없었다([ambient-basetemp.json](evidence/ambient-basetemp.json)). AGY reset 시험은 UA 상수와 임시 HOME을 쓰며, hints 시험은 임시 home/HOME_DIR/ctx와 mock lsof를 쓴다.
|
||||||
|
- 마지막 레이아웃 node 단독 재실행은 **1 passed / 0.04s**, 종료 guard도 통과했다([layout-rerun.log](evidence/layout-rerun.log)).
|
||||||
|
- AGY hints와 post-discovered stop/purge/create/재실행 경로의 선택 재실행: **2 passed / 64.39s, 종료 guard 통과**([agy-rerun.log](evidence/agy-rerun.log)). 재실행 수는 일반 회귀 합계에 중복 합산하지 않았다.
|
||||||
|
|
||||||
|
위 프로세스·fixture·재실행 증거로 이 종료 오류는 **외부 AGY 동시 활동에 따른 환경 감시 false positive로 판단한다(추론)**. 테스트 누수라고 단정하거나 실제 홈 파일을 제거하는 방식으로 감시기를 통과시키지 않았다. 제품의 이번 변경에 의한 기능 실패는 없어 PASS지만, main 명령 자체의 종료 오류는 그대로 증거와 함께 남긴다.
|
||||||
|
|
||||||
|
## 발견 사항·사용자 확인 필요
|
||||||
|
|
||||||
|
- **차단 사항 없음.** 이전 R1/R2는 해소됐으며 재계획이 필요하지 않다.
|
||||||
|
- 비차단: marker가 남아 있는 경로의 liveness 조회 비용은 위 설명과 같다. 추가 작업을 승인 조건으로 삼지 않는다.
|
||||||
|
- 순차 create, Grok 메모리 옵션 없음, stopped 초기 대상 제외는 작성자의 선택대로 검토했다.
|
||||||
|
- 이슈의 “--yes 없이는 실행하지 않는다”와 TTY 명시적 y/yes 동의 방식의 차이는 이전 판정처럼 사용자 확인 사항이다. 동의를 받는다는 의미로 검토했으며 flag 자체 강제를 요구한 것으로 보아 차단하지 않았다.
|
||||||
|
|
||||||
|
## 격리·종료 확인
|
||||||
|
|
||||||
|
실제 작업 공간에 reset/dry-run/purge를 실행하지 않았다. 파괴적 동작은 임시 HOME·자체 .mam·mock herdr 안에서만 수행했다. 실제 .mam.env 값 출력/export, 실제 herdr 서버/설정 변경, SQLite로 실제 에이전트 홈 DB 열기, 실제 opencode 서비스 제어를 하지 않았다. guard 오류 분석의 실제 홈 확인은 위 PID/실행 파일·나이 메타데이터뿐이며 DB 내용은 읽지 않았다. pkill/killall/패턴 kill을 쓰거나 실제 모니터에 신호를 보내지 않았다. native holder 탐침은 자신이 만든 Popen만 회수했다.
|
||||||
|
|
||||||
|
이번 basetemp에 속한 테스트 잔류 프로세스는 0개, 실제 모니터는 전후 PID 3714로 같았다([process-audit.json](evidence/process-audit.json)). 시작·종료 HEAD/tree는 위 identity와 일치했고 git status는 빈 출력이다. 소스/브랜치/이슈·PR 상태 변경, push/merge는 없었다. 제어 평면 쓰기는 승인된 lifecycle 이벤트와 리포트·증거뿐이다.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20261030T002908Z-7d0414",
|
||||||
|
"base_sha": "a3364e82e1f9e9c6ac504eef377e8dbefba02b8d",
|
||||||
|
"head_sha": "b49314c3d039027d3f9478cacecfda587fdc8abc",
|
||||||
|
"tree_sha": "d49ff60bbf89437e997f7da21e2fae8df570456b",
|
||||||
|
"review_commit": "b49314c3d039027d3f9478cacecfda587fdc8abc",
|
||||||
|
"reviewer": "reviewer-agy-01",
|
||||||
|
"job_id": "8eb66b73",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-10-30T00:44:15Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# Code Review Report — PR #82 (Issue #78) — Loop 3 Iteration 1 (`@b49314c3`)
|
||||||
|
|
||||||
|
- **Job ID**: `8eb66b73`
|
||||||
|
- **Reviewer**: `agy` (`herdr:reviewer-agy-01`)
|
||||||
|
- **Loop ID**: `loop-20261030T002908Z-7d0414`
|
||||||
|
- **Review Snapshot**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261030T002908Z-7d0414/1`
|
||||||
|
- **Commit Range**: `a3364e82e1f9e9c6ac504eef377e8dbefba02b8d..b49314c3d039027d3f9478cacecfda587fdc8abc` (11 commits)
|
||||||
|
- **Tree**: `d49ff60bbf89437e997f7da21e2fae8df570456b`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 확인한 것 (파일:줄)
|
||||||
|
|
||||||
|
### 1.1 `568385c7` 이후 `b49314c3` 변경분 검증 (R1: 다른 live reset의 fence 침범 금지, R2: 아직 존재하지 않는 행 레지스트리 fence)
|
||||||
|
- **`.agents/skills/lib_py/delegation/registry.py:97-136`**:
|
||||||
|
- `reset_marker(registry_dir)`(`97-117`)가 `<registry_dir>/.reset-in-progress` 마커 상태를 `"absent"`(파일 없음), `"stale"`(`pid` 숫자 아님 / 프로세스 종료 / `lstart` 불일치), `"live"`(동일 `pid` 및 `lstart`), `"unknown"`(파일 읽기 불가 `OSError`/`ValueError` 또는 현재 프로세스에 대한 `ps` 실패)으로 판별.
|
||||||
|
- 일반 잡 등록 경로(reset 미실행 시)에서는 `open(marker)`가 즉시 `FileNotFoundError`를 일으켜 `"absent"`로 반환되므로 **`ps` 서브프로세스 호출이 전혀 발생하지 않음**(성능 영향 없음).
|
||||||
|
- `reset_refusal(registry_dir, agent_session)`(`120-136`)은 `with registry_lock(registry_dir):`(`150`) 내부에서 호출되며, `"absent"`/`"stale"`이거나 `sessions=`에 해당 세션이 없거나 `MAM_RESET_OWNER_PID == fields["pid"]`(reset 자신의 `create_session.sh` 온보딩 잡)인 경우 통과시키고, `"unknown"` 및 `"live"`는 거부 사유를 반환.
|
||||||
|
- **`.agents/skills/lib_py/update.py:188-238`**:
|
||||||
|
- `jobs_dirs(workspace, state, env)`(`188-202`)가 `<ws>/.mam/jobs`, `MAM_JOBS_DIR` (`addressed_only=False`), 그리고 각 행의 `control_plane.mam_jobs_dir` (`addressed_only=True`)를 **디렉터리 존재 여부와 무관하게** `_real(c)` 기준으로 중복 제거하여 반환.
|
||||||
|
- `open_jobs(workspace, state, env)`(`205-238`)와 `fence_registries`(`reset.py:492-521`)가 동일한 `jobs_dirs()` 열거 함수를 공유하여 검사 대상과 fence 대상이 정확히 일치.
|
||||||
|
- **`.agents/skills/lib_py/reset.py:488-521, 887-900` 및 `.agents/skills/multi-agent-mux-reset/scripts/reset.sh:117-125, 150-175`**:
|
||||||
|
- `fence_registries`가 `jobs_dirs`의 각 디렉터리에 대해 `os.makedirs(d, exist_ok=True)`를 수행한 뒤(존재하지 않던 `control_plane.mam_jobs_dir`도 생성하여 fence), `with registry_lock(d):` 아래에서 `reset_marker(d)`를 먼저 확인.
|
||||||
|
- 이미 다른 live 또는 unknown reset(`held in ("live", "unknown") and fields.get("pid") != me`)이 fence를 보유하고 있으면 마커를 덮어쓰지 않고 `FenceBusy`를 발생시켜 거부(`refused ...`).
|
||||||
|
- 자신이 새로 쓴 디렉터리만 `fenced <realpath>`로 출력하고 `reset.sh`의 `FENCED` 배열에 기록하며, `_release_reset_lock`(`reset.sh:118-125`)은 `FENCED`에 기록된 경로 중 `pid == $$`인 마커만 제거하므로 다른 워크스페이스의 live reset이 잡은 공유 레지스트리 fence를 절대 뺏거나 지우지 않음.
|
||||||
|
|
||||||
|
### 1.2 제안 1–6 및 "함께 고칠 것" 대조표
|
||||||
|
|
||||||
|
| 항목 | 명세 요구사항 (Issue #78) | 구현 위치 (`파일:줄`) | 테스트 커버리지 | 상태 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 제안 1 | `multi-agent-mux-reset` 스킬 신설 (`SKILL.md`, `scripts/reset.sh`, `lib_py/reset.py`) | `.agents/skills/multi-agent-mux-reset/SKILL.md:1-48`, `scripts/reset.sh:1-349`, `.agents/skills/lib_py/reset.py:1-934` | `tests/test_issue78_reset.py`, `tests/test_issue42_skill_blocks.py` | 충족 |
|
||||||
|
| 제안 2 | 대상 선택 (`status: running` + `realpath(pane.cwd) == realpath(WS)` 만 포함; `stopped`/`terminated`, 다른 폴더/worktree, `orchestrator_uuids` 제외) 및 필터(`--session`, `--role`, `--agent`, `--all`) | `lib_py/reset.py:157-249` (`select_targets`), `reset.sh:29-68` | `test_select_targets_and_plan_cli` | 충족 |
|
||||||
|
| 제안 3 | 사전 확인 및 잠금(update admission -> `loop-guard-active` & `reset-in-progress` -> job registry fence -> open job / 호출자 자신 / 미기록 claude·grok ID 거부, `[y/N]` 프롬프트 및 `--yes`/`--dry-run`) | `reset.sh:81-208`, `lib.sh:198-214` (`mam_reset_admit`), `lib_py/reset.py:488-589`, `lib_py/delegation/registry.py:97-160`, `lib_py/delegation/remote.py:114-116` | `test_no_yes_non_tty_exits_3_without_touching_locks`, `test_dry_run_changes_nothing_in_mam_tree`, `test_precheck_refusals_change_nothing`, `test_registry_cli_is_refused_after_the_target_recheck`, `test_other_reset_cannot_take_a_live_fence` | 충족 |
|
||||||
|
| 제안 4 | `.mam/reset-spec.json` 원자적 기록(`0600`) 및 중단/시그널/실패(`exit 4`, `exit 5`) 후 재실행 시 이미 재생성된 에이전트 재-purge 방지 | `lib_py/reset.py:252-449` (`write_spec`, `read_spec`, `entries`), `reset.sh:210-348` | `test_spec_file_write_read_and_validation`, `test_entries_5_states_and_resumed_row_9b`, `test_mid_run_failure_and_rerun_resumes_from_spec`, `test_signal_interrupts_reset_preserves_spec_and_holds_lock_until_child_exits` | 충족 |
|
||||||
|
| 제안 5 | 에이전트별 순차 실행: ⓪ 대상 재확인(`target-check`) -> ① `stop_session.sh --reason reset` (post-discovered ID 포착) -> ② ID 일치 및 폴더 소유 검증(`id-owned`) -> ③ `stop_session.sh --reason reset --purge-conversation --yes` -> ④ `create_session.sh` | `reset.sh:218-323`, `lib_py/reset.py:645-736` (`target_check`, `record_id`, `target_id_owned`) | `test_full_reset_success_spec_ordering_table_and_loop_block`, `test_post_discovered_without_recorded_id_8b_and_exit_5`, `test_post_discovered_id_is_purged_only_when_it_verifies_for_its_folder` | 충족 |
|
||||||
|
| 제안 6 | 종료 코드 표(`0`, `1`, `2`, `3`, `4`, `5`) 및 결과 요약 테이블 출력 | `SKILL.md:31-40`, `reset.sh:325-348`, `lib_py/reset.py:739-782` | `test_exit_table_agrees_with_script`, `test_purge_refused_exit_4_preserves_stopped_row_and_rerun_completes`, `test_post_discovered_uncapturable_id_8b_ii` | 충족 |
|
||||||
|
| 함께 고칠 것 1 | `in_use_hints`에서 `lsof` holder PID·명령어 표시 (`opencode` 백그라운드 서비스 제외) | `.agents/skills/lib_py/agents/base.py:268-310`, `adapters/agy.py:136-138`, `adapters/opencode.py:259-273` | `tests/test_issue78_hints_codex.py::test_in_use_hints_*`, `test_stop_purge_refusal_prints_holders` | 충족 |
|
||||||
|
| 함께 고칠 것 2 | `codex` `purge_artifacts` 및 `residual_artifacts`에 `~/.codex/tui-thread-reference-capabilities/<uuid>` 포함 | `.agents/skills/lib_py/agents/adapters/codex.py:420-467` | `tests/test_issue78_hints_codex.py::test_codex_purge_removes_tui_thread_capability`, `tests/test_a4_adapter_contract.py::test_codex_purge_artifacts` | 충족 |
|
||||||
|
| 패키징·문서 | `deploy/install.sh`, `deploy/remove.sh`, `deploy/gitea-ci.yml`, `lib_py/update.py`, `README.md`, `README.ko.md`, `docs/SKILLS_SYNC.json` 반영 | `deploy/install.sh:23,121,164`, `deploy/remove.sh:12`, `deploy/gitea-ci.yml:69`, `lib_py/update.py:38`, `README.md:107`, `README.ko.md:107` | `tests/test_issue42_skill_blocks.py`, `tests/test_deploy_layout.py` | 충족 |
|
||||||
|
|
||||||
|
### 1.3 `INSTRUCTION.md` 기준 `SKILL.md` 줄 단위 대조
|
||||||
|
- `.agents/skills/multi-agent-mux-reset/SKILL.md:1-48`의 모든 문장을 `reset.sh` 및 `lib_py/reset.py`와 줄 단위로 대조함:
|
||||||
|
- 진입점 단일 호출(`bash .agents/skills/multi-agent-mux-reset/scripts/reset.sh ...`), 옵션(`--yes`, `--dry-run`, `--session`, `--role`, `--agent`, `--all`), 대상 선택 규칙, 확인 프롬프트, 사전 확인 거부 조건, 잠금·동시성 제어(`update` admission lock, `loop-guard-active`, `reset-in-progress`, job registry `.reset-in-progress` fence, `MAM_RESET_OWNER_PID`), `.mam/reset-spec.json` 재실행 복구, 순차 실행 단계(⓪~③ 및 `create_session.sh`), 건너뜀(`exit 4`) 및 중단(`exit 5`), 종료 코드(`0`~`5`), 플레이스홀더(`0.0.0-dev`) 모두 스크립트/파이썬 코드와 1:1로 일치하며 **SKILL.md에만 있고 코드로 강제되지 않는 규칙은 없음**.
|
||||||
|
|
||||||
|
### 1.4 macOS 이식성 확인
|
||||||
|
- `/bin/bash` 3.2 호환성: `reset.sh` 및 `lib.sh`에 `mapfile`, `readarray`, `declare -A`, `sed -i`, `stat -c`, `readlink -f` 미사용. `set -uo pipefail` 아래 빈 배열 확장 시 `${FENCED[@]+"${FENCED[@]}"}` 패턴(`reset.sh:120`)을 사용하여 bash 3.2 `nounset`에서도 안전함.
|
||||||
|
- Python 3.9.6 호환성: `lib_py/reset.py`, `lib_py/delegation/registry.py`, `lib_py/update.py` 모두 `Optional[...]` / `Tuple[...]` / `List[...]`(`typing`)만 사용하며 Python 3.10+ 전용 `X | Y` 타입 유니온이나 `match` 문을 사용하지 않음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 실행한 테스트와 결과 (명령·passed/failed)
|
||||||
|
|
||||||
|
검증 경로 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261030T002908Z-7d0414/1`에서 아래 명령을 실행함:
|
||||||
|
|
||||||
|
1. **셸 문법 검사**:
|
||||||
|
```bash
|
||||||
|
cd /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261030T002908Z-7d0414/1 && \
|
||||||
|
/bin/bash -n .agents/skills/multi-agent-mux-reset/scripts/reset.sh .agents/skills/lib.sh deploy/install.sh deploy/remove.sh
|
||||||
|
```
|
||||||
|
- 결과: **exit 0**
|
||||||
|
|
||||||
|
2. **Issue #78 및 Skill Blocks 테스트 스위트**:
|
||||||
|
```bash
|
||||||
|
cd /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261030T002908Z-7d0414/1 && \
|
||||||
|
env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider \
|
||||||
|
--basetemp=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-pytest-8eb66b73 \
|
||||||
|
-v tests/test_issue78_reset.py tests/test_issue78_hints_codex.py tests/test_issue42_skill_blocks.py
|
||||||
|
```
|
||||||
|
- 결과: **84 passed, 1 skipped** in `699.23s` (exit 0)
|
||||||
|
- `b49314c3` 신규/보강 케이스 모두 통과:
|
||||||
|
- `test_registry_cli_is_refused_after_the_target_recheck[canonical/alias/custom/row-absent]` (`4 passed`)
|
||||||
|
- `test_fence_waits_for_a_registration_and_covers_every_registry_read` (`PASSED`)
|
||||||
|
- `test_other_reset_cannot_take_a_live_fence[False/True]` (`2 passed`)
|
||||||
|
|
||||||
|
3. **Deploy Layout / Adapter Contract / Purge 관련 스위트**:
|
||||||
|
```bash
|
||||||
|
cd /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261030T002908Z-7d0414/1 && \
|
||||||
|
env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider \
|
||||||
|
--basetemp=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-pytest-8eb66b73-2 \
|
||||||
|
-v tests/test_deploy_layout.py tests/test_a4_adapter_contract.py tests/test_issue10_13_purge.py tests/test_issue31_purge_residual.py
|
||||||
|
```
|
||||||
|
- 결과: **128 passed** in `71.65s` (exit 0)
|
||||||
|
|
||||||
|
4. **작업 트리 상태 및 잔여 프로세스 확인**:
|
||||||
|
- 임시 `--basetemp` 디렉터리 정리 완료.
|
||||||
|
- `git status --short`: 빈 출력(깨끗함).
|
||||||
|
- `ps -ef | grep -E 'reconcile\.sh|reset\.sh'`: 본 체크아웃의 기존 모니터 데몬(`PID 3714`) 1개만 유지되고 테스트 잔여 프로세스 없음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 발견 사항
|
||||||
|
|
||||||
|
### 차단 (Blocking)
|
||||||
|
- 없음.
|
||||||
|
|
||||||
|
### 비차단 (Non-Blocking)
|
||||||
|
- 코드 양 및 중복: `lib_py/reset.py:139-145`의 `_real()` 헬퍼와 `lib_py/update.py:140-144`의 `_real()` 헬퍼가 동일한 역할(`os.path.realpath(os.path.abspath(p))`)을 수행하므로 추후 리팩터링 시 공통 유틸로 합칠 수 있음(동작·안전성에는 영향 없음).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 사용자 확인 필요
|
||||||
|
|
||||||
|
- 이슈 #78 본문의 "정할 것" 3가지에 대해 작성자가 선택한 설계(① 모든 에이전트를 멈추기만 하고 새로 띄우지 않는 옵션 없이 순서대로 `create_session.sh`까지 수행, ② `grok` 메모리 관찰 기록 보존 옵션 없이 기본 purge 수행, ③ 이미 `stopped`인 행은 기본 대상에서 제외)가 의도와 맞는지 최종 확인 바랍니다.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -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]
|
||||||
@@ -29,7 +29,7 @@ export WORKSPACE_ROOT
|
|||||||
# Framework semantic version. Single runtime source of truth; kept in lockstep
|
# Framework semantic version. Single runtime source of truth; kept in lockstep
|
||||||
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
|
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
|
||||||
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
|
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
|
||||||
MAM_VERSION="4.13.0"
|
MAM_VERSION="4.14.0"
|
||||||
export MAM_VERSION
|
export MAM_VERSION
|
||||||
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
|
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import sys
|
|||||||
from typing import List, Sequence
|
from typing import List, Sequence
|
||||||
|
|
||||||
# Framework version locked with framework constants
|
# Framework version locked with framework constants
|
||||||
FRAMEWORK_VERSION = "4.13.0"
|
FRAMEWORK_VERSION = "4.14.0"
|
||||||
|
|
||||||
CORE_MODULES: Sequence[str] = (
|
CORE_MODULES: Sequence[str] = (
|
||||||
# Standard library modules
|
# Standard library modules
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-create
|
name: multi-agent-mux-create
|
||||||
description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace."
|
description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-delegate-job
|
name: multi-agent-mux-delegate-job
|
||||||
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
|
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos, windows]
|
platforms: [linux, macos, windows]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-loop
|
name: multi-agent-mux-loop
|
||||||
description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached."
|
description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-monitor
|
name: multi-agent-mux-monitor
|
||||||
description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted."
|
description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-orc-onboard
|
name: multi-agent-mux-orc-onboard
|
||||||
description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture."
|
description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-reset
|
name: multi-agent-mux-reset
|
||||||
description: "Purge the control root's running resident agents and recreate each with the same name, agent, role, herdr server and workspace label, one at a time, with onboarding. Persists .mam/reset-spec.json before the first purge so an interrupted or skipped run resumes where it left off."
|
description: "Purge the control root's running resident agents and recreate each with the same name, agent, role, herdr server and workspace label, one at a time, with onboarding. Persists .mam/reset-spec.json before the first purge so an interrupted or skipped run resumes where it left off."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-resume
|
name: multi-agent-mux-resume
|
||||||
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
|
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-status
|
name: multi-agent-mux-status
|
||||||
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
|
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-stop
|
name: multi-agent-mux-stop
|
||||||
description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start."
|
description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-update
|
name: multi-agent-mux-update
|
||||||
description: "Update an installed MAM workspace in place to the latest version. Refuses while a session, an open job, a review loop or an unknown monitor is live; stops the monitor daemon and starts it again afterwards; rolls everything back if the install fails; converts the registry with the newly installed code. Use instead of the removed .mam_deploy/update.sh."
|
description: "Update an installed MAM workspace in place to the latest version. Refuses while a session, an open job, a review loop or an unknown monitor is live; stops the monitor daemon and starts it again afterwards; rolls everything back if the install fails; converts the registry with the newly installed code. Use instead of the removed .mam_deploy/update.sh."
|
||||||
version: 4.13.0
|
version: 4.14.0
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다.
|
본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다.
|
새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly.
|
This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment.
|
A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Messaging System Technical Analysis & Architecture Report
|
# Messaging System Technical Analysis & Architecture Report
|
||||||
|
|
||||||
Applies to: MAM v4.13.0
|
Applies to: MAM v4.14.0
|
||||||
|
|
||||||
This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations.
|
This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다.
|
[herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A **Multi-Agent Orchestration & Messaging Backplane** built on [herdr](https://herdr.dev) and MQTT. It runs long-lived coding agents (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`) in isolated herdr panes, delegates work to them as tracked jobs, and drives planner / creator / reviewer loops until every reviewer passes the change.
|
A **Multi-Agent Orchestration & Messaging Backplane** built on [herdr](https://herdr.dev) and MQTT. It runs long-lived coding agents (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`) in isolated herdr panes, delegates work to them as tracked jobs, and drives planner / creator / reviewer loops until every reviewer passes the change.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+43
-13
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
## 📌 현재 버전 개요 (Current Release)
|
## 📌 현재 버전 개요 (Current Release)
|
||||||
|
|
||||||
- **프레임워크 버전**: `v4.13.0`
|
- **프레임워크 버전**: `v4.14.0`
|
||||||
- **최신 릴리스 일시**: 2026-10-29 (KST)
|
- **최신 릴리스 일시**: 2026-10-30 (KST)
|
||||||
- **기준 브랜치**: `main`
|
- **기준 브랜치**: `main`
|
||||||
- **핵심 아키텍처**:
|
- **핵심 아키텍처**:
|
||||||
- **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입.
|
- **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입.
|
||||||
@@ -18,30 +18,60 @@
|
|||||||
- **워크트리 운영 모델 2단계 (Issue #21 항목 4·5·7)**: 워크트리 준비·해체 헬퍼(변경 전 판정, untracked 포함, 검증된 단일 `--force`), 리뷰 identity(라운드별 스냅샷·stale 재리뷰·승격 직전 재확인·`identity.json`), 수용 사례 A1–A8.
|
- **워크트리 운영 모델 2단계 (Issue #21 항목 4·5·7)**: 워크트리 준비·해체 헬퍼(변경 전 판정, untracked 포함, 검증된 단일 `--force`), 리뷰 identity(라운드별 스냅샷·stale 재리뷰·승격 직전 재확인·`identity.json`), 수용 사례 A1–A8.
|
||||||
- **잡 예산·라벨·멈춤 알림 (Issue #33·#30)**: 위임 잡 예산은 `--job-timeout`/`MAM_JOB_TIMEOUT_S`, 벽시계 초과는 `wall_clock_timeout`(무응답 `deadline_timeout` 과 구분), `lib_py/stall_watch.py` 가 `blocked`/`idle` 을 `progress stalled:*` 로 알리기만 한다(종결 근거는 #18 의 프로토콜 이벤트뿐).
|
- **잡 예산·라벨·멈춤 알림 (Issue #33·#30)**: 위임 잡 예산은 `--job-timeout`/`MAM_JOB_TIMEOUT_S`, 벽시계 초과는 `wall_clock_timeout`(무응답 `deadline_timeout` 과 구분), `lib_py/stall_watch.py` 가 `blocked`/`idle` 을 `progress stalled:*` 로 알리기만 한다(종결 근거는 #18 의 프로토콜 이벤트뿐).
|
||||||
- **설정 파일 로더 하나 (Issue #26)**: `.mam.env` 는 `lib_py/delegation/envfile.py` 한 곳이 읽는다(셸 해석 없음, 환경변수 우선, 읽을 수 없는 파일은 오류). bash 산술에 들어가는 정수 설정값은 `mam_int` 로 10진수.
|
- **설정 파일 로더 하나 (Issue #26)**: `.mam.env` 는 `lib_py/delegation/envfile.py` 한 곳이 읽는다(셸 해석 없음, 환경변수 우선, 읽을 수 없는 파일은 오류). bash 산술에 들어가는 정수 설정값은 `mam_int` 로 10진수.
|
||||||
- **4-Way 버전 락스텝**: `lib.sh`(`MAM_VERSION`) ↔ `lib_py/loop/eager.py`(`FRAMEWORK_VERSION`) ↔ `VERSIONS.md` ↔ 9개 `SKILL.md`.
|
- **4-Way 버전 락스텝**: `lib.sh`(`MAM_VERSION`) ↔ `lib_py/loop/eager.py`(`FRAMEWORK_VERSION`) ↔ `VERSIONS.md` ↔ 10개 `SKILL.md`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
|
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
|
||||||
|
|
||||||
모든 9개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.13.0`로 동기화되어 배포됩니다.
|
모든 10개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.14.0`로 동기화되어 배포됩니다.
|
||||||
|
|
||||||
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
|
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
|
||||||
| :--- | :---: | :--- | :---: |
|
| :--- | :---: | :--- | :---: |
|
||||||
| **`multi-agent-mux-create`** | `4.13.0` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
|
| **`multi-agent-mux-create`** | `4.14.0` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-stop`** | `4.13.0` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
|
| **`multi-agent-mux-stop`** | `4.14.0` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
|
||||||
| **`multi-agent-mux-resume`** | `4.13.0` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
|
| **`multi-agent-mux-resume`** | `4.14.0` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-status`** | `4.13.0` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
|
| **`multi-agent-mux-status`** | `4.14.0` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-monitor`** | `4.13.0` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
|
| **`multi-agent-mux-monitor`** | `4.14.0` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
|
||||||
| **`multi-agent-mux-delegate-job`** | `4.13.0` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
|
| **`multi-agent-mux-delegate-job`** | `4.14.0` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-loop`** | `4.13.0` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
|
| **`multi-agent-mux-loop`** | `4.14.0` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-orc-onboard`** | `4.13.0` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
|
| **`multi-agent-mux-orc-onboard`** | `4.14.0` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-update`** | `4.13.0` | 설치된 작업 공간의 제자리 업데이트·되돌리기와 업데이트 중 입장 차단 | ✅ 배포 |
|
| **`multi-agent-mux-update`** | `4.14.0` | 설치된 작업 공간의 제자리 업데이트·되돌리기와 업데이트 중 입장 차단 | ✅ 배포 |
|
||||||
|
| **`multi-agent-mux-reset`** | `4.14.0` | 제어 루트의 상주 에이전트를 하나씩 purge 하고 같은 사양으로 다시 만들기 | ✅ 배포 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 버전별 상세 변경 내역 (Changelog)
|
## 📋 버전별 상세 변경 내역 (Changelog)
|
||||||
|
|
||||||
|
### 🚀 `v4.14.0` — reset 스킬로 상주 에이전트의 대화를 지우고 같은 사양으로 다시 만든다 (Issue #78) (2026-10-30)
|
||||||
|
|
||||||
|
> **마이너 (MINOR Release)**: 제어 루트의 running 에이전트를 하나씩 stop·purge 하고 같은 이름·에이전트·역할·herdr 서버·작업 공간 라벨로 다시 만드는 새 `multi-agent-mux-reset` 스킬을 더했다. reset 중에는 잡 등록이 거부된다. purge 거부 안내가 대화 파일을 쥔 프로세스를 보여 주고, codex purge 가 `tui-thread-reference-capabilities/<uuid>` 도 지운다. 새 스킬과 기존 명령의 동작(reset 중 등록 거부)이 늘어 MINOR 로 올린다. PR #82.
|
||||||
|
|
||||||
|
#### ⚠️ 호환성
|
||||||
|
- 새 스킬 `multi-agent-mux-reset`(`scripts/reset.sh --yes [--dry-run]`). 터미널이 아니면 `--yes` 가 필요하다(없으면 exit 3). 종료 코드는 0·1·2·3·4(건너뛴 에이전트 있음)·5(새 대화 ID 미기록), 신호로 끊기면 130·143·129.
|
||||||
|
- reset 이 도는 동안 그 작업 공간이 읽는 잡 레지스트리(`.mam/jobs`, `MAM_JOBS_DIR`, 행의 `control_plane.mam_jobs_dir`)에 `.reset-in-progress` 를 두고, 그 레지스트리로의 등록(`delegate-job submit`, `registry.py register`, 등록 API)은 거부된다. reset 자신의 create 온보딩만 통과한다. 표식이 없으면 등록 경로는 전과 같다.
|
||||||
|
- purge 가 사용 중이라 거부될 때(exit 4) `in_use_hints` 가 `lsof` 로 찾은 프로세스(pid, 명령줄)를 함께 출력한다. 그 프로세스는 종료하지 않는다.
|
||||||
|
- codex purge 가 `tui-thread-reference-capabilities/<uuid>` 도 지운다.
|
||||||
|
- `.claude/skills/multi-agent-mux-reset` 링크는 추적되지 않으므로 설치본이 아닌 체크아웃에서는 손으로 만든다.
|
||||||
|
|
||||||
|
#### 🌟 주요 개선 사항
|
||||||
|
1. **reset 스킬 (`multi-agent-mux-reset/scripts/reset.sh`, `lib_py/reset.py`)**:
|
||||||
|
- 대상: `status: running` 이고 `pane.cwd` 가 제어 루트인 행. 다른 상태·워크트리 행·오케스트레이터는 이유와 함께 건너뛴다.
|
||||||
|
- 사전 확인(무엇도 바꾸기 전): 열린 잡, live 루프·업데이트 표식, 행의 agent·role 누락, 대화 ID 가 없는 claude·grok, 깨진 사양 파일, 호출자 자신이 대상.
|
||||||
|
- 잠금: 업데이트 입장 잠금을 배타로 잡고 `.mam/loop-guard-active` 와 `.mam/reset-in-progress` 를 건 뒤 놓는다. 이어 읽는 레지스트리마다 그 잠금 아래에서 표식을 둔다. 다른 작업 공간의 live reset 이 같은 레지스트리를 쥐고 있으면 아무것도 바꾸지 않고 거부한다.
|
||||||
|
- 순서: 사양 파일(`.mam/reset-spec.json`)을 원자적으로 쓴 뒤 에이전트마다 ⓪ 행·열린 잡 재확인 ① stop ② 기록된 ID 와 같은지, ID 가 나중에 생기는 에이전트는 그 폴더의 대화로 검증되는지 ③ purge ④ create(온보딩). 중간에 멈추면 다시 실행해 사양 파일에서 이어 가고, 이미 다시 만든 에이전트는 다시 purge 하지 않는다.
|
||||||
|
- 결과 표: 에이전트마다 새 대화 ID 가 기록됐는지, 이전 ID 와 다른지.
|
||||||
|
2. **등록 경계 (`lib_py/delegation/registry.py`, `remote.py`, `lib.sh` `mam_reset_admit`)**: `register_job` 이 레지스트리 잠금 아래에서 reset 표식을 읽고 거부한다. 원격 runner 는 거부를 dispatch 거부로 처리한다.
|
||||||
|
3. **purge 안내와 codex (`lib_py/agents/base.py`, `adapters/{agy,codex,opencode}.py`)**: `in_use_hints` 의 `lsof` holder, codex 의 capability 디렉터리 삭제.
|
||||||
|
4. **패키징·문서**: `deploy/install.sh`·`remove.sh`·`gitea-ci.yml`, README 두 개, 스킬 코드 블록 인벤토리, 버전 매트릭스와 `test_version_consistency.py` 의 스킬 목록(10개).
|
||||||
|
|
||||||
|
#### ✅ 품질 검증
|
||||||
|
- **회귀**: `tests/test_issue78_reset.py`, `tests/test_issue78_hints_codex.py`. 신호(SIGINT·SIGTERM·SIGHUP) 중단, 재실행, 공개 등록 CLI 경합, 다른 폴더의 codex 대화 보호, 레지스트리 표식을 포함한다.
|
||||||
|
- **리뷰 (`--all-reviewer`, 리뷰 전용 루프)**: 첫 루프(@f90a705) 1회차 4명 모두 NOT PASS, 2회차 3/4 PASS. codex 가 `registry.py register` 로 reset 입장 경계를 우회하는 경합과, ID 문자열 일치만으로 다른 폴더의 codex 대화를 지우는 경우를 재현했다. creator 의 수정 잡이 예산을 넘겨 루프가 멈췄고, 수정은 548c685 로 커밋됐다. 두 번째 루프(@548c685) 1·2회차 3/4 PASS, codex 의 지적으로 레지스트리 표식을 물리 경로 기준으로 모든 레지스트리에 두고(568385c), 다른 live reset 의 표식을 가져가지 않게 했다(b49314c). 3회차(@b49314c) claude·grok·codex PASS, agy 는 프롬프트 전달 실패로 따로 리뷰해 PASS — **4/4 PASS @b49314c**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||||||
|
- **전체 스위트 (macOS, Python 3.9.6, @b49314c)**: 2336 passed, 56 skipped, 2 failed (115분). 실패 둘은 SIGINT·SIGHUP 중단 테스트로, 스위트를 `nohup … &` 로 띄워 두 신호가 무시된 채 상속된 탓이다. 그렇게 띄우지 않고 다시 돌린 신호 테스트 3개는 모두 통과했다. 실제 `~/.claude`·`/private/tmp/claude-<uid>`·herdr 서버 pane 목록·opencode 의 data·state·config·본 체크아웃의 `.mam` 에 이 실행이 남긴 것은 없었고, 본 체크아웃의 모니터는 그대로 떠 있었다.
|
||||||
|
- **작성자 확인**: Linux(@f90a705 이전) 2330 passed, 2 failed(#80 의 flake). creator 의 macOS 실행(@548c685) 2333 passed, 56 skipped, 실패 0.
|
||||||
|
- **알려진 비차단**: 레지스트리 표식은 stopped·terminated 행의 `mam_jobs_dir` 까지 만들고, 만들 수 없는 경로가 있으면 reset 이 거부된다(어느 행인지 말하지 않는다). 표식을 위해 만든 빈 디렉터리와 `.lock` 은 남는다. 두 reset 이 공유 레지스트리를 다른 순서로 잡으면 둘 다 거부될 수 있다(아무것도 바뀌지 않음). 터미널에서는 `--yes` 없이 `[y/N]` 에 y 로 답해도 진행한다.
|
||||||
|
|
||||||
### 🚀 `v4.13.0` — worktree creator 도 기본으로 온보딩하고, create 의 --no-onboard 는 ID 가 나중에 생기는 에이전트에 경고한다 (Issue #68) (2026-10-29)
|
### 🚀 `v4.13.0` — worktree creator 도 기본으로 온보딩하고, create 의 --no-onboard 는 ID 가 나중에 생기는 에이전트에 경고한다 (Issue #68) (2026-10-29)
|
||||||
|
|
||||||
> **마이너 (MINOR Release)**: `worktree_prepare.sh --creator-agent` 가 온보딩 없이 creator 를 만들어, agy·codex·hermes·opencode 는 첫 대화 전까지 레지스트리에 대화 ID 가 없었다. 이제 worktree creator 도 기본으로 온보딩한다. create 의 `--no-onboard` 는 남기고, ID 가 첫 턴 뒤에야 생기는 에이전트에 쓰면 경고한다. 공개 스크립트의 기본 동작이 바뀌어 MINOR 로 올린다. PR #79.
|
> **마이너 (MINOR Release)**: `worktree_prepare.sh --creator-agent` 가 온보딩 없이 creator 를 만들어, agy·codex·hermes·opencode 는 첫 대화 전까지 레지스트리에 대화 ID 가 없었다. 이제 worktree creator 도 기본으로 온보딩한다. create 의 `--no-onboard` 는 남기고, ID 가 첫 턴 뒤에야 생기는 에이전트에 쓰면 경고한다. 공개 스크립트의 기본 동작이 바뀌어 MINOR 로 올린다. PR #79.
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ SKILL_NAMES = [
|
|||||||
"multi-agent-mux-loop",
|
"multi-agent-mux-loop",
|
||||||
"multi-agent-mux-orc-onboard",
|
"multi-agent-mux-orc-onboard",
|
||||||
"multi-agent-mux-update",
|
"multi-agent-mux-update",
|
||||||
|
"multi-agent-mux-reset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ def _get_skill_md_frontmatter_version(skill_name: str):
|
|||||||
|
|
||||||
|
|
||||||
def test_three_way_version_lockstep():
|
def test_three_way_version_lockstep():
|
||||||
"""T-4: lib.sh(MAM_VERSION) == VERSIONS.md(Current + Matrix 9 items) == 9x SKILL.md frontmatters."""
|
"""T-4: lib.sh(MAM_VERSION) == VERSIONS.md(Current + Matrix 10 items) == 10x SKILL.md frontmatters."""
|
||||||
# 1. lib.sh runtime constant
|
# 1. lib.sh runtime constant
|
||||||
lib_ver = _get_lib_sh_mam_version()
|
lib_ver = _get_lib_sh_mam_version()
|
||||||
assert lib_ver != "", "MAM_VERSION in lib.sh is empty"
|
assert lib_ver != "", "MAM_VERSION in lib.sh is empty"
|
||||||
@@ -89,7 +90,7 @@ def test_three_way_version_lockstep():
|
|||||||
assert skill in vmd_matrix, f"Skill {skill} missing from VERSIONS.md matrix"
|
assert skill in vmd_matrix, f"Skill {skill} missing from VERSIONS.md matrix"
|
||||||
assert vmd_matrix[skill] == lib_ver, f"VERSIONS.md matrix for {skill} ({vmd_matrix[skill]}) != lib.sh ({lib_ver})"
|
assert vmd_matrix[skill] == lib_ver, f"VERSIONS.md matrix for {skill} ({vmd_matrix[skill]}) != lib.sh ({lib_ver})"
|
||||||
|
|
||||||
# 3. 9x SKILL.md files
|
# 3. 10x SKILL.md files
|
||||||
for skill in SKILL_NAMES:
|
for skill in SKILL_NAMES:
|
||||||
fm_ver = _get_skill_md_frontmatter_version(skill)
|
fm_ver = _get_skill_md_frontmatter_version(skill)
|
||||||
assert fm_ver == lib_ver, f"{skill}/SKILL.md version ({fm_ver}) != lib.sh ({lib_ver})"
|
assert fm_ver == lib_ver, f"{skill}/SKILL.md version ({fm_ver}) != lib.sh ({lib_ver})"
|
||||||
|
|||||||
Reference in new issue
Block a user