release(v4.8.0): bump framework and eight skills to 4.8.0 in lockstep; archive PR #41 reviews (three rounds)
Issue #33 (job budget, wall_clock_timeout vs deadline_timeout) and Issue #30 (notify-only stall watcher). Reviews of PR #41 in the merge workspace: round 1 (@1673818) claude N1 / codex R1 R2 NOT PASS, agy grok PASS; round 2 (@af0a2a2) grok N1 / codex D1 D2 NOT PASS, claude agy PASS; round 3 (@9200562) 4/4 PASS. Reports and the round-3 identity sidecars go to .agents/reports/<session>/. The design consensus on items A-D is in the VERSIONS.md entry. 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
9200562c91
commit
53f87e4560
32 files changed
+1263
-26
No files matched your search
@@ -0,0 +1,48 @@
|
||||
# 🔍 Review Report — Job 0d52f435 (PR #41 @ `1673818`)
|
||||
|
||||
- **Reviewer**: `reviewer-grok-01` · 2026-09-26
|
||||
- **검증 경로**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260926T025230Z-a30a6d/1`
|
||||
- **identity**: base `8ea65bb` head `1673818` tree `00d5e03` (일치)
|
||||
- **본 체크아웃**: `main` `8ea65bb` 유지. stall_watch 를 실제 세션에 붙이지 않음.
|
||||
|
||||
`git diff --check 8ea65bb...HEAD` 클린. 6커밋.
|
||||
|
||||
## 코드
|
||||
|
||||
**(1) 예산.** `--timeout` > `MAM_JOB_TIMEOUT_S`(양의 정수, 아니면 경고 후 3600) > 3600. 루프 `contract_args` 가 모든 `_dispatch` 에 `--timeout str(job_budget)` 을 붙인다. `job_wait_s` = 예산 + `MAM_WAIT_GRACE_S`(기본 300). `MAM_CHILD_WAIT_S` 가 있으면 그 값, 없으면 `job_wait_s`. 원격 러너는 `timeout_sec` 초과를 `wall_clock_timeout: …` 로, 관측 실패를 `wall_clock_timeout (remote runner)` 로 닫고 첫 단어 분류가 `CLOSED-UNCONFIRMED` 에 넣는다.
|
||||
|
||||
**(2) 라벨.** 구독자 `RC_*` 와 래퍼 `SUB_RC_*` 가 0/1/2/3/4. `test_issue5_phase1_delegation.py` 가 이름과 값을 맞춘다. `finalize_unfinished` 는 rc 4 또는 디스크 유예가 `START_TIME+TIMEOUT` 에 닿으면 `wall_clock_timeout`, 그 외 침묵은 `deadline_timeout`. preflight/idle_wait 는 `cancelled`. `delegate-job wait` 는 rc 4 를 rc 2 로 접는다(G-8). 닫은 뒤 디스크가 `completed` 면 래퍼는 0 으로 돌아간다. 이미 `error` 인 레코드에 늦은 `completed` 는 엔진 터미널 전이가 거부한다.
|
||||
|
||||
**(3) 감시자.** `stall_watch.py` 의 유일한 발행은 `_notify` 의 `progress`. `close`/`error`/`cancelled` 호출이 없다. herdr `agent wait`(`watcher.wait_once`)로 상태를 받고, 판단 직전에 `agent get`·process-info·`agent read` 로 자손과 입력창을 본다. `blocked` 는 `stalled:blocked` + detection 한 줄, 키 입력 없음. `idle` 은 `MAM_STALL_IDLE_S`(600), 상주 자식·heartbeat 타이머 제외, 입력창이 비었을 때만 잡당 1회 `stalled:idle` + nudge. 원격·human·`MAM_STALL_WATCH=0` 은 기동하지 않는다. herdr 실패는 경고 후 `MAM_STALL_HERDR_FAIL_S`(120) 에 exit 2. 래퍼는 SIGTERM + `wait`. `.stall.out` 은 로그로 남는다(프로세스는 회수).
|
||||
|
||||
**(4) 경계.** 새 bash 판정은 셋이다. 항목 4 이전이 예고돼 있으므로 비차단.
|
||||
|
||||
| bash | 하는 일 | 분류 |
|
||||
|---|---|---|
|
||||
| `FINALIZE_CLOCK` | 디스크 유예가 벽시계에 닿았는지로 라벨을 고름 | 판정. 구독자 rc 4 는 Python 이 이미 정함 |
|
||||
| `MAM_JOB_TIMEOUT_S` | 양의 정수가 아니면 3600 | 작은 검증 |
|
||||
| rc → `close_req` | 2/4/preflight 를 라벨로 매핑 | 나르기. 매핑표는 bash 에 있음 |
|
||||
|
||||
**(5) 테스트.** G-8 은 두 시계가 같은 rc 2 였던 단언을 rc 4(벽시계)로 고쳤다. 새 테스트는 mock herdr·tmp. 1회차 스위트에서 `test_b3_a_heartbeating_job…` 가 공개 브로커가 heartbeat 를 놓쳐 구독자는 idle 로 끝났고(래퍼 라벨은 이미 `wall_clock_timeout`), 재실행은 **1 passed in 18s**.
|
||||
|
||||
**(6)–(7)** loop SKILL 하위 위임 절이 `wall_clock_timeout` 을 `CLOSED-UNCONFIRMED` 에 넣는다. delegate-job/loop SKILL 에 `--job-timeout`·`MAM_JOB_TIMEOUT_S`·감시자 변수가 있다. 4.8.0 bump 는 오케스트레이터 예정.
|
||||
|
||||
## 설계 합의
|
||||
|
||||
**(A) 동의.** 갱신 1(알림만, 자동 수락 없음, 판정으로 닫지 않음)을 `stall_watch.py` 가 강제한다. 종결은 #33 시계뿐이다.
|
||||
|
||||
**(B) 동의.** 러너가 래퍼를 동기 호출하므로 연장 정책을 그 안에서 물을 수 없다. #39 위험은 에이전트가 `progress` 로 연장을 반복하는 것이다. 보완: 연장 횟수 상한을 프로토콜과 별개로 두고, heartbeat 만 있고 `state_change_seq` 가 그대로면 거절한다.
|
||||
|
||||
**(C) 조건부 동의.** 트리거는 `completed` 를 검증하는 쪽이어야 한다. 워크트리 creator(`MAM_NO_SUBSCRIBER_DAEMON=1`)도 잡마다 래퍼 구독자가 `completed` 를 받으므로 핀은 그 구독자가 맞고, 루프 `wait_for_job` 은 구독자가 없을 때의 보조다. 항목 4 가 종결 처리를 Python 으로 옮긴 그 함수에 붙이면 된다.
|
||||
|
||||
**(D) 동의.** 라벨 분리만으로 #33 은 성립하고, 종결 처리 이전은 더 큰 변경이다. 항목 4 때 Python 으로 옮길 것은 `FINALIZE_CLOCK` 선택, `MAM_JOB_TIMEOUT_S` 검증, rc→`close_req` 매핑이다. bash 에는 `start_stall_watch`/`stop_stall_watch` 와 키 주입만 남긴다.
|
||||
|
||||
## 테스트
|
||||
|
||||
`test_issue30_stall_watch.py` + `test_issue33_budget.py` + `test_tier1_unit.py`: **1 failed, 86 passed, 48 skipped**, 이어서 실패 1건 재실행 **passed**.
|
||||
|
||||
## 종합
|
||||
|
||||
#33 두 시계와 #30 알림 전용이 코드와 맞다. bash 의 라벨 선택은 예고된 이전이 남았고 병합을 막지 않는다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20260926T062809Z-914379",
|
||||
"base_sha": "af0a2a2557768fa316203b177e1b90e9f7283a1a",
|
||||
"head_sha": "9200562c914ab4f6e6d8a599cac371a07f99b25d",
|
||||
"tree_sha": "c8425e2259544c3a34210858f331b0563f15325c",
|
||||
"review_commit": "9200562c914ab4f6e6d8a599cac371a07f99b25d",
|
||||
"reviewer": "reviewer-grok-01",
|
||||
"job_id": "47664c25",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-09-26T07:07:06Z"
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# 🔍 Review Report — Job 47664c25 (PR #41 3회차 `af0a2a2...9200562`)
|
||||
|
||||
- **Reviewer**: `reviewer-grok-01` · 2026-09-26
|
||||
- **검증 경로**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260926T062809Z-914379/1`
|
||||
- **identity**: head `9200562` tree `c8425e2` (일치)
|
||||
- **본 체크아웃**: `main` `8ea65bb` 유지
|
||||
|
||||
`git diff --check af0a2a2...HEAD` 클린. 커밋 1개.
|
||||
|
||||
## (1) KILL — 타당
|
||||
|
||||
`stop_stall_watch` (`multi-agent-mux-delegate-job:711-716`) 는 살아 있으면 `kill -9` 후 `wait`. 감시자의 TERM 핸들러는 `os._exit` 뿐이라 flush 할 상태가 없다. KILL 은 fork-exec 창에서도 삼켜지지 않고, `wait` 는 즉시 좀비를 거둔다. `STALL_PID` 가 비면 return. 이미 끝났으면 `wait` 로 rc 만 보고한다. herdr CLI 와 `publish_event` 자식은 각자 timeout(30s/120s) 으로 끝난다. 2회차의 5초 예산은 없다.
|
||||
|
||||
## (2)(3) prompt 앞 게이트
|
||||
|
||||
`_may_prompt` (`stall_watch.py`) 는 래퍼가 살아 있고 `strict` 레코드(읽힘, 비종결, 같은 token/attempt)일 때만 키를 보낸다. 발행 rc 는 보지 않고 레코드를 다시 읽는다. 읽기 오류는 strict 에서 거부. `_wrapper_gone` 은 루프 머리, 매 wait 직후(알림보다 앞), `_may_prompt` 에 있다. 게이트와 `prompt()` 사이 마이크로초 창은 레지스트리 락 없이는 못 닫는다. 알림 전용이라 그 창은 받아들여도 된다.
|
||||
|
||||
## (4) 테스트
|
||||
|
||||
신규 4건은 in-process (`wait_once`/`_notify` 주입). 프로세스 테스트는 finally 에서 kill. 잔류를 남기는 경로가 없다.
|
||||
|
||||
## 2회차 N1
|
||||
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| 5초 TERM 예산이 `deadline_timeout` 을 미룸 | **닫힘.** KILL 은 시계를 밀지 않는다. |
|
||||
| `test_issue14` pane-busy | 이 실행에서 **통과**. 작성자가 본 실패(구독자 handshake 타임아웃, `ignored TERM` 없음, 브로커가 ~14초 무전달)는 감시자 5초와 다른 플레이크다. 그 분류에 동의한다. |
|
||||
|
||||
## 테스트
|
||||
|
||||
`test_issue30_stall_watch_stop.py` + `test_issue30_stall_watch.py` + `test_issue14_delivery_ack.py`
|
||||
|
||||
**70 passed, 1 skipped in 426.71s.**
|
||||
|
||||
## 종합
|
||||
|
||||
2회차 N1 과 codex D1/D2 가 코드·이 실행과 맞다. 설계 재작업 불필요.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,43 @@
|
||||
# 🔍 Review Report — Job 4c34bbf5 (PR #41 델타 `1673818...af0a2a2`)
|
||||
|
||||
- **Reviewer**: `reviewer-grok-01` · 2026-09-26
|
||||
- **검증 경로**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260926T052018Z-597343/1`
|
||||
- **identity**: head `af0a2a2` tree `0310682` (일치)
|
||||
- **본 체크아웃**: `main` `8ea65bb` 유지
|
||||
|
||||
`git diff --check 1673818...HEAD` 클린. 커밋 1개.
|
||||
|
||||
## NOT PASS
|
||||
|
||||
### N1. TERM 을 삼킨 감시자를 죽이는 5초가 idle 유예보다 앞에서 돌아, 침묵 잡이 14초 안에 안 닫힌다
|
||||
|
||||
`stop_stall_watch` (`multi-agent-mux-delegate-job:715-723`) 는 TERM 후 최대 5초를 재고 나서야 KILL·`wait` 한다. 그 다음에야 `finalize_unfinished` 의 디스크 유예가 시작된다 (`:385` 다음 `:391`).
|
||||
|
||||
`test_wrapper_idle_timeout_grace_ignores_pane_busy_token` 은 `started` 뒤 14초 침묵이면 rc 2 / `deadline_timeout` 이고, 그 사이 `update --status completed` 는 거부되어야 한다 (`test_issue14_delivery_ack.py:352-361`). 이번 실행 stderr:
|
||||
|
||||
`subscriber ... idle timeout (3s)` → `stall watch ... ignored TERM; killed` → `liveness: ... diagnostic only` → **wrapper rc 0**.
|
||||
|
||||
감시자가 fork-exec 창에서 TERM 을 삼키면 닫기 시계가 약 5초 늦고, 3초 idle + 5초 kill + 3초 유예가 14초를 넘는다. 테스트의 completed 쓰기가 아직 열린 잡에 들어가 래퍼가 0 으로 끝난다. 같은 테스트 재실행도 실패.
|
||||
|
||||
**대안**: 유예 시계를 감시자 kill 과 겹치게 하거나, 이 테스트의 대기를 kill 예산+유예보다 길게(예: 25초) 하라. 전자가 맞다. 삼킨 TERM 이 `deadline_timeout` 자체를 미루면 안 된다.
|
||||
|
||||
## 이 델타에서 닫힌 것
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| N1 무한 `wait` | TERM 5초 후 KILL 뒤 `wait`. `STALL_PID` 비면 즉시 return. 이미 죽었으면 `wait` 로 회수하고 rc 를 stderr 에 남긴다. KILL 이 거부되면 `wait` 가 다시 무한이 될 수 있다(비차단, 같은 uid 에서는 안 남). |
|
||||
| `--watch-pid` | `ProcessLookupError` 만 죽음. `PermissionError` 는 살아 있음 (`stall_watch.py` `_alive`). pid 0 은 그룹 신호라 조기 종료되지 않음. |
|
||||
| R1 trap 없음 | 래퍼가 사라지면 감시자가 다음 herdr wait(≤60초) 안에 rc 0. herdr CLI 는 timeout 안에 끝난다. trap 합성은 항목 4 로 미룬 판단이 맞다. |
|
||||
| R2 | wait 직후와 nudge 직전 `_dispatch_open`. 파일 없음은 종료, 읽기 실패는 계속. 발행 거부면 prompt 없음(`nudged` 유지). blocked 알림도 wait 직후 검사 뒤에만 `_act` 에 들어가므로, 대기 중 닫힌 잡에는 안 나간다. |
|
||||
| M8 | S6 만 `/proc`. r4 stub 은 `time.time()`. macOS 에서 맞다. |
|
||||
| SKILL | 수명 한 줄, v4.8.0, rc 2 는 idle, 예산 소진은 `wall_clock_timeout`, 브로커 행은 예산이 닿으면 wall. |
|
||||
|
||||
1회차 비차단 중 이 커밋이 닫은 것: 감시자 수명(TERM/KILL, watch-pid), S6 의 macOS skip, SKILL 종료 코드 문구. 남은 것: `MAM_TEST_UNRELIABLE_AGENT` 훅, B3 공개 브로커 지연, 라벨 선택의 Python 이전(항목 4).
|
||||
|
||||
## 테스트
|
||||
|
||||
`test_issue30_stall_watch_stop.py` + `test_issue30_stall_watch.py` + `test_issue14_delivery_ack.py` + `test_tier1_unit.py`
|
||||
|
||||
**1 failed, 132 passed, 1 skipped in 575s.** 실패는 위 N1. 신규 종료 테스트 7건은 통과. 재실행도 같은 실패.
|
||||
|
||||
[VERDICT: NOT PASS]
|
||||
Reference in new issue
Block a user