From 5305cf21f94bbdae0366eb50629b405002922bad Mon Sep 17 00:00:00 2001 From: Godopu Date: Wed, 28 Oct 2026 14:56:04 +0000 Subject: [PATCH 1/7] feat(agents): expose MAM_ID_POST_DISCOVERED in facts bridge (Issue #68) --- .agents/skills/lib_py/agents/__main__.py | 2 + tests/test_issue68_onboard_default.py | 52 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/test_issue68_onboard_default.py diff --git a/.agents/skills/lib_py/agents/__main__.py b/.agents/skills/lib_py/agents/__main__.py index e9162f9..fd235cb 100644 --- a/.agents/skills/lib_py/agents/__main__.py +++ b/.agents/skills/lib_py/agents/__main__.py @@ -1,6 +1,7 @@ # __main__.py — CLI bridge for lib_py.agents facts and resolution (N4) import os, sys, json, shlex, time +from lib_py.agents.base import POST_DISCOVERED from lib_py.agents.registry import get_adapter, own_key, agent_of_row # `purge auto` probes adapters in this order; opencode last because its @@ -71,6 +72,7 @@ def main(): print(f"MAM_AGENT_STATUS_RELIABLE={1 if adapter.agent_status_reliable else 0}") print(f"MAM_EXIT_KEY={q(adapter.exit_key)}") print(f"MAM_DELEGATE_AGENT_KEY={q(adapter.delegate_agent_key)}") + print(f"MAM_ID_POST_DISCOVERED={1 if adapter.id_lifecycle == POST_DISCOVERED else 0}") elif cmd == 'resolve': name = sys.argv[2] if len(sys.argv) > 2 else '' diff --git a/tests/test_issue68_onboard_default.py b/tests/test_issue68_onboard_default.py new file mode 100644 index 0000000..35d1c3d --- /dev/null +++ b/tests/test_issue68_onboard_default.py @@ -0,0 +1,52 @@ +"""Issue #68: worktree_prepare.sh onboards by default (--no-onboard skips), and +create_session.sh --no-onboard warns for post-discovered-id agents (adapter +id_lifecycle == POST_DISCOVERED). +""" +from __future__ import annotations + +import json +import os +import re +import shlex +import subprocess +import sys +import time +from pathlib import Path + +import pytest +import yaml + +REPO_ROOT = Path(__file__).resolve().parent.parent +SKILLS = REPO_ROOT / ".agents" / "skills" +if str(SKILLS) not in sys.path: + sys.path.insert(0, str(SKILLS)) + +from lib_py.agents.base import POST_DISCOVERED # noqa: E402 +from lib_py.agents.registry import get_adapter # noqa: E402 + +ALL_AGENTS = ("claude", "agy", "hermes", "grok", "opencode", "codex") +EXPECTED_POST_DISCOVERED = { + "agy": 1, + "codex": 1, + "hermes": 1, + "opencode": 1, + "claude": 0, + "grok": 0, +} + + +@pytest.mark.parametrize("agent", ALL_AGENTS) +def test_facts_expose_post_discovered(agent): + env = dict(os.environ, PYTHONPATH=f"{SKILLS}:{os.environ.get('PYTHONPATH', '')}") + res = subprocess.run( + [sys.executable, "-m", "lib_py.agents", "facts", agent], + capture_output=True, text=True, check=True, env=env, + ) + eval_res = subprocess.run( + ["bash", "-c", f"set -euo pipefail; eval {shlex.quote(res.stdout)}; echo \"$MAM_ID_POST_DISCOVERED\""], + capture_output=True, text=True, check=True, + ) + val = int(eval_res.stdout.strip()) + adapter = get_adapter(agent) + assert val == (1 if adapter.id_lifecycle == POST_DISCOVERED else 0) + assert val == EXPECTED_POST_DISCOVERED[agent] -- 2.54.0 From 3fd2416a303015f2dc1525c9f34096b86f1a0152 Mon Sep 17 00:00:00 2001 From: Godopu Date: Wed, 28 Oct 2026 15:01:50 +0000 Subject: [PATCH 2/7] feat(create): warn on --no-onboard for post-discovered-id agents (Issue #68) --- .../skills/multi-agent-mux-create/SKILL.md | 2 +- .../scripts/create_session.sh | 4 + tests/data/skill_code_blocks.json | 2 +- tests/test_issue68_onboard_default.py | 130 ++++++++++++++++++ 4 files changed, 136 insertions(+), 2 deletions(-) diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index bf26670..d24b9d7 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -170,7 +170,7 @@ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace | `--herdr-session` (`--herdr-server`) | Isolated herdr server name | | `--herdr-workspace` | herdr workspace label recorded in the registry (a label only, never a socket) | | `--submit-job PROMPT` | Register a delegated job with this prompt and send it to the new agent | -| `--onboard` / `--no-onboard` | Submit the onboarding job (default: on) | +| `--onboard` / `--no-onboard` | Submit the onboarding job (default: on); with `--no-onboard` an agent whose id appears only after its first turn (adapter `id_lifecycle`) records none until then and blocks a same-folder create of its type (#63) - create warns | | `--auto-trust` / `--no-auto-trust` | Answer a start-up directory-trust dialog or leave it to a human (`MAM_AUTO_TRUST`; default: answer) | | `--isolate` / `--no-isolate` | Legacy, no-op | diff --git a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh index daac476..625bcfe 100755 --- a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh +++ b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh @@ -205,6 +205,10 @@ fi # Retrieve agent facts once eval "$("$(_delegate_py_bin)" -m lib_py.agents facts "$AGENT" 2>/dev/null || true)" +# Issue #68: warn when --no-onboard is used with a post-discovered-id agent (adapter id_lifecycle via facts). +if [ "$ONBOARD" = 0 ] && [ "${MAM_ID_POST_DISCOVERED:-0}" = 1 ]; then + echo "WARNING: --no-onboard: $AGENT records its conversation id only after its first turn; until then it is not in the registry, and another $AGENT session created in this folder waits for it (Issue #63, MAM_ID_ORDER_WAIT_S)." >&2 +fi CMD_FULL="$("$(_delegate_py_bin)" -m lib_py.agents spawn-spec "$AGENT" "$RESOLVED_BIN" "$SESSION_UUID" "${USE_WRAPPER:-0}" 2>/dev/null || true)" if [ -z "$CMD_FULL" ]; then diff --git a/tests/data/skill_code_blocks.json b/tests/data/skill_code_blocks.json index 0d4a81d..7a8acd1 100644 --- a/tests/data/skill_code_blocks.json +++ b/tests/data/skill_code_blocks.json @@ -633,7 +633,7 @@ "other" ], "passthrough": { - "4": "multi-agent-mux-create/scripts/create_session.sh:550" + "4": "multi-agent-mux-create/scripts/create_session.sh:554" } } }, diff --git a/tests/test_issue68_onboard_default.py b/tests/test_issue68_onboard_default.py index 35d1c3d..1d53319 100644 --- a/tests/test_issue68_onboard_default.py +++ b/tests/test_issue68_onboard_default.py @@ -23,6 +23,7 @@ if str(SKILLS) not in sys.path: from lib_py.agents.base import POST_DISCOVERED # noqa: E402 from lib_py.agents.registry import get_adapter # noqa: E402 +from test_issue63_same_cwd_ids import _mock_herdr_agent, _read_rows, _row, _write_rows # noqa: E402 ALL_AGENTS = ("claude", "agy", "hermes", "grok", "opencode", "codex") EXPECTED_POST_DISCOVERED = { @@ -33,6 +34,15 @@ EXPECTED_POST_DISCOVERED = { "claude": 0, "grok": 0, } +UUID_RE = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", re.I) + + +def _norm_uuid(text: str) -> str: + return UUID_RE.sub("", text) + + +def _warn_lines(stderr: str) -> list[str]: + return [l for l in stderr.splitlines() if l.startswith("WARNING: --no-onboard:")] @pytest.mark.parametrize("agent", ALL_AGENTS) @@ -50,3 +60,123 @@ def test_facts_expose_post_discovered(agent): adapter = get_adapter(agent) assert val == (1 if adapter.id_lifecycle == POST_DISCOVERED else 0) assert val == EXPECTED_POST_DISCOVERED[agent] + + +@pytest.mark.parametrize("agent", ALL_AGENTS) +def test_no_onboard_warning_dry_run(mam_sandbox, mock_herdr, mock_agents, monkeypatch, agent): + monkeypatch.setenv("XAI_API_KEY", "test-key") + ws = mam_sandbox / f"ws-dry-{agent}" + ws.mkdir(parents=True, exist_ok=True) + create_sh = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + + res = subprocess.run( + ["bash", str(create_sh), "--dry-run", "--no-onboard", "--workspace", str(ws), + "--agent", agent, "--role", "creator", "--session", f"t68-{agent}"], + capture_output=True, text=True, cwd=str(mam_sandbox), timeout=60, + ) + assert res.returncode == 0, res.stdout + res.stderr + warns = _warn_lines(res.stderr) + assert len(warns) == EXPECTED_POST_DISCOVERED[agent], res.stderr + if warns: + assert "Issue #63" in warns[0] and agent in warns[0] + + # Control: --dry-run with default onboarding emits 0 warning lines for all agents + ctl_res = subprocess.run( + ["bash", str(create_sh), "--dry-run", "--workspace", str(ws), + "--agent", agent, "--role", "creator", "--session", f"t68-ctl-{agent}"], + capture_output=True, text=True, cwd=str(mam_sandbox), timeout=60, + ) + assert ctl_res.returncode == 0, ctl_res.stdout + ctl_res.stderr + assert _warn_lines(ctl_res.stderr) == [], ctl_res.stderr + + +@pytest.mark.parametrize("agent", ALL_AGENTS) +def test_no_onboard_warning_keeps_exit_and_row(mam_sandbox, mock_herdr, mock_agents, monkeypatch, agent): + monkeypatch.setenv("XAI_API_KEY", "test-key") + ws = mam_sandbox / f"ws-real-{agent}" + ws.mkdir(parents=True, exist_ok=True) + session_name = f"t68-{agent}" + create_sh = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + lib_sh = mam_sandbox / ".agents" / "skills" / "lib.sh" + ws_slug = subprocess.run( + ["bash", "-c", f'source "{lib_sh}" && derive_workspace_slug "{ws}"'], + capture_output=True, text=True, check=True, cwd=str(mam_sandbox), + ).stdout.strip() + + res = subprocess.run( + ["bash", str(create_sh), "--no-onboard", "--workspace", str(ws), + "--agent", agent, "--role", "creator", "--session", session_name], + capture_output=True, text=True, cwd=str(mam_sandbox), timeout=120, + ) + # 1. Exit code + assert res.returncode == 0, res.stdout + res.stderr + + # 2. Stable row fields (checked BEFORE warning assertion so base b9c828f fails only at step 3) + rows = _read_rows(mam_sandbox) + assert session_name in rows + row = rows[session_name] + pre_assigned = agent in ("claude", "grok") + expected_cmd_full = _norm_uuid( + get_adapter(agent).spawn_spec( + str(mock_agents / agent), + "00000000-0000-0000-0000-000000000000" if pre_assigned else "", + ) + ) + expected_source = "assigned" if pre_assigned else ("pending-discovery" if agent in ("opencode", "codex") else None) + expected_verified = False if agent in ("claude", "grok", "opencode", "codex") else None + expected_vis = "assigned (awaiting first message)" if pre_assigned else "unverified" + expected_cp = { + "workspace_root": os.path.realpath(mam_sandbox), + "agent_sessions_yaml": os.path.realpath(mam_sandbox / ".mam" / "agent-sessions.yaml"), + "mam_jobs_dir": os.path.realpath(mam_sandbox / ".mam" / "jobs"), + "delegate_job_logs_dir": os.path.realpath(mam_sandbox / ".mam" / "delegate_job_logs"), + } + + assert row["name"] == session_name + assert row["status"] == "running" + assert row["role"] == "creator" + assert row["delegate_job_id"] is None + assert row["herdr_server"] == ws_slug + assert row["herdr_workspace"] == ws_slug.removeprefix("mam-") + assert row["pane"]["cmd"] == agent + assert _norm_uuid(row["pane"]["cmd_full"]) == expected_cmd_full + assert os.path.realpath(row["pane"]["cwd"]) == os.path.realpath(ws) + assert row.get("session_id_source") == expected_source + assert row.get("session_id_verified") == expected_verified + assert row["last_visible_status"] == expected_vis + assert row["control_plane"] == expected_cp + + # 3. Warning line count + warns = _warn_lines(res.stderr) + assert len(warns) == EXPECTED_POST_DISCOVERED[agent], res.stderr + if warns: + assert "Issue #63" in warns[0] and agent in warns[0] + + +def test_no_onboard_warning_precedes_a_later_refusal(mam_sandbox, mock_herdr, mock_agents): + ws = mam_sandbox / "c68-ws" + ws.mkdir(parents=True, exist_ok=True) + epoch_a = int(time.time()) - 100 + name_a, name_b = "c68-ws-creator-agy", "c68-ws-reviewer-agy" + create_sh = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + + holder = subprocess.Popen(["sleep", "120"]) + try: + _write_rows(mam_sandbox, [_row(name_a, "agy", ws, epoch_a, pid=holder.pid)]) + _mock_herdr_agent(mock_herdr, name_a, ws, holder.pid, epoch_a, pane_id="w1:p_a") + + env0 = dict(os.environ, MAM_ID_ORDER_WAIT_S="0") + res = subprocess.run( + ["bash", str(create_sh), "--workspace", str(ws), "--agent", "agy", + "--role", "reviewer", "--session", name_b, "--no-onboard"], + capture_output=True, text=True, cwd=str(mam_sandbox), env=env0, timeout=120, + ) + assert res.returncode == 1, res.stdout + res.stderr + warns = _warn_lines(res.stderr) + assert len(warns) == 1, res.stderr + assert "Issue #63" in warns[0] + assert name_a in res.stderr + assert res.stderr.index("WARNING: --no-onboard:") < res.stderr.index(name_a), res.stderr + finally: + holder.kill() + holder.wait() -- 2.54.0 From c0e9f5c8a5f0f1e9620cb2eee1c0dae9e7ed8a07 Mon Sep 17 00:00:00 2001 From: Godopu Date: Wed, 28 Oct 2026 15:04:51 +0000 Subject: [PATCH 3/7] feat(worktree): onboard creator by default in worktree_prepare.sh (Issue #68) --- .../skills/multi-agent-mux-create/SKILL.md | 6 +- .../scripts/worktree_prepare.sh | 11 ++-- tests/data/skill_code_blocks.json | 2 +- tests/test_issue21_phase2_loop_a.py | 2 +- tests/test_issue68_onboard_default.py | 66 +++++++++++++++++++ 5 files changed, 77 insertions(+), 10 deletions(-) diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index d24b9d7..c288cb3 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -202,15 +202,15 @@ Run from the control root's skills tree (lib.sh's `WORKSPACE_ROOT` = the main ch ```bash bash .agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh \ --path ../multi-agent-mux-wt/issue-42 --branch fix/issue-42 [--base ] [--submodules] \ - [--creator-agent claude [--session ] [--role creator] [--herdr-session ] [--herdr-workspace ] [--onboard]] [--dry-run] + [--creator-agent claude [--session ] [--role creator] [--herdr-session ] [--herdr-workspace ] [--no-onboard]] [--dry-run] ``` - Creates the worktree next to the control root (a path **inside** it is refused), idempotently; creates the worktree's **own** `.venv` from the control root's `.venv/bin/python` and installs the delegate-job skill's `requirements.txt` (paho-mqtt, PyYAML - what `deploy/install.sh` installs) plus `requirements.txt` / `requirements-dev.txt` when present (needs network access to the package index; a failed install removes the half-made venv and exits 1 so a re-run starts over; never a link - a leftover link is refused with exit 1; no control-root interpreter is a warning and `VENV=skipped`); adds `/.venv` and `.pytest_cache/` to the shared `info/exclude` (a linked worktree's exclude file is `$GIT_COMMON_DIR/info/exclude`, so it applies to the main checkout too). - **Submodules are opt-in** (`--submodules`). Initialisation writes `submodule..url` to the shared `.git/config`, and any attempt - even a failed one - leaves a `modules/` directory that makes a plain `git worktree remove` refuse. A failed `--submodules` undoes only what that attempt created - new submodule checkouts are deinitialised before their git dirs are deleted, so the worktree stays a usable checkout; submodules initialised earlier and pre-existing `submodule.*` values are kept - and exits 1; re-run without `--submodules` to finish. -- `--creator-agent` creates the creator through this skill with the control root's binding and `MAM_NO_SUBSCRIBER_DAEMON=1`; the row's `pane.cwd` is the worktree. +- `--creator-agent` creates the creator through this skill with the control root's binding and `MAM_NO_SUBSCRIBER_DAEMON=1` (the row's `pane.cwd` is the worktree) and onboards it (the onboarding job is sent; `--no-onboard` skips it, `--onboard` is the default and still accepted). - Prints `BASE=`: record it **before** delegating the implementation, then review with `run_loop.sh --worktree --base "$BASE" --review-only`. - A submodule path that is not initialised yet but already holds files refuses `--submodules` up front (exit 3, nothing changed): the clone would fail on it and a rollback could not tell those files from its own. -- Exit codes: 0 ok, 1 git / submodule / create failure, 2 usage or path rule, 3 refused to protect existing content (nothing changed); 44/45 are create's (onboarding error / timeout with `--onboard`; session and worktree kept). +- Exit codes: 0 ok, 1 git / submodule / create failure, 2 usage or path rule, 3 refused to protect existing content (nothing changed); 44/45 are create's (onboarding error / timeout; onboarding is the default, `--no-onboard` skips it; session and worktree kept). - create itself: a control-plane binding that cannot be computed is refused (exit 2) for a `--workspace` other than the control root and warned about for the control root; the immediate reconcile and the watchdog run the control root's code. ## Pitfalls diff --git a/.agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh b/.agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh index 1677478..07f84da 100755 --- a/.agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh +++ b/.agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh @@ -6,17 +6,17 @@ # Usage: # worktree_prepare.sh --path --branch [--base ] [--submodules] # [--creator-agent [--session ] [--role ] -# [--herdr-session ] [--herdr-workspace ] [--onboard]] [--dry-run] +# [--herdr-session ] [--herdr-workspace ] [--no-onboard]] [--dry-run] # # Steps: git worktree (idempotent) -> the worktree's own .venv (control root # interpreter + requirements*.txt) -> /.venv and .pytest_cache/ in the shared info/exclude -> submodules only with # --submodules (a failed init is rolled back: it would otherwise leave a # modules/ trace that makes a plain `git worktree remove` refuse) -> optional -# creator session registered in the control root's .mam -> summary with BASE=. +# creator session (onboarded unless --no-onboard) registered in the control root's .mam -> summary with BASE=. # # Exit codes: 0 ok, 1 git / submodule / create failure, 2 usage or path rule, # 3 refused to protect existing content (nothing changed); 44/45 are create's -# (onboarding error / timeout; session and worktree kept, Issue #50). +# (onboarding error / timeout - onboarding is the default, --no-onboard skips it; session and worktree kept, Issue #50). set -euo pipefail _script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -27,7 +27,7 @@ usage() { } WT=""; BRANCH=""; BASE_REF=""; SUBMODULES=0; DRY_RUN=0 -CREATOR_AGENT=""; SESSION=""; ROLE="creator"; HS=""; HW=""; ONBOARD=0 +CREATOR_AGENT=""; SESSION=""; ROLE="creator"; HS=""; HW=""; ONBOARD=1 while [ $# -gt 0 ]; do case "$1" in --path) WT="${2:-}"; shift 2 ;; @@ -39,7 +39,8 @@ while [ $# -gt 0 ]; do --role) ROLE="${2:-}"; shift 2 ;; --herdr-session) HS="${2:-}"; shift 2 ;; --herdr-workspace) HW="${2:-}"; shift 2 ;; - --onboard) ONBOARD=1; shift ;; + --onboard) ONBOARD=1; shift ;; # the default; still accepted + --no-onboard) ONBOARD=0; shift ;; --dry-run) DRY_RUN=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "ERROR: unknown arg: $1" >&2; usage >&2; exit 2 ;; diff --git a/tests/data/skill_code_blocks.json b/tests/data/skill_code_blocks.json index 7a8acd1..b0e7bdb 100644 --- a/tests/data/skill_code_blocks.json +++ b/tests/data/skill_code_blocks.json @@ -91,7 +91,7 @@ "start": 202, "end": 206, "lang": "bash", - "sha": "38836d31bf38", + "sha": "8bfabafb0328", "class": "c", "kind": "invocation", "evidence": [ diff --git a/tests/test_issue21_phase2_loop_a.py b/tests/test_issue21_phase2_loop_a.py index 3dda3b1..41669f6 100644 --- a/tests/test_issue21_phase2_loop_a.py +++ b/tests/test_issue21_phase2_loop_a.py @@ -419,7 +419,7 @@ def test_w6_remove_plain_and_with_stop(ctl, mock_herdr, mock_agents): assert os.path.realpath(wt1) not in _worktrees(main) assert _git(main, "show-ref", "--verify", "--quiet", f"refs/heads/b-{wt1.name}", check=False).returncode == 0 - res = _prepare(main, wt2, "--creator-agent", "claude") + res = _prepare(main, wt2, "--creator-agent", "claude", "--no-onboard") assert res.returncode == 0, res.stdout + res.stderr name = [r for r in _rows(main) if os.path.realpath(r["pane"]["cwd"]) == os.path.realpath(wt2)][0]["name"] res = _run(main, REM, "--path", str(wt2), "--stop") diff --git a/tests/test_issue68_onboard_default.py b/tests/test_issue68_onboard_default.py index 1d53319..6e3640f 100644 --- a/tests/test_issue68_onboard_default.py +++ b/tests/test_issue68_onboard_default.py @@ -23,6 +23,7 @@ if str(SKILLS) not in sys.path: from lib_py.agents.base import POST_DISCOVERED # noqa: E402 from lib_py.agents.registry import get_adapter # noqa: E402 +from test_issue21_phase2_loop_a import _prepare, _rows, _wt, ctl # noqa: E402,F401 from test_issue63_same_cwd_ids import _mock_herdr_agent, _read_rows, _row, _write_rows # noqa: E402 ALL_AGENTS = ("claude", "agy", "hermes", "grok", "opencode", "codex") @@ -180,3 +181,68 @@ def test_no_onboard_warning_precedes_a_later_refusal(mam_sandbox, mock_herdr, mo finally: holder.kill() holder.wait() + + +@pytest.mark.parametrize( + "opts,expect_onboard", + [ + ([], True), + (["--onboard"], True), + (["--no-onboard"], False), + (["--no-onboard", "--onboard"], True), + ], +) +def test_prepare_forwards_onboarding_choice(ctl, opts, expect_onboard): + main = ctl + args_file = main / "captured_create_args.txt" + stub = main / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + stub.write_text(f'#!/usr/bin/env bash\nprintf "%s\\n" "$*" > "{args_file}"\nexit 0\n') + wt = _wt(main, "fwd") + try: + res = _prepare(main, wt, "--creator-agent", "claude", *opts) + assert res.returncode == 0, res.stdout + res.stderr + tokens = args_file.read_text().split() + if expect_onboard: + assert "--onboard" in tokens, tokens + assert "--no-onboard" not in tokens, tokens + else: + assert "--no-onboard" in tokens, tokens + assert "--onboard" not in tokens, tokens + finally: + subprocess.run(["git", "worktree", "remove", "--force", str(wt)], cwd=str(main), capture_output=True) + + +def test_prepare_onboards_by_default_end_to_end(ctl, mock_herdr, mock_agents): + main = ctl + wt = _wt(main, "onboard-def") + try: + res = _prepare(main, wt, "--creator-agent", "claude") + assert res.returncode == 0, res.stdout + res.stderr + (row,) = _rows(main) + jid = row["delegate_job_id"] + assert jid, row + job_path = main / ".mam" / "jobs" / f"{jid}.json" + assert job_path.is_file(), f"missing job record {job_path}" + rec = json.loads(job_path.read_text()) + assert rec["status"] == "completed", rec + assert f"onboarding completed: job {jid}" in res.stdout, res.stdout + finally: + subprocess.run(["git", "worktree", "remove", "--force", str(wt)], cwd=str(main), capture_output=True) + + +def test_prepare_no_onboard_sends_none(ctl, mock_herdr, mock_agents): + main = ctl + wt = _wt(main, "no-onboard") + try: + res = _prepare(main, wt, "--creator-agent", "claude", "--no-onboard") + assert res.returncode == 0, res.stdout + res.stderr + (row,) = _rows(main) + assert row["delegate_job_id"] is None, row + jobs_dir = main / ".mam" / "jobs" + if jobs_dir.is_dir(): + for jf in jobs_dir.glob("*.json"): + rec = json.loads(jf.read_text()) + assert rec.get("agent_session") != f"herdr:{row['name']}", rec + finally: + subprocess.run(["git", "worktree", "remove", "--force", str(wt)], cwd=str(main), capture_output=True) + -- 2.54.0 From 7cf63f485844ea5ced3f7c4d93bdebf8c9212d93 Mon Sep 17 00:00:00 2001 From: Godopu Date: Wed, 28 Oct 2026 17:19:22 +0000 Subject: [PATCH 4/7] fix(tests): pass --no-onboard in test_issue21_acceptance and shift evidence pins (Issue #68, review round 1) --- tests/data/skill_code_blocks.json | 16 ++++++++-------- tests/test_issue21_acceptance.py | 4 ++-- tests/test_issue68_onboard_default.py | 1 - 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/data/skill_code_blocks.json b/tests/data/skill_code_blocks.json index b0e7bdb..9875008 100644 --- a/tests/data/skill_code_blocks.json +++ b/tests/data/skill_code_blocks.json @@ -11,7 +11,7 @@ "kind": "example", "evidence": [ "multi-agent-mux-create/scripts/create_session.sh:99", - "multi-agent-mux-create/scripts/create_session.sh:360" + "multi-agent-mux-create/scripts/create_session.sh:364" ], "mismatch": null, "note": "HERDR_SESSION_NAME environment example" @@ -67,7 +67,7 @@ "class": "c", "kind": "example", "evidence": [ - "multi-agent-mux-create/scripts/create_session.sh:518-545" + "multi-agent-mux-create/scripts/create_session.sh:522-549" ], "mismatch": null, "note": "Output format: the row create_session.sh writes" @@ -96,7 +96,7 @@ "kind": "invocation", "evidence": [ "multi-agent-mux-create/scripts/worktree_prepare.sh:24", - "multi-agent-mux-create/scripts/worktree_prepare.sh:250" + "multi-agent-mux-create/scripts/worktree_prepare.sh:251" ], "mismatch": null, "note": "worktree_prepare.sh call" @@ -175,7 +175,7 @@ "kind": "invocation", "evidence": [ "lib_py/loop/runner.py:155", - "multi-agent-mux-create/scripts/worktree_prepare.sh:250" + "multi-agent-mux-create/scripts/worktree_prepare.sh:251" ], "mismatch": null, "note": "--worktree/--base --review-only call pattern" @@ -560,9 +560,9 @@ "evidence": [ "multi-agent-mux-create/scripts/create_session.sh:166-168", "multi-agent-mux-create/scripts/create_session.sh:207", - "multi-agent-mux-create/scripts/create_session.sh:259-274", - "multi-agent-mux-create/scripts/create_session.sh:365-402", - "multi-agent-mux-create/scripts/create_session.sh:414-416" + "multi-agent-mux-create/scripts/create_session.sh:263-278", + "multi-agent-mux-create/scripts/create_session.sh:369-406", + "multi-agent-mux-create/scripts/create_session.sh:418-420" ], "mismatch": [ "C1: an existing session is exit 1 in the doc, exit 3 in the script", @@ -582,7 +582,7 @@ "base_sha": "f71be0f26fb8", "class": "a", "evidence": [ - "multi-agent-mux-create/scripts/create_session.sh:490-608" + "multi-agent-mux-create/scripts/create_session.sh:494-612" ], "mismatch": [], "replacement": "bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace --agent --role ", diff --git a/tests/test_issue21_acceptance.py b/tests/test_issue21_acceptance.py index 4caac28..29a4eba 100644 --- a/tests/test_issue21_acceptance.py +++ b/tests/test_issue21_acceptance.py @@ -44,7 +44,7 @@ def _sibling(main: Path, state_file): before = _rows(main) since = len(_raw_calls(state_file)) wt = _wt(main) - res = _prepare(main, wt, "--creator-agent", "claude", "--herdr-session", HS) + res = _prepare(main, wt, "--creator-agent", "claude", "--herdr-session", HS, "--no-onboard") assert res.returncode == 0, res.stdout + res.stderr return wt, since, before @@ -204,7 +204,7 @@ def test_a5b_a_stale_last_round_is_not_accepted(wt_repo, monkeypatch, tmp_path, def test_a6_row_recovery_and_resume_only_at_the_original_cwd(ctl, mock_herdr, mock_agents): main = ctl wt = _wt(main) - res = _prepare(main, wt, "--creator-agent", "claude") + res = _prepare(main, wt, "--creator-agent", "claude", "--no-onboard") assert res.returncode == 0, res.stdout + res.stderr (row,) = _rows(main) name, binding = row["name"], row["control_plane"] diff --git a/tests/test_issue68_onboard_default.py b/tests/test_issue68_onboard_default.py index 6e3640f..6fef745 100644 --- a/tests/test_issue68_onboard_default.py +++ b/tests/test_issue68_onboard_default.py @@ -245,4 +245,3 @@ def test_prepare_no_onboard_sends_none(ctl, mock_herdr, mock_agents): assert rec.get("agent_session") != f"herdr:{row['name']}", rec finally: subprocess.run(["git", "worktree", "remove", "--force", str(wt)], cwd=str(main), capture_output=True) - -- 2.54.0 From 3b81f9973223d2a910369711c08adc772003639d Mon Sep 17 00:00:00 2001 From: Godopu Date: Wed, 28 Oct 2026 18:46:40 +0000 Subject: [PATCH 5/7] test(issue26): classify MAM_ID_POST_DISCOVERED as a string compare (Issue #68) create_session.sh reads the new adapter fact as "${MAM_ID_POST_DISCOVERED:-0}" and compares it with "= 1"; test_b5 requires every such default to be classified. Co-Authored-By: Claude Opus 5.5 --- tests/test_issue26_ints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_issue26_ints.py b/tests/test_issue26_ints.py index b613a3f..47413a5 100644 --- a/tests/test_issue26_ints.py +++ b/tests/test_issue26_ints.py @@ -108,6 +108,7 @@ NOT_ARITHMETIC = { # sleep's fractional seconds are its meaning here (default 0.5): mam_int would refuse them "SKS_QUIESCENT_INTERVAL": "sleep, fractional by default (0.5) - left raw", "DELEGATE_JOB_ACK_S": "create_session.sh -> delegate_wait_started, which runs mam_int on it", + "MAM_ID_POST_DISCOVERED": "string compare (adapter fact from lib_py.agents facts)", } -- 2.54.0 From a7bf07bb1cf0eeb76d636328b102907a1a7afbeb Mon Sep 17 00:00:00 2001 From: Godopu Date: Thu, 29 Oct 2026 15:12:55 +0900 Subject: [PATCH 6/7] fix(create): SKILL.md says a same-folder create waits (#63); drop unused import (Issue #68, review) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff --- .agents/skills/multi-agent-mux-create/SKILL.md | 2 +- tests/test_issue68_onboard_default.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index c288cb3..d9e1753 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -170,7 +170,7 @@ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace | `--herdr-session` (`--herdr-server`) | Isolated herdr server name | | `--herdr-workspace` | herdr workspace label recorded in the registry (a label only, never a socket) | | `--submit-job PROMPT` | Register a delegated job with this prompt and send it to the new agent | -| `--onboard` / `--no-onboard` | Submit the onboarding job (default: on); with `--no-onboard` an agent whose id appears only after its first turn (adapter `id_lifecycle`) records none until then and blocks a same-folder create of its type (#63) - create warns | +| `--onboard` / `--no-onboard` | Submit the onboarding job (default: on); with `--no-onboard` an agent whose id appears only after its first turn (adapter `id_lifecycle`) records none until then, and a same-folder create of its type waits for it (#63, `MAM_ID_ORDER_WAIT_S`) - create warns | | `--auto-trust` / `--no-auto-trust` | Answer a start-up directory-trust dialog or leave it to a human (`MAM_AUTO_TRUST`; default: answer) | | `--isolate` / `--no-isolate` | Legacy, no-op | diff --git a/tests/test_issue68_onboard_default.py b/tests/test_issue68_onboard_default.py index 6fef745..ae5c91d 100644 --- a/tests/test_issue68_onboard_default.py +++ b/tests/test_issue68_onboard_default.py @@ -14,7 +14,6 @@ import time from pathlib import Path import pytest -import yaml REPO_ROOT = Path(__file__).resolve().parent.parent SKILLS = REPO_ROOT / ".agents" / "skills" -- 2.54.0 From a582934f6a27d3113538daea8929cf362dfbe8da Mon Sep 17 00:00:00 2001 From: Godopu Date: Thu, 29 Oct 2026 16:56:48 +0900 Subject: [PATCH 7/7] release(v4.13.0): worktree creator onboards by default; create --no-onboard warns for post-discovered-id agents (Issue #68) - archive the review reports, bump the lockstep version Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff --- .../report-2ab23f4e.identity.json | 14 ++ .../report-2ab23f4e.md | 133 ++++++++++++++++++ .../report-ee26d64c.identity.json | 14 ++ .../report-ee26d64c.md | 93 ++++++++++++ .../report-ace63ec3.identity.json | 14 ++ .../reviewer-agy-01/report-ace63ec3.md | 121 ++++++++++++++++ .../report-8900db9e.identity.json | 14 ++ .../reviewer-grok-01/report-8900db9e.md | 78 ++++++++++ .agents/skills/lib.sh | 2 +- .agents/skills/lib_py/loop/eager.py | 2 +- .../skills/multi-agent-mux-create/SKILL.md | 2 +- .../multi-agent-mux-delegate-job/SKILL.md | 2 +- .agents/skills/multi-agent-mux-loop/SKILL.md | 2 +- .../skills/multi-agent-mux-monitor/SKILL.md | 2 +- .../multi-agent-mux-orc-onboard/SKILL.md | 2 +- .../skills/multi-agent-mux-resume/SKILL.md | 2 +- .../skills/multi-agent-mux-status/SKILL.md | 2 +- .agents/skills/multi-agent-mux-stop/SKILL.md | 2 +- .../skills/multi-agent-mux-update/SKILL.md | 2 +- BOOTSTRAP.ko.md | 2 +- BOOTSTRAP.md | 2 +- MESSAGING.md | 2 +- README.ko.md | 2 +- README.md | 2 +- VERSIONS.md | 44 ++++-- 25 files changed, 529 insertions(+), 28 deletions(-) create mode 100644 .agents/reports/planner-reviewer-claude-01/report-2ab23f4e.identity.json create mode 100644 .agents/reports/planner-reviewer-claude-01/report-2ab23f4e.md create mode 100644 .agents/reports/planner-reviewer-codex-01/report-ee26d64c.identity.json create mode 100644 .agents/reports/planner-reviewer-codex-01/report-ee26d64c.md create mode 100644 .agents/reports/reviewer-agy-01/report-ace63ec3.identity.json create mode 100644 .agents/reports/reviewer-agy-01/report-ace63ec3.md create mode 100644 .agents/reports/reviewer-grok-01/report-8900db9e.identity.json create mode 100644 .agents/reports/reviewer-grok-01/report-8900db9e.md diff --git a/.agents/reports/planner-reviewer-claude-01/report-2ab23f4e.identity.json b/.agents/reports/planner-reviewer-claude-01/report-2ab23f4e.identity.json new file mode 100644 index 0000000..355f2e1 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-2ab23f4e.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261029T045625Z-74296d", + "base_sha": "b9c828f09214b4bed6651a223cbb941e7b432647", + "head_sha": "3b81f9973223d2a910369711c08adc772003639d", + "tree_sha": "ff705b61bf25fe40d0ff054c372a399eacc79730", + "review_commit": "3b81f9973223d2a910369711c08adc772003639d", + "reviewer": "planner-reviewer-claude-01", + "job_id": "2ab23f4e", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-29T06:08:06Z" +} \ No newline at end of file diff --git a/.agents/reports/planner-reviewer-claude-01/report-2ab23f4e.md b/.agents/reports/planner-reviewer-claude-01/report-2ab23f4e.md new file mode 100644 index 0000000..5e2f5c8 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-2ab23f4e.md @@ -0,0 +1,133 @@ +# PR #79 (Issue #68) 리뷰 — head 3b81f99 + +리뷰어: planner-reviewer-claude-01 (claude) · job 2ab23f4e + +## 0. 리뷰 경로 확인 + +- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T045625Z-74296d/1` + - HEAD 는 `3b81f9973223d2a910369711c08adc772003639d`, tree 는 `ff705b61bf25fe40d0ff054c372a399eacc79730` 로 [리뷰 identity] 와 같다. 리뷰 전과 후 두 번 확인했다. + - `git status --porcelain --ignored` 결과는 비어 있고, `.mam` 도 없다. +- 리뷰 범위: base `b9c828f` 와 HEAD 사이의 커밋 5개, 9개 파일, +277/−22 줄. +- 기준 문서(읽기만 했다): `tea issues 68 --comments` 의 결정 1–4 와 완료 조건, `tea pulls 79` 설명, `INSTRUCTION.md`. + +## 1. 결정·완료 조건 대조표 + +| 조건 | 구현(파일:줄) | 테스트 | 판정 | +|---|---|---|---| +| 결정 1: create 의 `--no-onboard` 유지, 기본은 온보딩 | `create_session.sh:67` `ONBOARD=1`, `:87-88` 그대로 | 기존 테스트 전체가 그대로 통과 | ✅ | +| 결정 2: worktree_prepare 기본을 온보딩으로 | `worktree_prepare.sh:30` `ONBOARD=1` | `test_prepare_onboards_by_default_end_to_end`(온보딩 잡 completed, "onboarding completed" 출력) | ✅ | +| 결정 2: `--no-onboard` 는 건너뛰고 create 에 그대로 넘김 | `:43`, `:232` `args+=(--no-onboard)` | `test_prepare_no_onboard_sends_none`, `test_prepare_forwards_onboarding_choice[opts2]` | ✅ | +| 결정 2: 기존 `--onboard` 도 같은 동작 | `:42` | `forwards[opts1]`, `[opts3]`(둘 다 주면 마지막 것이 이김) | ✅ | +| 결정 3: agy·codex·hermes·opencode 에만 경고, claude·grok 에는 없음 | `create_session.sh:208-211`, facts `lib_py/agents/__main__.py:75` | `test_facts_expose_post_discovered[6]`, `test_no_onboard_warning_dry_run[6]`, `..._keeps_exit_and_row[6]` | ✅ | +| 결정 3: 경고 내용 "첫 대화 전까지 ID 미기록, 같은 폴더 같은 종류는 기다림(#63)" | `:210` 의 문구가 이 두 가지와 `MAM_ID_ORDER_WAIT_S` 를 말한다 | 경고에 `Issue #63` 과 에이전트 이름이 들어 있는지 확인 | ✅ | +| 결정 3: 종료 코드·행이 바뀌지 않음 | 경고 출력만 하고 분기는 없다 | `keeps_exit_and_row`: rc 0, 행 필드(이름, status, role, server, workspace, cmd, cmd_full, cwd, id source·verified, 상태, control_plane)를 기대값과 비교 | ✅ | +| 결정 3: 뒤에서 거부돼도 경고가 먼저 보임 | spawn·#63 검사(`:287` 부근)보다 앞에서 출력 | `test_no_onboard_warning_precedes_a_later_refusal` | ✅ | +| 결정 4: 문서는 create SKILL.md 옵션 표와 worktree 절만 고침 | `SKILL.md:173`, `:205`, `:210`, `:213` 네 줄 | `test_issue42_skill_blocks`(블록 sha 와 근거 핀) | ✅ | +| 완료: "기본은 온보딩 안 함" 을 전제로 한 worktree 테스트를 고침 | `test_issue21_acceptance.py:47`, `:207`, `test_issue21_phase2_loop_a.py:422` | 모두 통과 | ✅ | +| 완료: macOS(bash 3.2, Python 3.9)에서 통과 | — | §3 | ✅ | + +빠진 조건은 없다. + +## 2. 꼼꼼히 본 것 + +### 2.1 경고 판정과 facts + +- 이 Mac 에서 `lib_py.agents facts ` 를 직접 실행했다. + - agy, codex, hermes, opencode 는 `MAM_ID_POST_DISCOVERED=1` 이다. + - claude, grok 은 `0` 이다. + - 값은 어댑터의 `id_lifecycle`(`base.py:174-175` 의 `PRE_ASSIGNED`/`POST_DISCOVERED`)에서 나온다. 셸 쪽에는 에이전트 목록을 두지 않았다. +- facts 를 쓰는 다른 곳에는 영향이 없다. + - `lib.sh:2596`(eval 뒤 필요한 키만 읽음), `:2957`(eval), `:3156`(`sed -n 's/^KEY=//p'` 로 키별 추출)은 모두 키 단위로 읽는다. 그래서 줄이 하나 늘어도 문제가 없다. + - facts 출력을 다루는 테스트(`test_a4_adapter_contract.py`, `test_issue23_24_startup_path.py`)도 이 Mac 에서 103 passed 였다. +- facts 를 얻지 못하는 경우(파이썬 실패 등) `eval "$(… || true)"` 는 아무 값도 정하지 않는다. 그러면 `${MAM_ID_POST_DISCOVERED:-0}` 가 0 이 되어 경고 없이 지나간다. 동작이 바뀌지 않는 쪽으로 실패하므로 안전하다. +- `MAM_DELEGATION_PY=0` 는 위임(delegation) 경로에만 쓰이는 스위치다. `lib_py.agents facts` 와는 관계가 없어 경고 판정에 영향이 없다. +- `test_issue26_ints.py:111` 은 이 값을 "문자열 비교" 로 분류해 정수 기본값 검사 대상에서 뺐다. 코드도 실제로 문자열로 비교하므로(`= 1`) 맞는 분류다. + +### 2.2 `--creator-agent` 를 부르는 모든 곳 (`git grep -n creator-agent`) + +- 테스트 + - `test_issue21_acceptance.py:47`, `:207` 와 `test_issue21_phase2_loop_a.py:422` 에는 이제 `--no-onboard` 가 붙어 있다. + - `test_issue21_phase2_loop_a.py:339`, `test_issue50_onboard.py:192`, `test_issue57_logs_follow_registry.py:276`, `test_issue50_dispatch_herdr.py:868` 는 원래 `--onboard` 를 명시하므로 동작이 같다. + - `test_issue21_phase2_loop_a.py:355`(w6c)는 옵션이 없다. 하지만 서브모듈 초기화가 실패해 create 이전에 exit 1 로 끝나고, 행이 없음을 확인하는 테스트다. 그래서 온보딩까지 가지 않는다. 뒤에 다시 부르는 `_prepare(main, wt)` 에는 `--creator-agent` 가 없다. + - 새 테스트 파일의 세 호출은 의도한 대로다. +- 문서와 기록 + - `docs/design/worktree-creator-model.md:48-51` 의 예시에는 옵션이 없다. 그래서 이제 온보딩하는데, 예시가 보여 주려는 creator 흐름에도 그게 맞다. 이 문서에는 "기본은 온보딩 안 함" 이라는 문장이 없어 고칠 내용도 없다. 결정 4 의 "최소 변경" 과도 맞는다. + - `VERSIONS.md:632` 는 과거 기록이고, `README*.md:51` 은 create 에 대한 설명이라 맞다. + - 그 밖의 스크립트에서는 `worktree_prepare.sh` 를 부르는 곳이 없다. +- 결론적으로 온보딩 대기(최대 `MAM_ONBOARD_WAIT_S`) 때문에 의도치 않게 느려지거나 44/45 로 끝날 호출은 남아 있지 않다. + +### 2.3 SKILL.md 와 코드의 줄 단위 대조 (INSTRUCTION.md) + +- `SKILL.md:173` 은 "`--no-onboard` 로 만든, ID 가 첫 턴 뒤에 생기는 에이전트(`id_lifecycle`)는 그때까지 ID 를 기록하지 않고, 같은 폴더의 같은 종류 create 를 막는다(#63). create 가 경고한다" 고 설명한다. + - 코드(`create_session.sh:209-210`)의 조건과 문구가 이와 같다. + - #63 쪽은 정확히는 "막는다" 가 아니라 "기다린 뒤 거부" 다. 하지만 표 한 줄 요약으로는 맞다. +- `SKILL.md:205` 의 시놉시스 `[--no-onboard]` 는 `worktree_prepare.sh:9`, `:43` 과 같다. +- `SKILL.md:210` 의 "onboards it … `--no-onboard` skips it, `--onboard` is the default and still accepted" 는 `:30`, `:42-43`, `:232` 와 같다. +- `SKILL.md:213` 의 종료 코드 "44/45 are create's (…onboarding is the default…)" 는 스크립트 머리말 `:17-19` 와 같고, create 의 44/45 정의(`create_session.sh:22-23`)와도 같다. +- 판단이 필요한 내용만 문서에 있고, 판정은 코드(adapter facts)에 있다. INSTRUCTION.md 를 따른 것이다. + +### 2.4 `tests/data/skill_code_blocks.json` + +- 근거 핀의 줄 번호 이동은 실제로 늘어난 줄 수와 맞는다. + - `create_session.sh` 는 경고 4줄을 `:208` 에 넣었으므로 그 뒤 근거가 모두 +4 줄 밀렸다(360→364, 518-545→522-549 등). + - `worktree_prepare.sh` 는 `--no-onboard` case 한 줄 때문에 +1 줄 밀렸다(250→251). +- 내용이 바뀐 블록은 하나다. `SKILL.md:202-206` 의 worktree 시놉시스가 `[--onboard]` 에서 `[--no-onboard]` 로 바뀌었고, 그 블록의 sha 만 `38836d31bf38` 에서 `8bfabafb0328` 로 바뀌었다. +- `test_issue42_skill_blocks.py` 가 sha 와 근거를 다시 계산해 통과했다(§3). + +### 2.5 새 테스트가 결함을 잡는지 + +- base 대조 실행: `git archive HEAD` 를 TMPDIR 에 풀고, 코드 3개 파일(`__main__.py`, `create_session.sh`, `worktree_prepare.sh`)만 b9c828f 판으로 바꾼 뒤 `tests/test_issue68_onboard_default.py` 를 돌렸다. + - 결과는 **20 failed, 5 passed** 로, 작성자의 주장과 같다. + - 통과한 5건은 base 에서도 맞아야 하는 경우다. claude·grok 의 dry-run 과 행 시험 4건, 그리고 `--onboard` 전달 1건이다. + - 사본은 확인한 뒤 지웠다. +- Python 3.9 호환성: `str.removeprefix` 는 3.9 부터 있다. `list[str]` 주석은 `from __future__ import annotations` 덕분에 3.9 에서도 문제가 없다. + +## 3. 실행한 테스트 (이 Mac: macOS, `/bin/bash` 3.2, `/usr/bin/python3` 3.9.6) + +``` +cd <검증 경로> +env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME \ + -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR \ + PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python \ + -m pytest -p no:cacheprovider --basetemp="$TMPDIR/claude-j2ab-bt" -q -rfs \ + tests/test_issue68_onboard_default.py tests/test_issue21_acceptance.py tests/test_issue21_phase2_loop_a.py \ + tests/test_issue26_ints.py tests/test_issue42_skill_blocks.py tests/test_issue50_onboard.py \ + tests/test_issue57_logs_follow_registry.py tests/test_issue63_same_cwd_ids.py +``` + +- 결과: **265 passed, 1 skipped, 0 failed** (724 s). + - skip 1건은 `test_issue42_skill_blocks.py:266` 이다. untracked `.claude/skills` 가 worktree 에 없어서 생기는 환경 문제다. +- facts 를 쓰는 다른 테스트 `tests/test_a4_adapter_contract.py`, `tests/test_issue23_24_startup_path.py` 는 **103 passed** (152 s). +- base 대조 실행(§2.5)은 `test_issue68_onboard_default.py` 기준 20 failed, 5 passed. +- `test_issue50_dispatch_herdr.py` 는 실제 herdr 서버를 띄우므로 브리프 제약에 따라 실행하지 않았다. 이 파일이 worktree_prepare 를 부르는 곳(`:868`)은 `--onboard` 를 명시하므로 이번 변경으로 동작이 바뀌지 않는다. +- 테스트가 끝난 뒤 남은 프로세스: 테스트가 띄운 `reconcile.sh`, mock herdr, 구독자는 없다. 남아 있는 것은 원래 있던 셋뿐이다. + - 본 체크아웃 모니터 3714 + - 이번 루프 88445 + - 이번 잡의 job_subscriber 88788 (13:56:28 시작) + +## 4. 발견 사항 + +### 차단 + +- 없음. + +### 비차단 + +- **N1.** `tests/test_issue68_onboard_default.py:17` 의 `import yaml` 은 쓰이지 않는다. PR 설명에서도 남긴 것으로 밝혔다. +- **N2.** `--no-onboard --submit-job "<프롬프트>"` 를 함께 주면, 제출한 잡이 첫 턴이 되는데도 경고가 나온다. + - 이 조합에서는 원래 온보딩 프롬프트를 만들지 않는다(`create_session.sh:440`). 그래서 `--no-onboard` 가 사실상 아무 일도 하지 않는다. + - 결정 3 의 조건("`--no-onboard` 를 주고 ID 후발견 에이전트이면")을 그대로 구현한 결과다. 그래서 결함은 아니고, 문구가 조금 지나치게 경고할 뿐이다. +- **N3.** facts 를 얻지 못했는데 호출자 환경에 `MAM_ID_POST_DISCOVERED=1` 이 export 돼 있으면, claude 에도 경고가 나올 수 있다. 현실적인 경로가 없어 참고로만 적는다. + +## 5. 사용자 확인 필요 + +- N2: `--submit-job` 과 함께 준 `--no-onboard` 에도 경고를 낼지. 지금은 결정 3 의 문언대로 경고를 낸다. + +## 6. 결론 + +- 결정 1–4 와 완료 조건이 모두 구현됐고 테스트로 덮였다. +- 새 테스트는 base 에서 20건이 실패하므로 결함을 실제로 잡는다. +- 바뀐 테스트 파일과 create/worktree 관련 테스트가 이 Mac 에서 모두 통과한다. +- SKILL.md 의 설명은 코드와 줄 단위로 맞는다. + +[VERDICT: PASS] diff --git a/.agents/reports/planner-reviewer-codex-01/report-ee26d64c.identity.json b/.agents/reports/planner-reviewer-codex-01/report-ee26d64c.identity.json new file mode 100644 index 0000000..513885f --- /dev/null +++ b/.agents/reports/planner-reviewer-codex-01/report-ee26d64c.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261029T045625Z-74296d", + "base_sha": "b9c828f09214b4bed6651a223cbb941e7b432647", + "head_sha": "3b81f9973223d2a910369711c08adc772003639d", + "tree_sha": "ff705b61bf25fe40d0ff054c372a399eacc79730", + "review_commit": "3b81f9973223d2a910369711c08adc772003639d", + "reviewer": "planner-reviewer-codex-01", + "job_id": "ee26d64c", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-29T06:08:06Z" +} \ No newline at end of file diff --git a/.agents/reports/planner-reviewer-codex-01/report-ee26d64c.md b/.agents/reports/planner-reviewer-codex-01/report-ee26d64c.md new file mode 100644 index 0000000..9235fc0 --- /dev/null +++ b/.agents/reports/planner-reviewer-codex-01/report-ee26d64c.md @@ -0,0 +1,93 @@ +# PR #79 / Issue #68 리뷰 — PASS + +- Job: ee26d64c / Reviewer: planner-reviewer-codex-01 +- 기준: `tea issues 68 --comments`의 “구현 전에 정한 것”(결정 1–4), `tea pulls 79`, `INSTRUCTION.md:5`. +- Loop: `loop-20261029T045625Z-74296d` +- Base: `b9c828f09214b4bed6651a223cbb941e7b432647` +- Head: `3b81f9973223d2a910369711c08adc772003639d` +- Tree: `ff705b61bf25fe40d0ff054c372a399eacc79730` +- 검증 루트: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T045625Z-74296d/1` +- 아래 파일:줄은 이 검증 루트 기준이다. 시작·종료 HEAD/tree 일치, `git status --short` 빈 출력, `git diff --check b9c828f..HEAD` 통과. creator 작업 트리에서 실행하거나 수정하지 않았다. + +## 결정·완료 조건 대조 + +| 요구사항 | 코드·문서 근거 | 검증 | 빠진 조건 | +|---|---|---|---| +| 결정 1: create의 기본 온보딩 및 --no-onboard 유지 | `create_session.sh:67`, `:87`, `:88`, `:440` | 새 파일의 기본 dry-run 대조군·6종 실제 skip 행 검증, 기존 Issue #50 완료/44/45 테스트 | 없음 | +| 결정 2: worktree creator 기본 온보딩, skip 전달, 기존 --onboard 수용 | `worktree_prepare.sh:30`, `:42`, `:43`, `:230`, `:232` | 새 테스트 `:195`, `:215`, `:233`: 옵션 전달 4조합 및 실제 mock 온보딩 완료/잡 미생성; 외부 탐침에서 반대 순서 옵션도 확인 | 없음 | +| 결정 3: agy/codex/hermes/opencode에만 경고 1줄 | `lib_py/agents/__main__.py:75`, `create_session.sh:207`–`:211` | 새 테스트 `:50`, `:67`, `:95`: 6종 facts, dry-run/실제 create 및 기본 옵션 대조군 | 없음 | +| 경고가 종료 코드·행을 바꾸지 않음 | 변경은 facts 출력과 stderr 조건문뿐; 행 기록 분기는 그대로 | 새 테스트 `:95`에서 rc=0, 이름·역할·status·cmd·cwd·ID 출처·검증 상태·control_plane·delegate_job_id 검증. base 대조에서도 이 검증 후 경고 단언에서만 실패 | 없음 | +| 경고 이후의 거부도 기존 동작 유지 | `create_session.sh:209` 및 기존 #63 순서 게이트 | 새 테스트 `:157`: 미발견 agy 선행 세션, wait=0, rc=1 및 경고가 거부 설명보다 먼저 출력 | 없음 | +| 결정 4: 문서 변경 최소화 | create `SKILL.md:173`, `:205`, `:210`, `:213` | 옵션 표 1줄·worktree 절 3줄만 변경. 각 설명을 위 코드와 대조 | 없음 | +| 기존 implicit skip 호출 수정 및 Mac 통과 | `test_issue21_acceptance.py:47`, `:207`; `test_issue21_phase2_loop_a.py:422` | 변경 파일 5개와 관련 파일 3개 직접 실행. 아래 경로 이름 충돌 1건은 경로 보정 재실행으로 해소 | 없음 | + +`create_session.sh`와 `worktree_prepare.sh`의 경로는 `.agents/skills/multi-agent-mux-create/scripts/` 아래다. 새 테스트 줄은 `tests/test_issue68_onboard_default.py` 기준이다. + +## 추가 코드·호출부 확인 + +- 어댑터: `lib_py/agents/base.py:379`–`:382`의 기본값은 POST_DISCOVERED. claude의 `adapters/claude.py:188`–`:190`, grok의 `adapters/grok.py:18`–`:20`만 PRE_ASSIGNED로 재정의한다. 나머지 4종은 기본값을 사용한다. facts는 이 값을 0/1로 출력하므로 셸에 별도 에이전트 목록을 복제하지 않는다. +- facts 소비자는 `lib.sh:2596`(readiness eval), `:2957`(send_keys_safe eval), `:3156`(dialog facts의 이름별 sed 선택), `create_session.sh:207`이다. 새 항목은 숫자 할당문이므로 기존 eval 계약을 유지하고, 이름별 파싱에도 섞이지 않는다. 기존 facts 계약·CLI 인용 테스트 통과. +- `MAM_DELEGATION_PY=0`도 agents facts 호출을 건너뛰지 않는다. 외부 탐침에서 6종 경고 판정 및 worktree 기본 온보딩을 확인했다. facts 출력이 비거나 조회가 실패하면, 값이 미설정인 경우 `${MAM_ID_POST_DISCOVERED:-0}`에 의해 경고만 생략된다. rc=0/1의 빈 facts 탐침 모두 dry-run rc=0, nounset 오류 없음. Python 조회 불가 상황에 새 hard failure를 추가하지 않는 기존 best-effort 동작이다. +- `git grep -n worktree_prepare.sh` 및 `git grep -n -- --creator-agent`로 추적 파일을 전수 확인했다. 실행 호출은 아래 표와 같다. 숨은 자동 호출 스크립트는 없다. + +| 호출 위치 | 바뀐 기본값의 영향 | +|---|---| +| `test_issue21_acceptance.py:47,207`, `test_issue21_phase2_loop_a.py:422` | 온보딩과 무관한 기존 검증이므로 명시적 --no-onboard 추가가 맞음 | +| `test_issue21_phase2_loop_a.py:339` | 이미 --onboard이며 실제 mock 완료를 검증 | +| `test_issue21_phase2_loop_a.py:355` | submodule 초기화 거부가 creator 단계보다 앞서 발생; 온보딩 대기 없음 | +| `test_issue50_onboard.py:192` | 기존 --onboard + stub의 44/45 전달 검증 유지 | +| `test_issue50_dispatch_herdr.py:868` | 이미 --onboard를 명시한 실제 herdr 통합 테스트. 이번 제약에 따라 실행하지 않음 | +| `test_issue57_logs_follow_registry.py:276` | 기존 --onboard 및 mock 완료. 이번 Mac 실행 통과 | +| 새 테스트 `:202,219,237` | 전달·기본 온보딩·skip를 의도적으로 각각 검증 | +| create `SKILL.md:205`, `docs/design/worktree-creator-model.md:48`–`:52` | 사용자 실행 예시는 이제 기본 온보딩을 수행. SKILL의 `:213`이 44/45 및 세션·worktree 보존을 명시 | +| README/README.ko의 `:51`, VERSIONS의 역사 기록, `lib_py/worktree.py:3` | 설명/주석이며 추가 실행 호출 없음 | + +- `tests/data/skill_code_blocks.json`: 변경된 evidence/passthrough 9곳을 base와 HEAD의 실제 줄 내용으로 비교해 모두 동일함을 확인했다. 변경된 비핀 필드는 worktree 사용 예시의 sha 1개(`38836d31bf38 → 8bfabafb0328`)뿐이며 --onboard → --no-onboard에 해당한다. inventory 테스트 통과. +- 새 테스트는 단순 문자열 존재 검사에 그치지 않고 옵션 전달, 실제 mock 잡의 completed 상태, skip 시 잡 미생성, 행의 안정 필드, 후속 거부를 확인한다. 아래 base 대조 결과도 예상과 일치했다. + +## Mac 실행 및 결과 + +환경: Darwin arm64, `bash`는 `/bin/bash` 3.2.57, `/usr/bin/python3` 및 사용한 제어 루트 venv Python은 3.9.6. 검증 트리에는 venv가 없어 브리프 상단에 지정된 제어 루트 인터프리터를 사용했다. 모든 pytest 실행 cwd는 검증 루트이며 공통 접두부는 다음과 같다. + +```bash +env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID \ + -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION \ + -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR \ + -u DELEGATE_JOB_LOGS_DIR -u AGENT_SESSIONS_YAML \ + PYTHONDONTWRITEBYTECODE=1 \ + /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python \ + -m pytest -p no:cacheprovider --basetemp=<아래의 TMPDIR 경로> -q <선택 항목> +``` + +TMPDIR 접두부는 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/`이다. + +| 실행 | 선택 항목·추가 옵션 | 결과·증거 | +|---|---|---| +| 주 실행, basetemp `mam-review-ee26d64c-main` | `tests/test_issue68_onboard_default.py tests/test_issue21_acceptance.py tests/test_issue21_phase2_loop_a.py tests/test_issue26_ints.py tests/test_issue42_skill_blocks.py tests/test_issue50_onboard.py tests/test_uuid_target.py tests/test_issue21_worktree_prereqs.py` | 265 passed, 1 failed, 1 skipped / 819.55s. [main.log](evidence/main.log) | +| 위 실패 사례 재실행, basetemp `mam-eval-ee26d64c-a4` | `tests/test_issue21_acceptance.py::test_a4_main_checkout_is_unchanged_by_the_whole_cycle` | 1 passed / 2.04s. [a4.log](evidence/a4.log) | +| 계약/create 회귀, basetemp `mam-review-ee26d64c-contract` | `tests/test_a4_adapter_contract.py::test_agent_adapter_registry ::test_facts_bridge_eval_contract ::test_cli_bridge_subcommands_and_quote_safety`(각각 전체 node 경로 사용), `tests/test_tier2_component.py`, `tests/test_issue57_logs_follow_registry.py::test_worktree_prepare_keeps_the_logs_next_to_the_control_root_registry`; `-k 'create or facts_bridge or agent_adapter_registry or cli_bridge'` | 16 passed, 30 deselected / 184.63s. 마지막 logs 노드는 이 필터에서 제외되어 아래 별도 실행. [contract.log](evidence/contract.log) | +| logs 별도 실행, basetemp `mam-eval-ee26d64c-logs` | `tests/test_issue57_logs_follow_registry.py::test_worktree_prepare_keeps_the_logs_next_to_the_control_root_registry` | 1 passed / 18.13s. [logs.log](evidence/logs.log) | +| 외부 탐침, basetemp `mam-review-ee26d64c-probes` | `tests/test_issue68_onboard_default.py -s -p probe_issue68`; 추가 env `PYTHONPATH=/tmp/mam-review-ee26d64c-harness` | 12 passed / 41.84s. legacy 6종 경고+기본 온보딩 1건, 빈 facts 2건, 마지막 skip 옵션 1건, 기본 44/45 전달 2건. [probes.log](evidence/probes.log), [probe_issue68.py](evidence/probe_issue68.py) | +| base 대조, basetemp `mam-eval-ee26d64c-base` | 같은 새 테스트 파일에 `-p base_issue68`; 같은 추가 PYTHONPATH | 의도한 20 failed, 5 passed / 77.25s. [base.log](evidence/base.log), [base_issue68.py](evidence/base_issue68.py) | + +주 실행 실패는 `tests/test_issue21_acceptance.py:142`의 “worktree list 어디에도 mam-review 문자열이 없어야 한다”는 기존 단언 때문이다. 이 검토의 basetemp 이름 자체에 그 문자열이 들어갔다. 바로 앞의 HEAD·branch·status·branch tip·worktree list 원상복구 단언은 통과했다. 소스나 단언을 바꾸지 않고 basetemp 이름만 `mam-eval-ee26d64c-a4`로 바꾼 재실행이 통과했으므로 제품 회귀로 판정하지 않는다. 새 테스트 25건은 주 실행에서 모두 통과했다. + +skip 1건은 `test_issue42_skill_blocks.py:266`: 검증 worktree에 추적되지 않는 `.claude/skills` 링크가 없는 경우다. 해당 링크의 합성 layout 테스트들은 실행됐다. 주 실행의 경로 보정까지 반영하면 제품 검증은 266건 통과·1건 skip이며, 추가 회귀/탐침 29건도 통과했다. 중복 확인한 A3 단독 실행은 별도 총계에 넣지 않았다. + +base 대조는 다른 checkout에서 실행한 것이 아니다. HEAD의 새 테스트와 fixtures를 그대로 두고, `git show b9c828f:`로 얻은 제품 파일 3개(create, worktree_prepare, agents CLI)만 임시 샌드박스에 넣었다. 직접 facts를 조회하는 6건은 base CLI를 임시 스크립트로 실행했고, 어댑터는 변경 없는 검증 트리 것을 사용했다. 실패 구성은 facts 6 + dry-run 경고 4 + 실제 행 검증 후 경고 4 + 후속 거부 경고 1 + 전달 3 + 기본 완료 1 + skip 1이다. 통과 5건은 claude/grok 경고 없음 각 2건과 기존 --onboard 전달 1건이다. + +추가 정적 검사: 변경 셸 2개 `/bin/bash -n` 통과, 변경 Python 5개 `ast.parse(..., feature_version=(3,9))` 통과, diff 공백 검사 통과. ShellCheck는 설치되어 있지 않아 실행하지 않았다. 전체 스위트는 실행하지 않았다. + +## 발견 사항 + +- **차단: 없음.** +- **비차단(P3):** `tests/test_issue68_onboard_default.py:17`의 `import yaml`은 사용되지 않는다. 해당 1줄을 제거하면 된다. 동작성·데이터 유실 문제는 아니며 판정을 막지 않는다. +- 위 A4 경로 문자열 단언의 민감성은 이번 diff에 도입되지 않았다. 이 리뷰에서는 경로 보정으로 검증을 완료했으며 PR 범위 확대를 요구하지 않는다. + +## 격리·잔존 확인 및 사용자 확인 필요 + +모든 create/worktree 동작은 테스트의 mock herdr와 임시 작업 공간에서만 실행했다. 실제 에이전트 홈 DB를 열거나 실제 opencode 서비스·herdr 서버를 사용하지 않았다. 실제 .mam.env를 읽어 출력하거나 export하지 않았다. 테스트 종료 후 이번 실행의 임시 경로를 가진 프로세스는 0개이고, 실제 제어 루트 모니터는 시작·종료 모두 PID 3714다([process-audit.json](evidence/process-audit.json)). 패턴 kill, pkill, killall 및 실제 모니터에 대한 신호는 사용하지 않았다. 제품 소스·브랜치·이슈/PR 상태를 수정하거나 push/merge하지 않았다. 제어 평면 쓰기는 승인된 잡 이벤트와 이 리포트·증거뿐이다. + +사용자 확인 필요: 없음. 결정 변경이나 재설계가 필요하지 않다. + +[VERDICT: PASS] diff --git a/.agents/reports/reviewer-agy-01/report-ace63ec3.identity.json b/.agents/reports/reviewer-agy-01/report-ace63ec3.identity.json new file mode 100644 index 0000000..4112e74 --- /dev/null +++ b/.agents/reports/reviewer-agy-01/report-ace63ec3.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261029T045625Z-74296d", + "base_sha": "b9c828f09214b4bed6651a223cbb941e7b432647", + "head_sha": "3b81f9973223d2a910369711c08adc772003639d", + "tree_sha": "ff705b61bf25fe40d0ff054c372a399eacc79730", + "review_commit": "3b81f9973223d2a910369711c08adc772003639d", + "reviewer": "reviewer-agy-01", + "job_id": "ace63ec3", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-29T06:08:06Z" +} \ No newline at end of file diff --git a/.agents/reports/reviewer-agy-01/report-ace63ec3.md b/.agents/reports/reviewer-agy-01/report-ace63ec3.md new file mode 100644 index 0000000..1708651 --- /dev/null +++ b/.agents/reports/reviewer-agy-01/report-ace63ec3.md @@ -0,0 +1,121 @@ +# Code Review Report — PR #79 (Issue #68: `worktree_prepare.sh` Default Creator Onboarding & `create --no-onboard` Warning) + +- **Job ID**: `ace63ec3` +- **Reviewer**: `agy` (`herdr:reviewer-agy-01`) +- **Loop ID**: `loop-20261029T045625Z-74296d` (Round 1) +- **Review Snapshot Path**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T045625Z-74296d/1` +- **Branch**: `fix/issue-68` +- **Base Commit**: `b9c828f09214b4bed6651a223cbb941e7b432647` +- **Head Commit**: `3b81f9973223d2a910369711c08adc772003639d` (5 commits) +- **Tree Hash**: `ff705b61bf25fe40d0ff054c372a399eacc79730` + +--- + +## 1. 확인한 것 (Files & Line Ranges Inspected in Review Snapshot) + +- **기준 문서 대조 (`INSTRUCTION.md`, `tea issues 68 --comments`, `tea pulls 79`)**: + - `INSTRUCTION.md:1-13` (스크립트·테스트 우선 원칙 및 `SKILL.md`와 코드의 줄 단위 일치 원칙 확인) + - Issue #68 댓글("구현 전에 정한 것", 결정 1–4 및 완료 조건)과 PR #79 설명 대조 +- **어댑터 `id_lifecycle` 및 `facts` 브리지**: + - `.agents/skills/lib_py/agents/__main__.py:4, 75` (`from lib_py.agents.base import POST_DISCOVERED`, `MAM_ID_POST_DISCOVERED={1 if adapter.id_lifecycle == POST_DISCOVERED else 0}`) + - `.agents/skills/lib_py/agents/base.py:174-175, 379` (기본값 `POST_DISCOVERED`: `agy`, `codex`, `hermes`, `opencode`) + - `.agents/skills/lib_py/agents/adapters/claude.py:188`, `grok.py:18` (`PRE_ASSIGNED`: `claude`, `grok`) + - 다른 `lib_py.agents facts` 호출 지점 영향 확인: + - `.agents/skills/lib.sh:2596-2598` (`wait_for_tui_ready` `eval "$_facts"`) + - `.agents/skills/lib.sh:2957` (`send_keys_safe` `eval ...`) + - `.agents/skills/lib.sh:3156-3170` (`_mam_load_dialog_facts` — `sed -n`으로 특정 키만 추출) + - `tests/test_issue23_24_startup_path.py:480-500` (`MAM_STARTUP_DIALOGS=` 행만 필터링) + - 모든 기존 호출자가 새 `MAM_ID_POST_DISCOVERED` 키 추가에 전혀 영향받지 않음을 확인. +- **`create_session.sh` 및 `worktree_prepare.sh` 구현·문서 대조**: + - `.agents/skills/multi-agent-mux-create/scripts/create_session.sh:67, 88, 207-211`: + - `ONBOARD=1` 기본 유지, `--no-onboard` 유지. + - `eval "$("$(_delegate_py_bin)" -m lib_py.agents facts "$AGENT" 2>/dev/null || true)"` 직후 `if [ "$ONBOARD" = 0 ] && [ "${MAM_ID_POST_DISCOVERED:-0}" = 1 ]; then`으로 stderr 경고 1줄 출력(`create_session.sh:209-211`). + - `facts` 호출이 실패하거나 빈 값일 때도 `${MAM_ID_POST_DISCOVERED:-0}` 기본값 `0`으로 `set -u` 아래 안전하게 통과. + - `spawn` 및 `--dry-run` 분기(`219`)보다 앞에 위치하여 `--dry-run`이나 이후 `#63` 대기 거부(`225`) 시에도 경고가 먼저 출력되며, 종료 코드와 레지스트리 행은 전혀 바꾸지 않음. + - `.agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh:6-19, 30, 42-43, 231-232`: + - `ONBOARD=1`로 기본값 변경(`30`), `--onboard`는 `ONBOARD=1`로 계속 허용(`42`), `--no-onboard`는 `ONBOARD=0` 설정(`43`), `create_session.sh`에 `--onboard` 또는 `--no-onboard` 전달(`232`). + - 상단 주석(`6-19`, `usage()`가 `sed -n '2,15p'`로 출력하는 범위 포함)이 코드와 정확히 일치. + - `.agents/skills/multi-agent-mux-create/SKILL.md:173, 202-214`: + - 옵션 표(`173`)와 `worktree_prepare.sh` 시놉시스(`205`), `--creator-agent` 설명(`210`), 종료 코드 설명(`213`)을 줄 단위로 코드와 대조하여 일치 확인. +- **저장소 전체 `--creator-agent` 호출 전수 조사 (`git grep -- "--creator-agent"`)**: + - `tests/test_issue21_acceptance.py:47, 207`: `--no-onboard` 명시 추가됨. + - `tests/test_issue21_phase2_loop_a.py:339`: 기존 `--onboard` 명시. + - `tests/test_issue21_phase2_loop_a.py:355`: 서브모듈 초기화 실패(단계 4) 테스트로 creator 단계(단계 5)에 도달하지 않음. + - `tests/test_issue21_phase2_loop_a.py:422`: `--no-onboard` 명시 추가됨. + - `tests/test_issue50_dispatch_herdr.py:868`, `tests/test_issue50_onboard.py:192`, `tests/test_issue57_logs_follow_registry.py:276`: 모두 기존 `--onboard` 명시. + - `docs/design/worktree-creator-model.md:50`: 실제 워크트리 creator 생성 예시로 기본 온보딩 의도와 부합. +- **핀 이동 및 정수 환경변수 분류**: + - `tests/data/skill_code_blocks.json`: `create_session.sh` 208행 이후 참조들이 정확히 `+4`행, `worktree_prepare.sh` 43행 이후 참조가 `+1`행(`250`→`251`), `SKILL.md:202-206` 블록 해시가 `8bfabafb0328`로 내용 그대로 이동됨을 확인. + - `tests/test_issue26_ints.py:111`: `NOT_ARITHMETIC`에 `"MAM_ID_POST_DISCOVERED"` 등록 확인. +- **신규 테스트 (`tests/test_issue68_onboard_default.py:1-247`) 검증**: + - 총 25개 케이스: `test_facts_expose_post_discovered`(6), `test_no_onboard_warning_dry_run`(6), `test_no_onboard_warning_keeps_exit_and_row`(6), `test_no_onboard_warning_precedes_a_later_refusal`(1), `test_prepare_forwards_onboarding_choice`(4), `test_prepare_onboards_by_default_end_to_end`(1), `test_prepare_no_onboard_sends_none`(1). + - base `b9c828f` 기준 대조 결과: `claude`·`grok` 경고 없음 4건(`dry_run` 2 + `keeps_exit_and_row` 2)과 `--onboard` 전달 1건을 제외한 **20건이 base에서 정확히 실패**함을 코드로 확인(작성자 주장 일치). + +--- + +## 2. 결정 1–4 및 완료 조건 대조표 + +| 항목 | 요구사항 요약 | 구현 위치 | 테스트 커버리지 | 상태 | +|---|---|---|---|---| +| 결정 1 | `create_session.sh`의 `--no-onboard` 유지, 기본값 온보딩(`ONBOARD=1`) 유지 | `create_session.sh:67, 88` | `test_no_onboard_warning_dry_run`, `test_no_onboard_warning_keeps_exit_and_row` | 충족 | +| 결정 2 | `worktree_prepare.sh --creator-agent` 기본을 온보딩(`ONBOARD=1`)으로 변경, `--no-onboard` 추가 및 전달, 기존 `--onboard`도 동일 동작으로 수용 | `worktree_prepare.sh:30, 42-43, 232` | `test_prepare_forwards_onboarding_choice`, `test_prepare_onboards_by_default_end_to_end`, `test_prepare_no_onboard_sends_none` | 충족 | +| 결정 3 | `create --no-onboard` 시 후발견 ID 에이전트(`agy`·`codex`·`hermes`·`opencode`)에만 `#63` 대기 가능성 경고 1줄 출력, `claude`·`grok`에는 미출력, 종료 코드·레지스트리 행 불변 | `lib_py/agents/__main__.py:75`, `create_session.sh:207-211` | `test_facts_expose_post_discovered`, `test_no_onboard_warning_dry_run`, `test_no_onboard_warning_keeps_exit_and_row`, `test_no_onboard_warning_precedes_a_later_refusal` | 충족 | +| 결정 4 | `multi-agent-mux-create/SKILL.md` 옵션 표와 worktree 준비 절의 기본값 설명만 최소 수정 | `multi-agent-mux-create/SKILL.md:173, 205, 210, 213` | `tests/test_issue42_skill_blocks.py` | 충족 | +| 완료 조건 3 | 기존 worktree 테스트 중 기본 미온보딩을 전제하던 호출 수정 및 macOS 통과 | `test_issue21_acceptance.py:47, 207`, `test_issue21_phase2_loop_a.py:422`, `test_issue26_ints.py:111` | 아래 macOS 실측 265 passed | 충족 | + +빠진 완료 조건: **없음**. + +--- + +## 3. 실행한 테스트와 결과 (macOS 실측) + +검증 경로(`/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T045625Z-74296d/1`)에서 브리프의 격리 실행 명령으로 바뀐 테스트 파일과 create/worktree 관련 테스트를 모두 실행했습니다. + +### 명령 1 (`test_issue68_onboard_default.py`, `test_issue26_ints.py`, `test_issue42_skill_blocks.py`, `test_issue50_onboard.py`) +```bash +BT="$(mktemp -d "${TMPDIR:-/tmp}/mam-rev-ace63ec3-1.XXXXXX")" +trap 'rm -rf "$BT"' EXIT +cd /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T045625Z-74296d/1 && \ +env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR \ +PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider --basetemp="$BT/pytest" \ + tests/test_issue68_onboard_default.py \ + tests/test_issue26_ints.py \ + tests/test_issue42_skill_blocks.py \ + tests/test_issue50_onboard.py +``` +- **결과**: `177 passed, 1 skipped in 245.18s (0:04:05)` (exit code `0`) + +### 명령 2 (`test_issue21_acceptance.py`, `test_issue21_phase2_loop_a.py`, `test_issue57_logs_follow_registry.py`, `test_issue63_same_cwd_ids.py`) +```bash +BT="$(mktemp -d "${TMPDIR:-/tmp}/mam-rev-ace63ec3-2.XXXXXX")" +trap 'rm -rf "$BT"' EXIT +cd /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T045625Z-74296d/1 && \ +env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR \ +PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider --basetemp="$BT/pytest" \ + tests/test_issue21_acceptance.py \ + tests/test_issue21_phase2_loop_a.py \ + tests/test_issue57_logs_follow_registry.py \ + tests/test_issue63_same_cwd_ids.py +``` +- **결과**: `88 passed in 485.68s (0:08:05)` (exit code `0`) + +### 명령 3 (`/bin/bash -n` 구문 검사 및 프로세스·워크트리 무결성 확인) +- `create_session.sh`, `worktree_prepare.sh` `/bin/bash -n` 통과 (`exit 0`). +- `git status --short`: 출력 없음 (검증 스냅샷 깨끗함). +- `ps -ef`: 본 체크아웃의 기존 `reconcile.sh --subscribe` 데몬(PID `3714`)만 정상 유지 중이며, 테스트가 남긴 자식·더미 프로세스 없음. + +--- + +## 4. 발견 사항 (차단 / 비차단) + +- **차단 (Blocking)**: 없음. +- **비차단 (Non-Blocking)**: + - `[N-1]` `tests/test_issue68_onboard_default.py:18`: 쓰이지 않는 `import yaml` 1줄이 남아 있습니다(PR #79 본문에서도 인지됨, 동작에 영향 없음). + +--- + +## 5. 사용자 확인 필요 + +- 없음. + +[VERDICT: PASS] diff --git a/.agents/reports/reviewer-grok-01/report-8900db9e.identity.json b/.agents/reports/reviewer-grok-01/report-8900db9e.identity.json new file mode 100644 index 0000000..ef7af21 --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-8900db9e.identity.json @@ -0,0 +1,14 @@ +{ + "loop_id": "loop-20261029T045625Z-74296d", + "base_sha": "b9c828f09214b4bed6651a223cbb941e7b432647", + "head_sha": "3b81f9973223d2a910369711c08adc772003639d", + "tree_sha": "ff705b61bf25fe40d0ff054c372a399eacc79730", + "review_commit": "3b81f9973223d2a910369711c08adc772003639d", + "reviewer": "reviewer-grok-01", + "job_id": "8900db9e", + "verdict": "PASS", + "transport": "herdr", + "evidence": "review-tree", + "isolation": "worktree", + "verified_at": "2026-10-29T06:08:06Z" +} \ No newline at end of file diff --git a/.agents/reports/reviewer-grok-01/report-8900db9e.md b/.agents/reports/reviewer-grok-01/report-8900db9e.md new file mode 100644 index 0000000..3d25493 --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-8900db9e.md @@ -0,0 +1,78 @@ +# PR #79 (Issue #68) 리뷰 + +검증 경로는 `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261029T045625Z-74296d/1` 뿐이다. HEAD `3b81f9973223d2a910369711c08adc772003639d`, tree `ff705b61bf25fe40d0ff054c372a399eacc79730`, base `b9c828f09214b4bed6651a223cbb941e7b432647` 는 조상이다. 작업 트리는 깨끗하고 `git diff --check` 도 깨끗하다. 변경은 9파일, +277/−22 이다. 스냅샷에는 `.venv` 가 없다. 테스트는 제어 평면 Python 3.9.6 과 `/bin/bash` 3.2.57 로 돌렸다. + +## 확인한 것 + +결정 1–4 는 코드와 같다. + +| 완료 조건 | 구현 | 테스트 | +|---|---|---| +| create 의 `--no-onboard` 유지, 기본은 onboarding | `create_session.sh:51` 도움말, `:67` `ONBOARD=1` | dry-run 기본 경로는 경고 0줄 (`test_issue68_onboard_default.py:84-91`) | +| `worktree_prepare.sh --creator-agent` 는 기본 onboarding. `--no-onboard` 는 create 로 넘기고, `--onboard` 는 같은 뜻 | `:30` `ONBOARD=1`, `:42-43` 마지막 플래그가 이김, `:232` 둘 중 하나만 전달. 44/45 는 `:247-248`, `exit "$rc"` 는 `:251` | 전달 4건, 기본 e2e 1건, `--no-onboard` 면 잡 없음 1건 | +| 경고는 agy·codex·hermes·opencode 만. claude·grok 는 없다. 종료 코드와 행은 그대로 | `:208-210`. 판정은 facts 의 `MAM_ID_POST_DISCOVERED` | dry-run 6, 실제 행 6, 이후 거절 1 | +| 문서 변경은 create SKILL.md 옵션 표와 worktree 절 | `SKILL.md:173`, `:205`, `:210`, `:213` | `tests/test_issue42_skill_blocks.py` 핀이 현재 본문과 일치 (아래 1건 스킵) | + +경고 판정은 셸의 에이전트 목록이 아니다. `__main__.py:75` 가 `adapter.id_lifecycle == POST_DISCOVERED` 이면 `MAM_ID_POST_DISCOVERED=1`, 아니면 `0` 을 찍는다. 기본값은 `base.py:379-382` 의 `post_discovered` 이고, `claude.py:188-190` 과 `grok.py:18-20` 만 `pre_assigned` 다. 기대표(`test_issue68_onboard_default.py:30-37`)와 같다. + +facts 를 통째로 eval 하는 `lib.sh:2957` 은 토큰 변수만 읽고, `_mam_load_dialog_facts` (`lib.sh:3157`) 는 `MAM_STARTUP_DIALOGS` 줄만 꺼낸다. 추가 대입 한 줄은 그 두 경로의 결과를 바꾸지 않는다. `MAM_DELEGATION_PY=0` 은 `lib.sh:2323` 근처의 레거시 헬퍼 스위치이고, create 의 facts 호출(`create_session.sh:207`)은 그 스위치 밖에 있다. facts 가 실패하면 `|| true` 로 변수가 비고, `${MAM_ID_POST_DISCOVERED:-0}` 이라 경고는 나오지 않는다. + +경고는 spawn 전이다. `--dry-run` 종료는 `create_session.sh:286` 이고, `#63` 대기는 `:291` 이다. 그래서 dry-run 에도 경고가 나오고, 이후 거절보다 경고가 먼저다. + +`--creator-agent` 호출: + +- `tests/test_issue21_acceptance.py:47`, `:207` 과 `tests/test_issue21_phase2_loop_a.py:422` 는 `--no-onboard` 를 넘긴다. `:339` 는 원래 `--onboard` 다. +- `test_w6c` (`test_issue21_phase2_loop_a.py:355`) 는 플래그가 없다. 서브모듈 초기화가 `:356` 에서 실패하고 `:360` 에서 행이 비어, creator 까지 가지 않는다. +- `tests/test_issue50_onboard.py:192` 와 `tests/test_issue57_logs_follow_registry.py:276` 은 `--onboard` 다. +- `tests/test_issue50_dispatch_herdr.py:868` 은 `--onboard` 이고 실제 herdr 를 띄운다. 이번 리뷰에서는 실행하지 않았다. +- 다른 스크립트가 `worktree_prepare.sh` 를 호출하지는 않는다. `docs/design/worktree-creator-model.md:50` 의 claude 예시는 플래그가 없어서 이제 기본 onboarding 이다. `README.md:51` 은 `--onboard` 와 준비 스크립트만 언급한다. `VERSIONS.md` 는 당시 기록이다. + +`tests/data/skill_code_blocks.json` 의 create 핀은 경고 4줄이 들어간 뒤쪽 범위가 +4 다 (`360`→`364`, `518-545`→`522-549`, `259-274`→`263-278`, `490-608`→`494-612`, exit 4 핀 `550`→`554`). worktree 의 `exit "$rc"` 핀은 옵션 한 줄 때문에 `250`→`251` 이고, `:24` 는 그대로다. sha `38836d31bf38`→`8bfabafb0328` 은 줄 이동이 아니라 SKILL 예시가 `--no-onboard` 로 바뀐 것이다 (`skill_code_blocks.json:90-102`, `SKILL.md:202-206`). 핀 테스트는 그 본문과 맞춰 통과했다. + +새 테스트 25건을 base 에 올려 놓지 않고 단정만 세면, facts 6건, dry-run 경고 4건, 행 검사의 경고 단계 4건(`:150-152`. 행 단정은 그 앞에 있어 base 는 경고에서 실패), 거절 1건, 전달 4건 중 명시적 `--onboard` 를 뺀 3건, 기본 e2e 1건, `--no-onboard` 준비 1건이 실패한다. 20실패·5통과로 작성자 주장과 같다. 통과하는 5건은 claude·grok 의 경고 없음(dry-run 과 행) 4건과 명시적 `--onboard` 전달 1건이다. + +## 실행한 테스트와 결과 + +스냅샷에 `.venv` 가 없어 제어 평면 인터프리터를 썼다. cwd 는 스냅샷, basetemp 는 `/tmp/mam-i68-8900db9e` 이다. + +``` +env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE -u MAM_JOBS_DIR -u DELEGATE_JOB_LOGS_DIR \ + PYTHONDONTWRITEBYTECODE=1 \ + /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest \ + -p no:cacheprovider --basetemp=/tmp/mam-i68-8900db9e \ + tests/test_issue68_onboard_default.py \ + tests/test_issue21_acceptance.py \ + tests/test_issue21_phase2_loop_a.py \ + tests/test_issue26_ints.py \ + tests/test_issue42_skill_blocks.py \ + tests/test_issue50_onboard.py \ + tests/test_issue57_logs_follow_registry.py \ + -q --tb=line +``` + +결과: `238 passed, 1 skipped, 1 error in 497.36s (0:08:17)`, 프로세스 종료 코드 1. 제품 단정이 실패한 테스트는 없다. `test_issue26_ints.py` 는 `MAM_ID_POST_DISCOVERED` 를 문자열 비교로 분류한다 (`:111`). + +스킵 1건은 quiet 로그가 노드 이름을 찍지 않는다. 스냅샷에 `.claude/skills` 가 없고, 그 조건의 스킵은 `tests/test_issue42_skill_blocks.py:263-266` `test_claude_skills_link_resolves_to_agents_skills` 이다. + +세션 종료 오류는 `tests/test_issue57_logs_follow_registry.py::test_a_symlinked_registry_dir_resolves_to_one_audit_log` 에 붙어 있고, 본문 단정이 아니다. `conftest.py:1581` 의 `agent_home_leak_guard` 가 세션 동안 생긴 agy 홈 항목 10개(brain 디렉터리 5, conversation db 5)를 보고했다. 가드는 이름을 읽고 지우지 않는다 (`conftest.py:1586-1592`). claude 만 예외이고 그 외 디렉터리는 예외가 없다 (`tests/_agent_home_guard.py:106`). + +그 10개 항목의 mtime 은 pytest 구간(로그 생성 1793252159, 종료 1793252657, 약 14:36–14:44) 안이다. 다섯 db 를 연 프로세스는 pid 83251, `agy --hub`, 시작 13:50:53, 부모는 Antigravity IDE 헬퍼(82581)이다. pytest 자식이 아니다. 이번 테스트의 agy 경로는 `mock_agents` 이다. 파일은 열거나 지우지 않았고 그 자리에 두었다. 이 오류는 동작 중인 agy 가 자기 홈에 새 대화를 쓴 경우이고, 제품 결함으로 보지 않는다. + +테스트 뒤 pytest 프로세스와 mock herdr, 추가 `reconcile.sh` 는 없었다. 체크아웃 모니터 pid 3714 (`reconcile.sh --subscribe --idle-timeout 0`, 부모 1) 는 그대로다. 신호를 보내지 않았다. `tests/test_issue50_dispatch_herdr.py` 는 실제 herdr 를 띄우므로 돌리지 않았다. 전체 스위트와 `test_issue34_job_tree.py` 도 돌리지 않았다. + +## 발견 사항 + +차단: 없다. + +비차단: + +- `tests/test_issue68_onboard_default.py:17` 의 `import yaml` 는 쓰이지 않는다. 같은 파일의 `agent_sessions_yaml` 은 딕셔너리 키이다. +- `SKILL.md:173` 은 같은 폴더의 같은 종류 create 가 막힌다고 쓰고, `create_session.sh:210` 은 그 생성이 기다린다고 쓴다 (`Issue #63`, `MAM_ID_ORDER_WAIT_S`). 종료 코드와 경고 조건은 스크립트와 결정이 같다. +- 전달 테스트는 `[]`, `--onboard`, `--no-onboard`, `--no-onboard --onboard` 네 가지다 (`:186-193`). `--onboard --no-onboard` 순서는 같은 while 루프(`worktree_prepare.sh:31-43`)라 마지막이 이기고, 파라미터에는 없다. +- 위의 세션 가드 오류. 재현하려면 동작 중인 agy 를 끄거나, 그 홈을 가드 범위 밖으로 두어야 한다. 제품 수정 대상은 아니다. + +## 사용자 확인 필요 + +없다. 본문의 `--no-onboard` 삭제 제안은 2026-10-28 결정으로 대체됐고, 구현은 그 결정(플래그 유지, worktree 기본 onboarding, 네 에이전트 경고, 문서 최소 변경)을 따른다. + +[VERDICT: PASS] diff --git a/.agents/skills/lib.sh b/.agents/skills/lib.sh index 75de8fc..e1bb19f 100644 --- a/.agents/skills/lib.sh +++ b/.agents/skills/lib.sh @@ -29,7 +29,7 @@ export WORKSPACE_ROOT # 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. # NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable. -MAM_VERSION="4.12.0" +MAM_VERSION="4.13.0" export MAM_VERSION AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}" diff --git a/.agents/skills/lib_py/loop/eager.py b/.agents/skills/lib_py/loop/eager.py index 25cf723..0be9a88 100644 --- a/.agents/skills/lib_py/loop/eager.py +++ b/.agents/skills/lib_py/loop/eager.py @@ -13,7 +13,7 @@ import sys from typing import List, Sequence # Framework version locked with framework constants -FRAMEWORK_VERSION = "4.12.0" +FRAMEWORK_VERSION = "4.13.0" CORE_MODULES: Sequence[str] = ( # Standard library modules diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index d9e1753..0da62ff 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md index 8c6e7e3..c13817a 100644 --- a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md +++ b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos, windows] diff --git a/.agents/skills/multi-agent-mux-loop/SKILL.md b/.agents/skills/multi-agent-mux-loop/SKILL.md index 0813d47..863bd0e 100644 --- a/.agents/skills/multi-agent-mux-loop/SKILL.md +++ b/.agents/skills/multi-agent-mux-loop/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-monitor/SKILL.md b/.agents/skills/multi-agent-mux-monitor/SKILL.md index 60c9c2a..3ebfff8 100644 --- a/.agents/skills/multi-agent-mux-monitor/SKILL.md +++ b/.agents/skills/multi-agent-mux-monitor/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md b/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md index 8104c4e..a9a17b5 100644 --- a/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md +++ b/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-resume/SKILL.md b/.agents/skills/multi-agent-mux-resume/SKILL.md index 94cb72d..be5484f 100644 --- a/.agents/skills/multi-agent-mux-resume/SKILL.md +++ b/.agents/skills/multi-agent-mux-resume/SKILL.md @@ -1,7 +1,7 @@ --- 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 ` or `agy --conversation ` 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.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-status/SKILL.md b/.agents/skills/multi-agent-mux-status/SKILL.md index 10f3469..d962977 100644 --- a/.agents/skills/multi-agent-mux-status/SKILL.md +++ b/.agents/skills/multi-agent-mux-status/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-stop/SKILL.md b/.agents/skills/multi-agent-mux-stop/SKILL.md index 7e527f2..1131f6b 100644 --- a/.agents/skills/multi-agent-mux-stop/SKILL.md +++ b/.agents/skills/multi-agent-mux-stop/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-update/SKILL.md b/.agents/skills/multi-agent-mux-update/SKILL.md index 5e81850..f5717a6 100644 --- a/.agents/skills/multi-agent-mux-update/SKILL.md +++ b/.agents/skills/multi-agent-mux-update/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-update description: "Update an installed MAM workspace in place to the latest version. Refuses while a session, an open job, a review loop or an unknown monitor is live; stops the monitor daemon and starts it again afterwards; rolls everything back if the install fails; converts the registry with the newly installed code. Use instead of the removed .mam_deploy/update.sh." -version: 4.12.0 +version: 4.13.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/BOOTSTRAP.ko.md b/BOOTSTRAP.ko.md index 59ab2b7..d03f8df 100644 --- a/BOOTSTRAP.ko.md +++ b/BOOTSTRAP.ko.md @@ -2,7 +2,7 @@ 본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다. -[![Version: v4.12.0](https://img.shields.io/badge/version-v4.12.0-blue.svg)](VERSIONS.md) +[![Version: v4.13.0](https://img.shields.io/badge/version-v4.13.0-blue.svg)](VERSIONS.md) 새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다. diff --git a/BOOTSTRAP.md b/BOOTSTRAP.md index 7b7b5dd..7cf8cf1 100644 --- a/BOOTSTRAP.md +++ b/BOOTSTRAP.md @@ -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. -[![Version: v4.12.0](https://img.shields.io/badge/version-v4.12.0-blue.svg)](VERSIONS.md) +[![Version: v4.13.0](https://img.shields.io/badge/version-v4.13.0-blue.svg)](VERSIONS.md) A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment. diff --git a/MESSAGING.md b/MESSAGING.md index ac002cb..6fa1cd6 100644 --- a/MESSAGING.md +++ b/MESSAGING.md @@ -1,6 +1,6 @@ # Messaging System Technical Analysis & Architecture Report -Applies to: MAM v4.12.0 +Applies to: MAM v4.13.0 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. diff --git a/README.ko.md b/README.ko.md index e56812f..2e6832d 100644 --- a/README.ko.md +++ b/README.ko.md @@ -2,7 +2,7 @@ [herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다. -[![Version: v4.12.0](https://img.shields.io/badge/version-v4.12.0-blue.svg)](VERSIONS.md) +[![Version: v4.13.0](https://img.shields.io/badge/version-v4.13.0-blue.svg)](VERSIONS.md) --- diff --git a/README.md b/README.md index 3da0774..9d596f1 100644 --- a/README.md +++ b/README.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. -[![Version: v4.12.0](https://img.shields.io/badge/version-v4.12.0-blue.svg)](VERSIONS.md) +[![Version: v4.13.0](https://img.shields.io/badge/version-v4.13.0-blue.svg)](VERSIONS.md) --- diff --git a/VERSIONS.md b/VERSIONS.md index 46f9419..2ae3953 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -6,8 +6,8 @@ ## 📌 현재 버전 개요 (Current Release) -- **프레임워크 버전**: `v4.12.0` -- **최신 릴리스 일시**: 2026-10-28 (KST) +- **프레임워크 버전**: `v4.13.0` +- **최신 릴리스 일시**: 2026-10-29 (KST) - **기준 브랜치**: `main` - **핵심 아키텍처**: - **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입. @@ -24,24 +24,44 @@ ## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix) -모든 9개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.12.0`로 동기화되어 배포됩니다. +모든 9개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.13.0`로 동기화되어 배포됩니다. | 스킬명 | 버전 | 역할 및 주요 책임 | 상태 | | :--- | :---: | :--- | :---: | -| **`multi-agent-mux-create`** | `4.12.0` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 | -| **`multi-agent-mux-stop`** | `4.12.0` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 | -| **`multi-agent-mux-resume`** | `4.12.0` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 | -| **`multi-agent-mux-status`** | `4.12.0` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 | -| **`multi-agent-mux-monitor`** | `4.12.0` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 | -| **`multi-agent-mux-delegate-job`** | `4.12.0` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 | -| **`multi-agent-mux-loop`** | `4.12.0` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 | -| **`multi-agent-mux-orc-onboard`** | `4.12.0` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 | -| **`multi-agent-mux-update`** | `4.12.0` | 설치된 작업 공간의 제자리 업데이트·되돌리기와 업데이트 중 입장 차단 | ✅ 배포 | +| **`multi-agent-mux-create`** | `4.13.0` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 | +| **`multi-agent-mux-stop`** | `4.13.0` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 | +| **`multi-agent-mux-resume`** | `4.13.0` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 | +| **`multi-agent-mux-status`** | `4.13.0` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 | +| **`multi-agent-mux-monitor`** | `4.13.0` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 | +| **`multi-agent-mux-delegate-job`** | `4.13.0` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 | +| **`multi-agent-mux-loop`** | `4.13.0` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 | +| **`multi-agent-mux-orc-onboard`** | `4.13.0` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 | +| **`multi-agent-mux-update`** | `4.13.0` | 설치된 작업 공간의 제자리 업데이트·되돌리기와 업데이트 중 입장 차단 | ✅ 배포 | --- ## 📋 버전별 상세 변경 내역 (Changelog) +### 🚀 `v4.13.0` — worktree creator 도 기본으로 온보딩하고, create 의 --no-onboard 는 ID 가 나중에 생기는 에이전트에 경고한다 (Issue #68) (2026-10-29) + +> **마이너 (MINOR Release)**: `worktree_prepare.sh --creator-agent` 가 온보딩 없이 creator 를 만들어, agy·codex·hermes·opencode 는 첫 대화 전까지 레지스트리에 대화 ID 가 없었다. 이제 worktree creator 도 기본으로 온보딩한다. create 의 `--no-onboard` 는 남기고, ID 가 첫 턴 뒤에야 생기는 에이전트에 쓰면 경고한다. 공개 스크립트의 기본 동작이 바뀌어 MINOR 로 올린다. PR #79. + +#### ⚠️ 호환성 +- `worktree_prepare.sh --creator-agent` 가 기본으로 온보딩한다. 온보딩이 끝날 때까지 기다리므로(`MAM_ONBOARD_WAIT_S`, 기본 600 s) 전보다 오래 걸리고, create 의 종료 코드 44(온보딩 `error`)·45(시간 초과)가 나올 수 있다. 건너뛰려면 `--no-onboard` 를 준다. `--onboard` 는 기본과 같은 뜻으로 계속 받는다. 둘 다 주면 마지막 것이 이긴다. +- create 에 `--no-onboard` 를 agy·codex·hermes·opencode 와 함께 주면 stderr 에 경고 한 줄이 나온다. 종료 코드와 레지스트리 행은 바뀌지 않는다. claude·grok 에는 나오지 않는다. + +#### 🌟 주요 개선 사항 +1. **경고 (`create_session.sh`, `lib_py/agents/__main__.py`)**: `lib_py.agents facts` 가 어댑터의 `id_lifecycle` 을 `MAM_ID_POST_DISCOVERED` 로 내보내고, create 는 이 값으로 판정한다(셸에 에이전트 목록을 두지 않는다). 첫 턴 전까지 ID 가 레지스트리에 없고, 같은 폴더에 같은 종류를 또 만들면 그 create 가 기다린다(#63, `MAM_ID_ORDER_WAIT_S`)는 내용이다. spawn 전에 출력하므로 `--dry-run` 에서도, 뒤에서 create 가 실패해도 보인다. +2. **worktree creator (`worktree_prepare.sh`)**: 기본으로 create 에 `--onboard` 를 넘긴다. 새 `--no-onboard` 는 create 에 그대로 넘긴다. 사용법과 종료 코드 주석을 고쳤다. +3. **문서**: create `SKILL.md` 의 옵션 표 한 줄과 worktree 절(시놉시스, `--creator-agent` 설명, 종료 코드). + +#### ✅ 품질 검증 +- **회귀**: `tests/test_issue68_onboard_default.py`(25개). base(v4.12.0)에서 20개가 실패한다. 나머지 5개는 base 에서도 맞아야 하는 경우다(claude·grok 은 경고 없음, `--onboard` 전달). 기본이 온보딩하지 않는 것을 전제로 하던 worktree 테스트 호출 3곳에 `--no-onboard` 를 붙였다. +- **리뷰 (`--all-reviewer`, 작성자 PR 의 리뷰 전용 루프)**: 작성자 루프 1회차 NOT PASS(`test_issue21_acceptance.py` 의 암묵적 no-onboard 호출 2곳), 2회차 4/4 PASS. 병합 전 리뷰 전용 루프(@3b81f99) 1회차 **4/4 PASS**. 리뷰어들이 `SKILL.md` 의 설명을 코드와 줄 단위로 대조했다. 그 지적으로 `SKILL.md` 의 "막힌다" 를 "기다린다" 로 고치고 쓰이지 않는 import 를 지웠다(@a7bf07b). 리포트는 `.agents/reports//` 에 있다. +- **전체 스위트 (macOS, Python 3.9.6, @a7bf07b)**: 2293 passed, 56 skipped, 실패 0 (102분). 실제 `~/.claude`·`/private/tmp/claude-`·herdr 서버 pane 목록·opencode 의 data·state·config·본 체크아웃의 `.mam` 에 이 실행이 남긴 것은 없었고, 본 체크아웃의 모니터는 그대로 떠 있었다. +- **작성자 확인 (Linux, @7cf63f4)**: 2327 passed, 20 skipped, 2 failed. 하나는 이번 변경의 분류 누락이었고 3b81f99 에서 고쳤다. 다른 하나(`test_issue34_job_tree.py::test_t5b_…[True]`)는 단독 실행에서 head·base 모두 통과했다. +- **알려진 비차단**: `--no-onboard --submit-job` 을 함께 주면 제출한 잡이 첫 턴이 되는데도 경고가 나온다(결정 3 의 조건 그대로). `worktree_prepare.sh --path ../<상대 경로>` 가 부모 폴더가 없을 때 "제어 루트 안" 으로 거부되는 문제는 별도 버그다. + ### 🚀 `v4.12.0` — update 스킬로 제자리 업데이트하고, 업데이트 중에는 세션·잡·루프의 시작을 막는다 (Issue #72) (2026-10-28) > **마이너 (MINOR Release)**: 설치된 작업 공간의 업데이트를 `.mam_deploy/update.sh` 대신 새 `multi-agent-mux-update` 스킬이 한다. 업데이트는 실행 중인 세션·열린 잡·루프가 있으면 거부하고, 모니터를 멈췄다가 다시 띄우며, 설치가 실패하면 되돌린다. 업데이트 중에는 create·resume·run_loop·delegate submit·stop·orc-onboard 가 거부한다. resume 이 모니터 데몬을 띄운다. 공개 진입점(`.mam_deploy/update.sh`)이 없어지고 기존 명령의 동작(업데이트 중 거부)이 늘어 MINOR 로 올린다. PR #75. -- 2.54.0