에이전트의 생존·전달 판정을 TUI 스크래핑(herdr agent_status, pane 문자열, 화면 변화)에서 떼어내어, 에이전트가 스스로 발행하는 이벤트(started/progress/completed/error) 계약으로 완결한다. 이는 #17 로드맵의 1번 항목을 독립시킨 것으로, 오케스트레이터와 다른 호스트에서 도는 원격 에이전트 통합의 선결 조건이다.
왜 지금인가 (Why)
#14 계열 수정(send_keys_safe 전달 확인, _inject_acknowledged, _agent_still_working, _recent_disk_heartbeat, record_local_event)은 같은 호스트에서는 효과가 있었지만 전부 휴리스틱을 한 겹씩 더 쌓는 방식이었다: herdr 상태(codex 는 항상 idle) → pane esc to interrupt → 화면 변화 → 디스크 heartbeat → lsof → ps. 문자열 계약이 없고, CLI 버전이 바뀌면 조용히 깨지며, 타이밍 경합(codex 의 1~2초 턴)이 있고, 틀려도 예외가 아니라 잘못된 판정으로 나타난다.
이 안전장치들은 오케스트레이터의 디스크(events.log, 레지스트리)와 로컬 herdr pane 을 전제한다. 원격 에이전트는 둘 다 공유하지 않으므로 원격에서는 와이어(구독자)가 유일한 신호가 되고, 오늘 구독자가 idle 120초에 살아 있는 job 을 닫은 사례(fa90cc35, 9c6eb8b8, e503701e)가 원격에서는 그대로 재현된다.
반대 근거도 있다: 오늘 브리프로 heartbeat 를 60초 이내로 강제하고 디스크 heartbeat 를 판정에 넣은 뒤로는 루프가 안정적으로 수렴했다. 프로토콜 신호가 충분하면 스크래핑 없이도 된다는 뜻이다.
계약 (Contract)
새 이벤트 종류를 만들지 않는다. 기존 이벤트를 의무·주기·타임아웃 규칙이 있는 계약으로 격상한다.
규칙
내용
heartbeat 의무
에이전트는 작업 중 N초(기본 30s) 이내마다 progress 를 발행한다. 브리프 문장에 기대지 않고 에이전트 쪽 헬퍼가 자동 발행한다(예: publish_event.py --heartbeat <N> 백그라운드 타이머, 또는 래퍼가 주입하는 시작 명령이 타이머를 띄움). 에이전트가 잊어도 된다.
응답 없음 판정
오케스트레이터는 마지막 이벤트로부터 K×N초(기본 3회 미수신 = 90s) 가 지나면 무응답으로 본다. 이 판정에 herdr 상태·pane·디스크는 쓰지 않는다.
전달 확인
프롬프트 주입 후 T초(기본 60s) 안에 started 가 없으면 전달 실패로 fail-closed(재주입 없음 — 단일 주입 계약 유지). pane 변화·busy 토큰은 전달 판정에서 제외.
종료 판정
completed/error 만이 종료다. 터미널 상태 불변(Issue #6 CAS)은 유지.
와이어 내구성
heartbeat QoS 1, job 상태는 retained 토픽(예: <topic_prefix>/state)으로도 발행해 재접속 시 즉시 복구, seq 갭으로 유실 감지, 구독자는 재접속 시 자동 재구독하고 progress 를 포함한 모든 메시지로 idle 창을 리셋.
자격 증명
각 호스트가 자기 .mam.env 로 브로커에 접속(#12 와 정합), job 별 auth_token(HMAC) 유지.
TUI 스크래핑의 남는 자리
(a) 프롬프트 주입과 trust/permission 다이얼로그 처리처럼 화면을 봐야만 하는 조작, (b) 같은 호스트에서 이벤트가 끊겼을 때 "죽었는지 멈췄는지"를 구분하는 진단 정보(로그에 남기되 판정에는 쓰지 않음). 남는 스크래핑은 CLI 버전별 계약 테스트(test_a4, test_c1 이 씨앗)로 고정한다.
구현 범위 (Scope)
lib_py/delegation/publish_event.py: --heartbeat <N> (백그라운드 타이머, 부모 종료 시 함께 종료, 같은 dispatch_token/attempt_id 로 서명).
래퍼 multi-agent-mux-delegate-job 의 주입 명령: 에이전트에게 heartbeat 헬퍼 기동을 포함한 시작 명령을 전달. _inject_acknowledged 는 "started 수신"으로, R1 유예(_agent_still_working·_recent_disk_heartbeat)는 "마지막 이벤트로부터 K×N" 규칙으로 단순화.
lib_py/delegation/job_subscriber.py: 재접속·재구독, seq 갭 감지, idle 창 리셋 규칙, retained 상태 토픽 구독.
lib.sh send_keys_safe: 전달 확인을 started 대기로 교체(pane 변화 신호 제거). herdr agent prompt 는 주입 수단으로만.
lib_py/loop/runner.py: N·K·T 를 job 등록 시 전달(--idle-timeout 을 K×N 으로 유도).
원격 세션 어댑터(또는 agent_session 이 herdr: 가 아닌 경우)에서는 herdr·pane·lsof·ps 경로를 건너뛰는 분기.
문서: MESSAGING.md(계약 표), MULTI_AGENT_RULES.md Timeout 절, 8개 SKILL.md 의 관련 서술.
수용 기준 (Acceptance)
herdr 상태가 항상 idle 이고 pane 이 비어 있어도(헤드리스), heartbeat 만으로 K×N 동안 job 이 살아 있고 completed 를 받으면 rc 0.
주입 후 T초 안에 started 가 없으면 rc 1(전달 실패), 재주입 없음, 레지스트리 error.
heartbeat 가 K×N 을 넘겨 끊기면 deadline_timeout 으로 닫히고, 그 뒤 도착한 completed 는 거부(기존 CAS).
브로커 재접속 후 retained 상태로 즉시 복구되고, seq 갭이 로그·감사 기록에 남는다.
send_keys_safe 의 pane 변화·busy 토큰 신호 제거 후에도 test_issue14_delivery_ack.py 의 fail-closed 계약(무신호 rc 4 등)은 started 기반으로 동치 유지.
우선순위
High.#17 항목 2(정족수)와 함께 루프 수렴 시간에 직결되고, 원격 통합의 전제이다. #17 항목 4(셸 계층 축소)는 이 작업이 자리 잡은 뒤 진행.
관련
#17 로드맵(항목 1 → 본 이슈로 분리), #16 전달 확인·생존 판정 빈틈(항목 2·3·4·8 흡수), #14(closed) 같은 호스트 해결분, #12(closed 예정) 호스트별 자격 증명, IMPROVEMENTS.md A-2.
## 목표 (Goal)
에이전트의 **생존·전달 판정을 TUI 스크래핑(herdr `agent_status`, pane 문자열, 화면 변화)에서 떼어내어, 에이전트가 스스로 발행하는 이벤트(`started`/`progress`/`completed`/`error`) 계약으로 완결**한다. 이는 #17 로드맵의 1번 항목을 독립시킨 것으로, 오케스트레이터와 다른 호스트에서 도는 **원격 에이전트 통합의 선결 조건**이다.
## 왜 지금인가 (Why)
- #14 계열 수정(`send_keys_safe` 전달 확인, `_inject_acknowledged`, `_agent_still_working`, `_recent_disk_heartbeat`, `record_local_event`)은 같은 호스트에서는 효과가 있었지만 전부 **휴리스틱을 한 겹씩 더 쌓는** 방식이었다: herdr 상태(codex 는 항상 `idle`) → pane `esc to interrupt` → 화면 변화 → 디스크 heartbeat → `lsof` → `ps`. 문자열 계약이 없고, CLI 버전이 바뀌면 조용히 깨지며, 타이밍 경합(codex 의 1~2초 턴)이 있고, 틀려도 예외가 아니라 잘못된 판정으로 나타난다.
- 이 안전장치들은 **오케스트레이터의 디스크**(`events.log`, 레지스트리)와 **로컬 herdr pane** 을 전제한다. 원격 에이전트는 둘 다 공유하지 않으므로 원격에서는 와이어(구독자)가 유일한 신호가 되고, 오늘 구독자가 idle 120초에 살아 있는 job 을 닫은 사례(`fa90cc35`, `9c6eb8b8`, `e503701e`)가 원격에서는 그대로 재현된다.
- 반대 근거도 있다: 오늘 브리프로 heartbeat 를 60초 이내로 강제하고 디스크 heartbeat 를 판정에 넣은 뒤로는 루프가 안정적으로 수렴했다. 프로토콜 신호가 충분하면 스크래핑 없이도 된다는 뜻이다.
## 계약 (Contract)
새 이벤트 종류를 만들지 않는다. 기존 이벤트를 **의무·주기·타임아웃 규칙이 있는 계약**으로 격상한다.
| 규칙 | 내용 |
|---|---|
| **heartbeat 의무** | 에이전트는 작업 중 N초(기본 30s) 이내마다 `progress` 를 발행한다. 브리프 문장에 기대지 않고 **에이전트 쪽 헬퍼가 자동 발행**한다(예: `publish_event.py --heartbeat <N>` 백그라운드 타이머, 또는 래퍼가 주입하는 시작 명령이 타이머를 띄움). 에이전트가 잊어도 된다. |
| **응답 없음 판정** | 오케스트레이터는 마지막 이벤트로부터 K×N초(기본 3회 미수신 = 90s) 가 지나면 무응답으로 본다. 이 판정에 herdr 상태·pane·디스크는 쓰지 않는다. |
| **전달 확인** | 프롬프트 주입 후 T초(기본 60s) 안에 `started` 가 없으면 전달 실패로 fail-closed(재주입 없음 — 단일 주입 계약 유지). pane 변화·busy 토큰은 전달 판정에서 제외. |
| **종료 판정** | `completed`/`error` 만이 종료다. 터미널 상태 불변(Issue #6 CAS)은 유지. |
| **와이어 내구성** | heartbeat QoS 1, job 상태는 retained 토픽(예: `<topic_prefix>/state`)으로도 발행해 재접속 시 즉시 복구, `seq` 갭으로 유실 감지, 구독자는 재접속 시 자동 재구독하고 `progress` 를 포함한 모든 메시지로 idle 창을 리셋. |
| **자격 증명** | 각 호스트가 자기 `.mam.env` 로 브로커에 접속(#12 와 정합), job 별 `auth_token`(HMAC) 유지. |
| **TUI 스크래핑의 남는 자리** | (a) 프롬프트 주입과 trust/permission 다이얼로그 처리처럼 화면을 봐야만 하는 조작, (b) 같은 호스트에서 이벤트가 끊겼을 때 "죽었는지 멈췄는지"를 구분하는 **진단 정보**(로그에 남기되 판정에는 쓰지 않음). 남는 스크래핑은 CLI 버전별 계약 테스트(`test_a4`, `test_c1` 이 씨앗)로 고정한다. |
## 구현 범위 (Scope)
- `lib_py/delegation/publish_event.py`: `--heartbeat <N>` (백그라운드 타이머, 부모 종료 시 함께 종료, 같은 `dispatch_token`/`attempt_id` 로 서명).
- 래퍼 `multi-agent-mux-delegate-job` 의 주입 명령: 에이전트에게 heartbeat 헬퍼 기동을 포함한 시작 명령을 전달. `_inject_acknowledged` 는 "`started` 수신"으로, R1 유예(`_agent_still_working`·`_recent_disk_heartbeat`)는 "마지막 이벤트로부터 K×N" 규칙으로 단순화.
- `lib_py/delegation/job_subscriber.py`: 재접속·재구독, `seq` 갭 감지, idle 창 리셋 규칙, retained 상태 토픽 구독.
- `lib.sh send_keys_safe`: 전달 확인을 `started` 대기로 교체(pane 변화 신호 제거). herdr `agent prompt` 는 주입 수단으로만.
- `lib_py/loop/runner.py`: N·K·T 를 job 등록 시 전달(`--idle-timeout` 을 K×N 으로 유도).
- 원격 세션 어댑터(또는 `agent_session` 이 `herdr:` 가 아닌 경우)에서는 herdr·pane·`lsof`·`ps` 경로를 건너뛰는 분기.
- 문서: `MESSAGING.md`(계약 표), `MULTI_AGENT_RULES.md` Timeout 절, 8개 SKILL.md 의 관련 서술.
## 수용 기준 (Acceptance)
1. herdr 상태가 항상 `idle` 이고 pane 이 비어 있어도(헤드리스), heartbeat 만으로 K×N 동안 job 이 살아 있고 `completed` 를 받으면 rc 0.
2. 주입 후 T초 안에 `started` 가 없으면 rc 1(전달 실패), 재주입 없음, 레지스트리 `error`.
3. heartbeat 가 K×N 을 넘겨 끊기면 `deadline_timeout` 으로 닫히고, 그 뒤 도착한 `completed` 는 거부(기존 CAS).
4. 브로커 재접속 후 retained 상태로 즉시 복구되고, `seq` 갭이 로그·감사 기록에 남는다.
5. 오케스트레이터 디스크를 공유하지 않는 "원격" 시뮬레이션(별도 tmp HOME, 별도 프로세스, 브로커만 공유)으로 `--all-reviewer` 루프가 수렴한다.
6. `send_keys_safe` 의 pane 변화·busy 토큰 신호 제거 후에도 `test_issue14_delivery_ack.py` 의 fail-closed 계약(무신호 rc 4 등)은 `started` 기반으로 동치 유지.
## 우선순위
**High.** #17 항목 2(정족수)와 함께 루프 수렴 시간에 직결되고, 원격 통합의 전제이다. #17 항목 4(셸 계층 축소)는 이 작업이 자리 잡은 뒤 진행.
## 관련
- #17 로드맵(항목 1 → 본 이슈로 분리), #16 전달 확인·생존 판정 빈틈(항목 2·3·4·8 흡수), #14(closed) 같은 호스트 해결분, #12(closed 예정) 호스트별 자격 증명, `IMPROVEMENTS.md` A-2.
main 5611859 에 병합되어 해결되었습니다 (v4.5.0, 브랜치 fix/issue-18).
계약 (MESSAGING.md §2.5)
heartbeat 의무: publish_event --event started --heartbeat N(기본 30)이 분리 타이머로 progress를 자동 발행. 타이머는 터미널 이벤트(seq lock 아래 barrier + drain), 재dispatch, timeout_sec, 감시 대상 에이전트 프로세스 소멸(--watch-pid, 기본은 가장 가까운 에이전트 CLI 조상) 시 멈추고, 잡별 lifecycle lock 으로 소유권이 직렬화됩니다.
무응답 판정: 마지막 이벤트로부터 K×N(기본 4×30=120 s, --idle-timeout override). herdr 상태·pane 은 진단 로그로만 남깁니다.
전달 확인: 주입 후 T(기본 180 s) 내 started 없으면 rc 1 agent_blocked, 재주입 없음. send_keys_safe 의 pane 변화·busy 토큰 신호 제거.
와이어 내구성: 이벤트마다 같은 연결로 retained <prefix>/state; 구독자는 재접속 시 터미널 복구, seq 갭·재접속 감사 기록, seq 가 진전된 state 만 idle 갱신.
원격 전송: --agent-session remote:<name> → 서명(MAM_DISPATCH_SECRET)된 retained dispatch, remote.py run 러너가 자기 레지스트리에 미러링·실행·터미널 발행, 구독자 --remote 가 오케스트레이터 레지스트리에 반영. attempt 원자적 admission, 명령 프로세스 그룹 종료.
수용 기준: 1·2·3·4·6 테스트로 검증(tests/test_issue18_liveness_contract.py 28건). 5는 브로커만 공유하는 원격 러너의 단일 잡 시뮬레이션과 러너 라벨 단위 테스트까지 검증했고, 원격 리뷰어를 포함한 --all-reviewer 루프 수렴은 실제 원격 호스트 환경에서 확인이 필요합니다. 레거시 MAM_LOOP_PY=0 경로는 원격 미지원(문서화).
리뷰: claude·agy·grok·codex 4인 [VERDICT: PASS](최종 HEAD 263d6bf). 전체 스위트 906 통과. 후속: #20(테스트 감사 로그 위생).
main `5611859` 에 병합되어 해결되었습니다 (v4.5.0, 브랜치 `fix/issue-18`).
**계약 (MESSAGING.md §2.5)**
- heartbeat 의무: `publish_event --event started --heartbeat N`(기본 30)이 분리 타이머로 `progress`를 자동 발행. 타이머는 터미널 이벤트(seq lock 아래 barrier + drain), 재dispatch, `timeout_sec`, 감시 대상 에이전트 프로세스 소멸(`--watch-pid`, 기본은 가장 가까운 에이전트 CLI 조상) 시 멈추고, 잡별 lifecycle lock 으로 소유권이 직렬화됩니다.
- 무응답 판정: 마지막 이벤트로부터 K×N(기본 4×30=120 s, `--idle-timeout` override). herdr 상태·pane 은 진단 로그로만 남깁니다.
- 전달 확인: 주입 후 T(기본 180 s) 내 `started` 없으면 rc 1 `agent_blocked`, 재주입 없음. send_keys_safe 의 pane 변화·busy 토큰 신호 제거.
- 와이어 내구성: 이벤트마다 같은 연결로 retained `<prefix>/state`; 구독자는 재접속 시 터미널 복구, seq 갭·재접속 감사 기록, seq 가 진전된 state 만 idle 갱신.
- 원격 전송: `--agent-session remote:<name>` → 서명(`MAM_DISPATCH_SECRET`)된 retained dispatch, `remote.py run` 러너가 자기 레지스트리에 미러링·실행·터미널 발행, 구독자 `--remote` 가 오케스트레이터 레지스트리에 반영. attempt 원자적 admission, 명령 프로세스 그룹 종료.
- 루프 러너: `MAM_HEARTBEAT_SEC/MAM_HEARTBEAT_MISSES/MAM_ACK_TIMEOUT_S` 전달, YAML `transport: remote` 행 → `remote:<name>`.
**수용 기준**: 1·2·3·4·6 테스트로 검증(`tests/test_issue18_liveness_contract.py` 28건). 5는 브로커만 공유하는 원격 러너의 단일 잡 시뮬레이션과 러너 라벨 단위 테스트까지 검증했고, 원격 리뷰어를 포함한 `--all-reviewer` 루프 수렴은 실제 원격 호스트 환경에서 확인이 필요합니다. 레거시 `MAM_LOOP_PY=0` 경로는 원격 미지원(문서화).
**리뷰**: claude·agy·grok·codex 4인 `[VERDICT: PASS]`(최종 HEAD 263d6bf). 전체 스위트 906 통과. 후속: #20(테스트 감사 로그 위생).
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.
목표 (Goal)
에이전트의 생존·전달 판정을 TUI 스크래핑(herdr
agent_status, pane 문자열, 화면 변화)에서 떼어내어, 에이전트가 스스로 발행하는 이벤트(started/progress/completed/error) 계약으로 완결한다. 이는 #17 로드맵의 1번 항목을 독립시킨 것으로, 오케스트레이터와 다른 호스트에서 도는 원격 에이전트 통합의 선결 조건이다.왜 지금인가 (Why)
send_keys_safe전달 확인,_inject_acknowledged,_agent_still_working,_recent_disk_heartbeat,record_local_event)은 같은 호스트에서는 효과가 있었지만 전부 휴리스틱을 한 겹씩 더 쌓는 방식이었다: herdr 상태(codex 는 항상idle) → paneesc to interrupt→ 화면 변화 → 디스크 heartbeat →lsof→ps. 문자열 계약이 없고, CLI 버전이 바뀌면 조용히 깨지며, 타이밍 경합(codex 의 1~2초 턴)이 있고, 틀려도 예외가 아니라 잘못된 판정으로 나타난다.events.log, 레지스트리)와 로컬 herdr pane 을 전제한다. 원격 에이전트는 둘 다 공유하지 않으므로 원격에서는 와이어(구독자)가 유일한 신호가 되고, 오늘 구독자가 idle 120초에 살아 있는 job 을 닫은 사례(fa90cc35,9c6eb8b8,e503701e)가 원격에서는 그대로 재현된다.계약 (Contract)
새 이벤트 종류를 만들지 않는다. 기존 이벤트를 의무·주기·타임아웃 규칙이 있는 계약으로 격상한다.
progress를 발행한다. 브리프 문장에 기대지 않고 에이전트 쪽 헬퍼가 자동 발행한다(예:publish_event.py --heartbeat <N>백그라운드 타이머, 또는 래퍼가 주입하는 시작 명령이 타이머를 띄움). 에이전트가 잊어도 된다.started가 없으면 전달 실패로 fail-closed(재주입 없음 — 단일 주입 계약 유지). pane 변화·busy 토큰은 전달 판정에서 제외.completed/error만이 종료다. 터미널 상태 불변(Issue #6 CAS)은 유지.<topic_prefix>/state)으로도 발행해 재접속 시 즉시 복구,seq갭으로 유실 감지, 구독자는 재접속 시 자동 재구독하고progress를 포함한 모든 메시지로 idle 창을 리셋..mam.env로 브로커에 접속(#12 와 정합), job 별auth_token(HMAC) 유지.test_a4,test_c1이 씨앗)로 고정한다.구현 범위 (Scope)
lib_py/delegation/publish_event.py:--heartbeat <N>(백그라운드 타이머, 부모 종료 시 함께 종료, 같은dispatch_token/attempt_id로 서명).multi-agent-mux-delegate-job의 주입 명령: 에이전트에게 heartbeat 헬퍼 기동을 포함한 시작 명령을 전달._inject_acknowledged는 "started수신"으로, R1 유예(_agent_still_working·_recent_disk_heartbeat)는 "마지막 이벤트로부터 K×N" 규칙으로 단순화.lib_py/delegation/job_subscriber.py: 재접속·재구독,seq갭 감지, idle 창 리셋 규칙, retained 상태 토픽 구독.lib.sh send_keys_safe: 전달 확인을started대기로 교체(pane 변화 신호 제거). herdragent prompt는 주입 수단으로만.lib_py/loop/runner.py: N·K·T 를 job 등록 시 전달(--idle-timeout을 K×N 으로 유도).agent_session이herdr:가 아닌 경우)에서는 herdr·pane·lsof·ps경로를 건너뛰는 분기.MESSAGING.md(계약 표),MULTI_AGENT_RULES.mdTimeout 절, 8개 SKILL.md 의 관련 서술.수용 기준 (Acceptance)
idle이고 pane 이 비어 있어도(헤드리스), heartbeat 만으로 K×N 동안 job 이 살아 있고completed를 받으면 rc 0.started가 없으면 rc 1(전달 실패), 재주입 없음, 레지스트리error.deadline_timeout으로 닫히고, 그 뒤 도착한completed는 거부(기존 CAS).seq갭이 로그·감사 기록에 남는다.--all-reviewer루프가 수렴한다.send_keys_safe의 pane 변화·busy 토큰 신호 제거 후에도test_issue14_delivery_ack.py의 fail-closed 계약(무신호 rc 4 등)은started기반으로 동치 유지.우선순위
High. #17 항목 2(정족수)와 함께 루프 수렴 시간에 직결되고, 원격 통합의 전제이다. #17 항목 4(셸 계층 축소)는 이 작업이 자리 잡은 뒤 진행.
관련
IMPROVEMENTS.mdA-2.main
5611859에 병합되어 해결되었습니다 (v4.5.0, 브랜치fix/issue-18).계약 (MESSAGING.md §2.5)
publish_event --event started --heartbeat N(기본 30)이 분리 타이머로progress를 자동 발행. 타이머는 터미널 이벤트(seq lock 아래 barrier + drain), 재dispatch,timeout_sec, 감시 대상 에이전트 프로세스 소멸(--watch-pid, 기본은 가장 가까운 에이전트 CLI 조상) 시 멈추고, 잡별 lifecycle lock 으로 소유권이 직렬화됩니다.--idle-timeoutoverride). herdr 상태·pane 은 진단 로그로만 남깁니다.started없으면 rc 1agent_blocked, 재주입 없음. send_keys_safe 의 pane 변화·busy 토큰 신호 제거.<prefix>/state; 구독자는 재접속 시 터미널 복구, seq 갭·재접속 감사 기록, seq 가 진전된 state 만 idle 갱신.--agent-session remote:<name>→ 서명(MAM_DISPATCH_SECRET)된 retained dispatch,remote.py run러너가 자기 레지스트리에 미러링·실행·터미널 발행, 구독자--remote가 오케스트레이터 레지스트리에 반영. attempt 원자적 admission, 명령 프로세스 그룹 종료.MAM_HEARTBEAT_SEC/MAM_HEARTBEAT_MISSES/MAM_ACK_TIMEOUT_S전달, YAMLtransport: remote행 →remote:<name>.수용 기준: 1·2·3·4·6 테스트로 검증(
tests/test_issue18_liveness_contract.py28건). 5는 브로커만 공유하는 원격 러너의 단일 잡 시뮬레이션과 러너 라벨 단위 테스트까지 검증했고, 원격 리뷰어를 포함한--all-reviewer루프 수렴은 실제 원격 호스트 환경에서 확인이 필요합니다. 레거시MAM_LOOP_PY=0경로는 원격 미지원(문서화).리뷰: claude·agy·grok·codex 4인
[VERDICT: PASS](최종 HEAD263d6bf). 전체 스위트 906 통과. 후속: #20(테스트 감사 로그 위생).