같은 workspace 에서 agy 세션 2개(creator-agy-image, creator-agy-writer)를 운영하다가 multi-agent-mux-stop 으로 둘 다 종료한 뒤 multi-agent-mux-resume 을 실행하니,
두 행의 agy_conversation_id_own 이 모두 creator-agy-image 의 대화(5fa7efe7…)로 기록되어 있었다.
creator-agy-image: 정상 resume (자기 대화)
creator-agy-writer: resume 실패 (exit 1) VALIDATE: Duplicate running conversation ID '5fa7efe7-…' detected between 'creator-agy-image' and 'creator-agy-writer'
writer 의 실제 대화는 b9a9490e… 였다. 대화 db 안의 작업 지시서 경로·세션 이름으로 확인했다.
등록부(.mam/agent-sessions.yaml, .mam/agent-sessions.db)의 값을 손으로 고친 뒤에야 resume 되었다.
검사가 없었다면 writer 가 image 의 대화를 이어받아 두 세션이 같은 대화를 쓰게 되었을 것이다.
원인
lib_py/agents/adapters/agy.py 의 discover() 는 ~/.gemini/antigravity-cli/cache/last_conversations.json 에서 workspace 경로를 키로 대화 ID 하나를 읽는다.
cand = json.load(f).get(ctx.workspace)
이 파일은 workspace 마다 "마지막 대화" 하나만 기억한다. 그래서 같은 workspace 의 agy 세션이 여럿이면
모두 같은 ID(가장 최근에 저장된 대화)로 판정된다.
create 로 새로 만든 agy 행은 처음에 own id 가 비어 있다. 이후 reconcile·stop(capture_conversation_id → find_workspace_uuid tier-2 디스크 스캔)이
이 값을 own id 로 기록하면서 다른 세션의 ID가 들어간다. 이렇게 한 번 잘못 들어가면 tier-1 으로 굳어져 이후 stop 에서도 그대로 유지된다.
재현
같은 workspace 에 multi-agent-mux-create --agent agy 로 세션 A, B 를 만든다.
A, B 모두 작업을 시켜 각자 대화를 만든다 (마지막 작업은 A).
multi-agent-mux-stop 으로 A, B 를 종료한다.
등록부의 두 행 agy_conversation_id_own 이 같은 값인지 확인한다.
multi-agent-mux-resume 으로 A, B 를 차례로 연다. B 에서 Duplicate running conversation ID 로 실패한다.
제안
agy 대화 ID 를 workspace 가 아니라 세션(pane) 단위로 확정한다.
예: create 직후 onboarding 응답을 받을 때 그 pane 의 대화 ID 를 own id 로 기록하고, 이후에는 디스크 스캔으로 덮어쓰지 않는다.
tier-2 스캔 결과가 이미 다른 행이 쓰는 ID와 같으면 기록하지 않고 경고한다 (stop·reconcile 공통).
같은 workspace 에 agy 행이 2개 이상일 때 last_conversations.json 결과는 신뢰할 수 없다고 보고 own id 가 없는 행은 resume 을 거부하고 안내한다.
환경
대상 저장소: lac/python_getting_started_lecturenotes (herdr session: mam-pylecturenotes)
세션: creator-agy-image, creator-agy-writer (둘 다 agy, 같은 workspace)
발생일: 2026-10-19
## 증상
같은 workspace 에서 agy 세션 2개(creator-agy-image, creator-agy-writer)를 운영하다가
`multi-agent-mux-stop` 으로 둘 다 종료한 뒤 `multi-agent-mux-resume` 을 실행하니,
두 행의 `agy_conversation_id_own` 이 모두 creator-agy-image 의 대화(5fa7efe7…)로 기록되어 있었다.
- creator-agy-image: 정상 resume (자기 대화)
- creator-agy-writer: resume 실패 (exit 1)
`VALIDATE: Duplicate running conversation ID '5fa7efe7-…' detected between 'creator-agy-image' and 'creator-agy-writer'`
writer 의 실제 대화는 b9a9490e… 였다. 대화 db 안의 작업 지시서 경로·세션 이름으로 확인했다.
등록부(`.mam/agent-sessions.yaml`, `.mam/agent-sessions.db`)의 값을 손으로 고친 뒤에야 resume 되었다.
검사가 없었다면 writer 가 image 의 대화를 이어받아 두 세션이 같은 대화를 쓰게 되었을 것이다.
## 원인
`lib_py/agents/adapters/agy.py` 의 `discover()` 는
`~/.gemini/antigravity-cli/cache/last_conversations.json` 에서 **workspace 경로를 키로** 대화 ID 하나를 읽는다.
cand = json.load(f).get(ctx.workspace)
이 파일은 workspace 마다 "마지막 대화" 하나만 기억한다. 그래서 같은 workspace 의 agy 세션이 여럿이면
모두 같은 ID(가장 최근에 저장된 대화)로 판정된다.
`create` 로 새로 만든 agy 행은 처음에 own id 가 비어 있다. 이후 reconcile·stop(`capture_conversation_id` → `find_workspace_uuid` tier-2 디스크 스캔)이
이 값을 own id 로 기록하면서 다른 세션의 ID가 들어간다. 이렇게 한 번 잘못 들어가면 tier-1 으로 굳어져 이후 stop 에서도 그대로 유지된다.
## 재현
1. 같은 workspace 에 `multi-agent-mux-create --agent agy` 로 세션 A, B 를 만든다.
2. A, B 모두 작업을 시켜 각자 대화를 만든다 (마지막 작업은 A).
3. `multi-agent-mux-stop` 으로 A, B 를 종료한다.
4. 등록부의 두 행 `agy_conversation_id_own` 이 같은 값인지 확인한다.
5. `multi-agent-mux-resume` 으로 A, B 를 차례로 연다. B 에서 Duplicate running conversation ID 로 실패한다.
## 제안
- agy 대화 ID 를 workspace 가 아니라 **세션(pane) 단위**로 확정한다.
예: create 직후 onboarding 응답을 받을 때 그 pane 의 대화 ID 를 own id 로 기록하고, 이후에는 디스크 스캔으로 덮어쓰지 않는다.
- tier-2 스캔 결과가 이미 **다른 행이 쓰는 ID**와 같으면 기록하지 않고 경고한다 (stop·reconcile 공통).
- 같은 workspace 에 agy 행이 2개 이상일 때 `last_conversations.json` 결과는 신뢰할 수 없다고 보고 own id 가 없는 행은 resume 을 거부하고 안내한다.
## 환경
- 대상 저장소: lac/python_getting_started_lecturenotes (herdr session: mam-pylecturenotes)
- 세션: creator-agy-image, creator-agy-writer (둘 다 agy, 같은 workspace)
- 발생일: 2026-10-19
#40(v4.9.9)은 reconcile 과 completed 처리 경로만 고쳤다. 이 이슈의 경로(stop·purge·resume 의 대화 ID 조회)는 아직 그대로다.
1. 잘못된 ID 가 들어오는 곳: lib_py/workspace_uuid.py 의 find_workspace_uuid
stop: 프로세스를 끄기 전에 이 함수로 ID 를 찾아 행에 기록한다(stop_session.sh:223, capture_conversation_id).
purge: --purge-conversation 의 삭제 대상도 이 함수로 정한다(stop_session.sh:204).
resume: 행에 ID 가 없으면 같은 함수를 쓴다(resolve_session_id.sh:43).
2. 이 함수의 구멍 세 개
running 행만 제외 (workspace_uuid.py:30-38): 다른 행의 ID 를 걸러낼 때 status == running 인 행만 본다. 그래서 A 를 먼저 stop 하면 A 의 ID 가 제외 목록에서 빠지고, 이어서 B 를 stop 할 때 B 의 행에 기록된다. 본문의 재현 순서와 같다.
형제 행의 ID 를 그대로 내보냄 (workspace_uuid.py:69-73): 같은 폴더에서 이름이 -creator-<agent> 로 끝나는 행의 own id 를 검증만 하고 내보낸다. 대상 세션의 것인지는 보지 않는다.
캐시 값 하나 (workspace_uuid.py:82, adapter.discover): agy 는 last_conversations.json[cwd] 값 하나를 돌려준다. 같은 폴더에 agy 가 여럿이면 마지막에 연 쪽의 대화다.
3. purge 위험
B 를 --purge-conversation 으로 지우면 위 경로로 A 의 대화 ID 가 삭제 대상이 될 수 있다(코드 흐름으로 확인, 재현은 안 함). 삭제 직전의 사용 중 검사(stop_session.sh:290-294)는 A 가 실행 중일 때만 막는다. A 가 이미 stop 된 상태라면 A 의 대화가 지워진다.
4. 고치는 방향
조회 교체: 세션 ID 가 나중에 생기는 에이전트(agy·opencode·codex·hermes)는 이 탐색 대신 #40 의 BaseAgentAdapter.capture_active_session_id 를 쓴다.
이 함수는 다른 행이 가진 ID 를 멈춘 행 것까지 제외한다.
agy 는 pane 프로세스(또는 자손)가 그 대화의 presence lock 이나 DB 를 열고 있는지 lsof·ps 로 확인한다.
판정을 새로 만들지 말고 이 함수를 다시 쓴다.
stop 순서: 프로세스를 끄기 전에 ID 를 찾아야 소유 확인이 통과한다. 지금도 kill(:237 이후)보다 앞에서 찾는다. herdr 가 이미 죽은 행은 확인할 수 없으니 기록하지 않고 이유를 남긴다.
purge: 행에 기록된 own id 이거나 확인을 통과한 ID 만 지운다. 캐시가 추측한 ID 로는 지우지 않는다.
resume: own id 가 없고 같은 폴더에 같은 에이전트 행이 둘 이상이면 추측하지 않는다. Class A 는 새 대화로 띄우고 그 사실을 알린다(본문 제안 3).
claude·grok: ID 를 미리 정하는 에이전트(pre_assigned)라 이 문제와 무관하다. 동작을 바꾸지 않는다.
5. (선택) 먼저 뜬 agy 가 등록되지 않는 문제
#40 Linux 확인에서 같은 폴더에 agy 를 하나 더 열면 last_conversations.json 의 그 폴더 항목이 나중에 연 쪽으로 바뀌는 것을 확인했다.
그래서 먼저 뜬 agy 가 등록되기 전에 두 번째가 열리면, 먼저 뜬 agy 는 자동 등록되지 않는다(owner-mismatch, 잘못된 ID 를 기록하지는 않음).
캐시 대신 pane 프로세스(또는 자손)가 연 presence/<uuid>.lock 에서 ID 를 얻으면 같은 폴더에 agy 가 여럿이어도 세션마다 정해진다(본문 제안 1).
근거: #40 Linux 확인에서 resume 된 agy 는 lock 만, 새로 띄운 agy 는 lock 과 DB 를 연다. 두 경우 모두 연 프로세스가 pane pid 였다.
범위가 커지면 따로 떼어도 된다.
6. 완료 조건 (회귀 테스트로)
같은 폴더의 agy A·B 가 각자 대화를 만들고 마지막 작업이 A 일 때, A→B 순서로 stop 해도 B 의 행에 A 의 ID 가 기록되지 않는다. B→A 순서도 같다.
B 를 --purge-conversation --yes 로 지워도 A 의 대화 파일(conversations/<A>.db, 사이드카, brain/, annotations/, presence/)은 남는다.
own id 가 없는 B 를 resume 할 때 A 의 대화로 이어지지 않는다.
행에 own id 가 이미 있는 경우와 claude·grok 행은 동작이 그대로다.
기존 tests/test_issue40_auto_pin.py, tests/test_issue64_ro_connect.py, tests/test_issue10_13_purge.py 가 그대로 통과한다.
7. 테스트와 제약
임시 HOME·임시 대화 DB·mock herdr 와 가짜 lsof·ps 로 재현한다(#40 테스트의 픽스처를 재사용할 수 있다). reconcile 을 거치는 테스트는 mock herdr 에 그 세션을 running agent 로 넣어야 drift C 까지 간다.
실제 에이전트 홈(~/.gemini, ~/.codex, ~/.hermes, ~/.claude, opencode data)의 DB 는 sqlite 로 열지 않는다(읽기 전용도 금지). DB 조회는 lib_py.agents.base._ro_connect 를 쓴다.
.mam.env 를 export 하지 않는다. 실제 .mam, 모니터, herdr 서버를 건드리지 않는다. lib.sh 를 source 하는 테스트는 own_workspace_root fixture 를 쓴다.
macOS(bash 3.2, Python 3.9)와 Linux 양쪽에서 돌아야 한다. 새 판정은 셸이 아니라 Python 에 둔다.
버전 번호와 VERSIONS.md 는 건드리지 않는다. PR 본문은 Refs #63.
## 작업 참고 (v4.9.9 기준 코드 확인)
#40(v4.9.9)은 reconcile 과 completed 처리 경로만 고쳤다. 이 이슈의 경로(stop·purge·resume 의 대화 ID 조회)는 아직 그대로다.
### 1. 잘못된 ID 가 들어오는 곳: `lib_py/workspace_uuid.py` 의 `find_workspace_uuid`
- **stop**: 프로세스를 끄기 전에 이 함수로 ID 를 찾아 행에 기록한다(`stop_session.sh:223`, `capture_conversation_id`).
- **purge**: `--purge-conversation` 의 삭제 대상도 이 함수로 정한다(`stop_session.sh:204`).
- **resume**: 행에 ID 가 없으면 같은 함수를 쓴다(`resolve_session_id.sh:43`).
### 2. 이 함수의 구멍 세 개
- **`running` 행만 제외** (`workspace_uuid.py:30-38`): 다른 행의 ID 를 걸러낼 때 `status == running` 인 행만 본다. 그래서 A 를 먼저 stop 하면 A 의 ID 가 제외 목록에서 빠지고, 이어서 B 를 stop 할 때 B 의 행에 기록된다. 본문의 재현 순서와 같다.
- **형제 행의 ID 를 그대로 내보냄** (`workspace_uuid.py:69-73`): 같은 폴더에서 이름이 `-creator-<agent>` 로 끝나는 행의 own id 를 검증만 하고 내보낸다. 대상 세션의 것인지는 보지 않는다.
- **캐시 값 하나** (`workspace_uuid.py:82`, `adapter.discover`): agy 는 `last_conversations.json[cwd]` 값 하나를 돌려준다. 같은 폴더에 agy 가 여럿이면 마지막에 연 쪽의 대화다.
### 3. purge 위험
B 를 `--purge-conversation` 으로 지우면 위 경로로 A 의 대화 ID 가 삭제 대상이 될 수 있다(코드 흐름으로 확인, 재현은 안 함). 삭제 직전의 사용 중 검사(`stop_session.sh:290-294`)는 A 가 실행 중일 때만 막는다. A 가 이미 stop 된 상태라면 A 의 대화가 지워진다.
### 4. 고치는 방향
- **조회 교체**: 세션 ID 가 나중에 생기는 에이전트(agy·opencode·codex·hermes)는 이 탐색 대신 #40 의 `BaseAgentAdapter.capture_active_session_id` 를 쓴다.
- 이 함수는 다른 행이 가진 ID 를 멈춘 행 것까지 제외한다.
- agy 는 pane 프로세스(또는 자손)가 그 대화의 presence lock 이나 DB 를 열고 있는지 `lsof`·`ps` 로 확인한다.
- 판정을 새로 만들지 말고 이 함수를 다시 쓴다.
- **stop 순서**: 프로세스를 끄기 전에 ID 를 찾아야 소유 확인이 통과한다. 지금도 kill(:237 이후)보다 앞에서 찾는다. herdr 가 이미 죽은 행은 확인할 수 없으니 기록하지 않고 이유를 남긴다.
- **purge**: 행에 기록된 own id 이거나 확인을 통과한 ID 만 지운다. 캐시가 추측한 ID 로는 지우지 않는다.
- **resume**: own id 가 없고 같은 폴더에 같은 에이전트 행이 둘 이상이면 추측하지 않는다. Class A 는 새 대화로 띄우고 그 사실을 알린다(본문 제안 3).
- **claude·grok**: ID 를 미리 정하는 에이전트(`pre_assigned`)라 이 문제와 무관하다. 동작을 바꾸지 않는다.
### 5. (선택) 먼저 뜬 agy 가 등록되지 않는 문제
- #40 Linux 확인에서 같은 폴더에 agy 를 하나 더 열면 `last_conversations.json` 의 그 폴더 항목이 나중에 연 쪽으로 바뀌는 것을 확인했다.
- 그래서 먼저 뜬 agy 가 등록되기 전에 두 번째가 열리면, 먼저 뜬 agy 는 자동 등록되지 않는다(`owner-mismatch`, 잘못된 ID 를 기록하지는 않음).
- 캐시 대신 **pane 프로세스(또는 자손)가 연 `presence/<uuid>.lock`** 에서 ID 를 얻으면 같은 폴더에 agy 가 여럿이어도 세션마다 정해진다(본문 제안 1).
- 근거: #40 Linux 확인에서 resume 된 agy 는 lock 만, 새로 띄운 agy 는 lock 과 DB 를 연다. 두 경우 모두 연 프로세스가 pane pid 였다.
- 범위가 커지면 따로 떼어도 된다.
### 6. 완료 조건 (회귀 테스트로)
1. 같은 폴더의 agy A·B 가 각자 대화를 만들고 마지막 작업이 A 일 때, A→B 순서로 stop 해도 B 의 행에 A 의 ID 가 기록되지 않는다. B→A 순서도 같다.
2. B 를 `--purge-conversation --yes` 로 지워도 A 의 대화 파일(`conversations/<A>.db`, 사이드카, `brain/`, `annotations/`, `presence/`)은 남는다.
3. own id 가 없는 B 를 resume 할 때 A 의 대화로 이어지지 않는다.
4. 행에 own id 가 이미 있는 경우와 claude·grok 행은 동작이 그대로다.
5. 기존 `tests/test_issue40_auto_pin.py`, `tests/test_issue64_ro_connect.py`, `tests/test_issue10_13_purge.py` 가 그대로 통과한다.
### 7. 테스트와 제약
- 임시 HOME·임시 대화 DB·mock herdr 와 가짜 `lsof`·`ps` 로 재현한다(#40 테스트의 픽스처를 재사용할 수 있다). reconcile 을 거치는 테스트는 mock herdr 에 그 세션을 running agent 로 넣어야 drift C 까지 간다.
- 실제 에이전트 홈(`~/.gemini`, `~/.codex`, `~/.hermes`, `~/.claude`, opencode data)의 DB 는 sqlite 로 열지 않는다(읽기 전용도 금지). DB 조회는 `lib_py.agents.base._ro_connect` 를 쓴다.
- `.mam.env` 를 export 하지 않는다. 실제 `.mam`, 모니터, herdr 서버를 건드리지 않는다. lib.sh 를 source 하는 테스트는 `own_workspace_root` fixture 를 쓴다.
- macOS(bash 3.2, Python 3.9)와 Linux 양쪽에서 돌아야 한다. 새 판정은 셸이 아니라 Python 에 둔다.
- 버전 번호와 VERSIONS.md 는 건드리지 않는다. PR 본문은 `Refs #63`.
위 댓글의 구멍 세 개를 임시 HOME 으로 재현했다(find_workspace_uuid 를 직접 호출). 같은 폴더의 agy A·B, 캐시는 A 를 가리킴, B 는 own id 없음:
A 실행 중이고 A 의 ID 가 기록됨 → B 는 빈 값 (정상)
A 를 먼저 stop → B 가 A 의 ID 를 받음
둘 다 ID 없음 → B 가 A 의 ID 를 받음
이름이 -creator-agy 로 끝나는 stop 된 행이 A 의 ID 를 가짐 → B 가 A 의 ID 를 받음
본문의 세션 이름(creator-agy-<역할>)은 -creator-agy 로 끝나지 않으므로, 본문 사례의 원인은 캐시 쪽이다.
추가로 찾은 것
resume 은 띄운 뒤에 검사한다.resume_session.sh 가 agy --conversation <추측한 ID> 로 세션을 띄운 다음(:245) 레지스트리를 갱신하고, 중복 검사(atomic_yaml.py:143)는 그 갱신에서야 돈다. 본문의 VALIDATE: Duplicate running conversation ID 가 났을 때 writer 쪽 pane 에는 이미 image 의 대화가 열려 있었다.
resume 의 "이미 실행 중" 분기(:155)도 행에 ID 가 없으면 추측한 ID 를 실행 중인 행에 기록한다. 상대 행이 stop 된 상태면 중복 검사도 통과한다.
codex·hermes·opencode 의 기본 판정(BaseAgentAdapter.capture_active_session_id)에는 소유 확인이 없다(후보 하나·세션보다 새것·다른 행이 기록하지 않은 것만). 같은 폴더에 같은 종류 둘이 다 ID 가 없으면 틀릴 수 있다. 아래 "생성 순서 규칙" 으로 함께 막는다.
이번에 고치는 것
조회 교체 (위 댓글 4): stop·resume 에서 ID 가 나중에 생기는 에이전트(agy·codex·hermes·opencode)는 find_workspace_uuid 의 추측 대신 #40 의 판정(autopin.capture → capture_active_session_id)을 쓴다. claude·grok 은 바꾸지 않는다.
stop: 프로세스를 끄기 전에 확인한다. herdr 가 이미 죽은 행은 확인할 수 없으므로 ID 를 기록하지 않고 이유를 남긴다.
purge: 행에 기록된 own id 이거나 확인을 통과한 ID 만 지운다. 다른 행이 기록한 ID 는 지우지 않는다.
resume:
own id 가 없고 같은 폴더에 같은 종류 행이 둘 이상이면 추측하지 않는다. 새 대화로 띄우고 그 사실을 알린다. 하나뿐이면 지금처럼 찾는다.
띄우기 전에 해결한 ID 를 다른 행이 이미 기록했는지 확인하고, 그렇다면 띄우지 않고 거부한다.
"이미 실행 중" 분기도 같은 판정을 쓴다.
생성 순서 규칙 (새로 추가): 같은 폴더에 ID 가 나중에 생기는 같은 종류 에이전트가 생성 중이거나 아직 ID 가 기록되지 않은 채 실행 중이면, 새 create·resume 은 그 ID 가 기록될 때까지 기다렸다가 진행한다.
대기 상한을 넘으면 거부하고 이유(어느 세션을 기다렸는지)를 알린다. 상한은 환경 변수로 바꿀 수 있게 한다.
"생성 중" 은 레지스트리에 행이 아직 없으므로, 폴더 단위 잠금으로 알린다(같은 herdr 서버 안의 pane 잠금만으로는 서버가 다르면 지나친다).
이 규칙이 있으면 먼저 뜬 세션의 ID 가 캐시 덮어쓰기 전에 기록되므로, 위 댓글 5(presence lock 으로 ID 찾기)는 이번에는 하지 않는다. MAM 밖에서 사람이 직접 같은 폴더에 띄운 에이전트까지 다뤄야 할 때 다시 본다.
--no-onboard 로 만든 세션은 첫 대화 전까지 ID 가 생기지 않아 뒤 세션이 상한까지 기다린다. 이 옵션은 #68 에서 없앤다.
완료 조건
위 댓글 6 의 1~5 에 더해:
6. 같은 폴더·같은 종류 에이전트의 create 가 겹치면 뒤 create 는 앞 세션의 ID 가 기록된 뒤에 띄워진다. 상한을 넘기면 아무것도 띄우지 않고 거부한다.
7. resume 이 다른 행이 기록한 ID 로 에이전트를 띄우지 않는다(띄우기 전에 거부).
테스트와 제약은 위 댓글 7 그대로다. 버전 번호와 VERSIONS.md 는 건드리지 않고, PR 본문은 Refs #63.
## 작업 범위 (v4.9.9 기준 확인 후 확정)
### 확인 결과
- 위 댓글의 구멍 세 개를 임시 HOME 으로 재현했다(`find_workspace_uuid` 를 직접 호출). 같은 폴더의 agy A·B, 캐시는 A 를 가리킴, B 는 own id 없음:
- A 실행 중이고 A 의 ID 가 기록됨 → B 는 빈 값 (정상)
- **A 를 먼저 stop** → B 가 A 의 ID 를 받음
- **둘 다 ID 없음** → B 가 A 의 ID 를 받음
- 이름이 `-creator-agy` 로 끝나는 stop 된 행이 A 의 ID 를 가짐 → B 가 A 의 ID 를 받음
- 본문의 세션 이름(`creator-agy-<역할>`)은 `-creator-agy` 로 끝나지 않으므로, 본문 사례의 원인은 캐시 쪽이다.
### 추가로 찾은 것
1. **resume 은 띄운 뒤에 검사한다.** `resume_session.sh` 가 `agy --conversation <추측한 ID>` 로 세션을 띄운 다음(`:245`) 레지스트리를 갱신하고, 중복 검사(`atomic_yaml.py:143`)는 그 갱신에서야 돈다. 본문의 `VALIDATE: Duplicate running conversation ID` 가 났을 때 writer 쪽 pane 에는 이미 image 의 대화가 열려 있었다.
2. **resume 의 "이미 실행 중" 분기**(`:155`)도 행에 ID 가 없으면 추측한 ID 를 실행 중인 행에 기록한다. 상대 행이 stop 된 상태면 중복 검사도 통과한다.
3. codex·hermes·opencode 의 기본 판정(`BaseAgentAdapter.capture_active_session_id`)에는 소유 확인이 없다(후보 하나·세션보다 새것·다른 행이 기록하지 않은 것만). 같은 폴더에 같은 종류 둘이 다 ID 가 없으면 틀릴 수 있다. 아래 "생성 순서 규칙" 으로 함께 막는다.
### 이번에 고치는 것
1. **조회 교체** (위 댓글 4): stop·resume 에서 ID 가 나중에 생기는 에이전트(agy·codex·hermes·opencode)는 `find_workspace_uuid` 의 추측 대신 #40 의 판정(`autopin.capture` → `capture_active_session_id`)을 쓴다. claude·grok 은 바꾸지 않는다.
2. **stop**: 프로세스를 끄기 전에 확인한다. herdr 가 이미 죽은 행은 확인할 수 없으므로 ID 를 기록하지 않고 이유를 남긴다.
3. **purge**: 행에 기록된 own id 이거나 확인을 통과한 ID 만 지운다. 다른 행이 기록한 ID 는 지우지 않는다.
4. **resume**:
- own id 가 없고 같은 폴더에 같은 종류 행이 둘 이상이면 추측하지 않는다. 새 대화로 띄우고 그 사실을 알린다. 하나뿐이면 지금처럼 찾는다.
- **띄우기 전에** 해결한 ID 를 다른 행이 이미 기록했는지 확인하고, 그렇다면 띄우지 않고 거부한다.
- "이미 실행 중" 분기도 같은 판정을 쓴다.
5. **생성 순서 규칙 (새로 추가)**: 같은 폴더에 ID 가 나중에 생기는 같은 종류 에이전트가 **생성 중이거나 아직 ID 가 기록되지 않은 채 실행 중**이면, 새 create·resume 은 그 ID 가 기록될 때까지 **기다렸다가** 진행한다.
- 대기 상한을 넘으면 거부하고 이유(어느 세션을 기다렸는지)를 알린다. 상한은 환경 변수로 바꿀 수 있게 한다.
- "생성 중" 은 레지스트리에 행이 아직 없으므로, 폴더 단위 잠금으로 알린다(같은 herdr 서버 안의 pane 잠금만으로는 서버가 다르면 지나친다).
- 이 규칙이 있으면 먼저 뜬 세션의 ID 가 캐시 덮어쓰기 전에 기록되므로, 위 댓글 5(presence lock 으로 ID 찾기)는 이번에는 하지 않는다. MAM 밖에서 사람이 직접 같은 폴더에 띄운 에이전트까지 다뤄야 할 때 다시 본다.
- `--no-onboard` 로 만든 세션은 첫 대화 전까지 ID 가 생기지 않아 뒤 세션이 상한까지 기다린다. 이 옵션은 #68 에서 없앤다.
### 완료 조건
위 댓글 6 의 1~5 에 더해:
6. 같은 폴더·같은 종류 에이전트의 create 가 겹치면 뒤 create 는 앞 세션의 ID 가 기록된 뒤에 띄워진다. 상한을 넘기면 아무것도 띄우지 않고 거부한다.
7. resume 이 다른 행이 기록한 ID 로 에이전트를 띄우지 않는다(띄우기 전에 거부).
테스트와 제약은 위 댓글 7 그대로다. 버전 번호와 VERSIONS.md 는 건드리지 않고, PR 본문은 `Refs #63`.
resume 의 대기 호출은 셸 단계 테스트로 고정되어 있지 않다(dry-run 은 대기 전에 끝남).
Linux 확인 제안
작성자의 Linux 스위트와 실제 agy 두 개 시험은 a907cc5·d115bf7 기준이었다. 그 뒤 id_order.py 의 herdr 조회 부분이 바뀌었으므로, Linux 에서 다음을 한 번 확인하면 이슈를 닫을 수 있다.
main(v4.10.0)에서 tests/test_issue63_same_cwd_ids.py 와 전체 스위트.
같은 폴더 agy 동시 create: 뒤 세션이 기다렸다가 앞 세션의 ID 가 기록된 뒤 뜨고, 두 ID 가 다른지.
(선택) 앞 create 를 spawn 직후 kill 했을 때, 다음 create 가 상한까지 기다리고 거부 메시지에 정리 명령이 나오는지.
## PR #69 병합 (v4.10.0)
PR #69 를 리뷰·테스트한 뒤 `main` 에 병합했다(머지 커밋 `0461a63`, 릴리스 커밋 `0454fe9`). 변경 요약은 `VERSIONS.md` 의 `v4.10.0` 항목에 있다.
### 리뷰 중 추가된 수정
4인 리뷰(`--all-reviewer`, 리뷰 전용 루프)에서 codex 가 고아 "생성 중" 표시 처리의 차단 결함을 회차마다 찾았고, creator 가 아래 커밋으로 고쳤다.
| 커밋 | 지적 | 수정 |
|---|---|---|
| `ede9c62` | create·resume 이 pane 을 띄운 뒤 행 등록 전에 죽으면, 다음 대기가 owner 가 죽었다는 이유만으로 표시를 지움 | 표시에 herdr 서버를 기록하고, 그 세션이 살아 있으면 계속 막음 |
| `8f64174` | herdr 조회 실패를 "세션 없음"으로 판단 | 세션이 없다는 확실한 응답에서만 지우고, 조회는 대기 상한 안에서만 |
| `7cac6f6` | 대기 상한 0 이어도 조회가 최대 30 초 | `server_not_running` 은 "모름", 상한 0 이면 herdr 를 묻지 않음 |
| `1614af5` | 같은 세션 이름의 죽은 owner 표시는 확인 없이 지움 | 같은 고아 규칙을 적용 |
마지막 루프 1회차(@`1614af5`)에서 4/4 PASS.
### 검증 (macOS, Python 3.9.6, @`1614af5`)
- 전체 스위트: 2097 passed, 56 skipped, 1 failed.
- 실패는 `tests/test_issue50_dispatch_herdr.py::test_c11_…` 하나다. 이 PR 은 위임 코드를 건드리지 않고, 단독으로 세 번 돌리면 매번 통과했다(시간 의존).
- `tests/test_issue63_same_cwd_ids.py` 27개 통과.
- 스위트가 실제 에이전트 홈, herdr 서버 pane 목록, opencode 데이터에 남긴 것은 없었다.
### 남은 비차단 (리뷰어 의견)
- herdr 조회가 계속 실패하면 고아 표시가 남아, 이후 create·resume 이 매번 `MAM_ID_ORDER_WAIT_S` 까지 기다린다. 거부 메시지가 `herdr kill-session` 명령을 알려 준다.
- 등록 중에 herdr 목록이 잠깐 비어 있으면 표시가 풀릴 수 있다.
- `--no-onboard` 세션 뒤 create 의 대기는 #68 에서 다룬다.
- presence lock 으로 ID 를 찾는 것은 범위 밖이다.
- resume 의 대기 호출은 셸 단계 테스트로 고정되어 있지 않다(dry-run 은 대기 전에 끝남).
### Linux 확인 제안
작성자의 Linux 스위트와 실제 agy 두 개 시험은 `a907cc5`·`d115bf7` 기준이었다. 그 뒤 `id_order.py` 의 herdr 조회 부분이 바뀌었으므로, Linux 에서 다음을 한 번 확인하면 이슈를 닫을 수 있다.
1. `main`(v4.10.0)에서 `tests/test_issue63_same_cwd_ids.py` 와 전체 스위트.
2. 같은 폴더 agy 동시 create: 뒤 세션이 기다렸다가 앞 세션의 ID 가 기록된 뒤 뜨고, 두 ID 가 다른지.
3. (선택) 앞 create 를 spawn 직후 kill 했을 때, 다음 create 가 상한까지 기다리고 거부 메시지에 정리 명령이 나오는지.
전체 스위트: 2135 passed, 19 skipped, 0 failed. XDG_* 를 비우고 -p no:cacheprovider, 트리 밖 --basetemp 로 돌렸다.
2. 같은 폴더 agy 동시 create
main 의 별도 복사본과 별도 herdr 서버를 썼다. A 를 띄우고 3 초 뒤 B 를 띄웠다.
B 는 id-order: waiting for <A> … 를 내고 기다렸다.
A 의 온보딩이 completed 된 직후(자동 등록 2 초 이내) B 의 pane 이 떴다.
두 create 모두 exit 0 이었다. 두 ID 는 서로 달랐고, 각 대화의 presence lock 은 자기 pane pid 만 쥐고 있었다.
3. 앞 create 를 띄운 직후 kill
C 의 create 를 pane 이 뜨자마자(약 1 초) 프로세스 그룹째 SIGKILL 했다. C 의 pane 은 살아 있고 행은 등록되지 않았다. "생성 중" 표시에는 herdr 서버가 기록되어 있었다.
MAM_ID_ORDER_WAIT_S=30 으로 D 를 create 했다.
D 는 C 를 기다렸고, 31 초 뒤 exit 1 로 거부했다. D 의 pane 은 뜨지 않았고 행도 생기지 않았다.
거부 메시지 두 줄:
ERROR: <C> in <cwd> still has no recorded agy id after 30s; not starting <D> …
ERROR: <C>'s create/resume exited before recording it, and its herdr session is alive; close it with: HERDR_SESSION_NAME=<server> herdr kill-session -t <C>
메시지의 명령을 그대로 실행하자 C 의 pane 이 내려갔다.
다시 create 한 D 는 기다리지 않고 바로 떴다(exit 0). C 의 고아 표시는 그때 지워졌다.
D 의 ID 는 A·B 와 달랐다.
정리
시험 세션 A·B·D 는 --purge-conversation --yes 로 지웠다. 대화 파일이 남지 않았고 행도 없다.
시험 서버와 복사본도 지웠다.
agy 의 cache/last_conversations.json·history.jsonl 에는 지운 ID 가 남아 있다(#40 과 같음).
고칠 것은 찾지 못했다. 이 이슈는 닫아도 된다.
## Linux 확인 (v4.10.0, `main` @`0461a63`)
환경: Linux, Python 3.14.7, bash 5.3, herdr 0.9.3, agy 1.4.6.
### 1. 테스트
- `tests/test_issue63_same_cwd_ids.py`: 27 passed.
- 전체 스위트: 2135 passed, 19 skipped, 0 failed. `XDG_*` 를 비우고 `-p no:cacheprovider`, 트리 밖 `--basetemp` 로 돌렸다.
### 2. 같은 폴더 agy 동시 create
`main` 의 별도 복사본과 별도 herdr 서버를 썼다. A 를 띄우고 3 초 뒤 B 를 띄웠다.
- B 는 `id-order: waiting for <A> …` 를 내고 기다렸다.
- A 의 온보딩이 completed 된 직후(자동 등록 2 초 이내) B 의 pane 이 떴다.
- 두 create 모두 exit 0 이었다. 두 ID 는 서로 달랐고, 각 대화의 presence lock 은 자기 pane pid 만 쥐고 있었다.
### 3. 앞 create 를 띄운 직후 kill
C 의 create 를 pane 이 뜨자마자(약 1 초) 프로세스 그룹째 SIGKILL 했다. C 의 pane 은 살아 있고 행은 등록되지 않았다. "생성 중" 표시에는 herdr 서버가 기록되어 있었다.
- `MAM_ID_ORDER_WAIT_S=30` 으로 D 를 create 했다.
- D 는 C 를 기다렸고, 31 초 뒤 exit 1 로 거부했다. D 의 pane 은 뜨지 않았고 행도 생기지 않았다.
- 거부 메시지 두 줄:
- `ERROR: <C> in <cwd> still has no recorded agy id after 30s; not starting <D> …`
- `ERROR: <C>'s create/resume exited before recording it, and its herdr session is alive; close it with: HERDR_SESSION_NAME=<server> herdr kill-session -t <C>`
- 메시지의 명령을 그대로 실행하자 C 의 pane 이 내려갔다.
- 다시 create 한 D 는 기다리지 않고 바로 떴다(exit 0). C 의 고아 표시는 그때 지워졌다.
- D 의 ID 는 A·B 와 달랐다.
### 정리
- 시험 세션 A·B·D 는 `--purge-conversation --yes` 로 지웠다. 대화 파일이 남지 않았고 행도 없다.
- 시험 서버와 복사본도 지웠다.
- agy 의 `cache/last_conversations.json`·`history.jsonl` 에는 지운 ID 가 남아 있다(#40 과 같음).
고칠 것은 찾지 못했다. 이 이슈는 닫아도 된다.
v4.10.0(main @0461a63)의 Linux 확인 결과, 제안한 세 항목이 모두 기대대로 동작했다.
테스트: test_issue63_same_cwd_ids.py 27 passed. 전체 스위트는 실패 없이 2135 passed, 19 skipped. macOS 에서 한 번 실패한 test_issue50_dispatch_herdr.py::test_c11_… 도 통과했다.
같은 폴더 agy 동시 create: B 는 A 의 ID 가 기록될 때까지 기다린 뒤 떴다. 두 ID 는 달랐고, 각 presence lock 은 자기 pane 만 쥐고 있었다.
띄운 직후 kill 한 create(C):
다음 create(D)는 상한(30 s)까지 기다린 뒤, 아무것도 띄우지 않고 거부했다(exit 1).
거부 메시지가 알려 준 herdr kill-session 명령으로 C 를 정리하자, D 가 바로 떴다. 이때 고아 표시도 지워졌다.
리뷰 중 고친 고아 "생성 중" 표시 처리(ede9c62·8f64174·7cac6f6·1614af5)가 실제 herdr 에서도 의도대로 동작한다.
agy 의 last_conversations.json·history.jsonl 에 지운 ID 가 남는 것은 #40 과 같은 기존 동작이라 이 이슈의 범위가 아니다. 완료 조건을 모두 확인했으므로 닫는다.
## Linux 확인 결과 확인
v4.10.0(main @`0461a63`)의 Linux 확인 결과, 제안한 세 항목이 모두 기대대로 동작했다.
1. 테스트: `test_issue63_same_cwd_ids.py` 27 passed. 전체 스위트는 실패 없이 2135 passed, 19 skipped. macOS 에서 한 번 실패한 `test_issue50_dispatch_herdr.py::test_c11_…` 도 통과했다.
2. 같은 폴더 agy 동시 create: B 는 A 의 ID 가 기록될 때까지 기다린 뒤 떴다. 두 ID 는 달랐고, 각 presence lock 은 자기 pane 만 쥐고 있었다.
3. 띄운 직후 kill 한 create(C):
- 다음 create(D)는 상한(30 s)까지 기다린 뒤, 아무것도 띄우지 않고 거부했다(exit 1).
- 거부 메시지가 알려 준 `herdr kill-session` 명령으로 C 를 정리하자, D 가 바로 떴다. 이때 고아 표시도 지워졌다.
- 리뷰 중 고친 고아 "생성 중" 표시 처리(`ede9c62`·`8f64174`·`7cac6f6`·`1614af5`)가 실제 herdr 에서도 의도대로 동작한다.
agy 의 `last_conversations.json`·`history.jsonl` 에 지운 ID 가 남는 것은 #40 과 같은 기존 동작이라 이 이슈의 범위가 아니다. 완료 조건을 모두 확인했으므로 닫는다.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
증상
같은 workspace 에서 agy 세션 2개(creator-agy-image, creator-agy-writer)를 운영하다가
multi-agent-mux-stop으로 둘 다 종료한 뒤multi-agent-mux-resume을 실행하니,두 행의
agy_conversation_id_own이 모두 creator-agy-image 의 대화(5fa7efe7…)로 기록되어 있었다.VALIDATE: Duplicate running conversation ID '5fa7efe7-…' detected between 'creator-agy-image' and 'creator-agy-writer'writer 의 실제 대화는 b9a9490e… 였다. 대화 db 안의 작업 지시서 경로·세션 이름으로 확인했다.
등록부(
.mam/agent-sessions.yaml,.mam/agent-sessions.db)의 값을 손으로 고친 뒤에야 resume 되었다.검사가 없었다면 writer 가 image 의 대화를 이어받아 두 세션이 같은 대화를 쓰게 되었을 것이다.
원인
lib_py/agents/adapters/agy.py의discover()는~/.gemini/antigravity-cli/cache/last_conversations.json에서 workspace 경로를 키로 대화 ID 하나를 읽는다.이 파일은 workspace 마다 "마지막 대화" 하나만 기억한다. 그래서 같은 workspace 의 agy 세션이 여럿이면
모두 같은 ID(가장 최근에 저장된 대화)로 판정된다.
create로 새로 만든 agy 행은 처음에 own id 가 비어 있다. 이후 reconcile·stop(capture_conversation_id→find_workspace_uuidtier-2 디스크 스캔)이이 값을 own id 로 기록하면서 다른 세션의 ID가 들어간다. 이렇게 한 번 잘못 들어가면 tier-1 으로 굳어져 이후 stop 에서도 그대로 유지된다.
재현
multi-agent-mux-create --agent agy로 세션 A, B 를 만든다.multi-agent-mux-stop으로 A, B 를 종료한다.agy_conversation_id_own이 같은 값인지 확인한다.multi-agent-mux-resume으로 A, B 를 차례로 연다. B 에서 Duplicate running conversation ID 로 실패한다.제안
예: create 직후 onboarding 응답을 받을 때 그 pane 의 대화 ID 를 own id 로 기록하고, 이후에는 디스크 스캔으로 덮어쓰지 않는다.
last_conversations.json결과는 신뢰할 수 없다고 보고 own id 가 없는 행은 resume 을 거부하고 안내한다.환경
작업 참고 (v4.9.9 기준 코드 확인)
#40(v4.9.9)은 reconcile 과 completed 처리 경로만 고쳤다. 이 이슈의 경로(stop·purge·resume 의 대화 ID 조회)는 아직 그대로다.
1. 잘못된 ID 가 들어오는 곳:
lib_py/workspace_uuid.py의find_workspace_uuidstop_session.sh:223,capture_conversation_id).--purge-conversation의 삭제 대상도 이 함수로 정한다(stop_session.sh:204).resolve_session_id.sh:43).2. 이 함수의 구멍 세 개
running행만 제외 (workspace_uuid.py:30-38): 다른 행의 ID 를 걸러낼 때status == running인 행만 본다. 그래서 A 를 먼저 stop 하면 A 의 ID 가 제외 목록에서 빠지고, 이어서 B 를 stop 할 때 B 의 행에 기록된다. 본문의 재현 순서와 같다.workspace_uuid.py:69-73): 같은 폴더에서 이름이-creator-<agent>로 끝나는 행의 own id 를 검증만 하고 내보낸다. 대상 세션의 것인지는 보지 않는다.workspace_uuid.py:82,adapter.discover): agy 는last_conversations.json[cwd]값 하나를 돌려준다. 같은 폴더에 agy 가 여럿이면 마지막에 연 쪽의 대화다.3. purge 위험
B 를
--purge-conversation으로 지우면 위 경로로 A 의 대화 ID 가 삭제 대상이 될 수 있다(코드 흐름으로 확인, 재현은 안 함). 삭제 직전의 사용 중 검사(stop_session.sh:290-294)는 A 가 실행 중일 때만 막는다. A 가 이미 stop 된 상태라면 A 의 대화가 지워진다.4. 고치는 방향
BaseAgentAdapter.capture_active_session_id를 쓴다.lsof·ps로 확인한다.pre_assigned)라 이 문제와 무관하다. 동작을 바꾸지 않는다.5. (선택) 먼저 뜬 agy 가 등록되지 않는 문제
last_conversations.json의 그 폴더 항목이 나중에 연 쪽으로 바뀌는 것을 확인했다.owner-mismatch, 잘못된 ID 를 기록하지는 않음).presence/<uuid>.lock에서 ID 를 얻으면 같은 폴더에 agy 가 여럿이어도 세션마다 정해진다(본문 제안 1).6. 완료 조건 (회귀 테스트로)
--purge-conversation --yes로 지워도 A 의 대화 파일(conversations/<A>.db, 사이드카,brain/,annotations/,presence/)은 남는다.tests/test_issue40_auto_pin.py,tests/test_issue64_ro_connect.py,tests/test_issue10_13_purge.py가 그대로 통과한다.7. 테스트와 제약
lsof·ps로 재현한다(#40 테스트의 픽스처를 재사용할 수 있다). reconcile 을 거치는 테스트는 mock herdr 에 그 세션을 running agent 로 넣어야 drift C 까지 간다.~/.gemini,~/.codex,~/.hermes,~/.claude, opencode data)의 DB 는 sqlite 로 열지 않는다(읽기 전용도 금지). DB 조회는lib_py.agents.base._ro_connect를 쓴다..mam.env를 export 하지 않는다. 실제.mam, 모니터, herdr 서버를 건드리지 않는다. lib.sh 를 source 하는 테스트는own_workspace_rootfixture 를 쓴다.Refs #63.작업 범위 (v4.9.9 기준 확인 후 확정)
확인 결과
find_workspace_uuid를 직접 호출). 같은 폴더의 agy A·B, 캐시는 A 를 가리킴, B 는 own id 없음:-creator-agy로 끝나는 stop 된 행이 A 의 ID 를 가짐 → B 가 A 의 ID 를 받음creator-agy-<역할>)은-creator-agy로 끝나지 않으므로, 본문 사례의 원인은 캐시 쪽이다.추가로 찾은 것
resume_session.sh가agy --conversation <추측한 ID>로 세션을 띄운 다음(:245) 레지스트리를 갱신하고, 중복 검사(atomic_yaml.py:143)는 그 갱신에서야 돈다. 본문의VALIDATE: Duplicate running conversation ID가 났을 때 writer 쪽 pane 에는 이미 image 의 대화가 열려 있었다.:155)도 행에 ID 가 없으면 추측한 ID 를 실행 중인 행에 기록한다. 상대 행이 stop 된 상태면 중복 검사도 통과한다.BaseAgentAdapter.capture_active_session_id)에는 소유 확인이 없다(후보 하나·세션보다 새것·다른 행이 기록하지 않은 것만). 같은 폴더에 같은 종류 둘이 다 ID 가 없으면 틀릴 수 있다. 아래 "생성 순서 규칙" 으로 함께 막는다.이번에 고치는 것
find_workspace_uuid의 추측 대신 #40 의 판정(autopin.capture→capture_active_session_id)을 쓴다. claude·grok 은 바꾸지 않는다.--no-onboard로 만든 세션은 첫 대화 전까지 ID 가 생기지 않아 뒤 세션이 상한까지 기다린다. 이 옵션은 #68 에서 없앤다.완료 조건
위 댓글 6 의 1~5 에 더해:
6. 같은 폴더·같은 종류 에이전트의 create 가 겹치면 뒤 create 는 앞 세션의 ID 가 기록된 뒤에 띄워진다. 상한을 넘기면 아무것도 띄우지 않고 거부한다.
7. resume 이 다른 행이 기록한 ID 로 에이전트를 띄우지 않는다(띄우기 전에 거부).
테스트와 제약은 위 댓글 7 그대로다. 버전 번호와 VERSIONS.md 는 건드리지 않고, PR 본문은
Refs #63.PR #69 병합 (v4.10.0)
PR #69 를 리뷰·테스트한 뒤
main에 병합했다(머지 커밋0461a63, 릴리스 커밋0454fe9). 변경 요약은VERSIONS.md의v4.10.0항목에 있다.리뷰 중 추가된 수정
4인 리뷰(
--all-reviewer, 리뷰 전용 루프)에서 codex 가 고아 "생성 중" 표시 처리의 차단 결함을 회차마다 찾았고, creator 가 아래 커밋으로 고쳤다.ede9c628f641747cac6f6server_not_running은 "모름", 상한 0 이면 herdr 를 묻지 않음1614af5마지막 루프 1회차(@
1614af5)에서 4/4 PASS.검증 (macOS, Python 3.9.6, @
1614af5)tests/test_issue50_dispatch_herdr.py::test_c11_…하나다. 이 PR 은 위임 코드를 건드리지 않고, 단독으로 세 번 돌리면 매번 통과했다(시간 의존).tests/test_issue63_same_cwd_ids.py27개 통과.남은 비차단 (리뷰어 의견)
MAM_ID_ORDER_WAIT_S까지 기다린다. 거부 메시지가herdr kill-session명령을 알려 준다.--no-onboard세션 뒤 create 의 대기는 #68 에서 다룬다.Linux 확인 제안
작성자의 Linux 스위트와 실제 agy 두 개 시험은
a907cc5·d115bf7기준이었다. 그 뒤id_order.py의 herdr 조회 부분이 바뀌었으므로, Linux 에서 다음을 한 번 확인하면 이슈를 닫을 수 있다.main(v4.10.0)에서tests/test_issue63_same_cwd_ids.py와 전체 스위트.Linux 확인 (v4.10.0,
main@0461a63)환경: Linux, Python 3.14.7, bash 5.3, herdr 0.9.3, agy 1.4.6.
1. 테스트
tests/test_issue63_same_cwd_ids.py: 27 passed.XDG_*를 비우고-p no:cacheprovider, 트리 밖--basetemp로 돌렸다.2. 같은 폴더 agy 동시 create
main의 별도 복사본과 별도 herdr 서버를 썼다. A 를 띄우고 3 초 뒤 B 를 띄웠다.id-order: waiting for <A> …를 내고 기다렸다.3. 앞 create 를 띄운 직후 kill
C 의 create 를 pane 이 뜨자마자(약 1 초) 프로세스 그룹째 SIGKILL 했다. C 의 pane 은 살아 있고 행은 등록되지 않았다. "생성 중" 표시에는 herdr 서버가 기록되어 있었다.
MAM_ID_ORDER_WAIT_S=30으로 D 를 create 했다.ERROR: <C> in <cwd> still has no recorded agy id after 30s; not starting <D> …ERROR: <C>'s create/resume exited before recording it, and its herdr session is alive; close it with: HERDR_SESSION_NAME=<server> herdr kill-session -t <C>정리
--purge-conversation --yes로 지웠다. 대화 파일이 남지 않았고 행도 없다.cache/last_conversations.json·history.jsonl에는 지운 ID 가 남아 있다(#40 과 같음).고칠 것은 찾지 못했다. 이 이슈는 닫아도 된다.
Linux 확인 결과 확인
v4.10.0(main @
0461a63)의 Linux 확인 결과, 제안한 세 항목이 모두 기대대로 동작했다.test_issue63_same_cwd_ids.py27 passed. 전체 스위트는 실패 없이 2135 passed, 19 skipped. macOS 에서 한 번 실패한test_issue50_dispatch_herdr.py::test_c11_…도 통과했다.herdr kill-session명령으로 C 를 정리하자, D 가 바로 떴다. 이때 고아 표시도 지워졌다.ede9c62·8f64174·7cac6f6·1614af5)가 실제 herdr 에서도 의도대로 동작한다.agy 의
last_conversations.json·history.jsonl에 지운 ID 가 남는 것은 #40 과 같은 기존 동작이라 이 이슈의 범위가 아니다. 완료 조건을 모두 확인했으므로 닫는다.