[Bug] herdr 데몬 프로세스 분리(setsid) 누락으로 인한 세션 리셋 및 UUID 미할당 세션 resume 데드락 문제 #3

Closed
opened 2026-08-30 23:14:58 +00:00 by Godopu · 1 comment
Owner

문제 개요 (Summary)

다중 에이전트 환경에서 multi-agent-mux-stopmulti-agent-mux-resume 수행 시 herdr 세션 데몬이 반복적으로 재시작되며 페인/세션 상태가 리셋되는 현상 및 대화 ID가 미할당된 세션의 복원 실패 문제가 발생했습니다.


세부 원인 분석 (Detailed Root Causes)

1. herdr 데몬 백그라운드 부트스트랩 시 프로세스 세션 분리(setsid) 누락

  • 위치: .mam/shim/herdrlib.sh (211행 부근)
  • 증상:
    nohup "$REAL_HERDR" --session "$_MAM_SESSION" server >/dev/null 2>&1 &
    
    • non-interactive 서브셸에서 위 방식으로 데몬을 띄울 경우, setsid를 통한 별도 프로세스 그룹 분리가 되지 않아 상위 서브셸 프로세스가 종료될 때 herdr 서버 데몬에 SIGTERM/SIGHUP이 전달되어 프로세스가 함께 종료됩니다.
    • 이로 인해 CLI 명령(예: resume_session.sh, status.sh 등)이 호출될 때마다 "running": false로 감지되어 서버를 새로 띄우고, 인메모리로 관리되던 기존 pane 및 agent 정보가 모두 초기화되는 문제가 발생합니다.
  • 제안 해결책:
    setsid 또는 완전한 백그라운드 데몬화(setsid nohup "$REAL_HERDR" ... >/dev/null 2>&1 &) 처리.

2. 첫 메시지 전 중지된 세션(UUID null)의 Resume/Create 핸드오프 데드락

  • 위치: resume_session.shcreate_session.sh
  • 증상:
    • creator-agy-01 등 첫 메시지 전송 전 stop된 세션은 agy_conversation_id_own: null 상태로 저장됩니다.
    • resume_session.shresolve_session_id.sh 결과가 빈 문자열(null)인 경우 즉시 exit 1 (No saved session ... Use multi-agent-mux-create first)로 실패합니다.
    • 하지만 안내대로 create_session.sh를 실행하면 .mam/agent-sessions.yaml에 이미 해당 세션 이름이 등록되어 있어 exit 4 (Already registered)로 실패하며 복구 불가 데드락에 빠집니다.
  • 제안 해결책:
    • resume_session.sh: UUID가 null인 경우 기존 등록 정보를 활용하여 fresh 컨테이너 모드로 재시작하거나,
    • create_session.sh: 기존 YAML 엔트리가 status: stopped인 경우 이를 덮어쓰거나 재사용하도록 허용.

환경 정보 (Environment)

  • OS: Linux
  • MAM Version: 4.1.2
## 문제 개요 (Summary) 다중 에이전트 환경에서 `multi-agent-mux-stop` 후 `multi-agent-mux-resume` 수행 시 herdr 세션 데몬이 반복적으로 재시작되며 페인/세션 상태가 리셋되는 현상 및 대화 ID가 미할당된 세션의 복원 실패 문제가 발생했습니다. --- ## 세부 원인 분석 (Detailed Root Causes) ### 1. herdr 데몬 백그라운드 부트스트랩 시 프로세스 세션 분리(`setsid`) 누락 - **위치**: `.mam/shim/herdr` 및 `lib.sh` (211행 부근) - **증상**: ```bash nohup "$REAL_HERDR" --session "$_MAM_SESSION" server >/dev/null 2>&1 & ``` - non-interactive 서브셸에서 위 방식으로 데몬을 띄울 경우, `setsid`를 통한 별도 프로세스 그룹 분리가 되지 않아 상위 서브셸 프로세스가 종료될 때 herdr 서버 데몬에 SIGTERM/SIGHUP이 전달되어 프로세스가 함께 종료됩니다. - 이로 인해 CLI 명령(예: `resume_session.sh`, `status.sh` 등)이 호출될 때마다 `"running": false`로 감지되어 서버를 새로 띄우고, 인메모리로 관리되던 기존 pane 및 agent 정보가 모두 초기화되는 문제가 발생합니다. - **제안 해결책**: `setsid` 또는 완전한 백그라운드 데몬화(`setsid nohup "$REAL_HERDR" ... >/dev/null 2>&1 &`) 처리. --- ### 2. 첫 메시지 전 중지된 세션(UUID `null`)의 Resume/Create 핸드오프 데드락 - **위치**: `resume_session.sh` 및 `create_session.sh` - **증상**: - `creator-agy-01` 등 첫 메시지 전송 전 stop된 세션은 `agy_conversation_id_own: null` 상태로 저장됩니다. - `resume_session.sh`는 `resolve_session_id.sh` 결과가 빈 문자열(null)인 경우 즉시 `exit 1` (`No saved session ... Use multi-agent-mux-create first`)로 실패합니다. - 하지만 안내대로 `create_session.sh`를 실행하면 `.mam/agent-sessions.yaml`에 이미 해당 세션 이름이 등록되어 있어 `exit 4` (Already registered)로 실패하며 복구 불가 데드락에 빠집니다. - **제안 해결책**: - `resume_session.sh`: UUID가 `null`인 경우 기존 등록 정보를 활용하여 fresh 컨테이너 모드로 재시작하거나, - `create_session.sh`: 기존 YAML 엔트리가 `status: stopped`인 경우 이를 덮어쓰거나 재사용하도록 허용. --- ## 환경 정보 (Environment) - OS: Linux - MAM Version: 4.1.2
Author
Owner

🔬 Technical Analysis Report: Issue #3 (Rev. 3)

Herdr Daemon Process Separation (setsid) & 0-Turn Stopped Session Resume Recovery Gap

  • Author: creator-agy-01 (Worker / Lead Technical Analyst)
  • Job ID: 6b391a80 (Follow-up to 842b96bf, incorporating peer review feedback from planner-reviewer-claude-01 [job 0338e7de] and reviewer-opencode-01 [job 7b6c16df])
  • Target Issue: Issue #3 ([Bug] herdr 데몬 프로세스 분리(setsid) 누락으로 인한 세션 리셋 및 UUID 미할당 세션 resume 데드락 문제)
  • Analyzed Components:
    1. .agents/skills/lib.sh (lines 198–220, .mam/shim/herdr daemon bootstrap)
    2. .agents/skills/lib_py/verify_session.py (lines 83–109, verify_session_uuid escape hatch at lines 99–101)
    3. .agents/skills/lib_py/workspace_uuid.py (lines 55–80, find_workspace_uuid)
    4. .agents/skills/multi-agent-mux-resume/scripts/resume_session.sh (lines 50–57, 98–109)
    5. .agents/skills/multi-agent-mux-create/scripts/create_session.sh (lines 177–195, 370–440)
    6. .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh (lines 183–191, 280–293)
    7. tests/test_uuid_target.py (test_t8_resume_unmaterialized_assigned_id)
  • Scope Restriction: Analysis and defect determination only — no implementation or code changes performed in this job.

1. Executive Summary & Defect Verdict Matrix

Item # Reported Issue Target Location Live Mechanism & Root Cause Structural Defect Status Severity
1 Missing setsid on headless Herdr daemon bootstrap causes daemon termination and session resets during process-group teardown. lib.sh:211 (and .mam/shim/herdr) Uses nohup "$REAL_HERDR" ... server >/dev/null 2>&1 & disown. While SIGHUP is ignored on normal subshell exit, the daemon remains in the calling subshell's Process Group (PGID) because set -m is disabled in non-interactive scripts. Process group signal broadcasts (SIGTERM/SIGINT from test runners, CI fixtures, timeout wrappers, or supervisor group kills) kill the Herdr daemon, terminating all child panes and resetting session state. CONFIRMED REAL DEFECT (Process Group Signal Vulnerability) HIGH (Causes total session/pane loss during process group teardown)
2 (Class A) Resuming a Discovery-bucket session (agy/hermes/opencode) stopped at 0-turns (before first message) fails with an unrecoverable error. resume_session.sh:54-57 Created with session_id_source: pending-discovery and own-key null. When stopped at 0-turns, no disk artifact exists, resolve_session_id.sh outputs empty string "", and resume_session.sh executes a hard exit ([ -z "$UUID" ] -> exit 1), blocking automated recovery. CONFIRMED REAL DEFECT (Automation / Reconciler Recovery Gap) MEDIUM (Affects 0-turn recovery for agy, hermes, opencode)
2 (Class B) Resuming an Assigned-bucket session (claude/grok) stopped at 0-turns. verify_session.py:99-101, resume_session.sh Created with session_id_source: assigned and session_id_verified: false. When stopped at 0-turns, own-key is preserved. verify_session_uuid in revalidate mode hits the early-return escape hatch at verify_session.py:99-101 and returns True without checking disk artifacts. resume_session.sh succeeds with RC=0 using --session-id <uuid>. NOT A DEFECT (Working as designed; verified by passing test_t8) N/A (0-turn resume already functions correctly)

2. Deep-Dive Analysis: Item 1 (Herdr Daemon setsid Omission)

2.1. Codebase Verification

In .agents/skills/lib.sh (lines 198–220), inside the heredoc template that generates .mam/shim/herdr:

# lib.sh:207-219
if [ "$_mam_session_running" != "1" ]; then
  # Headless bootstrap avoids herdr's "nested herdr is disabled" guard...
  nohup "$REAL_HERDR" --session "$_MAM_SESSION" server >/dev/null 2>&1 &
  _mam_server_pid=$!
  disown 2>/dev/null || true
  for _mam_wait_i in $(seq 1 40); do
    [ -S "${HOME:-$HOME_DIR}/.config/herdr/sessions/$_MAM_SESSION/herdr.sock" ] && break
    kill -0 "$_mam_server_pid" 2>/dev/null || break
    sleep 0.25
  done
fi

2.2. OS Process Group & Signal Dynamics

  1. The Role of nohup and disown:
    • nohup ensures SIGHUP is ignored (SIG_IGN).
    • disown unlinks the job from the current bash shell's active job table so the shell does not emit SIGHUP upon exit.
    • Upon clean subshell termination, the child is reparented to init/launchd (PID 1) and survives.
  2. The Vulnerability — Process Group (PGID) Retention:
    • In non-interactive bash scripts (where job monitor set -m is disabled by default), background processes (&) are placed in the same Process Group (PGID) as the caller.
    • nohup and disown do not create a new process group or session ID.
    • Inspection of live production processes confirmed this vulnerability:
      PID 7623  PGID 7526  PPID 1  TTY ttys001  /opt/homebrew/bin/herdr --session multi-agent-mux server
      
      The daemon is reparented to PPID 1 but continues to run in its dead spawner's PGID (7526) and remains attached to controlling TTY ttys001.
    • When test runners (e.g. pytest subshell teardowns), timeout wrappers (timeout --kill-after ...), CI runners, or parent supervisor scripts terminate a workflow via process group kill (kill -- -$PGID or kill -TERM -$PGID), the signal (SIGTERM or SIGINT) is broadcast to every process in that PGID.
    • Because nohup only shields against SIGHUP, the Herdr daemon receives SIGTERM/SIGINT and terminates immediately.
  3. Consequences:
    • The Herdr daemon socket (herdr.sock) is deleted.
    • All active workspaces, tabs, and agent panes managed under that Herdr session instance are destroyed, resulting in an unrecoverable runtime session reset.

2.3. Platform Portability Consideration (macOS vs. Linux)

  • On standard Linux systems, setsid is commonly installed via util-linux.
  • On macOS (Darwin), the setsid command-line utility is not installed by default (command -v setsid exits with 1). Calling setsid "$REAL_HERDR" ... in bash causes command not found: setsid.
  • Portable Solution: Python is a mandatory runtime dependency across MAM (lib_py). In Python, subprocess.Popen(..., start_new_session=True) natively calls os.setsid() in the child process before exec on all POSIX platforms (macOS, Linux, BSD), providing true session and process group detachment.

3. Deep-Dive Analysis: Item 2 (0-Turn Stopped Session Resume Recovery Gap)

3.1. Breakdown by Spawn Class

Class A: Discovery-Bucket Agents (agy, hermes, opencode) — [CONFIRMED REAL DEFECT]

  1. Creation:
    • create_session.sh (lines 189–193) spawns these agents without a UUID (agy --dangerously-skip-permissions, hermes --yolo --accept-hooks, opencode --auto --agent build).
    • In .mam/agent-sessions.yaml, the row is stamped with session_id_source: pending-discovery and own-keys as null/empty.
  2. 0-Turn Stop:
    • When stopped before sending any message, stop_session.sh:185 calls capture_conversation_id, which finds no on-disk conversation files and returns "".
    • stop_session.sh:281 (if captured and not purge:) is skipped; resumable: true is not set and own-keys remain null. The row is marked status: stopped.
  3. Resume Attempt:
    • resume_session.sh:51 calls resolve_session_id.sh, which searches the YAML row and scans disk via find_workspace_uuid. Both are empty, returning "".
    • resume_session.sh:54-57 executes:
      if [ -z "$UUID" ]; then
        echo "ERROR: No saved session for $WORKSPACE ($AGENT). Use multi-agent-mux-create first." >&2
        exit 1
      fi
      
    • Hard exit with code 1.
  4. Impact:
    • Reconcilers and automated workflows that hold only --session, --workspace, and --agent cannot recover a stopped 0-turn Class A session via resume_session.sh.
    • Handoff to create_session.sh fails because create_session.sh strictly requires --role ([ -n "$ROLE" ] || exit 2).

Class B: Assigned-Bucket Agents (claude, grok) — [NOT A DEFECT, WORKING AS DESIGNED]

  1. Creation:
    • create_session.sh:178-180 pre-allocates a UUID via mam_gen_uuid and records it into claude_session_id_own / grok_session_id_own with session_id_source: assigned and session_id_verified: false (create_session.sh:412-413/435-436).
  2. 0-Turn Stop:
    • When stopped before sending any message, stop_session.sh does not null or erase the create-time assigned own-key (stop_session.sh:281 only updates if captured is non-empty, leaving existing own-keys intact).
  3. Resume Attempt & The verify_session.py Escape Hatch:
    • When resolve_session_id.sh calls find_workspace_uuid (lib_py/workspace_uuid.py:65, 72), it evaluates candidate own-keys using verify_session_uuid(ws, agent, cand, s, mode="revalidate").
    • In lib_py/verify_session.py (lines 99–101):
      if (mode == "revalidate" and row.get("session_id_source") == "assigned"
              and not row.get("session_id_verified")):
          return True
      
    • Because the session is stopped at 0 turns, session_id_source is "assigned" and session_id_verified remains False.
    • verify_session_uuid early-returns True immediately, bypassing the on-disk transcript check (adapter.verify_artifact()).
    • find_workspace_uuid returns the candidate UUID.
    • resume_session.sh calls resume-spec (claude.py:97), which detects materialized: False and generates:
      CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions --session-id ${SESSION_UUID}"
      
    • resume_session.sh executes with RC=0, successfully spawning the agent pane with its pre-assigned UUID!
  4. Empirical Evidence:
    • Covered and protected by existing regression test:
      tests/test_uuid_target.py::test_t8_resume_unmaterialized_assigned_id PASSED [100%]
      
    • All 12/12 tests in tests/test_uuid_target.py pass.

4. Architectural Recommendations for Future Planning

  1. For Item 1 (Headless Herdr Daemon Detachment):
    • Replace nohup "$REAL_HERDR" ... server & disown in .mam/shim/herdr / lib.sh with a portable Python daemon spawner:
      python3 -c "import subprocess, sys; subprocess.Popen(sys.argv[1:], start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)" "$REAL_HERDR" --session "$_MAM_SESSION" server
      
    • This isolates the daemon in its own Session ID and Process Group across macOS and Linux, protecting it from parent process-group signals (SIGTERM/SIGINT).
  2. For Item 2 (Class A 0-Turn Stopped Session Recovery):
    • Target Class A agents (agy, hermes, opencode) only:
      • When UUID is empty for a status: stopped session in resume_session.sh, instead of hard-failing at line 54, allow resume_session.sh to fall back to launching the initial clean spawn command (CMD_FULL) into the pane (or allow --role-less creation handoff).
    • Do NOT modify Class B: Preserve the existing verify_session.py:99-101 escape hatch and test_t8 contract unchanged.

# 🔬 Technical Analysis Report: Issue #3 (Rev. 3) ## Herdr Daemon Process Separation (`setsid`) & 0-Turn Stopped Session Resume Recovery Gap - **Author**: `creator-agy-01` (Worker / Lead Technical Analyst) - **Job ID**: `6b391a80` (Follow-up to `842b96bf`, incorporating peer review feedback from `planner-reviewer-claude-01` [job `0338e7de`] and `reviewer-opencode-01` [job `7b6c16df`]) - **Target Issue**: Issue #3 (`[Bug] herdr 데몬 프로세스 분리(setsid) 누락으로 인한 세션 리셋 및 UUID 미할당 세션 resume 데드락 문제`) - **Analyzed Components**: 1. `.agents/skills/lib.sh` (lines 198–220, `.mam/shim/herdr` daemon bootstrap) 2. `.agents/skills/lib_py/verify_session.py` (lines 83–109, `verify_session_uuid` escape hatch at lines 99–101) 3. `.agents/skills/lib_py/workspace_uuid.py` (lines 55–80, `find_workspace_uuid`) 4. `.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh` (lines 50–57, 98–109) 5. `.agents/skills/multi-agent-mux-create/scripts/create_session.sh` (lines 177–195, 370–440) 6. `.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh` (lines 183–191, 280–293) 7. `tests/test_uuid_target.py` (`test_t8_resume_unmaterialized_assigned_id`) - **Scope Restriction**: Analysis and defect determination only — no implementation or code changes performed in this job. --- ## 1. Executive Summary & Defect Verdict Matrix | Item # | Reported Issue | Target Location | Live Mechanism & Root Cause | Structural Defect Status | Severity | |:---:|---|---|---|:---:|:---:| | **1** | Missing `setsid` on headless Herdr daemon bootstrap causes daemon termination and session resets during process-group teardown. | `lib.sh:211` (and `.mam/shim/herdr`) | Uses `nohup "$REAL_HERDR" ... server >/dev/null 2>&1 & disown`. While `SIGHUP` is ignored on normal subshell exit, the daemon remains in the calling subshell's Process Group (PGID) because `set -m` is disabled in non-interactive scripts. Process group signal broadcasts (`SIGTERM`/`SIGINT` from test runners, CI fixtures, timeout wrappers, or supervisor group kills) kill the Herdr daemon, terminating all child panes and resetting session state. | **CONFIRMED REAL DEFECT** (Process Group Signal Vulnerability) | **HIGH** (Causes total session/pane loss during process group teardown) | | **2 (Class A)** | Resuming a Discovery-bucket session (`agy`/`hermes`/`opencode`) stopped at 0-turns (before first message) fails with an unrecoverable error. | `resume_session.sh:54-57` | Created with `session_id_source: pending-discovery` and own-key `null`. When stopped at 0-turns, no disk artifact exists, `resolve_session_id.sh` outputs empty string `""`, and `resume_session.sh` executes a hard exit (`[ -z "$UUID" ] -> exit 1`), blocking automated recovery. | **CONFIRMED REAL DEFECT** (Automation / Reconciler Recovery Gap) | **MEDIUM** (Affects 0-turn recovery for `agy`, `hermes`, `opencode`) | | **2 (Class B)** | Resuming an Assigned-bucket session (`claude`/`grok`) stopped at 0-turns. | `verify_session.py:99-101`, `resume_session.sh` | Created with `session_id_source: assigned` and `session_id_verified: false`. When stopped at 0-turns, own-key is preserved. `verify_session_uuid` in `revalidate` mode hits the early-return escape hatch at `verify_session.py:99-101` and returns `True` without checking disk artifacts. `resume_session.sh` succeeds with RC=0 using `--session-id <uuid>`. | **NOT A DEFECT** (Working as designed; verified by passing `test_t8`) | **N/A** (0-turn resume already functions correctly) | --- ## 2. Deep-Dive Analysis: Item 1 (Herdr Daemon `setsid` Omission) ### 2.1. Codebase Verification In `.agents/skills/lib.sh` (lines 198–220), inside the heredoc template that generates `.mam/shim/herdr`: ```bash # lib.sh:207-219 if [ "$_mam_session_running" != "1" ]; then # Headless bootstrap avoids herdr's "nested herdr is disabled" guard... nohup "$REAL_HERDR" --session "$_MAM_SESSION" server >/dev/null 2>&1 & _mam_server_pid=$! disown 2>/dev/null || true for _mam_wait_i in $(seq 1 40); do [ -S "${HOME:-$HOME_DIR}/.config/herdr/sessions/$_MAM_SESSION/herdr.sock" ] && break kill -0 "$_mam_server_pid" 2>/dev/null || break sleep 0.25 done fi ``` ### 2.2. OS Process Group & Signal Dynamics 1. **The Role of `nohup` and `disown`**: - `nohup` ensures `SIGHUP` is ignored (`SIG_IGN`). - `disown` unlinks the job from the current bash shell's active job table so the shell does not emit `SIGHUP` upon exit. - Upon clean subshell termination, the child is reparented to `init`/`launchd` (PID 1) and survives. 2. **The Vulnerability — Process Group (PGID) Retention**: - In non-interactive bash scripts (where job monitor `set -m` is disabled by default), background processes (`&`) are placed in the **same Process Group (PGID)** as the caller. - `nohup` and `disown` **do not create a new process group or session ID**. - Inspection of live production processes confirmed this vulnerability: ``` PID 7623 PGID 7526 PPID 1 TTY ttys001 /opt/homebrew/bin/herdr --session multi-agent-mux server ``` The daemon is reparented to PPID 1 but continues to run in its dead spawner's PGID (7526) and remains attached to controlling TTY `ttys001`. - When test runners (e.g. pytest subshell teardowns), timeout wrappers (`timeout --kill-after ...`), CI runners, or parent supervisor scripts terminate a workflow via process group kill (`kill -- -$PGID` or `kill -TERM -$PGID`), the signal (`SIGTERM` or `SIGINT`) is broadcast to **every process in that PGID**. - Because `nohup` only shields against `SIGHUP`, the Herdr daemon receives `SIGTERM`/`SIGINT` and terminates immediately. 3. **Consequences**: - The Herdr daemon socket (`herdr.sock`) is deleted. - All active workspaces, tabs, and agent panes managed under that Herdr session instance are destroyed, resulting in an unrecoverable runtime session reset. ### 2.3. Platform Portability Consideration (macOS vs. Linux) - On standard Linux systems, `setsid` is commonly installed via `util-linux`. - On **macOS (Darwin)**, the `setsid` command-line utility **is not installed by default** (`command -v setsid` exits with 1). Calling `setsid "$REAL_HERDR" ...` in bash causes `command not found: setsid`. - **Portable Solution**: Python is a mandatory runtime dependency across MAM (`lib_py`). In Python, `subprocess.Popen(..., start_new_session=True)` natively calls `os.setsid()` in the child process before `exec` on all POSIX platforms (macOS, Linux, BSD), providing true session and process group detachment. --- ## 3. Deep-Dive Analysis: Item 2 (0-Turn Stopped Session Resume Recovery Gap) ### 3.1. Breakdown by Spawn Class #### Class A: Discovery-Bucket Agents (`agy`, `hermes`, `opencode`) — [CONFIRMED REAL DEFECT] 1. **Creation**: - `create_session.sh` (lines 189–193) spawns these agents without a UUID (`agy --dangerously-skip-permissions`, `hermes --yolo --accept-hooks`, `opencode --auto --agent build`). - In `.mam/agent-sessions.yaml`, the row is stamped with `session_id_source: pending-discovery` and own-keys as `null`/empty. 2. **0-Turn Stop**: - When stopped before sending any message, `stop_session.sh:185` calls `capture_conversation_id`, which finds no on-disk conversation files and returns `""`. - `stop_session.sh:281` (`if captured and not purge:`) is skipped; `resumable: true` is not set and own-keys remain `null`. The row is marked `status: stopped`. 3. **Resume Attempt**: - `resume_session.sh:51` calls `resolve_session_id.sh`, which searches the YAML row and scans disk via `find_workspace_uuid`. Both are empty, returning `""`. - `resume_session.sh:54-57` executes: ```bash if [ -z "$UUID" ]; then echo "ERROR: No saved session for $WORKSPACE ($AGENT). Use multi-agent-mux-create first." >&2 exit 1 fi ``` - Hard exit with code 1. 4. **Impact**: - Reconcilers and automated workflows that hold only `--session`, `--workspace`, and `--agent` cannot recover a stopped 0-turn Class A session via `resume_session.sh`. - Handoff to `create_session.sh` fails because `create_session.sh` strictly requires `--role` (`[ -n "$ROLE" ] || exit 2`). --- #### Class B: Assigned-Bucket Agents (`claude`, `grok`) — [NOT A DEFECT, WORKING AS DESIGNED] 1. **Creation**: - `create_session.sh:178-180` pre-allocates a UUID via `mam_gen_uuid` and records it into `claude_session_id_own` / `grok_session_id_own` with `session_id_source: assigned` and `session_id_verified: false` (`create_session.sh:412-413`/`435-436`). 2. **0-Turn Stop**: - When stopped before sending any message, `stop_session.sh` does **not** null or erase the create-time assigned own-key (`stop_session.sh:281` only updates if `captured` is non-empty, leaving existing own-keys intact). 3. **Resume Attempt & The `verify_session.py` Escape Hatch**: - When `resolve_session_id.sh` calls `find_workspace_uuid` (`lib_py/workspace_uuid.py:65, 72`), it evaluates candidate own-keys using `verify_session_uuid(ws, agent, cand, s, mode="revalidate")`. - In `lib_py/verify_session.py` (lines 99–101): ```python if (mode == "revalidate" and row.get("session_id_source") == "assigned" and not row.get("session_id_verified")): return True ``` - Because the session is stopped at 0 turns, `session_id_source` is `"assigned"` and `session_id_verified` remains `False`. - **`verify_session_uuid` early-returns `True` immediately**, bypassing the on-disk transcript check (`adapter.verify_artifact()`). - `find_workspace_uuid` returns the candidate UUID. - `resume_session.sh` calls `resume-spec` (`claude.py:97`), which detects `materialized: False` and generates: ```bash CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions --session-id ${SESSION_UUID}" ``` - `resume_session.sh` executes with **RC=0**, successfully spawning the agent pane with its pre-assigned UUID! 4. **Empirical Evidence**: - Covered and protected by existing regression test: ``` tests/test_uuid_target.py::test_t8_resume_unmaterialized_assigned_id PASSED [100%] ``` - All 12/12 tests in `tests/test_uuid_target.py` pass. --- ## 4. Architectural Recommendations for Future Planning 1. **For Item 1 (Headless Herdr Daemon Detachment)**: - Replace `nohup "$REAL_HERDR" ... server & disown` in `.mam/shim/herdr` / `lib.sh` with a portable Python daemon spawner: ```python python3 -c "import subprocess, sys; subprocess.Popen(sys.argv[1:], start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)" "$REAL_HERDR" --session "$_MAM_SESSION" server ``` - This isolates the daemon in its own Session ID and Process Group across macOS and Linux, protecting it from parent process-group signals (`SIGTERM`/`SIGINT`). 2. **For Item 2 (Class A 0-Turn Stopped Session Recovery)**: - Target **Class A agents (`agy`, `hermes`, `opencode`) only**: - When `UUID` is empty for a `status: stopped` session in `resume_session.sh`, instead of hard-failing at line 54, allow `resume_session.sh` to fall back to launching the initial clean spawn command (`CMD_FULL`) into the pane (or allow `--role`-less creation handoff). - **Do NOT modify Class B**: Preserve the existing `verify_session.py:99-101` escape hatch and `test_t8` contract unchanged. ---
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tmpl/multi-agent-mux#3