docs(improvements): record O-4 Python Abstract Agent Adapter architecture proposal (100% PASS)

This commit is contained in:
2026-08-09 19:17:34 +09:00
parent 245abe62c8
commit 29f0be5296
4 changed files with 607 additions and 3 deletions
@@ -0,0 +1,82 @@
# Cross-Code Review — Job `0d9712c6`
- **Job ID**: 0d9712c6 · **Reviewer**: cline · **Base**: `245abe6` (working-tree, uncommitted)
- **Task**: `BaseAgentAdapter` (A-4) 아키텍처 설계를 `IMPROVEMENTS.md` 백로그에 등재한 누적 변경분에 대한 교차 코드 리뷰 (lint / 동작성 / 유실)
- **Diff scope**: `IMPROVEMENTS.md` 단일 파일 — `git diff --stat` = **1 file changed, 49 insertions(+), 3 deletions(-)**
---
## 1. 변경분 요약 및 검증 대상
변경분은 코드가 아니라 **문서(백로그)**다. `IMPROVEMENTS.md` §1(아키텍처 결함)에 **A-4 (설계 제안): `BaseAgentAdapter` 어댑터 계층 도입 (Rev.2)** 항목을 신규 등재하고, 상단 집계(`11건 → 12건`, `아키텍처 1건 → 2건`)와 §1 제목 카운트(`1건 → 2건`), 최종 갱신일을 갱신했다. 저장소에 손댄 파일은 `IMPROVEMENTS.md` 하나뿐이다(`git status --porcelain`: ` M IMPROVEMENTS.md`).
브리프에 포함된 diff 헤더와 실제 `git diff`는 정확히 일치한다. `mam_agents` 패키지/자산은 skills·deploy·tests 어디에도 존재하지 않음을 확인(`grep -rn mam_agents` 결과 0건, 보고서 제외) — 즉 이 변경은 순수 설계 기록이며 런타임 영향은 0이다.
| 검증 항목 | 방법 | 결과 |
|---|---|---|
| Diff 일치 (브리프 vs working tree) | `git --no-pager diff IMPROVEMENTS.md` | ✅ 정확 일치 |
| 코드/자산 부재 확인 | `grep -rn mam_agents .agents/skills deploy tests` | ✅ 0건 (순수 문서) |
| 참조 프로토타입 보고서 존재 | `ls .mam/jobs/44062a63/claude-reports/report-final.md` | ✅ 존재 (18 KB) |
---
## 2. Lint (정적 품질)
`IMPROVEMENTS.md`는 Markdown 문서이므로 셸/파이썬 린트 대상이 아니다. Markdown 구조 정합성만 점검했다.
- 헤더 계층(`#`/`##`/`###`/`####`) 일관, 테이블(`단계|내용`) 열 수 정합(2열), 인용 블록(`> 결함 조치가 아니라...`) 정상 종료.
- 인라인 코드 백틱 쌍 정합, 한국어/영문 혼용 깨짐 없음.
- 집계 숫자 변경(상단 `12건`/`아키텍처 2건` ↔ §1 제목 `2건`) 정합. `완료된 과제 10건` 줄과 기존 A-2 항목은 미변경(손대지 않음).
**Lint 결과: PASS** — 구조적 결함 없음.
---
## 3. 동작성 (설계 주장의 코드베이스 정합성)
코드 변경이 없으므로, 설계 제안이 현 코드베이스 사실과 일치하는지(거짓 주장·과장 여부)를 검증했다.
| 설계 주장 | 코드베이스 실측 | 판정 |
|---|---|---|
| `agent → *_id_own` 키 맵 **4벌** | 프로덕션 맵 3곳(`reconcile.sh:434`, `reconcile.sh:583`, `lib.sh:1393`) + 테스트 헬퍼 1곳(`conftest.py:262`) = 4 | ✅ 정합 |
| 세션명→에이전트 추론 **2벌**(규칙 상이) | `reconcile.sh:568 row_agent`(pane.cmd→cmd_full→접미사) vs `run_loop.sh:233-243`(세그먼트 매칭 + 실패 시 `claude` 기본값) | ✅ 정합 — 후자 오판 가능성 실재 확인 |
| `deploy/remove.sh:83-91` `fallback_assets` 미등록 | `remove.sh:83` `fallback_assets=(...)` 리스트 확인 — `.agents/skills/mam_agents` 누락 | ✅ 선행 체크리스트 #1 유효 |
| `tests/test_deploy_freshness.py::test_d2` 가드 | `test_d2_manifestless_removal_strands_no_framework_assets` 존재 | ✅ 선행 체크리스트 #1 근거 유효 |
| `gitea-ci.yml:69-77` flake8/py_compile 범위 제한 | `deploy/gitea-ci.yml:69,71,76``multi-agent-mux-delegate-job/scripts/` 한정 | ✅ 선행 체크리스트 #3 유효 |
| herdr shim `python3 -c` 9곳 에이전트 지식 0 | 본 리뷰 범위 외(프로토타입 실측)이나 참조 보고서 존재 | ⚠️ 미검증(프로토타입 영역) |
| 162 passed / 변이 6/6 / 배포 25/25 | 프로토타입 트리(저장소 미반영) — 재실행 불가 | ⚠️ 미검증(프로토타입 영역) |
프로토타입 실측 수치(hermes shim, 162 passed 등)는 저장소에 반영되지 않은 scratchpad 결과이므로 본 리뷰에서 재검증할 수 없다. 다만 **저장소에 존재하는 사실**(키 맵 산재, 추론 2벌, fallback_assets/CI 범위)은 전부 정확히 확인됐다. 설계가 허위/과장에 기대지 않음.
**동작성 결과: PASS**
---
## 4. 유실 (Loss / Orphan)
`git diff`**삭제 3줄** 모두 교체성 갱신(최종 갱신일, 총 건수, §1 제목 카운트)이며 원 정보 손실 아님:
- `2026-08-08 (B-4 ...)``2026-08-09 (A-4 ... 등재)` : 갱신일 갱신(정당)
- `11건 (아키텍처 1건...)``12건 (아키텍처 2건...)` : 신규 항목 반영(정당)
- `Architecture Flaws — 1건``— 2건` : 항목 증가 반영(정당)
기존 `A-2` 항목 본문, `완료된 과제 10건` 줄, §2~§6 섹션은 미변경(존재 보존 확인). 신규 자산(import/경로) 추가 없으므로 orphan 임포트/변수도 발생하지 않는다.
**유실 결과: PASS** — 부당 삭제/잔재 없음.
---
## 5. 비차단 발견 (Non-blocking Findings)
**N-1 (참조 보고서 내 diff stat 불일치, 비본 diff).** 참조된 프로토타입 보고서(44062a63) 본문에 `git diff --stat: +44 / -3`로 기재됐으나, 실제 working-tree diff는 **+49 / -3**이다. 이는 *참조 보고서*의 기재 오류로, 본 리뷰 대상 diff(`IMPROVEMENTS.md`) 자체의 결함은 아니다. 비차단.
**N-2 (섹션 명칭 vs 항목 성격).** §1 제목이 "아키텍처 **결함**"인데 A-4는 결함이 아닌 **설계 제안**이다. 등재자는 이를 인지하고 항목 제목에 `(설계 제안)`을 명시했으며, "아키텍처 과제"로의 개명은 문서 소유자 판단으로 남겨둠을 명시했다. 비차단 — 의도적 보존.
**N-3 (프로토타입 수치 미검증).** "162 passed / 변이 6/6 / 배포 25/25" 및 hermes shim 9곳 지식-0 주장은 scratchpad 프로토타입 결과로, 저장소에 반영되지 않아 본 리뷰에서 재실행 불가. 설계 근거로서는 참조 보고서 존재로 충분하나, 정식 구현(M0~) 시점에 재측정이 권장됨. 비차단.
---
## 6. 종합 판정
변경분은 `IMPROVEMENTS.md` 단일 문서에 대한 순수 추가적 설계 기록이다. 코드·배포 자산·테스트에 대한 변경이 전무하여 런타임·린트·회귀 영향은 0이다. 설계가 인용한 코드베이스 사실(키 맵 4벌, 추론 2벌 상이, fallback_assets/CI 범위 제한)은 실측 결과 전부 정확하며, 선행 필수 체크리스트 3항이 현 코드베이스의 실제 제약에 기반해 있다. 부당 삭제나 잔재도 없다. 단순 버그 수정 이상의 설계 재작업이 필요한 근거(escalation)는 발견되지 않는다 — 이 변경은 애초에 백로그 설계 제안 등재라는 명시적 산출물이며 그 목표를 충족한다.
[VERDICT: PASS]
@@ -0,0 +1,90 @@
# Cross-Code Review Report — Job 3b42cc9b
- **Job ID**: 3b42cc9b
- **Reviewer**: cline (herdr:canary-projects-multi-agent-mux-creator-cline)
- **Base commit**: `245abe6` (working tree clean — diff reviewed: `9df0fc3..245abe6`)
- **Scope**: Audit `create_session.sh`, `reconcile.sh`, `resolve_session_id.sh`, `lib.sh` for 5 objectives.
- **Output**: `.mam/jobs/3b42cc9b/cline-reports/report-final.md`
## 1. Audit Scope & Method
The task is an **audit** of the current committed state of the four target scripts against five stated objectives:
1. Sequential prompt injection
2. Post-spawn auto-pinning
3. Occupied-ID preemption guard
4. Stage 3 viewport verification
5. No UUID cross-talk or shadowing
Method: read each target file end-to-end, trace each objective from creation → reconcile → resume, run `bash -n` (×4) + embedded-Python `compile()` (×8), then execute the three relevant test suites against the live tree.
## 2. Lint & Test Results
| Check | Result |
|---|---|
| `bash -n` `lib.sh` | PASS |
## 3. Objective-by-Objective Audit
### 3.1 Sequential Prompt Injection — PASS
`create_session.sh` enforces a strict spawn→ready→inject sequence:
- `spawn` (L165) → `wait_for_tui_ready` (L205, polls up to 30×1s for agent-specific ready tokens) → `handle_startup_dialogs` (claude only, L211) → pane meta capture → YAML append → **single** `inject_instructions` call (L376).
- No prompt is injected before the TUI is ready; only one prompt is injected per creation (no concurrent multi-prompt race).
- `inject_instructions` (lib.sh L1847) delegates to `send_keys_safe` (lib.sh L1932), which waits for `_pane_quiescent`, clears blocking dialogs (timeout-bounded), then atomically `set-buffer`/`paste-buffer`/`delete-buffer` + `C-m`. Submission is verified against rendered tokens (`●`, `✽`, `…ing`, `esc to interrupt`) over up to 3 retries.
- The `--submit-job` path publishes `started` **only after** injection returns rc 0 (L382); on failure it publishes `error` and exits 1 (L378-380). Sequential and ordered.
### 3.2 Post-Spawn Auto-Pinning — PASS
- `create_session.sh` (claude, L153/L157): `SESSION_UUID="$(mam_gen_uuid)"``CMD_FULL="... --session-id ${SESSION_UUID}"` → YAML stores `claude_session_id_own=assigned`, `session_id_source='assigned'`, `session_id_verified=False` (L320-323).
- `reconcile.sh` drift C0 confirms the assigned ID once the transcript materializes: `verify_session_uuid(mode="revalidate")` (lib.sh L1207-1209 shortcut returns True when workspace matches + source==assigned + verified==False, then the on-disk `.jsonl` check at L1212-1244 confirms it), after which `_pin_and_verify_resume` (reconcile.sh L432) sets `session_id_verified=True` and `last_visible_status='pinned'`.
- An immediate priority reconcile cycle is kicked off asynchronously right after creation (create_session.sh L384: `reconcile.sh --once &`), so pinning is attempted promptly without waiting for the next scheduled cycle.
### 3.3 Occupied-ID Preemption Guard — PASS
Four independent layers enforce that a fresh/resume session never gets an ID already occupied:
1. **Assign-time**: `mam_gen_uuid` generates a fresh random UUID (no reuse of existing).
2. **Resolve-time** (`find_workspace_uuid`, lib.sh L1406-1420): builds `running_ids` from ALL running sessions' own-IDs; `emit(u)` silently skips any UUID in `running_ids`. A resume will never be handed a live session's ID.
3. **Discover-time** (agy path, `verify_session_uuid` lib.sh L1263-1265): rejects a candidate present in `row['_sibling_claimed_uuids']` — collected in reconcile.sh L664-673 from sibling rows sharing the same cwd that are not stopped/terminated.
4. **Write-time** (validation layer, lib.sh L1083-1094): ID Uniqueness Check raises `SystemExit` if two running sessions share the same own-ID — defense-in-depth at persistence time.
| `bash -n` `create_session.sh` | PASS |
| `bash -n` `reconcile.sh` | PASS |
| `bash -n` `resolve_session_id.sh` | PASS |
| Embedded Python `compile()` (8 blocks across 5 files) | PASS |
| `tests/test_uuid_target.py` | **13/13 PASS** (53.43s) |
### 3.4 Stage 3 Viewport Verification — PASS
`verify_tui_viewport` (lib.sh L1338-1363) implements the 3-stage viewport check:
- rc 2: session gone or pane capture empty/unavailable (degraded).
- rc 0: workspace `basename` (whitespace-stripped) appears in pane content (match).
- rc 1: a `/path/` pattern appears but the workspace basename does not (mismatch).
`reconcile.sh` (all 4 agents, e.g. agy L686-695) gates pinning on this: with exactly one valid candidate, rc 0 → `_pin_and_verify_resume(degraded=False)`; rc 1 → `C-warn`, **not pinned** (will retry); rc 2 → `_pin_and_verify_resume(degraded=True)` (pin via stages 1-3 only, documented degraded path). Tests T-6 (degraded) and T-7 (mismatch) cover the non-happy paths.
### 3.5 No UUID Cross-Talk or Shadowing — PASS
- **Workspace scoping**: `verify_session_uuid` ORDERING INVARIANT (lib.sh L1199-1205) — the `workspace_key(cwd) != workspace_key(ws)` check runs BEFORE the assigned-id shortcut, so a row from a **different** workspace is rejected first even when assigned+unverified (tested T-12). `find_workspace_uuid` only considers sessions whose `pane.cwd == ws` (L1426).
- **C-ambiguous guard** (reconcile.sh, all 4 agents): when `len(valid_candidates) > 1`, reports `C-ambiguous` and does **not** pin (tested T-4) — no silent attribution of a possibly-wrong UUID.
- **Path canonicalization**: `mam_abs_workspace` uses `cd -P && pwd -P` (physical path) and `workspace_key` uses `os.path.realpath`. Shell (create/resolve) and Python (verify/find) therefore agree on the workspace key, preventing cross-talk from symlink/logical-path divergence (tested T-10 symlink + 6/6 path forms).
- `resolve_session_id.sh` (L44) is a thin wrapper over `find_workspace_uuid`, preserving the same workspace-isolated resolution path (P0-C: never returns a global id whose `project_cwd` differs from this workspace).
| `tests/test_o3_scoped_guard.py` + `test_sanity.py` + `test_b4_session_created.py` | **47/47 PASS** (18.03s) |
| `tests/test_tier3_integration.py::test_integration_stop_purge_combination` | **1/1 PASS** (33.26s) |
## 4. Findings (Non-Blocking)
All findings are non-blocking; none require design rework.
| # | Finding | Severity | Location |
|---|---|---|---|
| A-1 | **Wrapper-mode clears `SESSION_UUID` after `CMD_FULL` is composed.** In `spawn`'s claude wrapper branch (L170), `SESSION_UUID=""` is set *after* `CMD_FULL` already baked `--session-id ${SESSION_UUID}` (L157). The YAML `cmd_full` display field (L304) therefore records `--session-id <uuid>` even though the wrapper launch cleared it. The authoritative fields (`claude_session_id_own`, `session_id_verified`) are unaffected, so pinning/resume are correct; only the cosmetic `cmd_full` string is misleading. | Low / cosmetic | create_session.sh L157, L170, L304 |
| A-2 | **`verify_session_uuid` breaks on first cwd-bearing line.** In the claude branch (L1234-1236), the loop breaks as soon as a line carrying `cwd` is found, even if `sessionId` has not yet been confirmed on that line. If a transcript interleaves an unrelated `cwd` before the matching `sessionId` line, the function could `break` before `valid_session` is set. Safe-direction (fails closed — returns False rather than mis-attribute), so not a correctness bug, but worth a comment. | Low | lib.sh L1232-1236 |
| A-3 | **`mam_session_iso_root`/`mam_workspace_key` spawn Python per resume.** Each resume call re-launches a Python interpreter for these helpers, adding minor latency. No correctness impact. | Low / perf | lib.sh (helpers) |
| A-4 | **Drift-B `endswith` vs `row_agent()`.** The drift-C loops in `reconcile.sh` use the `row_agent()` helper, but the drift-B A-1 gate (the `endswith('-creator-<agent>')` inference in `find_workspace_uuid` L1473-1488) uses inline `endswith`. Different context (B is offline resolution; C is live reconcile), so not a bug, but a single-source consolidation would reduce drift. | Low / consistency | lib.sh L1473-1488 |
No `[ESCALATE: PLANNER]` is warranted: every objective is satisfied and all findings are local fixes or cosmetic.
## 5. Completeness & Loss Check
- **Completeness**: all 5 objectives are implemented and exercised by `test_uuid_target.py` (13 cases) plus the cross-regression suite (47 cases). Every objective maps to at least one passing test (T-4 ambiguous, T-6/T-7 viewport, T-10 symlink, T-12 ordering invariant, T-5 custom name pinning).
- **Loss check**: the committed diff (`9df0fc3..245abe6`) adds `mam_gen_uuid`, `mam_abs_workspace`, `mam_workspace_key`, `mam_session_iso_root`, the C0/C-ambiguous/revalidate logic, and path canonicalization without removing prior resolution behavior for non-claude agents (agy/hermes/cline branches preserved and extended). No functional regression (47/47 + 1/1).
- **Working tree**: clean — no uncommitted changes outstanding for this scope.
[VERDICT: PASS]