feat(issue-21): persist and forward the control-plane binding in create/resume/stop; status and reconcile honor MAM_JOBS_DIR

A session row now records the control plane it belongs to, and the pane
gets it explicitly instead of relying on env inheritance.

- lib_py/control_plane.py (+ thin lib.sh wrappers mam_control_plane_json /
  mam_control_plane_prefix): the binding is WORKSPACE_ROOT,
  AGENT_SESSIONS_YAML, MAM_JOBS_DIR, DELEGATE_JOB_LOGS_DIR as absolute paths.
- create: the binding is always persisted on the row (`control_plane`) and
  forwarded to the pane only when it differs from what --workspace alone
  implies, as leading K=V tokens that lib.sh's new-session already turns into
  --env (fresh pane) or `env K=V` (reused idle pane). pane.cmd_full and
  start_command stay unprefixed. A binding with whitespace that would have to
  be forwarded is refused with exit 2 (lib.sh splits the flags unquoted); the
  ~/.local/bin wrapper path records the binding but warns it is not forwarded.
- resume: the row's binding wins over the caller's env (a difference is
  reported on stderr) and is forwarded again; a legacy row without one uses
  the caller's binding and update_yaml_resumed.sh records it.
- stop: registry order MAM_JOBS_DIR > the row's mam_jobs_dir >
  $WORKSPACE_ROOT/.mam/jobs > .mam/jobs, exported for delegate_publish_event.
- status: both get_job_status copies look in MAM_JOBS_DIR, the row's
  registry and the matching audit logs before the old candidates.
- reconcile: MAM_JOBS_DIR wins in the subscribe heredoc and is passed in.

Tests T3-0..T3-8 (mock herdr: forwarding is verified at the herdr-call level
only; real pane env propagation is left to item 7). T3-7 is a static guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
This commit is contained in:
GodopuandClaude Opus 5 committed 2026-09-14 03:34:48 +00:00
1 parent d907359746
commit ee1d8966c1
9 files changed
+409 -11

No files matched your search

@@ -150,6 +150,29 @@ else
fi
fi
# Issue #21 (item 3, D11): the control-plane binding recorded on the row wins
# over the caller's environment (a difference is reported on stderr); a legacy
# row without one uses the caller's binding, which update_yaml_resumed.sh then
# records. Forwarded to the pane only when it differs from --workspace's own.
ROW_CP="$(MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$SESSION_NAME" python3 -c "
import json, os
d = json.loads(os.environ.get('MAM_STATE_JSON') or '{}')
for s in d.get('herdr_sessions', []):
if s.get('name') == os.environ['SESSION_NAME'] and isinstance(s.get('control_plane'), dict):
print(json.dumps(s['control_plane']))
break
" 2>/dev/null || true)"
CP_JSON="$(mam_control_plane_json "$WORKSPACE" "$ROW_CP" || true)"
CP_PREFIX=""
if [ -n "$CP_JSON" ]; then
cp_rc=0
CP_PREFIX="$(mam_control_plane_prefix "$CP_JSON")" || cp_rc=$?
if [ "$cp_rc" -eq 3 ]; then
echo "ERROR: control-plane path contains whitespace; cannot forward to the pane" >&2
exit 2
fi
fi
if [ "${DRY_RUN:-0}" = "1" ]; then
echo "[dry-run] would spawn: $CMD_FULL"
exit 0
@@ -162,7 +185,7 @@ if [ "$AGENT" = "opencode" ]; then
fi
# 4. Spawn new agent session (delegates to herdr translation shim)
_herdr new-session -d -s "$SESSION_NAME" -c "$WORKSPACE" "$CMD_FULL"
_herdr new-session -d -s "$SESSION_NAME" -c "$WORKSPACE" "$CP_PREFIX$CMD_FULL"
if [ "$AGENT" = "claude" ]; then
# auto-handle trust / bypass dialogs
handle_startup_dialogs "$SESSION_NAME" 20
@@ -172,7 +195,7 @@ fi
sleep 2
# 5. Update agent-sessions.yaml: status running, last_visible_status
bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \
CONTROL_PLANE_JSON="$CP_JSON" bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \
--session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" \
--herdr-session "$HERDR_SESSION_NAME" --cmd-full "$CMD_FULL" \
${HERDR_WORKSPACE_OPT:+--herdr-workspace "$HERDR_WORKSPACE_OPT"}