release(v4.9.8): agy, codex and hermes read their databases read-only (Issue #64) - archive the review reports, bump the lockstep version
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
b0eccc5301
commit
38ea4f3b8e
24 files changed
+408
-26
No files matched your search
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20261019T134017Z-d90c23",
|
||||||
|
"base_sha": "3ce5d5fdc108b0435e933a251fe1c1256b5d7973",
|
||||||
|
"head_sha": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"tree_sha": "f5a412a3ee96465db7c4b3223d2d76e95716ddfa",
|
||||||
|
"review_commit": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"reviewer": "planner-reviewer-claude-01",
|
||||||
|
"job_id": "b78af5ad",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-10-19T14:59:46Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# Issue #64 리뷰 — agy·codex·hermes DB 읽기 전용 열기 (job b78af5ad)
|
||||||
|
|
||||||
|
- 리뷰어: planner-reviewer-claude-01
|
||||||
|
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1`
|
||||||
|
- identity: HEAD `b0eccc530149bc6e9211727e19921665c7b80492`, tree `f5a412a3ee96465db7c4b3223d2d76e95716ddfa` 가 일치한다. 작업 트리는 clean 이고 `.mam` 은 없다.
|
||||||
|
- 범위: base `3ce5d5f`(v4.9.7)..head 1커밋. 판정은 base..head 전체에 대한 것이다.
|
||||||
|
- 참고 자료: creator(agy) 리포트 `.mam/jobs/c721e4f5/agy-reports/report-final.md`
|
||||||
|
- 환경: macOS, Python 3.9.6, sqlite 3.51.0. 실제 에이전트 홈은 `ls`·`lsof`·`ps` 로만 봤고, sqlite 로 연 적은 없다.
|
||||||
|
|
||||||
|
## 결론
|
||||||
|
|
||||||
|
이슈 범위는 빠짐없이 바뀌었다. 조회 전용 열기는 모두 `_ro_connect` 로 바뀌었고, 쓰기와 MAM 자신의 DB 는 그대로다. 새 테스트는 base 에서 실패하고 head 에서 통과한다. 원인도 직접 재현해 확인했다. 차단 결함은 없다. 다만 새 런타임 테스트의 reconcile 단계가 실제로는 drift C 를 실행하지 않는다(N1). 또 기존부터 있던 workspace 간 오고정 문제를 새 테스트가 기대값으로 고정해 버렸다(N2). 둘 다 테스트를 보강하라는 권고다.
|
||||||
|
|
||||||
|
## 1. 원인 확인 — 직접 재현함
|
||||||
|
|
||||||
|
creator 의 결론: `reconcile.sh` drift C(agy, `:786-839`)는 고정되지 않은(unpinned) 실행 중 agy 행이 있으면 호스트 공용 `conversations/*.db` 를 전부 훑는다. 각 DB 에 `verify_session_uuid` 를 부르고, 이어서 `AgyAgentAdapter.verify_artifact` 가 `sqlite3.connect(path)` 로 연다. `last_conversations.json` 이 그 대화를 다른 workspace 의 것으로 적어 두어도 `cache_match=False` 는 `_sibling_claimed_uuids` 만 보고 거르지 않는다(`agy.py:60-72`). 그러면 macOS persistent WAL 때문에 `-wal`·`-shm` 이 남는다.
|
||||||
|
|
||||||
|
재현 방법:
|
||||||
|
- git archive 사본에 임시 테스트를 넣었다(mam_sandbox, 임시 HOME, mock herdr).
|
||||||
|
- 사이드카 없는 WAL agy DB 하나를 만들고, `last_conversations.json` 은 그 대화를 다른 workspace `ws_b` 에 매핑했다.
|
||||||
|
- ws 의 고정되지 않은 running 행을 mock herdr 에 살아 있는 pane 으로 등록했다.
|
||||||
|
- `reconcile.sh --once --emit-diff --dry-run` 을 실행했다.
|
||||||
|
|
||||||
|
| 사본 | DB 디렉터리 결과 |
|
||||||
|
|---|---|
|
||||||
|
| base | `<uuid>.db`, `.db-shm`, `.db-wal` — 사이드카가 생긴다 |
|
||||||
|
| head | `<uuid>.db` 만 — 바뀌지 않는다 |
|
||||||
|
|
||||||
|
이로써 v4.9.7 리뷰 때 #55 가드가 다른 workspace 의 agy DB 옆에서 잡은 사이드카는 MAM 의 reconcile drift C(status·create 가 부르는 경로 포함)가 만든 것으로 설명된다. 다만 그때 그 시각에 고정되지 않은 agy 행이 실제로 있었는지는 기록이 없어 확인하지 못했다.
|
||||||
|
|
||||||
|
## 2. 바뀐 곳 확인
|
||||||
|
|
||||||
|
- **공용 helper.** `_ro_connect` 가 `base.py:111-135` 로 옮겨졌다. 본문은 그대로이고, 임시 복사본 이름만 `os.path.basename(path)` 를 쓴다.
|
||||||
|
- opencode 는 base 의 것을 import 한다(`opencode._ro_connect is base._ro_connect`, 테스트로 고정).
|
||||||
|
- opencode 에서 쓰지 않게 된 `quote`·`tempfile` import 는 지워졌다. `shutil`·`sqlite3` 는 아직 쓰고 있다(`which`, purge).
|
||||||
|
- **`db_rows_naming` 기본 연결.** 기본값이 `_ro_connect` 가 됐다(`base.py:145`). 그래서 codex·opencode 의 residual 확인도 clean WAL DB 를 읽는다. base 에서는 `mode=ro` 가 Mac 에서 `unable to open` 으로 실패해 빈 결과를 냈다.
|
||||||
|
- **조회 전용 열기를 바꾼 곳:**
|
||||||
|
- agy `verify_artifact`(`agy.py:74`)
|
||||||
|
- codex `_find_rollout_via_db`(`codex.py:80`), `discover`(`:195`)
|
||||||
|
- hermes `verify_artifact`(`hermes.py:51`), `discover`(`:100`)
|
||||||
|
- status.sh hermes·codex 두 모드(`:90-91`, `:131-132`, `:290-291`, `:331-332`)
|
||||||
|
- reconcile.sh hermes drift(`:865`), codex drift(`:1048`)
|
||||||
|
- **남은 `sqlite3.connect`(grep 으로 전수 확인).** 모두 대상이 아니라는 판단이 맞다.
|
||||||
|
- 의도된 쓰기: codex·hermes·opencode purge 의 DELETE
|
||||||
|
- MAM 자신의 DB: `lib.sh:1599,2068`, `atomic_yaml.py:61`, `state.py:17`, `verify_session.py:41`(agent-sessions.db), `loop/runner.py:416`
|
||||||
|
- **import 정리.** agy 에서 `sqlite3` import 를 지웠고 다른 사용처는 없다. status.sh 의 opencode 블록은 이제 `lib_py.agents.base` 에서 import 한다.
|
||||||
|
|
||||||
|
## 3. 테스트 실행
|
||||||
|
|
||||||
|
조건: `env -i`, 임시 HOME, `XDG_DATA_HOME=<scratchpad>`, basetemp 는 scratchpad, `-p no:cacheprovider`, `PYTHONDONTWRITEBYTECODE=1`.
|
||||||
|
|
||||||
|
**새 모듈 `tests/test_issue64_ro_connect.py`**
|
||||||
|
- head: 43 passed.
|
||||||
|
- base 사본에 이 파일만 넣으면: **23 failed, 20 passed**. creator 의 수치와 같다.
|
||||||
|
- base 에서 실패한 것은 모든 clean·walonly 경우, 정적 검사, workspace 간 테스트다. 실패 메시지는 사이드카 `-shm`·`-wal` 이 생겼다는 것이다.
|
||||||
|
|
||||||
|
**관련 13개 모듈**
|
||||||
|
- 대상: issue64, issue60_linux_fixes, issue60_opencode_v2, a4, issue10_13_purge, issue31, orc_onboard, tier1, tier2, uuid_target, issue55, issue57, issue21_worktree_prereqs.
|
||||||
|
- 결과: **461 passed, 1 skipped, 실패 0**, 15분. skip 은 PATH 에 opencode 가 없는 실바이너리 계약 테스트다. #55 가드 오탐은 없었다.
|
||||||
|
|
||||||
|
**뮤테이션** (git archive 사본, issue64 모듈)
|
||||||
|
|
||||||
|
| 뮤테이션 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| agy `verify_artifact` 를 `sqlite3.connect` 로 | 5 failed(workspace 간 테스트 포함) |
|
||||||
|
| `db_rows_naming` 기본값을 옛 `mode=ro` URI 로 | 4 failed |
|
||||||
|
| hermes `discover` 를 `sqlite3.connect` 로 | 2 failed |
|
||||||
|
| reconcile hermes drift 를 `sqlite3.connect` 로 | 1 failed — 정적 검사만 잡음 |
|
||||||
|
| reconcile codex drift 를 `sqlite3.connect` 로 | 1 failed — 정적 검사만 잡음 |
|
||||||
|
|
||||||
|
**린트**
|
||||||
|
- `git diff --check` 는 깨끗하다.
|
||||||
|
- 바뀐 Python 6개 파일이 3.9 로 파싱된다.
|
||||||
|
- status.sh·reconcile.sh 는 `/bin/bash -n`(3.2)을 통과한다.
|
||||||
|
|
||||||
|
## 비차단
|
||||||
|
|
||||||
|
- **N1. 런타임 테스트의 reconcile 단계는 drift C 를 실행하지 않는다.**
|
||||||
|
- 위치: `test_status_and_reconcile_runtime_leave_sidecars_alone`(`:241-...`).
|
||||||
|
- 원인: 세 행을 mock herdr 에 등록하지 않는다. 그래서 drift A 가 먼저 `herdr gone … Marked terminated` 로 처리하고 drift C 를 건너뛴다. base 에서 같은 상황을 실행해 drift 결과로 확인했다.
|
||||||
|
- 결과: reconcile 의 hermes·codex 변경은 정적 문자열 검사로만 고정된다(위 뮤테이션 2개).
|
||||||
|
- 권고: 이슈 64 요구는 "실제 실행 또는 최소한 정적 검사"라 충족은 된다. 그래도 mock herdr state 에 세 세션을 `running` 으로 넣으면 실제 drift C 경로가 검증된다(내 재현처럼 `state["agents"][name] = {...}`). status 단계는 실제로 열린다. base 에서 hermes `state.db` 사이드카로 실패한다.
|
||||||
|
- **N2. 기존 결함: drift C(agy)가 다른 workspace 의 대화를 고정한다.**
|
||||||
|
- 내 재현에서 `last_conversations.json` 이 `ws_b` 의 것으로 적은 대화가 epoch 뒤 유일한 후보였다. 그러자 base·head 모두 ws 행에 `updated conversation id: <uuid>` 로 고정했다(dry-run 기준).
|
||||||
|
- 이 PR 이 만든 문제는 아니고 #64 범위 밖이다. 다만 새 테스트 `test_unpinned_agy_reconcile_…`(`:288`)가 `verify_session_uuid(ws_a, …, uuid_b) is True` 를 단언해 이 동작을 기대값으로 고정한다.
|
||||||
|
- 권고: 이 단언은 빼고 디렉터리 목록만 본다. 오고정은 별도 이슈로 다룬다. 예를 들어 `cache_match` 가 아니면서 lc 가 그 uuid 를 다른 cwd 에 매핑하면 거절한다.
|
||||||
|
- **N3.** `_ro_connect` 의 immutable 경쟁 창(ms 수준)은 #60 에서 남긴 것이 공용 helper 를 통해 agy·codex·hermes 로도 넓어졌다. 세 에이전트의 실제 DB 는 모두 WAL 형태다(`ls`: hermes·codex 는 사이드카 쌍이 있고, agy 대화 72개 중 7개에 사이드카가 있음). 실행 중에는 사이드카가 있으므로 `mode=ro` 갈래를 탄다.
|
||||||
|
|
||||||
|
## 관찰·공개
|
||||||
|
|
||||||
|
- **실제 codex DB 의 사이드카.** 관련 모듈 실행 전후로 `~/.codex/` 목록을 비교했더니 `state_5.sqlite-shm`·`-wal` 이 다시 나타나 있었다(mtime 23:29/23:35, 지금은 보유 프로세스 없음).
|
||||||
|
- 이 두 파일은 잡 시작 직후(23:04)에도 있었다가 사라졌던 것이다.
|
||||||
|
- 같은 시간대에 실제 `~/.codex` 를 쓰는 codex 프로세스들이 떠 있다: `codex resume`(이 루프의 리뷰어), app-server daemon, IDE 확장.
|
||||||
|
- 내 실행은 `env -i`·임시 HOME 이었고, head 코드는 사이드카를 만들지 않으며, #55 가드도 아무것도 잡지 않았다. 그래서 테스트 산출물이 아니라 실제 codex 활동으로 본다. 판정 근거로는 쓰지 않았다.
|
||||||
|
- **검증 트리에 임시 파일이 들어갔다 지워짐.** 재현 중 내 셸 명령 실수로 임시 테스트 파일(`tests/test_zz_review_agy.py`)이 검증 트리에 몇 초간 복사됐다. 그 파일로 아무것도 실행하지 않았고 즉시 지웠다. 이후 `git status --porcelain --ignored` 는 비어 있고 HEAD·tree 는 그대로다. 모든 재현은 scratchpad 사본에서 했다.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20261019T134017Z-d90c23",
|
||||||
|
"base_sha": "3ce5d5fdc108b0435e933a251fe1c1256b5d7973",
|
||||||
|
"head_sha": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"tree_sha": "f5a412a3ee96465db7c4b3223d2d76e95716ddfa",
|
||||||
|
"review_commit": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"reviewer": "planner-reviewer-codex-01",
|
||||||
|
"job_id": "d22e5336",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-10-19T14:59:46Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Issue #64 코드 리뷰 — 읽기 전용 에이전트 DB 조회
|
||||||
|
|
||||||
|
## 판정 요약
|
||||||
|
|
||||||
|
head `b0eccc530149bc6e9211727e19921665c7b80492`의 변경은 조회 전용 SQLite 경로를 공용 `_ro_connect`로 일관되게 바꾸며, 의도된 DELETE 경로와 MAM 자체 DB 쓰기는 보존합니다. 다른 워크스페이스 agy DB에 WAL/SHM이 생긴 원인 경로도 코드와 격리 재현 테스트로 확인했습니다.
|
||||||
|
|
||||||
|
검증은 브리프의 검증 트리 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1`에서만 수행했습니다. identity는 base `3ce5d5fdc108b0435e933a251fe1c1256b5d7973`, head `b0eccc530149bc6e9211727e19921665c7b80492`, tree `f5a412a3ee96465db7c4b3223d2d76e95716ddfa`이며, `git status --short`는 비어 있습니다. 소스 워크트리는 실행하거나 수정하지 않았습니다.
|
||||||
|
|
||||||
|
## 원인
|
||||||
|
|
||||||
|
원인은 `reconcile.sh`의 agy drift C 경로입니다. [reconcile.sh:786](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:786)에서 대화 DB 디렉터리를 설정하고, [reconcile.sh:800](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:800)에서 해당 홈의 모든 `*.db`를 훑은 뒤 [reconcile.sh:816](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:816)에서 각 UUID에 `verify_session_uuid(..., mode="discover")`를 호출합니다. 이는 adapter의 `verify_artifact`로 이어집니다. agy의 [agy.py:74-76](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/adapters/agy.py:74)는 이전에 DB를 쓰기 가능 모드로 열어 `steps`를 조회했습니다. 캐시에 다른 workspace가 기록된 UUID도 sibling claim에 막히지 않으면 조회 대상이 될 수 있었고, macOS SQLite가 새 `-wal` 및 `-shm`을 남겨 #55 홈 가드에 걸릴 수 있었습니다. 연결이 이미 닫혀 프로세스가 없던 관찰과도 맞습니다.
|
||||||
|
|
||||||
|
신규 테스트 `test_unpinned_agy_reconcile_leaves_other_workspace_clean_db_without_sidecars`는 workspace A의 미고정 agy 행과 workspace B에 매핑된 DB를 임시 HOME에 만들고 `verify_session_uuid`가 그 DB를 조회하는 상황을 재현합니다. head에서는 조회 결과를 유지하면서 디렉터리 항목이 바뀌지 않습니다. 이번 변경은 다른 workspace 후보를 선택할 수 있는 기존 정책 자체는 바꾸지 않습니다.
|
||||||
|
|
||||||
|
## 변경 범위와 코드 리뷰
|
||||||
|
|
||||||
|
- [base.py:111-134](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/base.py:111)에 기존 opencode `_ro_connect`를 공용화했습니다. WAL/SHM 쌍은 `mode=ro`, WAL이 없으면 `mode=ro&immutable=1`, WAL만 있으면 DB와 WAL의 임시 사본을 메모리에 backup합니다. [base.py:137-145](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/base.py:137)의 `db_rows_naming` 기본 연결도 helper를 사용하며 사용자 지정 `connect` 인자는 유지됩니다.
|
||||||
|
- [agy.py:74](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/adapters/agy.py:74): `steps` 검증 조회. `discover`도 이 검증을 거칩니다.
|
||||||
|
- [codex.py:80,195](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/adapters/codex.py:80): rollout 조회와 state DB 검색. residual 조회는 `db_rows_naming`의 기본 helper를 사용합니다.
|
||||||
|
- [hermes.py:51,100](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/adapters/hermes.py:51): artifact 검증과 discover 조회.
|
||||||
|
- [status.sh:90-132,290-332](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/multi-agent-mux-status/scripts/status.sh:90): JSON/text 양쪽 hermes·codex 및 opencode 분기를 전환했습니다. agy status는 기존처럼 DB를 열지 않고 파일 존재만 확인합니다.
|
||||||
|
- [reconcile.sh:423,865,954,1048](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:423): 공용 helper를 import하고 hermes, opencode, codex drift 조회에 사용합니다. agy 경로의 `verify_artifact` 전환도 포함됩니다.
|
||||||
|
- 신규 `tests/test_issue64_ro_connect.py`: helper 공유, agy/codex/hermes 조회, 4가지 WAL 상태, status/reconcile 정적·런타임 동작, 다른 workspace agy 재현을 확인합니다.
|
||||||
|
|
||||||
|
제외한 쓰기 연결은 그대로입니다: [codex.py:152-155](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/adapters/codex.py:152)의 purge DELETE, [hermes.py:74-78](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/adapters/hermes.py:74)의 purge DELETE, [opencode.py:193-199](/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1/.agents/skills/lib_py/agents/adapters/opencode.py:193)의 legacy-row DELETE. `lib.sh`, `lib_py/state.py`, `atomic_yaml.py`, `verify_session.py`, loop runner의 연결은 MAM 자체 상태/트랜잭션 용도입니다. Claude/grok adapter에는 대화 SQLite 조회가 없습니다. helper 내부의 read-only URI와 메모리 DB, 임시 사본 연결도 의도된 구현입니다.
|
||||||
|
|
||||||
|
변경된 조회 호출은 기존 쿼리·정렬·필터 조건을 보존하면서 helper를 경유합니다. `git diff --check base..head`와 `bash -n`으로 두 셸 스크립트 문법도 확인했습니다. 커밋은 8개 파일, 461 insertions / 56 deletions이고 검증 트리는 clean입니다. 버전 파일과 문서는 바뀌지 않았습니다.
|
||||||
|
|
||||||
|
## 테스트 결과
|
||||||
|
|
||||||
|
- Python 3.9.6 / pytest 8.4.2, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`; basetemp는 검증 트리 밖을 사용했습니다. 임시 경로와 fixture로만 DB를 만들었으며 실제 agent home DB는 열지 않았습니다.
|
||||||
|
- `tests/test_issue64_ro_connect.py`: **43 passed** (11.59초).
|
||||||
|
- 관련 회귀 묶음 `test_issue60_linux_fixes.py`, `test_issue60_opencode_v2.py`, `test_a4_adapter_contract.py`, `test_issue31_purge_residual.py`, `test_issue50_dispatch.py`: **216 passed** (148.47초).
|
||||||
|
- 이번 리뷰에서 base 별도 checkout을 만들지는 않았습니다. 동일 identity/head/base를 사용한 독립 리뷰 `.mam/jobs/366a4d58/grok-reports/report-final.md`에 기록된 base 실행은 새 테스트 **23 failed, 20 passed**였습니다. 실패에는 helper 이동, clean/WAL-only 조회 뒤 sidecar 생성, status/reconcile helper 부재, 다른 workspace agy 재현이 포함되며, 남은 20개는 sidecar가 이미 있던 상태입니다. 이 수치는 그 독립 리뷰의 결과이지 이번 실행에서 재현한 수치는 아닙니다.
|
||||||
|
|
||||||
|
## 남은 위험
|
||||||
|
|
||||||
|
1. helper가 `-wal` 존재 여부를 확인한 뒤 immutable 연결을 여는 사이 writer가 WAL을 만들면 해당 연결은 새 WAL을 보지 못할 수 있습니다. 기존 구현에서 온 경쟁 조건입니다.
|
||||||
|
2. stale WAL/SHM을 동반한 파일은 `mode=ro` 분기로 처리됩니다. 파일명 생성·삭제는 막지만 SQLite가 기존 공유 메모리 영역을 업데이트할 가능성은 남습니다.
|
||||||
|
3. reconcile의 agy drift C가 다른 workspace DB를 후보로 보는 정책은 그대로입니다. 수정은 해당 검사가 원본 sidecar를 만들지 않게 하며 후보 귀속 규칙은 변경하지 않습니다.
|
||||||
|
4. 브리프가 제외한 purge 쓰기는 WAL/SHM을 만들거나 변경할 수 있습니다.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20261019T134017Z-d90c23",
|
||||||
|
"base_sha": "3ce5d5fdc108b0435e933a251fe1c1256b5d7973",
|
||||||
|
"head_sha": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"tree_sha": "f5a412a3ee96465db7c4b3223d2d76e95716ddfa",
|
||||||
|
"review_commit": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"reviewer": "reviewer-agy-01",
|
||||||
|
"job_id": "e95527ef",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-10-19T14:59:46Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Issue #64 Code Review Report (`3ce5d5f..b0eccc5`)
|
||||||
|
|
||||||
|
- **Job ID**: `e95527ef`
|
||||||
|
- **Reviewer**: `agy` (`herdr:reviewer-agy-01`)
|
||||||
|
- **Branch**: `fix/issue-64-ro-connect`
|
||||||
|
- **Base .. Head**: `3ce5d5fdc108b0435e933a251fe1c1256b5d7973` (`v4.9.7`) .. `b0eccc530149bc6e9211727e19921665c7b80492` (1 commit)
|
||||||
|
- **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Root Cause Analysis (Task 1)
|
||||||
|
|
||||||
|
**Why did the Issue #55 agent home guard detect newly created `.db-wal` and `.db-shm` sidecars beside another workspace's `agy` conversation DB during a macOS test run?**
|
||||||
|
|
||||||
|
1. **Global `agy` conversation directory scan in `reconcile.sh` drift C (`.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:797-817`)**:
|
||||||
|
- When an `agy` session is `running` and unpinned (`agy_conversation_id_own` is `None`), `reconcile.sh` globs every `*.db` in `${HOME_DIR}/.gemini/antigravity-cli/conversations/` (a global directory shared across all workspaces under that `HOME`) and invokes `verify_session_uuid(cwd, 'agy', uuid, s_eval, mode="discover")`.
|
||||||
|
2. **`AgyAgentAdapter.verify_artifact` opened every candidate `.db` with read-write `sqlite3.connect(path)` (`.agents/skills/lib_py/agents/adapters/agy.py:60-80`)**:
|
||||||
|
- In `discover` mode, `verify_artifact` reads `cache/last_conversations.json`. When `lc_data.get(ctx.cwd) == uuid` is `False` (for example, `uuid` belongs to another workspace), `verify_artifact` only filters out `uuid` if it is in `ctx.row.get("_sibling_claimed_uuids")` (which tracks siblings in the **same** workspace `cwd`) or if `os.path.getmtime(path) < ctx.epoch`.
|
||||||
|
- For any conversation `.db` from another workspace whose `mtime >= ctx.epoch` (or when `ctx.epoch` is not set), `verify_artifact` proceeded to `conn = sqlite3.connect(path)` to run `SELECT count(*) FROM steps`.
|
||||||
|
3. **macOS persistent WAL sidecar creation**:
|
||||||
|
- On macOS, Apple's system `libsqlite3.dylib` sets `SQLITE_FCNTL_PERSIST_WAL = 1` by default. Opening a WAL-mode `.db` that currently has no `-wal`/`-shm` sidecars with read-write `sqlite3.connect(path)` and closing it creates `<uuid>.db-wal` and `<uuid>.db-shm` and leaves them on disk.
|
||||||
|
- Similarly, if any `reconcile.sh` or `verify_artifact` call ran against the real `HOME` (or if another workspace's `reconcile.sh` / `agy` process scanned `conversations/*.db` while the test suite was running), opening a clean WAL `.db` with `sqlite3.connect(path)` created `<uuid>.db-wal` and `<uuid>.db-shm` in `~/.gemini/antigravity-cli/conversations/`, which `_agent_home_guard` flagged at session teardown.
|
||||||
|
- Replacing `sqlite3.connect(path)` with `_ro_connect(path)` (`mode=ro&immutable=1` when no `-wal` exists) prevents MAM from ever creating or removing `-wal`/`-shm` sidecars when inspecting `agy` conversation databases (verified by `test_unpinned_agy_reconcile_leaves_other_workspace_clean_db_without_sidecars`, which fails on `base` `3ce5d5f` and passes on `head` `b0eccc5`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Summary of Changes (`3ce5d5f..b0eccc5`)
|
||||||
|
|
||||||
|
### Changed Files & Lines (Read-Only Agent DB Queries)
|
||||||
|
1. **`.agents/skills/lib_py/agents/base.py:111-145`**:
|
||||||
|
- Moved `_ro_connect(db: str)` to `lib_py.agents.base` as a shared helper (with `tempfile.TemporaryDirectory(prefix="mam-ro-")` and `os.path.basename(path) or "state.db"` for the `walonly` branch).
|
||||||
|
- Updated `db_rows_naming` default `connect` to `_ro_connect` (`conn = (connect or _ro_connect)(db)`), so `codex.residual_artifacts` (`state_*.sqlite`, `thread_history_*.sqlite`) and any other `db_rows_naming` callers also use `_ro_connect` instead of plain `mode=ro`.
|
||||||
|
2. **`.agents/skills/lib_py/agents/adapters/opencode.py:3, 161-185`**:
|
||||||
|
- Imports and reuses `_ro_connect` from `lib_py.agents.base`.
|
||||||
|
3. **`.agents/skills/lib_py/agents/adapters/agy.py:3, 74`**:
|
||||||
|
- `verify_artifact` (and `discover`, which delegates to `verify_artifact`) opens `conversations/<uuid>.db` via `_ro_connect(path)`.
|
||||||
|
4. **`.agents/skills/lib_py/agents/adapters/codex.py:3, 80, 195`**:
|
||||||
|
- `_find_rollout_via_db` (used by `_find_rollout`, `artifact_path`, `verify_artifact`) and `discover` open `state_*.sqlite` via `_ro_connect(db)`.
|
||||||
|
5. **`.agents/skills/lib_py/agents/adapters/hermes.py:3, 51, 100`**:
|
||||||
|
- `verify_artifact` and `discover` open `~/.hermes/state.db` via `_ro_connect(hdb)`.
|
||||||
|
6. **`.agents/skills/multi-agent-mux-status/scripts/status.sh:90-91, 103, 120-121, 290-291, 303, 320-321`**:
|
||||||
|
- Both `--json` and text modes use `_ro_connect` from `lib_py.agents.base` for `hermes` (`hdb`), `opencode` (`odb`), and `codex` (`db`). Zero raw `sqlite3.connect` calls remain in `status.sh`.
|
||||||
|
7. **`.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh:423, 865, 954, 1047`**:
|
||||||
|
- Imports `_ro_connect` from `lib_py.agents.base` at top level and uses it for `hermes` drift C (`_ro_connect(hdb)`), `opencode` drift C (`_ro_connect(odb)`), and `codex` drift C (`_ro_connect(state_db)`). Zero raw `sqlite3.connect` calls remain in `reconcile.sh`.
|
||||||
|
8. **`tests/test_issue64_ro_connect.py` (lines 1-408)**:
|
||||||
|
- 43 new regression tests covering shared helper identity, all 4 WAL states (`clean`, `leftover`, `walonly`, `writer`) across `agy`, `codex`, `hermes`, and `db_rows_naming`, static and runtime checks of `status.sh` and `reconcile.sh`, and cross-workspace unpinned `agy` drift-C discovery.
|
||||||
|
|
||||||
|
### Intentionally Excluded `sqlite3.connect` Call Sites
|
||||||
|
- **Agent DB write paths (`purge_artifacts`)**:
|
||||||
|
- `lib_py/agents/adapters/codex.py:152` (`DELETE FROM threads WHERE id=?`)
|
||||||
|
- `lib_py/agents/adapters/hermes.py:74` (`DELETE FROM sessions` / `DELETE FROM messages`)
|
||||||
|
- `lib_py/agents/adapters/opencode.py:193` (`DELETE` from 1.x legacy tables)
|
||||||
|
- **MAM's own state database (`agent-sessions.db`)**:
|
||||||
|
- `lib_py/atomic_yaml.py:61`, `lib_py/state.py:17`, `lib_py/verify_session.py:41`, `lib_py/loop/runner.py:416`, `lib.sh:1599, 2068`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Test Results (Base vs. Head) & Remaining Risks
|
||||||
|
|
||||||
|
1. **Negative verification on `base` (`3ce5d5f`)**:
|
||||||
|
- Running `tests/test_issue64_ro_connect.py` against an archive copy of `base` (`3ce5d5f`) on macOS: **`23 failed, 20 passed in 10.97s`** (every `clean` and `walonly` state across `agy`, `codex`, `hermes`, `db_rows_naming`, `status.sh`, `reconcile.sh`, plus `test_ro_connect_lives_in_base_and_opencode_reuses_it`, `test_status_and_reconcile_static_ro_connect_usage`, and `test_unpinned_agy_reconcile_leaves_other_workspace_clean_db_without_sidecars` failed as expected).
|
||||||
|
2. **Verification on `head` (`b0eccc5`)**:
|
||||||
|
- `tests/test_issue64_ro_connect.py`: **43 passed**
|
||||||
|
- `tests/test_issue60_linux_fixes.py`: **52 passed**
|
||||||
|
- `tests/test_issue60_opencode_v2.py`: **15 passed**
|
||||||
|
- `tests/test_a4_adapter_contract.py`: **33 passed**
|
||||||
|
- `tests/test_issue55_agent_homes.py`: **11 passed**
|
||||||
|
- `tests/test_issue31_purge_residual.py`: **67 passed**
|
||||||
|
- **Total**: **`221 passed in 119.66s`**
|
||||||
|
- `git status -s` and `git diff --check` in the verification worktree are completely clean.
|
||||||
|
3. **Remaining risks**:
|
||||||
|
- External processes outside MAM (such as an active `agy` IDE/CLI session running in another workspace on the same host) can still create or remove their own conversation `.db-wal`/`.db-shm` sidecars in `~/.gemini/antigravity-cli/conversations/` while a pytest session is running, which `_agent_home_guard` (which only exempts live non-temp workspace directories for `claude`) can observe as a new directory entry. Within MAM itself, all read-only queries against `agy`, `codex`, `hermes`, and `opencode` databases are now sidecar-safe.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20261019T134017Z-d90c23",
|
||||||
|
"base_sha": "3ce5d5fdc108b0435e933a251fe1c1256b5d7973",
|
||||||
|
"head_sha": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"tree_sha": "f5a412a3ee96465db7c4b3223d2d76e95716ddfa",
|
||||||
|
"review_commit": "b0eccc530149bc6e9211727e19921665c7b80492",
|
||||||
|
"reviewer": "reviewer-grok-01",
|
||||||
|
"job_id": "366a4d58",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-10-19T14:59:46Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Review 366a4d58 — Issue #64
|
||||||
|
|
||||||
|
PASS. Commit `b0eccc5` moves `_ro_connect` into `base.py` and uses it for every query-only open of an agy, codex, or hermes database found in the skills tree. The other-workspace agy sidecars come from reconcile drift C: it walks every conversation `*.db` and `verify_artifact` used to open each one read-write. On this host that open leaves `-wal` and `-shm` after close, which is what the #55 name guard counts. Head related tests: 95 passed. The new file on base: 23 failed, 20 passed.
|
||||||
|
|
||||||
|
Reviewed only the snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1` (`loop-20261019T134017Z-d90c23`). Head `b0eccc530149bc6e9211727e19921665c7b80492`, tree `f5a412a3ee96465db7c4b3223d2d76e95716ddfa`, base `3ce5d5fdc108b0435e933a251fe1c1256b5d7973` is an ancestor and is the control-plane HEAD. One commit, 8 files, +461/−56. `git diff --check` is clean. Snapshot porcelain is empty and the snapshot has no `.mam`. Control-plane porcelain is empty. No product file was edited. The source worktree was not run.
|
||||||
|
|
||||||
|
The commit is `b0eccc5` `fix(agents): open agy, codex and hermes databases read-only (Issue #64)`. Files: `agy.py`, `codex.py`, `hermes.py`, `opencode.py`, `base.py`, `reconcile.sh`, `status.sh`, and new `tests/test_issue64_ro_connect.py`.
|
||||||
|
|
||||||
|
Host tools: control-plane venv Python 3.9.6, sqlite 3.51.0, bash 3.2.57(1)-release. `bash -n` accepts `status.sh` and `reconcile.sh`. The changed modules imported and ran under pytest. The opencode binary was not launched. Linux was not executed here. The real database was not opened.
|
||||||
|
|
||||||
|
## Cause
|
||||||
|
|
||||||
|
Reconcile drift C for an unpinned running agy row (`reconcile.sh:786-817`) lists every `<HOME_DIR>/.gemini/antigravity-cli/conversations/*.db` (`:800`) and calls `verify_session_uuid(cwd, 'agy', uuid, s_eval, mode="discover")` (`:816`) for each basename. `home` in that script is `os.environ['HOME_DIR']` (`:437`). `verify_session_uuid` (`verify_session.py:83-108`) builds a `DiscoveryContext` and returns `adapter.verify_artifact`. `DiscoveryContext` fills a missing `home_dir` through `resolve_home` (`paths.py:11`): the argument, then `HOME_DIR`, then `HOME`, then `~`. The reconcile call omits `home_dir`, so the open uses the same home as the glob.
|
||||||
|
|
||||||
|
On base, `AgyAgentAdapter.verify_artifact` did `sqlite3.connect(path)` and `SELECT count(*) FROM steps` once the file existed and was new enough for the epoch. A discover-mode cache miss returns false only when the uuid is in `_sibling_claimed_uuids` (siblings on the same pane cwd). `last_conversations.json` mapping that uuid to a different workspace does not skip the open. A read-write open of a WAL database on macOS sqlite 3.51.0 creates `-wal` and `-shm` when they are absent and leaves those names after close. The #55 guard snapshots entry names at session start and fails when a new name appears. At check time no process held the database because the connection had already closed. That matches the v4.9.7 timing: the review loop and the suite were running reconcile against the real home.
|
||||||
|
|
||||||
|
`status.sh` agy (`:78-81` and `:278-281`) returns from `os.path.exists` of the `.db`. It does not open sqlite.
|
||||||
|
|
||||||
|
Head `agy.py:74` uses `_ro_connect(path)` for the same `SELECT count(*) FROM steps`. With no `-wal`, that open is `mode=ro&immutable=1` and does not create sidecars.
|
||||||
|
|
||||||
|
`test_unpinned_agy_reconcile_leaves_other_workspace_clean_db_without_sidecars` reproduces this on a temporary home. `last_conversations.json` maps the conversation to workspace B, the row is an unpinned agy session whose pane cwd is workspace A, and the test calls `verify_session_uuid` (the function drift C calls). The function returns true and the directory stays one file. On base the same test failed because the listing gained `-wal` and `-shm`. The runtime test also spawns `reconcile.sh --once --emit-diff --dry-run` for a same-workspace unpinned agy session, with `HOME` and `HOME_DIR` pointed at the sandbox, and that listing stays unchanged.
|
||||||
|
|
||||||
|
A cache miss still accepts the other workspace's database when it has at least one step and is not sibling-claimed. The new test asserts that true return. Drift C can still treat that file as a candidate. The open no longer creates names.
|
||||||
|
|
||||||
|
## Changed opens
|
||||||
|
|
||||||
|
`_ro_connect` is `base.py:111-134`. Both sidecars use `file:<abspath>?mode=ro`. No `-wal` uses `mode=ro&immutable=1`. A `-wal` without `-shm` is copied into `tempfile.TemporaryDirectory(prefix="mam-ro-")`, opened read-write on that copy only, backed into a memory connection, and the temp directory is removed. The copy name is `os.path.basename(path)` or `state.db`, so `<uuid>.db`, `state.db`, and `state_5.sqlite` keep their `-wal` sibling. The temp prefix was `mam-opencode-` while the helper lived in `opencode.py`.
|
||||||
|
|
||||||
|
`db_rows_naming` (`base.py:137-145`) now defaults to `_ro_connect`. A caller can still pass `connect`.
|
||||||
|
|
||||||
|
Query sites on the helper:
|
||||||
|
|
||||||
|
- `agy.py:74` `verify_artifact` (`SELECT count(*) FROM steps`). `discover` (`agy.py:165-175`) reads `last_conversations.json` and calls `verify_artifact`.
|
||||||
|
- `codex.py:80` `_find_rollout_via_db`. `codex.py:195` `discover` (`PRAGMA table_info(threads)` and `SELECT id, cwd FROM threads`). `codex.py:167-169` `residual_artifacts` goes through `db_rows_naming` for `state_*.sqlite` and `thread_history_*.sqlite`.
|
||||||
|
- `hermes.py:51` `verify_artifact`. `hermes.py:100` `discover`.
|
||||||
|
- `opencode.py` imports `_ro_connect` from `base` (`:3`). Query sites stay `:149`, `:212`, `:234`, `:249`, `:316`, and `connect=_ro_connect` at `:249`. The identity test asserts `opencode_mod._ro_connect is base_mod._ro_connect`.
|
||||||
|
- `status.sh`: `_ro_connect(hdb)` at `:91` and `:291`, `_ro_connect(odb)` at `:116` and `:316`, `_ro_connect(db)` at `:132` and `:332`. The static test requires each of those counts to be 2.
|
||||||
|
- `reconcile.sh`: import at `:423`. Hermes drift `_ro_connect(hdb)` at `:865`, opencode `_ro_connect(odb)` at `:954`, codex drift `_ro_connect(state_db)` at `:1048`. The static test requires each count to be 1.
|
||||||
|
|
||||||
|
`status.sh` and `reconcile.sh` contain no `sqlite3.connect` literal.
|
||||||
|
|
||||||
|
## Left as read-write
|
||||||
|
|
||||||
|
These opens write, or they are MAM's own databases. They stay `sqlite3.connect`:
|
||||||
|
|
||||||
|
- `opencode.py:193` deletes 1.x rows after `session delete`.
|
||||||
|
- `hermes.py:74` purge `DELETE`.
|
||||||
|
- `codex.py:152` purge `DELETE FROM threads`.
|
||||||
|
- `lib.sh:1599` and `lib.sh:2068` (timeout 60).
|
||||||
|
- `lib_py/state.py:17`.
|
||||||
|
- `lib_py/verify_session.py:41` opens the state database beside `AGENT_SESSIONS_YAML` or `YAML_PATH` (`<yaml stem>.db`, `SELECT data FROM state`, timeout 5). That is MAM's `agent-sessions` state, not an agent conversation database.
|
||||||
|
- `lib_py/loop/runner.py:416`.
|
||||||
|
- `lib_py/atomic_yaml.py:61`.
|
||||||
|
|
||||||
|
Agy purge removes files and does not open sqlite. Claude and grok adapters have no `sqlite3.connect`. The three `sqlite3.connect` calls inside `_ro_connect` are the helper: the `mode=ro` URI, the immutable URI, and the read-write open of the private copy.
|
||||||
|
|
||||||
|
`VERSIONS.md` and the skill guides are unchanged. Version bumps stay with the orchestrator.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Pytest used the control-plane venv, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and a `--basetemp` outside the snapshot. The basetemp path contains neither `mam-review` nor `shim`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*`, `DELEGATE_JOB_LOGS_DIR`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, `CLAUDE_CONFIG_DIR`, `CLAUDE_CODE_TMPDIR`, `CODEX_HOME`, `PYTHONPATH`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `AGENT_SESSIONS_YAML`, `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`, and `XDG_DATA_HOME` were dropped. `WORKSPACE_ROOT` was not pointed at the snapshot.
|
||||||
|
|
||||||
|
Head, cwd the snapshot, basetemp `/tmp/mam366-366a4d58-pybase`, files `tests/test_issue64_ro_connect.py` and `tests/test_issue60_linux_fixes.py`: 95 passed in 72.05 s. Exit 0. No skip and no error. The new file is 43 of those (helper identity, agy, codex including `db_rows_naming`, hermes, one static script check, four runtime script states, and the cross-workspace case). The issue #60 file is the other 52, so the move still passes the existing opencode read-only tests.
|
||||||
|
|
||||||
|
Base proof used a throwaway `git clone --shared` of the snapshot, checked out at `3ce5d5f`, with only `tests/test_issue64_ro_connect.py` taken from `b0eccc5`. Cwd was the clone. Basetemp was `/tmp/mam366-366a4d58-pybase-base`. Result: 23 failed, 20 passed in 10.84 s. Exit 1. The failures are the helper-location test, every clean and wal-only adapter query, the static script count (`_ro_connect(hdb)` was 0), runtime clean and wal-only, and the cross-workspace clean listing. Clean `db_rows_naming` returned `[]` and clean `residual_artifacts` returned only the rollout file; on base that helper's default open is a `mode=ro` URI (`base.py` at `3ce5d5f`). The other clean and wal-only failures are listings that gained `-wal` and `-shm` after a query that still returned the row. The 20 passes are the leftover and live-writer states, where those names were already present, so a read-write open did not change the listing.
|
||||||
|
|
||||||
|
The four states in the new file are clean (close, then unlink `-wal` and `-shm` when they exist), leftover sidecars, `-wal` without `-shm`, and a connection left open. Each query asserts the right row and an unchanged directory listing. Runtime covers `reconcile.sh --once --emit-diff --dry-run` and `status.sh --json` plus text mode. Agy, hermes, and codex `resume_state` are `yes`.
|
||||||
|
|
||||||
|
## Remaining risk
|
||||||
|
|
||||||
|
The check for `-wal` and the immutable connect are two steps. A wal created in that gap is invisible to the immutable open. A writer that already has both sidecars is read with `mode=ro`.
|
||||||
|
|
||||||
|
`mode=ro` with both sidecars rewrites the existing `-shm` bytes (the reader index). The `-wal` bytes and the file names stay. That rewrite was measured on the Issue #60 review and left in place.
|
||||||
|
|
||||||
|
Purge `DELETE` opens stay read-write, so a purge can still create or remove sidecars. That is the write the brief left out.
|
||||||
|
|
||||||
|
Drift C can still accept another workspace's agy database when the file has steps and the uuid is not sibling-claimed. The candidate decision is unchanged. The open no longer adds names.
|
||||||
|
|
||||||
|
`verify_session.py:41` still opens MAM's own state database read-write.
|
||||||
|
|
||||||
|
The user's background service stayed up. Its pid is 23630 and the command is `/Users/godopu16/.opencode/bin/opencode serve --service`. The real database mtime is 1792420196 and its size is 36712448. `~/.config/opencode/service.json` mtime is 1792246838. `~/.local/state/opencode/service.json` mtime is 1792365227. The real `opencode.log` mtime is 1792420391 and its size is 2699345. The herdr config mtime is 1784456394.
|
||||||
|
|
||||||
|
After the report, snapshot porcelain is still empty, the snapshot still has no `.mam`, and control-plane porcelain is still empty at `3ce5d5fdc108b0435e933a251fe1c1256b5d7973`. The herdr config mtime is still 1784456394. The opencode database mtime is still 1792420196 and its size is still 36712448. The opencode log mtime is still 1792420391 and its size is still 2699345. Service pid 23630 is still `opencode serve --service`.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -29,7 +29,7 @@ export WORKSPACE_ROOT
|
|||||||
# Framework semantic version. Single runtime source of truth; kept in lockstep
|
# Framework semantic version. Single runtime source of truth; kept in lockstep
|
||||||
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
|
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
|
||||||
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
|
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
|
||||||
MAM_VERSION="4.9.7"
|
MAM_VERSION="4.9.8"
|
||||||
export MAM_VERSION
|
export MAM_VERSION
|
||||||
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
|
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import sys
|
|||||||
from typing import List, Sequence
|
from typing import List, Sequence
|
||||||
|
|
||||||
# Framework version locked with framework constants
|
# Framework version locked with framework constants
|
||||||
FRAMEWORK_VERSION = "4.9.7"
|
FRAMEWORK_VERSION = "4.9.8"
|
||||||
|
|
||||||
CORE_MODULES: Sequence[str] = (
|
CORE_MODULES: Sequence[str] = (
|
||||||
# Standard library modules
|
# Standard library modules
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-create
|
name: multi-agent-mux-create
|
||||||
description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace."
|
description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-delegate-job
|
name: multi-agent-mux-delegate-job
|
||||||
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
|
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos, windows]
|
platforms: [linux, macos, windows]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-loop
|
name: multi-agent-mux-loop
|
||||||
description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached."
|
description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-monitor
|
name: multi-agent-mux-monitor
|
||||||
description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted."
|
description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-orc-onboard
|
name: multi-agent-mux-orc-onboard
|
||||||
description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture."
|
description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-resume
|
name: multi-agent-mux-resume
|
||||||
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
|
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-status
|
name: multi-agent-mux-status
|
||||||
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
|
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-stop
|
name: multi-agent-mux-stop
|
||||||
description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start."
|
description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start."
|
||||||
version: 4.9.7
|
version: 4.9.8
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다.
|
본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다.
|
새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly.
|
This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment.
|
A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Messaging System Technical Analysis & Architecture Report
|
# Messaging System Technical Analysis & Architecture Report
|
||||||
|
|
||||||
Applies to: MAM v4.9.7
|
Applies to: MAM v4.9.8
|
||||||
|
|
||||||
This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations.
|
This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다.
|
[herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A **Multi-Agent Orchestration & Messaging Backplane** built on [herdr](https://herdr.dev) and MQTT. It runs long-lived coding agents (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`) in isolated herdr panes, delegates work to them as tracked jobs, and drives planner / creator / reviewer loops until every reviewer passes the change.
|
A **Multi-Agent Orchestration & Messaging Backplane** built on [herdr](https://herdr.dev) and MQTT. It runs long-lived coding agents (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`) in isolated herdr panes, delegates work to them as tracked jobs, and drives planner / creator / reviewer loops until every reviewer passes the change.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+38
-11
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
## 📌 현재 버전 개요 (Current Release)
|
## 📌 현재 버전 개요 (Current Release)
|
||||||
|
|
||||||
- **프레임워크 버전**: `v4.9.7`
|
- **프레임워크 버전**: `v4.9.8`
|
||||||
- **최신 릴리스 일시**: 2026-10-19 (KST)
|
- **최신 릴리스 일시**: 2026-10-20 (KST)
|
||||||
- **기준 브랜치**: `main`
|
- **기준 브랜치**: `main`
|
||||||
- **핵심 아키텍처**:
|
- **핵심 아키텍처**:
|
||||||
- **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입.
|
- **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입.
|
||||||
@@ -24,23 +24,50 @@
|
|||||||
|
|
||||||
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
|
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
|
||||||
|
|
||||||
모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.9.7`로 동기화되어 배포됩니다.
|
모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.9.8`로 동기화되어 배포됩니다.
|
||||||
|
|
||||||
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
|
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
|
||||||
| :--- | :---: | :--- | :---: |
|
| :--- | :---: | :--- | :---: |
|
||||||
| **`multi-agent-mux-create`** | `4.9.7` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
|
| **`multi-agent-mux-create`** | `4.9.8` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-stop`** | `4.9.7` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
|
| **`multi-agent-mux-stop`** | `4.9.8` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
|
||||||
| **`multi-agent-mux-resume`** | `4.9.7` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
|
| **`multi-agent-mux-resume`** | `4.9.8` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-status`** | `4.9.7` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
|
| **`multi-agent-mux-status`** | `4.9.8` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-monitor`** | `4.9.7` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
|
| **`multi-agent-mux-monitor`** | `4.9.8` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
|
||||||
| **`multi-agent-mux-delegate-job`** | `4.9.7` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
|
| **`multi-agent-mux-delegate-job`** | `4.9.8` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-loop`** | `4.9.7` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
|
| **`multi-agent-mux-loop`** | `4.9.8` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-orc-onboard`** | `4.9.7` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
|
| **`multi-agent-mux-orc-onboard`** | `4.9.8` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 버전별 상세 변경 내역 (Changelog)
|
## 📋 버전별 상세 변경 내역 (Changelog)
|
||||||
|
|
||||||
|
### 🚀 `v4.9.8` — agy·codex·hermes DB 조회도 읽기 전용 (Issue #64) (2026-10-20)
|
||||||
|
|
||||||
|
> **패치 (PATCH Release)**: v4.9.7 이 opencode 에 적용한 읽기 전용 열기(`_ro_connect`)를 agy·codex·hermes 의 조회에도 적용한다. macOS 의 시스템 SQLite 는 쓰기 가능으로 열었다 닫기만 해도 `-wal`·`-shm` 을 남긴다(persistent WAL). 그래서 MAM 의 조회가 다른 작업 공간의 agy 대화 DB 옆에 사이드카를 만들 수 있었다. Linux 는 닫을 때 지우므로 Linux 확인은 이슈에서 따로 한다.
|
||||||
|
|
||||||
|
#### 🌟 주요 개선 사항
|
||||||
|
1. **원인**: `reconcile.sh` 의 drift C(agy)는 고정되지 않은 agy 행이 있으면 대화 폴더(호스트 공용)의 모든 `*.db` 를 후보로 보고 `verify_session_uuid(..., mode="discover")` 를 부른다. 다른 작업 공간의 대화도 같은 작업 공간의 형제가 가진 것이 아니면 걸러지지 않는다. 이어지는 `AgyAgentAdapter.verify_artifact` 가 `SELECT count(*) FROM steps` 를 쓰기 가능 연결로 실행했다. `status.sh`·`create_session.sh` 도 reconcile 을 부르므로 같은 경로를 탄다.
|
||||||
|
2. **공용 `_ro_connect`**: opencode 어댑터에 있던 함수를 `lib_py/agents/base.py` 로 옮겼다. 사이드카 상태에 따른 열기 방식(둘 다 있으면 `mode=ro`, `-wal` 이 없으면 `mode=ro&immutable=1`, `-shm` 없는 `-wal` 은 임시 사본을 메모리로)은 그대로다. opencode 는 같은 함수를 import 한다.
|
||||||
|
3. **바뀐 조회**:
|
||||||
|
- agy `verify_artifact`
|
||||||
|
- codex `_find_rollout_via_db`·`discover`
|
||||||
|
- hermes `verify_artifact`·`discover`
|
||||||
|
- `status.sh` 의 hermes·codex 확인(두 출력 모드 모두)
|
||||||
|
- `reconcile.sh` drift C 의 hermes·codex
|
||||||
|
- `db_rows_naming` 의 기본 열기(codex residual)
|
||||||
|
|
||||||
|
purge 의 의도된 쓰기(codex·hermes DELETE, opencode 옛 행 정리)와 MAM 자신의 DB 는 그대로다.
|
||||||
|
|
||||||
|
#### ✅ 품질 검증
|
||||||
|
- **회귀**: `tests/test_issue64_ro_connect.py`(43개). 네 가지 사이드카 상태(clean·leftover·walonly·writer)에서 조회 결과와 DB 폴더 목록을 확인한다. 범위는 세 어댑터, `db_rows_naming`, `status.sh`·`reconcile.sh` 실행, 다른 작업 공간의 agy 대화 재현이다. base(v4.9.7)에서 23개가 실패한다.
|
||||||
|
- **리뷰 (`--all-reviewer`, 구현은 agy creator)**: 1회차(@b0eccc5) **4/4 PASS**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||||||
|
- **전체 스위트 (macOS, Python 3.9.6, @b0eccc5)**: 2044 passed, 56 skipped, 실패 0. 실제 `~/.claude`·`/private/tmp/claude-<uid>`·opencode 의 data·state·config 폴더·herdr 서버 pane 목록에 이 실행이 남긴 것은 없었다. agy·codex·hermes 홈의 사이드카 목록도 그대로였다.
|
||||||
|
- **알려진 비차단**:
|
||||||
|
- 새 런타임 테스트의 reconcile 단계는 mock herdr 에 pane 이 없어 drift C 까지 가지 않는다. drift C 의 hermes·codex 는 정적 검사가 지킨다.
|
||||||
|
- drift C(agy)는 `last_conversations.json` 이 다른 작업 공간에 기록한 대화도 후보로 받는다(기존 동작). 새 테스트가 이 동작을 단언하므로 그 결함을 고칠 때 함께 바꾼다.
|
||||||
|
- `-wal` 없음을 판단한 직후 쓰는 쪽이 생기면 immutable 읽기가 틀릴 수 있다(#60 에서 남긴 창이 세 에이전트로 넓어짐, ms 수준). 실행 중인 에이전트의 DB 는 사이드카가 있어 `mode=ro` 로 읽는다.
|
||||||
|
- MAM 밖의 프로세스(다른 작업 공간의 agy 등)는 계속 자기 사이드카를 만들고 지운다. 홈 가드가 그것을 볼 수 있다.
|
||||||
|
|
||||||
### 🚀 `v4.9.7` — opencode 2.x 의 Linux 확인 후속: 안내 문구, 읽기 전용 조회, 인증 확인 (Issue #60, PR #62) (2026-10-19)
|
### 🚀 `v4.9.7` — opencode 2.x 의 Linux 확인 후속: 안내 문구, 읽기 전용 조회, 인증 확인 (Issue #60, PR #62) (2026-10-19)
|
||||||
|
|
||||||
> **패치 (PATCH Release)**: v4.9.6 을 Linux(Ubuntu, Python 3.14, opencode 2.0.32, herdr 0.9.3)에서 확인했다. 경로·XDG·`--standalone` 자식의 수명·거부되는 플래그·`/exit`·status 는 Mac 과 같았다. 아래는 그 확인에서 찾은 결함으로, 특정 OS 에 한정되지 않는다. Issue #60 은 이 릴리스로 닫힌다.
|
> **패치 (PATCH Release)**: v4.9.6 을 Linux(Ubuntu, Python 3.14, opencode 2.0.32, herdr 0.9.3)에서 확인했다. 경로·XDG·`--standalone` 자식의 수명·거부되는 플래그·`/exit`·status 는 Mac 과 같았다. 아래는 그 확인에서 찾은 결함으로, 특정 OS 에 한정되지 않는다. Issue #60 은 이 릴리스로 닫힌다.
|
||||||
|
|||||||
Reference in new issue
Block a user