The handshake only said the agent saw the prompt (#23). create_session.sh
now waits, after `started`, for the onboarding job to reach a terminal
status and makes that its own result, so the orchestrator learns it from
create's exit (MULTI_AGENT_RULES §6) instead of guessing from herdr's
agent_status:
- lib_py/delegation/wrapper.py: `wait-terminal` (completed|error|cancelled,
`timeout` after --timeout, `missing` when the record is gone; stops with
its caller via --watch-pid) and `onboard-exit` (completed 0, error or
cancelled 44, timeout or missing 45). bash branches on the printed answer.
- lib.sh: delegate_wait_terminal / delegate_onboard_exit, thin calls.
- create_session.sh: `onboarding completed: job <id> (<n>s)` on stdout, or
`last_visible_status: onboarding-<status>` on the row, the reason on stderr
and `exit 44` / `exit 45` after `trap - EXIT` - the session is kept, as for
a missing handshake. The "no started" path (error, exit 0 before) now ends
with 44. MAM_ONBOARD_WAIT_S (default 600, floor 1) bounds the wait.
- worktree_prepare.sh: 44/45 pass through with "session and worktree kept".
- create SKILL.md exit table (44, 45) and the knob; the #42 inventory follows.
- tests/conftest.py: --onboard is the default, so ~60 sandbox create tests
reach the new wait. The mock herdr now acts as the agent for create's
onboarding prompt - it runs the publish commands the prompt names through
the real publish_event.py - and MAM_ONBOARD_WAIT_S=3 caps a test that turns
that off (MAM_MOCK_ONBOARD_HANDSHAKE=0 -> 44, `started` -> 45). The
delegate-job wrapper's prompt has another shape and is never answered.
- tests/test_issue50_onboard.py: the decision tables, create in the sandbox
(0 / 44 / 45, session kept, row note, trap window), the knob's integer
rule, worktree_prepare's pass-through.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- worktree_prepare.sh installs the same files deploy/install.sh does
(.agents/skills/multi-agent-mux-delegate-job/requirements.txt: paho-mqtt,
PyYAML) plus requirements*.txt. Without paho the loop wrapper, stop_session
and the subscriber tests fail in the worktree - seen as 4 suite failures
in a worktree without its own venv, all passing with one.
- review D1: a failed pip install removes the venv it half made, so the next
run recreates it instead of reporting VENV=kept (test w1c).
- review D3: the [worktree context] block's uncommitted-changes flag uses
--untracked-files=all like the guards.
- review D2: the create skill doc says the install needs the package index.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
R1/R2: `git status --porcelain` honours status.showUntrackedFiles, so a
`no` setting in a submodule (or the worktree) hid untracked files from the
checks that justify worktree_remove.sh's single --force and from the review
snapshot guard (_dirty_submodules), which then approved content it never
captured. All three checks (lib_py/worktree.py, worktree_remove.sh,
runner._dirty_submodules) now pass --untracked-files=all. The source tree's
own snapshot was already safe: `git add -A` ignores that setting.
R3: worktree_prepare.sh no longer links .venv to the control root's. It
creates the worktree's own venv with the control root's interpreter and
installs requirements*.txt; a leftover link is refused (exit 1), a control
root without an interpreter is a warning (VENV=skipped); VENV= is printed.
Tests: w1 (real venv, kept on re-run), w1b (link refused / skipped), w6e
parametrised over the hidden setting for the post-stop recheck, w6e2
(hidden untracked file in the submodule and in the worktree blocks the
removal), l17 untracked-hidden / during-hidden (guard refuses before the
first review; a mid-round hidden file makes the round stale). Each new
case fails without the fix.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
- N1 (planner-reviewer-claude-01) worktree_remove.sh: when `git status` fails
in the worktree (e.g. its directory was deleted), judge_tree returned before
METHOD was set and the summary line died under set -u with exit 1. METHOD
now has its default before the function; the refusal is exit 3 with
"nothing was changed", plus a `git worktree prune` hint when the directory
is gone. Same for --dry-run.
- R4 (planner-reviewer-codex-01) worktree_prepare.sh: a submodule path that is
not initialised yet but already holds files made the clone fail, and the
rollback's `deinit -f` then deleted those files. --submodules now refuses
such a path up front (new exit 3, nothing changed), which also makes the
rollback's premise true: after a failed attempt, everything under a
not-yet-initialised path was created by that attempt.
- R5 (planner-reviewer-codex-01) lib_py/worktree.py: `submodule foreach`
visits initialised checkouts only, but a deinit'd submodule keeps its
repository - stash and unpushed commits included - under the worktree's
modules/, which the forced removal deletes. Every repository under modules/
(nested ones included) is now checked directly (`--git-dir`): commits on no
remote (HEAD included) and refs/stash with its reflog. A directory that looks
like a partial repository cannot be checked and refuses.
- Also: the rollback loops are C-style (`seq 0 -1` counts down on BSD), and
prepare warns that --base is ignored for an existing branch.
Tests W6i (deleted worktree directory, with and without --dry-run), W6j and
W6j2 (stash / unpushed commit in a deinit'd submodule's repository) and W6k
(existing files in an uninitialised submodule path). Skill docs and the design
doc describe the new refusal and the wider repository check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
Review ffb5055e (planner-reviewer-codex-01):
- R1 [P1] worktree_remove.sh: the clean-tree and submodule checks ran only
before --stop, so work a stopping session wrote (or committed, or stashed)
was deleted by the forced removal. The checks are one function now, run
before any change and again right before the removal; any finding then
refuses with exit 3 (sessions already stopped, worktree kept), and the
removal method follows that latest look.
- R2 [P1] lib_py/worktree.py: a submodule stash is reachable from no branch
and leaves the status clean, and the forced removal deletes the worktree's
own submodule repositories. `git stash list` in every submodule is a
separate finding (`stashes`), refused like dirty/unpushed; errors fail
closed as before.
- R3 [P2] worktree_prepare.sh: when one submodule cloned and a later one
failed, the rollback deleted modules/ while the cloned checkout's .git file
still pointed into it, and `git status` failed in the worktree. The rollback
now undoes only this attempt: each submodule whose git dir did not exist
before is deinitialised before its git dir is deleted; submodules
initialised earlier are kept; shared submodule.* config values are restored
exactly (new keys removed, pre-existing values put back - init and deinit
both write .git/config). A final `git status` either confirms a usable
checkout or prints what to inspect.
Tests W6e (a write during stop keeps the worktree and the file), W6f (a
submodule stash refuses), W6g (partial failure on a fresh worktree: usable,
no modules/, no new config, then prepare and a plain removal work) and W6h (a
failing retry keeps the earlier `sm` init and its config value). The skill
docs and the design doc describe the recheck, the stash rule and the scoped
rollback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
- worktree_prepare.sh (create skill): idempotent `git worktree add` next to the
control root (a path inside it is refused), .venv linked to the control
root's, /.venv and .pytest_cache/ in the shared info/exclude, submodules only
with --submodules - a failed init removes its own traces (modules/, new
submodule.* keys in the shared config) and exits 1, since git refuses a plain
`worktree remove` once modules/ exists - and an optional creator registered
in the control root's .mam with MAM_NO_SUBSCRIBER_DAEMON=1. Prints BASE= for
`run_loop.sh --worktree ... --base ... --review-only`.
- worktree_remove.sh (stop skill): every check first, nothing changed on a
refusal (exit 3): linked and not locked, running sessions only with --stop,
no open job of those sessions except the delegate jobs stop cancels, clean
tree including submodules, and inside submodules no modification and no
commit on no remote (HEAD included - submodules sit on a detached HEAD).
Then stop, re-check jobs, and remove: plain, or a single --force when the
worktree holds submodule state.
- lib_py/worktree.py: the parsing behind both (stdlib only).
- A8: create's immediate reconcile and watchdog use the control root's code
and WORKSPACE_ROOT, not the worktree --workspace's copy.
Tests W1-W6d (local submodules allowed through GIT_CONFIG_* env only).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G