Issue #34: the loop waits for the creator job's sub-jobs, reviewers get the sub-job table as evidence, a run with sub-jobs voids a round whose source changed during review, and a pane caller reaches an agent of another workspace on its own server. VERSIONS.md carries the entry; the merge-side reports (round 1 @ed293dc and round 2 @e80186f with identity sidecars, baseed293dc-> heade80186f, evidence review-tree) are archived under .agents/reports/<session>/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
21 KiB
name, description, version, author, license, platforms, environments, metadata
| name | description | version | author | license | platforms | environments | metadata | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| multi-agent-mux-create | 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. | 4.7.4 | godopu | MIT |
|
|
|
Multi-Agent Create — Start a Fresh Agent in a herdr Session
Companion skills:
multi-agent-mux-resume(resume an existing UUID),multi-agent-mux-stop(terminate),multi-agent-mux-monitor(live status). Single source of truth:./.mam/agent-sessions.yaml(this skill writes to it; never read it ad-hoc — go through this skill).
What this skill does
Spawn a new agent (claude or agy/antigravity-cli) in a dedicated herdr session for context-preserving long-running work. The herdr session is the container; the agent's session ID is data inside the container. This skill creates the container + starts the agent — but does not resume an old conversation (use multi-agent-mux-resume for that).
For all agents: the herdr session name is produced by lib.sh::derive_session_name — the single source of truth shared by create/resume/stop/status/monitor (P0-A). The rule (verbatim from the function):
slug = the two trailing path components of the absolute workspace,
_→-, lowercased, joined with-; name =<slug>-creator-<agent>.
So $WORKSPACE_ROOT/landing_page/refer_landing_page + claude → landing-page-refer-landing-page-creator-claude. The workspace basename (refer_landing_page) is included; the hand-written historical entry that dropped it (lab-landing-page-creator-claude) was the bug, not the convention.
Pre-flight checks
Before doing anything, verify the environment:
# 1) herdr available and isolated server status
# Use lib.sh's has_real_herdr, NOT `command -v herdr` / `type -P herdr`: once
# lib.sh is sourced the former matches its herdr() function and the latter
# matches the .mam/shim wrapper, so both pass on a host with no herdr (B-3).
has_real_herdr || { echo "ERROR: herdr not installed"; exit 1; }
echo "Herdr session name: ${HERDR_SESSION_NAME:-default}"
# 2) claude / agy available
command -v claude # required for --agent claude
command -v agy # required for --agent agy
# 3) claude auth (if --agent claude)
claude auth status 2>&1 | python3 -c "import json,sys; d=json.load(sys.stdin); assert d.get('loggedIn'), 'claude not logged in'"
# 4) target workspace exists
test -d "$WORKSPACE" || { echo "ERROR: workspace $WORKSPACE not a directory"; exit 1; }
If any check fails → abort with a non-zero exit and report the reason (automated path) or report to user (interactive path). Do not proceed with a half-broken setup.
Standard names
- herdr session name:
derive_session_name <workspace> <agent>(lib.sh)<workspace-slug>=basename $(dirname $WORKSPACE)-basename $WORKSPACE(lowercase,_→-)- examples:
landing-page-refer-landing-page-creator-claude,paper-pdf2md-creator-agy - never re-derive this by hand — source lib.sh and call the function
- wrapper script (claude only):
~/.local/bin/<workspace-slug>-creator-claude- contents: herdr new-session with
claudeinside; trust/bypass dialogs are handled by the skill for every agent (see--no-auto-trustbelow) - see
<workdir>/agent_sessions.mdfor the canonical wrapper template
- contents: herdr new-session with
Herdr Session Isolation (격리 세션)
When running multiple agent sessions alongside other workflows (e.g., cmux, background workers, manual herdr sessions), sharing the default herdr server can lead to session name conflicts, monitoring clutter, and accidental destruction of user sessions via global commands.
To prevent this, you can run this skill inside an isolated herdr session using the HERDR_SESSION_NAME environment variable or the --herdr-session <name> flag (opt-in; alias: --herdr-server; legacy env alias: HERDR_SERVER_NAME).
On success the script also starts the per-workspace monitor daemon through start_watchdog (reconcile.sh --subscribe --idle-timeout 0, one per workspace, see the lifecycle contract in multi-agent-mux-monitor/SKILL.md). Set MAM_NO_SUBSCRIBER_DAEMON=1 to skip that spawn when the monitor is supervised elsewhere or the workspace is ephemeral (Issue #19); the watchdog PID: line is then empty unless a daemon already runs for the workspace, whose PID is still reported.
Additionally, you can specify --herdr-workspace <name> (default: workspace slug without mam- prefix) to name and group the agent panes inside a dedicated Herdr workspace tab in the Herdr runtime (herdr workspace create --label / rename) as well as recording it in .mam/agent-sessions.yaml. Note that --herdr-workspace configures the workspace tab label within the session, whereas --herdr-session selects the daemon socket itself.
Under the hood this maps to a real, separate herdr session (herdr --session <name> — its own socket, its own agent list/workspace list, completely invisible to the default session and vice versa), not just a workspace label inside the same server. lib.sh's shim bootstraps the named session's server headlessly (herdr --session <name> server, backgrounded) the first time it's needed, and scopes every subsequent herdr call to it automatically — this headless bootstrap is what lets it work even when the skill itself is running from inside another herdr-managed pane (a plain interactive herdr --session <name> launch is blocked there by herdr's "nested herdr is disabled" guard; headless server mode isn't).
The bootstrapped server receives a sanitized copy of the caller's environment (Issue #9): per-session agent runtime markers — CLAUDECODE, CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_SESSION_ID, the Claude Code messaging/bridge/pid markers, and the ANTIGRAVITY_CONVERSATION_ID / HERMES_SESSION_ID / GROK_SESSION_ID / OPENCODE_SESSION_ID equivalents — are dropped, because herdr hands its environment to every pane it spawns and an inherited CLAUDE_CODE_CHILD_SESSION silently turns transcript saving off in every sub-agent claude (no <uuid>.jsonl, so multi-agent-mux-resume can never find it). User configuration such as CLAUDE_PROJECT_DIR or CLAUDE_CONFIG_DIR is inherited unchanged. Query-only callers set MAM_NO_AUTOSTART=1 to skip the bootstrap entirely (status.sh and reconcile.sh do). A server bootstrapped before this fix keeps its polluted environment until it is restarted (herdr session stop <name>, then create/resume again).
How to use
-
Via Environment Variable:
export HERDR_SESSION_NAME=multi-agent-canary # All subsequent commands (create, status, stop, etc.) will run in the isolated 'multi-agent-canary' herdr session. -
Via Option Flag:
bash scripts/create_session.sh --workspace /path/to/project --agent claude --role developer --herdr-session multi-agent-canary --herdr-workspace my-project -
Submit Job Integration: You can automatically register a delegated job with a prompt when creating a session:
bash scripts/create_session.sh --workspace /path/to/project --agent claude --role developer --submit-job "Task prompt here" -
Onboard Integration: You can automatically submit a project alignment/orientation job to the new agent when creating a session:
bash scripts/create_session.sh --workspace /path/to/project --agent claude --role developer --onboardDelivery is judged by the agent's own
startedevent (#23), within the job'sack_timeout_sec(default 180 s) — not by what the pane looked like two seconds after the keys were sent. The script no longer publishesstartedon the agent's behalf, so a job that showsstartedreally was picked up. A send_keys rc 4 ("Enter not seen accepted", where a still-starting TUI lands) is a warning; rc 1/2/3 still abort at once. Nostartedwithin the ack timeout marks the joberrorand warns, but keeps the session: the pane is up and the row is valid, only the handshake never landed, and killing a live agent over a slow onboarding would destroy more than it protects (the same rule this script applies to a TUI that never signalled ready). Re-send withmulti-agent-mux-delegate-job submitonce the agent is up; the prompt is never re-injected automatically.MAM_ONBOARD_ACK_Soverrides how long that wait is — distinct from the wrapper'sMAM_INJECT_ACK_GRACE_S.Start-up dialogs are answered for every agent, not only claude (#24). The blocking-modal signatures and their accept keys live in each agent adapter (
lib_py/agents/adapters/*.py, published asMAM_STARTUP_DIALOGS), and they are answered while waiting for the TUI rather than after it.--no-auto-trust(orMAM_AUTO_TRUST=0in the environment; an explicit flag wins over the environment) leaves directory-trust dialogs for a human to accept and still answers other blocking notices (for example codex's update prompt, where Skip changes nothing). Trust matters most for codex: trusting a directory makes it load that project's local config, hooks and exec policy, so--no-auto-trustis the switch to reach for when the workspace is not yours. Re-sending is rate-limited byMAM_DIALOG_RESEND_S(default 5 s) because a--source visiblesnapshot keeps showing a dialog for a while after it was accepted.Exit codes: 42 = the TUI never became ready and the process is gone; 43 = the rollback could not kill the herdr session and it is still running. On 43 the message names the session and the manual cleanup command; the YAML row keeps
status: runningwithlast_visible_status: rollback-failedand nodelegate_job_id, and the onboarding job is closed ascancelled.
Why use --herdr-session?
- By default, all skills target
defaultherdr session socket — fine for single-workspace use. - By using an isolated session via
HERDR_SESSION_NAME(or--herdr-session), your agent sessions are completely separated from your default user workspace, ensuring 0% interference — this is backed by a genuinely separateherdrsession/socket, not merely a workspace label. - To deliberately tear down an entire isolated group at once (all its workspaces and agents), use
herdr session stop <HERDR_SESSION_NAME>followed byherdr session delete <HERDR_SESSION_NAME>— this only affects that named session, never the default one.
Output format
When invoked, the script creates or updates ./.mam/agent-sessions.yaml with:
herdr_sessions:
- name: <workspace>-creator-<agent> # E.g. landing-page-creator-claude
status: running # Initial status for a freshly created agent
role: Developer
herdr_session_created_at: '2026-08-04T12:00:00Z'
herdr_session_epoch: 1785844800
herdr_session: <HERDR_SESSION_NAME> # Isolated session name (default: 'mam-<ws-slug>')
delegate_job_id: null
pane:
index: 0
pid: 12345
cmd: claude
cmd_full: claude --dangerously-skip-permissions
cwd: /path/to/project
start_command: "HERDR_SESSION_NAME=<herdr_session> herdr new-session -d -s <SESSION_NAME> -x 140 -y 40 -c <WORKSPACE> <CMD_FULL>"
attach_command: "HERDR_SESSION_NAME=<herdr_session> herdr agent attach <SESSION_NAME>"
kill_command: "HERDR_SESSION_NAME=<herdr_session> herdr kill-session -t <SESSION_NAME>"
Workflow
WORKSPACE=/path/to/project
AGENT=claude # claude | agy | hermes | grok | opencode | codex — always pass it explicitly
source .agents/skills/lib.sh
SESSION_NAME="$(derive_session_name "$WORKSPACE" "$AGENT")"
# 1. If session already alive, fail fast
herdr has-session -t "$SESSION_NAME" 2>/dev/null && {
echo "ERROR: herdr session '$SESSION_NAME' already exists. Use multi-agent-mux-resume to attach or multi-agent-mux-stop first."
exit 1
}
# 2. Spawn the herdr session with the agent inside
case "$AGENT" in
claude)
# Use the wrapper if it exists, else inline herdr new-session
# Use the wrapper if it exists (LOCAL_BIN env var overrides default $HOME/.local/bin)
local_bin="${LOCAL_BIN:-$HOME/.local/bin}"
if [ -x "$local_bin/$SESSION_NAME" ]; then
nohup "$local_bin/$SESSION_NAME" >/dev/null 2>&1 &
else
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "claude"
fi
;;
agy)
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "agy --dangerously-skip-permissions"
;;
hermes)
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "hermes --yolo --accept-hooks"
;;
grok)
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "grok --permission-mode bypassPermissions"
;;
opencode)
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "opencode --auto --agent build"
;;
codex)
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "codex --dangerously-bypass-approvals-and-sandbox"
;;
*) echo "ERROR: --agent must be claude, agy, hermes, grok, opencode, or codex, got: $AGENT"; exit 2 ;;
esac
# 3. Wait for agent TUI to be ready (varies: claude ~5s, agy ~3s)
sleep 6
# 4. Capture pane metadata
PANE_PID=$(herdr list-panes -t "$SESSION_NAME" -F '#{pane_pid}')
PANE_CWD=$(herdr list-panes -t "$SESSION_NAME" -F '#{pane_current_path}')
PANE_CMD=$(herdr list-panes -t "$SESSION_NAME" -F '#{pane_current_command}')
# `herdr list-sessions` doesn't exist (real or shimmed) — we just spawned this
# session ourselves, so stamp the epoch locally instead of round-tripping herdr.
HERDR_EPOCH=$(date +%s)
Registering the session in agent-sessions.yaml
After spawn, append a new herdr_sessions[] entry to .mam/agent-sessions.yaml:
- name: <SESSION_NAME>
status: running
herdr_session_created_at: 2026-06-17T...Z # ISO 8601 UTC
herdr_session_epoch: <HERDR_EPOCH>
herdr_session: <HERDR_SESSION_NAME> # Isolated session name (default: 'mam-<ws-slug>')
herdr_server: <HERDR_SESSION_NAME> # Alias for herdr_session
pane:
index: 0
pid: <PANE_PID>
cmd: <AGENT> # 'claude' or 'agy'
cmd_full: <full command line, see table below>
cwd: <PANE_CWD>
tui: # only for claude
model: <from TUI status>
provider: <from TUI status>
plan: <from TUI status>
account: <from TUI status>
version: <from TUI status>
start_command: "HERDR_SESSION_NAME=<herdr_session> herdr new-session -d -s <SESSION_NAME> -x 140 -y 40 -c <WORKSPACE> <CMD_FULL>"
attach_command: "HERDR_SESSION_NAME=<herdr_session> herdr agent attach <SESSION_NAME>"
kill_command: "HERDR_SESSION_NAME=<herdr_session> herdr kill-session -t <SESSION_NAME>"
# All three require `source .agents/skills/lib.sh` first — `new-session`/`kill-session`
# are tmux-compat pseudo-commands the shim translates, and `HERDR_SESSION_NAME` is what
# the shim reads to route to the right isolated herdr *session* (real `herdr` has no
# env-var-based scoping of its own; `herdr_session: default` needs no prefix at all).
cmd_full per agent (this is the actual command line in the pane, not the resume command):
| agent | cmd_full |
|---|---|
| claude (interactive) | claude |
| agy (interactive) | agy --dangerously-skip-permissions |
Use the agent-sessions-yaml-edit script in scripts/ to safely append (preserves comments + format):
bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \
--workspace "$WORKSPACE" --agent "$AGENT" --role "$ROLE" --session "$SESSION_NAME"
The script handles the YAML append, pane capture, and the last_visible_status placeholder.
Issue worktrees: worktree_prepare.sh (Issue #21 item 4)
Run from the control root's skills tree (lib.sh's WORKSPACE_ROOT = the main checkout that owns .mam):
bash .agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh \
--path ../multi-agent-mux-wt/issue-42 --branch fix/issue-42 [--base <ref>] [--submodules] \
[--creator-agent claude [--session <name>] [--role creator] [--herdr-session <s>] [--herdr-workspace <w>] [--onboard]] [--dry-run]
- Creates the worktree next to the control root (a path inside it is refused), idempotently; creates the worktree's own
.venvfrom the control root's.venv/bin/pythonand installs the delegate-job skill'srequirements.txt(paho-mqtt, PyYAML - whatdeploy/install.shinstalls) plusrequirements.txt/requirements-dev.txtwhen 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 andVENV=skipped); adds/.venvand.pytest_cache/to the sharedinfo/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 writessubmodule.<name>.urlto the shared.git/config, and any attempt - even a failed one - leaves amodules/directory that makes a plaingit worktree removerefuse. A failed--submodulesundoes 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-existingsubmodule.*values are kept - and exits 1; re-run without--submodulesto finish. --creator-agentcreates the creator through this skill with the control root's binding andMAM_NO_SUBSCRIBER_DAEMON=1; the row'spane.cwdis the worktree.- Prints
BASE=<sha>: record it before delegating the implementation, then review withrun_loop.sh --worktree <WT> --base "$BASE" --review-only. - A submodule path that is not initialised yet but already holds files refuses
--submodulesup 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).
- create itself: a control-plane binding that cannot be computed is refused (exit 2) for a
--workspaceother than the control root and warned about for the control root; the immediate reconcile and the watchdog run the control root's code.
Pitfalls
- Don't use
nohup/disown/setsidfor the agent itself — those background the agent outside herdr. The whole point of this skill is the herdr session is the supervisor.nohupis OK only for launching the wrapper (which itself creates the herdr session viaherdr new-session -d). - Claude Session ID Auto-Assignment: Fresh
claudesessions automatically generate a new UUID (mam_gen_uuid) passed viaclaude --session-id <uuid>.claude_session_id_ownis stored in.mam/agent-sessions.yamlwithsession_id_source: assignedandsession_id_verified: false. - First Message Materialization: The transcript file
.jsonlis created on disk when the first user prompt is sent. The monitor loop (reconcile.sh) verifies the transcript and promotessession_id_verified: trueandlast_visible_status: pinned. - Wrapper script MUST NOT be created via
hermes profile alias— that command writes ahermes -p <profile>wrapper that destroys the herdr behavior. Create wrappers manually (seelab-landing-page-creator-claudetemplate). - Always use the workspace-relative path in herdr
cwd— relative paths break when herdr respawns in a different shell context.
Verification
After spawn + YAML append:
# 1. herdr session is alive (real native command — no lib.sh needed)
herdr agent get "$SESSION_NAME" >/dev/null 2>&1 && echo OK || echo MISSING
# 2. pane has the expected cmd + cwd
herdr agent get "$SESSION_NAME" | python3 -c "
import sys, json
a = json.load(sys.stdin)['result']['agent']
print(f\"cmd={a['agent']} cwd={a['cwd']}\")
"
# 3. agent-sessions.yaml has the new entry
python3 -c "
import yaml
d = yaml.safe_load(open('.mam/agent-sessions.yaml'))
names = [s['name'] for s in d['herdr_sessions']]
assert '$SESSION_NAME' in names, 'session not registered'
print('OK:', names)
"
# 4. Optional: check the TUI status (real native command)
herdr agent read "$SESSION_NAME" --source visible --lines 20 # TUI ready = agent banner visible, no dialog text
herdr has-session/herdr list-panes/herdr capture-paneabove are tmux-compat pseudo-commands only understood aftersource .agents/skills/lib.sh(seeWorkflow) — the realherdrbinary has no such subcommands. The block above uses the realherdr agent get/herdr agent readequivalents so it also works standalone.
When NOT to use this skill
- Resuming an old conversation →
multi-agent-mux-resume - Killing an existing session →
multi-agent-mux-stop - Just attaching to an existing session →
herdr agent attach <name>(no skill needed) - One-shot print mode (claude -p "...") → no herdr needed; use
claude-codeskill's print mode