fix(issue-21): absolute brief, start-command registry, audit-log root and report paths in the delegate-job wrapper

The injected prompt, the start/success/error commands and the brief's Output
Report Path used cwd-relative `.mam/jobs/...`, and the wrapper ignored
MAM_JOBS_DIR. A creator whose cwd is a git worktree could not run them as
written, and its audit log fell back to <cwd>/.mam/delegate_job_logs.

- REGISTRY_DIR defaults to ${MAM_JOBS_DIR:-.mam/jobs} and is made absolute
  right after `cd "$WORKDIR"` (a relative value still resolves against WORKDIR)
- the publish command is shell-quoted (printf %q) and prefixed with
  DELEGATE_JOB_LOGS_DIR=<abs>, resolved by the same get_logs_dir() the
  wrapper's subscriber uses; the heartbeat timer inherits it
- direct and loop jobs share one _instructions helper; brief and report paths
  are $REGISTRY_DIR/$JOB_ID/... (absolute)
- create --onboard: absolute publish_event.py, --registry-dir and log root

Tests T1-1..T1-5 (a space in the control root, commands run from another cwd
with no workspace env).

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:20:27 +00:00
1 parent dcf2fab06e
commit d3d3a76c83
3 files changed
+260 -9

No files matched your search

@@ -503,7 +503,20 @@ if [ -n "$DELEGATE_JOB_ID" ]; then
# Construct instructions matching multi-agent-mux-delegate-job format
py_bin="$(_delegate_py_bin)"
pub="$py_bin .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py --job $DELEGATE_JOB_ID"
# Issue #21: absolute script, registry and audit-log paths (shell-quoted), so
# an agent whose cwd is a git worktree can run the command as written.
pub_script="$WORKSPACE_ROOT/.agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py"
[ -f "$pub_script" ] || pub_script="$(_delegate_script publish_event.py)"
pub_script="$(cd -P "$(dirname "$pub_script")" && pwd -P)/$(basename "$pub_script")"
pub_reg="${MAM_JOBS_DIR:-${WORKSPACE_ROOT:+$WORKSPACE_ROOT/.mam/jobs}}"
pub_reg="${pub_reg:-.mam/jobs}"
mkdir -p "$pub_reg" && pub_reg="$(cd -P "$pub_reg" && pwd -P)"
pub_logs="$(PYTHONPATH="$SKILL_DIR:${PYTHONPATH:-}" "$py_bin" -c 'import os; from lib_py.delegation.mqtt_common import get_logs_dir; print(os.path.abspath(get_logs_dir()))' 2>/dev/null || true)"
pub="$(
if [ -n "$pub_logs" ]; then printf 'DELEGATE_JOB_LOGS_DIR=%q ' "$pub_logs"; fi
printf '%q ' "$py_bin" "$pub_script" --registry-dir "$pub_reg" --job "$DELEGATE_JOB_ID"
)"
pub="${pub% }"
instructions="Your job_id is \"$DELEGATE_JOB_ID\" (the one just registered for THIS delegation).
On start run: $pub --event started.
@@ -68,7 +68,42 @@ pick_python() {
echo "$py_bin"
}
REGISTRY_DIR_DEFAULT=".mam/jobs"
REGISTRY_DIR_DEFAULT="${MAM_JOBS_DIR:-.mam/jobs}"
# Issue #21: the injected brief path, the start/success/error commands and the
# Output Report Path must work from any cwd - a creator in a git worktree runs
# them from there. Called right after `cd "$WORKDIR"`, so a relative registry
# still resolves against WORKDIR, as before.
_abs_registry_dir() {
mkdir -p "$REGISTRY_DIR"
REGISTRY_DIR="$(cd -P "$REGISTRY_DIR" && pwd -P)"
export SKS_REGISTRY_DIR="$REGISTRY_DIR"
}
# The audit-log root this wrapper's own subscriber resolves (same resolver),
# pinned into the injected commands so an agent publishing from another cwd
# does not fall back to <cwd>/.mam/delegate_job_logs.
_logs_dir_abs() {
"$PY" -c 'import os, sys; sys.path.insert(0, sys.argv[1]); from lib_py.delegation.mqtt_common import get_logs_dir; print(os.path.abspath(get_logs_dir()))' "$SCRIPT_DIR/.."
}
# _pub_cmd <extra...>: the publish command as one shell-quoted string.
_pub_cmd() {
local s
s="$(
if [ -n "${LOGS_DIR_ABS:-}" ]; then printf 'DELEGATE_JOB_LOGS_DIR=%q ' "$LOGS_DIR_ABS"; fi
printf '%q ' "$PY" "$SCRIPT_DIR/scripts/publish_event.py" --registry-dir "$REGISTRY_DIR" --job "$JOB_ID" "$@"
)"
printf '%s' "${s% }"
}
# _instructions <pub> [iteration]: the prompt injected into the agent (direct
# and loop jobs share it; test18 pins parts of this wording).
_instructions() {
local pub="$1" iter="${2:-}" brief="$REGISTRY_DIR/$JOB_ID/brief.md"
printf 'Your job_id is "%s". Detailed task requirements, instructions, and target output paths%s are documented in the task brief file at: %s. Please READ and follow %s to complete your work. Commands: start='"'"'%s --event started --heartbeat %s'"'"' (run it FIRST, within %s s; it also starts an automatic heartbeat timer), success='"'"'%s --event completed --detail <summary>'"'"', error='"'"'%s --event error --detail <reason>'"'"'.' \
"$JOB_ID" "${iter:+ for iteration $iter}" "$brief" "$brief" "$pub" "$HEARTBEAT_SEC" "$ACK_TIMEOUT" "$pub" "$pub"
}
usage() {
cat <<'EOF'
@@ -150,8 +185,10 @@ cmd_submit() {
parse_opts "$@"
[[ -n "$PROMPT" ]] || { echo "submit requires --prompt" >&2; exit 1; }
PY="$(pick_python)"
[[ "$PY" == */* && "$PY" != /* ]] && PY="$(pwd)/$PY"
cd "$WORKDIR"
mkdir -p "$REGISTRY_DIR"
_abs_registry_dir
LOGS_DIR_ABS="$(_logs_dir_abs 2>/dev/null || true)"
# 1) register job (prints the new job id)
JOB_ID="$("$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" register \
@@ -175,7 +212,7 @@ cmd_submit() {
- **Role**: $DELEGATE_ROLE
- **Timeout**: $TIMEOUT s (Idle: $IDLE_TIMEOUT s)
- **Liveness contract**: heartbeat every $HEARTBEAT_SEC s (automatic once you publish \`started --heartbeat $HEARTBEAT_SEC\`), unresponsive after $HEARTBEAT_MISSES missed heartbeats, \`started\` expected within $ACK_TIMEOUT s
- **Output Report Path**: .mam/jobs/$JOB_ID/$AGENT-reports/report-final.md
- **Output Report Path**: $REGISTRY_DIR/$JOB_ID/$AGENT-reports/report-final.md
## 🔎 Task Description
$PROMPT
@@ -195,8 +232,9 @@ EOF
local pub_extra=()
[ -n "$_disp_tok" ] && pub_extra+=(--expect-dispatch-token "$_disp_tok")
[ -n "$_attempt_id" ] && pub_extra+=(--attempt-id "$_attempt_id")
local pub="$PY $SCRIPT_DIR/scripts/publish_event.py --registry-dir $REGISTRY_DIR --job $JOB_ID ${pub_extra[*]}"
local instructions="Your job_id is \"$JOB_ID\". Detailed task requirements, instructions, and target output paths are documented in the task brief file at: .mam/jobs/$JOB_ID/brief.md. Please READ and follow .mam/jobs/$JOB_ID/brief.md to complete your work. Commands: start='$pub --event started --heartbeat $HEARTBEAT_SEC' (run it FIRST, within $ACK_TIMEOUT s; it also starts an automatic heartbeat timer), success='$pub --event completed --detail <summary>', error='$pub --event error --detail <reason>'."
local pub instructions
pub="$(_pub_cmd ${pub_extra[@]+"${pub_extra[@]}"})"
instructions="$(_instructions "$pub")"
if [[ "$DRY_RUN" == "1" ]]; then
run_agent "$JOB_ID" "$instructions"
@@ -349,7 +387,7 @@ EOF
- **Role**: $display_role
- **Iteration**: $iteration
- **Liveness contract**: heartbeat every $HEARTBEAT_SEC s (automatic once you publish \`started --heartbeat $HEARTBEAT_SEC\`), unresponsive after $HEARTBEAT_MISSES missed heartbeats, \`started\` expected within $ACK_TIMEOUT s
- **Output Report Path**: .mam/jobs/$JOB_ID/${clean_session}-reports/report-final.md
- **Output Report Path**: $REGISTRY_DIR/$JOB_ID/${clean_session}-reports/report-final.md
## 🔎 Task Description
$current_prompt
@@ -451,8 +489,9 @@ EOF
# Format instruction block
local loop_pub_extra=(--attempt-id "$attempt")
[ -n "$_disp_tok" ] && loop_pub_extra+=(--expect-dispatch-token "$_disp_tok")
local pub="$PY $SCRIPT_DIR/scripts/publish_event.py --registry-dir $REGISTRY_DIR --job $JOB_ID ${loop_pub_extra[*]}"
local instructions="Your job_id is \"$JOB_ID\". Detailed task requirements, instructions, and target output paths for iteration $iteration are documented in the task brief file at: .mam/jobs/$JOB_ID/brief.md. Please READ and follow .mam/jobs/$JOB_ID/brief.md to complete your work. Commands: start='$pub --event started --heartbeat $HEARTBEAT_SEC' (run it FIRST, within $ACK_TIMEOUT s; it also starts an automatic heartbeat timer), success='$pub --event completed --detail <summary>', error='$pub --event error --detail <reason>'."
local pub instructions
pub="$(_pub_cmd "${loop_pub_extra[@]}")"
instructions="$(_instructions "$pub" "$iteration")"
# Trigger agent
if ! deliver_prompt "$JOB_ID" "$instructions" "$current_session" "$logf"; then