feat: multi-agent-mux-reset skill; purge refusals list lsof holders; codex purge removes its tui-thread capability (Issue #78) #82

Open
Godopu wants to merge 6 commits from fix/issue-78 into main
pull from: fix/issue-78
Owner

Refs #78

변경

  • 새 스킬 multi-agent-mux-reset: reset.sh [--yes] [--dry-run].
    • 제어 루트에서 실행 중인 상주 에이전트를 purge 한 뒤, 같은 이름·에이전트·역할·herdr 서버·워크스페이스 라벨로 다시 만든다. 그래서 컨텍스트가 빈 상태로 돌아간다.
    • 로직은 lib_py/reset.py 에 있고, reset.sh 는 진입점·잠금·확인만 맡는다.
  • 대상
    • status: running 이고 pane.cwd 가 제어 루트인 행만 대상이다.
    • worktree creator, stopped·terminated 행, orchestrator_uuids 는 대상이 아니다. --dry-run 이 이 행들을 사유와 함께 보여 준다.
  • 순서
    • 한 번에 한 에이전트씩 처리한다.
    • 먼저 일반 stop 을 한다. 이때 확인된 대화 ID 가 행에 기록되고, 행은 stopped 가 된다.
    • 그다음 행에 기록된 ID 로 --purge-conversation --yes 를 한다. 명령줄에 uuid 를 넣지 않는다(#63).
    • 마지막으로 create 를 한다. onboarding 은 create 의 기본대로 켜진다.
    • create 를 병렬로 돌리지 않는다.
  • 사전 검사
    • 다음 중 하나라도 해당하면 아무것도 바꾸지 않고 거부하며, 각각 어떻게 풀면 되는지 출력한다.
      • 열린 잡이 있다.
      • loop marker 가 살아 있다.
      • update 가 진행 중이다.
      • 대상 행에 에이전트나 역할이 없다.
      • 호출자 자신이 대상이다.
    • update 의 "실행 중인 행이 있으면 거부" 검사는 쓰지 않는다. 실행 중인 행이 reset 의 대상이기 때문이다.
  • 잠금
    • update admission(mam_update_admit)을 잡은 채로 loop lock(.mam/loop-guard-active)을 얻는다.
    • 그다음 admission 을 놓는다.
    • reset 이 도는 동안 run_loop.sh, update, 다른 reset 은 모두 거부된다.
  • 확인
    • --yes 가 없으면 터미널에서는 [y/N] 으로 묻고, 터미널이 아니면 exit 3 으로 거부한다.
    • --dry-run 은 아무것도 바꾸지 않는다.
    • force 옵션은 없다.
  • spec 파일과 재실행
    • 첫 stop 전에 .mam/reset-spec.json 을 원자적으로 쓴다. 항목마다 이름·에이전트·역할·서버·라벨·이전 ID 가 들어간다.
    • 이전 ID 가 비어 있던 에이전트는, stop 이 확인한 ID 를 purge 전에 spec 에 기록한다.
    • 재실행하면 spec 을 읽고 끝나지 않은 에이전트부터 이어 간다. 끝났다는 판정은 다음 두 가지가 모두 맞을 때다.
      • 같은 생성 인스턴스의 행에 기록된 ID 가 있다.
      • 그 ID 가 이전 ID 와 다르다.
    • 전부 끝나면 spec 을 지운다.
    • spec 이 손상됐거나, 알 수 없는 에이전트가 들어 있으면 거부한다.
    • SIGINT, SIGTERM, SIGHUP 을 받으면 spec 을 남기고 끝난다. 이때 loop lock 은 자식 프로세스가 끝날 때까지 잡고 있는다.
  • purge 가 거부될 때 (stop exit 4)
    • 그 에이전트를 건너뛰고 다음 에이전트로 넘어간다. 끝나면 exit 4 로 끝난다.
    • 건너뛴 에이전트는 이미 일반 stop 을 거쳤으므로 stopped(resumable) 로 남는다. 대화 파일은 그대로다.
    • 파일을 잡고 있는 프로세스를 출력하되, 외부 프로세스는 종료하지 않는다.
    • 재실행하면 그 에이전트를 다시 시도한다.
  • exit 5
    • 아무것도 건너뛰거나 실패하지 않았지만, 새로 만든 post-discovered 에이전트(agy, codex, hermes, opencode)의 ID 가 아직 기록되지 않았다는 뜻이다.
    • spec 은 남는다. 재실행하면 purge 없이 확인만 다시 한다.
  • 결과 표: 에이전트마다 이름·에이전트·이전 ID·새 ID·기록 여부·이전 ID 와 다른지를 보여 준다.
  • in_use_hints 가 lsof 로 찾은 holder 를 보여 준다.
    • 대화 파일을 연 프로세스의 pid 와 명령줄을 보여 준다.
    • conversation_in_use 와 같은 경로 목록(in_use_paths)을 쓴다.
    • 기존 ps | grep 힌트는 그대로 둔다.
    • opencode 는 사용자별 백그라운드 서비스(serve --service)를 holder 에서 뺀다.
  • codex purge_artifacts
    • codex delete 가 남기던 ~/.codex/tui-thread-reference-capabilities/<uuid> 도 지운다.
    • 대체 경로에서 rollout 파일을 지우다 실패해도 purge 전체가 멈추지 않는다.
  • 패키징
    • multi-agent-mux-update 를 추가할 때와 같은 자리에 새 스킬을 넣었다: deploy/install.sh, deploy/remove.sh, deploy/gitea-ci.yml(shellcheck), README 두 개.
    • tests/data/skill_code_blocks.json 에 새 스킬 핀을 넣었고, test_issue42_skill_blocks.py 의 목록에 새 스킬을 추가했다.
    • main(v4.13.0)으로 rebase 한 뒤, 새 SKILL.md 의 version 을 main 과 같은 4.13.0 으로 맞췄다.

테스트

  • 새 테스트 파일은 두 개다.
    • tests/test_issue78_reset.py: 대상 선택, 사전 검사별 거부, --yes 없음, --dry-run, spec 을 쓰는 순서, 중간 실패 뒤 재실행, purge 거부(exit 4)와 재시도, exit 5, 결과 표, reset 중 루프 시작 거부, 시그널을 다룬다.
    • tests/test_issue78_hints_codex.py: 에이전트별 lsof holder 힌트, opencode 서비스 제외, stop 의 거부 출력, codex 잔여 파일 삭제를 다룬다.
  • base b9c828f 에서는 새 테스트 25건 중 24건이 실패한다. 나머지 1건은 lsof 결과가 없으면 힌트가 예전과 같은지 확인하는 테스트라 base 에서도 통과하는 게 맞다.
  • Linux, rebase 전 0a902b9: 전체 스위트 2330 passed, 20 skipped, 2 failed (73분).
    • 실패 2건은 test_issue34_job_tree.py::test_t5b_…[True], [False] 이고 #80 의 경합이다. 하위 잡 사유가 deadline_timeout 이 아니라 - 로 나온다.
    • 이 브랜치는 lib_py/delegation, lib_py/loop 를 건드리지 않는다.
    • base 에서도 같은 증상으로 실패한다.
  • Linux, rebase 후 f90a705: 대상 테스트 (새 테스트 두 파일과 위 변경이 닿는 12개 파일, test_issue68_onboard_default.py 포함) 414 passed, 1 skipped.
  • 테스트는 mock herdr, 임시 HOME, 자체 .mam 을 가진 임시 작업 공간을 쓴다. 실제 레지스트리·모니터·herdr 서버·에이전트 홈은 건드리지 않는다.
  • macOS(bash 3.2, Python 3.9)에서는 실행하지 않았다.

리뷰

  • 리뷰어 4명(claude, agy, grok, codex)이 2회차에서 모두 PASS 를 냈다.
  • 1회차에서 고친 것:
    • 이전 ID 가 비어 있을 때 stop 이 확인한 ID 를 spec 에 남기지 않았다. 그래서 건너뛴 에이전트를 resume 한 뒤 재실행하면, 그 에이전트가 끝난 것으로 잘못 판정됐다.
    • INT trap 이 종료하지 않았다.
    • spec 에 알 수 없는 에이전트가 들어 있어도 받아들였다.
  • grok 의 지적 하나는 creator 의 반론을 grok 이 받아들여 철회됐다.
  • f90a705(version 한 줄)는 리뷰 이후 커밋이다.

알려진 한계

  • multi-agent-mux-delegate-job submit 은 loop lock 을 보지 않는다. reset 중에 잡을 제출하지 말라는 주의만 SKILL.md 에 있다.
  • 같은 종류의 post-discovered 에이전트를 여럿 reset 하면, 두 번째 create 가 첫 번째 ID 가 기록될 때까지 기다린다(MAM_ID_ORDER_WAIT_S). 시간이 넘으면 재실행해야 한다.

범위 밖

  • grok 메모리(~/.grok/memory-v2/) 삭제.
  • 병렬 create.
  • worktree creator reset.
  • 버전 번호와 VERSIONS.md.

릴리스 노트에 적을 것

  • 새 스킬 multi-agent-mux-reset: 제어 루트의 상주 에이전트를 purge 하고 같은 설정으로 다시 만든다.
  • purge 가 거부되면 대화 파일을 잡고 있는 프로세스(pid, 명령줄)를 보여 준다.
  • codex purge 가 tui-thread-reference-capabilities/<uuid> 도 지운다.

🤖 Generated with Claude Code

Refs #78 ## 변경 - **새 스킬 `multi-agent-mux-reset`**: `reset.sh [--yes] [--dry-run]`. - 제어 루트에서 실행 중인 상주 에이전트를 purge 한 뒤, 같은 이름·에이전트·역할·herdr 서버·워크스페이스 라벨로 다시 만든다. 그래서 컨텍스트가 빈 상태로 돌아간다. - 로직은 `lib_py/reset.py` 에 있고, `reset.sh` 는 진입점·잠금·확인만 맡는다. - **대상** - `status: running` 이고 `pane.cwd` 가 제어 루트인 행만 대상이다. - worktree creator, `stopped`·`terminated` 행, `orchestrator_uuids` 는 대상이 아니다. `--dry-run` 이 이 행들을 사유와 함께 보여 준다. - **순서** - 한 번에 한 에이전트씩 처리한다. - 먼저 일반 stop 을 한다. 이때 확인된 대화 ID 가 행에 기록되고, 행은 `stopped` 가 된다. - 그다음 행에 기록된 ID 로 `--purge-conversation --yes` 를 한다. 명령줄에 uuid 를 넣지 않는다(#63). - 마지막으로 create 를 한다. onboarding 은 create 의 기본대로 켜진다. - create 를 병렬로 돌리지 않는다. - **사전 검사** - 다음 중 하나라도 해당하면 아무것도 바꾸지 않고 거부하며, 각각 어떻게 풀면 되는지 출력한다. - 열린 잡이 있다. - loop marker 가 살아 있다. - update 가 진행 중이다. - 대상 행에 에이전트나 역할이 없다. - 호출자 자신이 대상이다. - update 의 "실행 중인 행이 있으면 거부" 검사는 쓰지 않는다. 실행 중인 행이 reset 의 대상이기 때문이다. - **잠금** - update admission(`mam_update_admit`)을 잡은 채로 loop lock(`.mam/loop-guard-active`)을 얻는다. - 그다음 admission 을 놓는다. - reset 이 도는 동안 `run_loop.sh`, update, 다른 reset 은 모두 거부된다. - **확인** - `--yes` 가 없으면 터미널에서는 `[y/N]` 으로 묻고, 터미널이 아니면 exit 3 으로 거부한다. - `--dry-run` 은 아무것도 바꾸지 않는다. - force 옵션은 없다. - **spec 파일과 재실행** - 첫 stop 전에 `.mam/reset-spec.json` 을 원자적으로 쓴다. 항목마다 이름·에이전트·역할·서버·라벨·이전 ID 가 들어간다. - 이전 ID 가 비어 있던 에이전트는, stop 이 확인한 ID 를 purge 전에 spec 에 기록한다. - 재실행하면 spec 을 읽고 끝나지 않은 에이전트부터 이어 간다. 끝났다는 판정은 다음 두 가지가 모두 맞을 때다. - 같은 생성 인스턴스의 행에 기록된 ID 가 있다. - 그 ID 가 이전 ID 와 다르다. - 전부 끝나면 spec 을 지운다. - spec 이 손상됐거나, 알 수 없는 에이전트가 들어 있으면 거부한다. - SIGINT, SIGTERM, SIGHUP 을 받으면 spec 을 남기고 끝난다. 이때 loop lock 은 자식 프로세스가 끝날 때까지 잡고 있는다. - **purge 가 거부될 때 (stop exit 4)** - 그 에이전트를 건너뛰고 다음 에이전트로 넘어간다. 끝나면 exit 4 로 끝난다. - 건너뛴 에이전트는 이미 일반 stop 을 거쳤으므로 `stopped`(resumable) 로 남는다. 대화 파일은 그대로다. - 파일을 잡고 있는 프로세스를 출력하되, 외부 프로세스는 종료하지 않는다. - 재실행하면 그 에이전트를 다시 시도한다. - **exit 5** - 아무것도 건너뛰거나 실패하지 않았지만, 새로 만든 post-discovered 에이전트(agy, codex, hermes, opencode)의 ID 가 아직 기록되지 않았다는 뜻이다. - spec 은 남는다. 재실행하면 purge 없이 확인만 다시 한다. - **결과 표**: 에이전트마다 이름·에이전트·이전 ID·새 ID·기록 여부·이전 ID 와 다른지를 보여 준다. - **`in_use_hints` 가 lsof 로 찾은 holder 를 보여 준다.** - 대화 파일을 연 프로세스의 pid 와 명령줄을 보여 준다. - `conversation_in_use` 와 같은 경로 목록(`in_use_paths`)을 쓴다. - 기존 `ps | grep` 힌트는 그대로 둔다. - opencode 는 사용자별 백그라운드 서비스(`serve --service`)를 holder 에서 뺀다. - **codex `purge_artifacts`** - `codex delete` 가 남기던 `~/.codex/tui-thread-reference-capabilities/<uuid>` 도 지운다. - 대체 경로에서 rollout 파일을 지우다 실패해도 purge 전체가 멈추지 않는다. - **패키징** - `multi-agent-mux-update` 를 추가할 때와 같은 자리에 새 스킬을 넣었다: `deploy/install.sh`, `deploy/remove.sh`, `deploy/gitea-ci.yml`(shellcheck), README 두 개. - `tests/data/skill_code_blocks.json` 에 새 스킬 핀을 넣었고, `test_issue42_skill_blocks.py` 의 목록에 새 스킬을 추가했다. - main(v4.13.0)으로 rebase 한 뒤, 새 SKILL.md 의 version 을 main 과 같은 4.13.0 으로 맞췄다. ## 테스트 - 새 테스트 파일은 두 개다. - `tests/test_issue78_reset.py`: 대상 선택, 사전 검사별 거부, `--yes` 없음, `--dry-run`, spec 을 쓰는 순서, 중간 실패 뒤 재실행, purge 거부(exit 4)와 재시도, exit 5, 결과 표, reset 중 루프 시작 거부, 시그널을 다룬다. - `tests/test_issue78_hints_codex.py`: 에이전트별 lsof holder 힌트, opencode 서비스 제외, stop 의 거부 출력, codex 잔여 파일 삭제를 다룬다. - base `b9c828f` 에서는 새 테스트 25건 중 24건이 실패한다. 나머지 1건은 lsof 결과가 없으면 힌트가 예전과 같은지 확인하는 테스트라 base 에서도 통과하는 게 맞다. - Linux, rebase 전 `0a902b9`: 전체 스위트 2330 passed, 20 skipped, 2 failed (73분). - 실패 2건은 `test_issue34_job_tree.py::test_t5b_…[True]`, `[False]` 이고 #80 의 경합이다. 하위 잡 사유가 `deadline_timeout` 이 아니라 `-` 로 나온다. - 이 브랜치는 `lib_py/delegation`, `lib_py/loop` 를 건드리지 않는다. - base 에서도 같은 증상으로 실패한다. - Linux, rebase 후 `f90a705`: 대상 테스트 (새 테스트 두 파일과 위 변경이 닿는 12개 파일, `test_issue68_onboard_default.py` 포함) 414 passed, 1 skipped. - 테스트는 mock herdr, 임시 HOME, 자체 `.mam` 을 가진 임시 작업 공간을 쓴다. 실제 레지스트리·모니터·herdr 서버·에이전트 홈은 건드리지 않는다. - macOS(bash 3.2, Python 3.9)에서는 실행하지 않았다. ## 리뷰 - 리뷰어 4명(claude, agy, grok, codex)이 2회차에서 모두 PASS 를 냈다. - 1회차에서 고친 것: - 이전 ID 가 비어 있을 때 stop 이 확인한 ID 를 spec 에 남기지 않았다. 그래서 건너뛴 에이전트를 resume 한 뒤 재실행하면, 그 에이전트가 끝난 것으로 잘못 판정됐다. - INT trap 이 종료하지 않았다. - spec 에 알 수 없는 에이전트가 들어 있어도 받아들였다. - grok 의 지적 하나는 creator 의 반론을 grok 이 받아들여 철회됐다. - `f90a705`(version 한 줄)는 리뷰 이후 커밋이다. ## 알려진 한계 - `multi-agent-mux-delegate-job submit` 은 loop lock 을 보지 않는다. reset 중에 잡을 제출하지 말라는 주의만 SKILL.md 에 있다. - 같은 종류의 post-discovered 에이전트를 여럿 reset 하면, 두 번째 create 가 첫 번째 ID 가 기록될 때까지 기다린다(`MAM_ID_ORDER_WAIT_S`). 시간이 넘으면 재실행해야 한다. ## 범위 밖 - grok 메모리(`~/.grok/memory-v2/`) 삭제. - 병렬 create. - worktree creator reset. - 버전 번호와 VERSIONS.md. ## 릴리스 노트에 적을 것 - 새 스킬 `multi-agent-mux-reset`: 제어 루트의 상주 에이전트를 purge 하고 같은 설정으로 다시 만든다. - purge 가 거부되면 대화 파일을 잡고 있는 프로세스(pid, 명령줄)를 보여 준다. - codex purge 가 `tui-thread-reference-capabilities/<uuid>` 도 지운다. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Godopu added 6 commits 2026-10-29 13:03:28 +00:00
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/issue-78:fix/issue-78
git checkout fix/issue-78
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tmpl/multi-agent-mux#82