Wave 1 of a 6-session parallel build revealed three failure modes: (1) hallucinated completion (status=completed after 2/5 steps, last tool call was an arbitrary file review), (2) fail-late bash (3/6 sessions had push blocked inside sub-agent sandbox after all work was done), (3) no objective verification (plans were prose). v1.7 closes all three by making the plan an executable contract. Per-step YAML manifest (expected_paths, commit_message_pattern, bash_syntax_check, forbidden_paths, must_contain) is the objective completion predicate. Plan-critic dimension 10 (Manifest quality) is a hard gate. Session decomposer propagates manifests verbatim and emits an obligatory Step 0 pre-flight (git push --dry-run, exit 77 sentinel) in every session spec. ultraexecute-local gets Phase 7.5 (independent manifest audit from git log + filesystem, ignoring agent bookkeeping) and Phase 7.6 (bounded recovery dispatch, recovery_depth ≤ 2). Hard Rule 17 forbids marking a step passed without manifest verification. Hard Rule 18 forbids ending on an arbitrary tool call before reporting. Division of labor is made explicit: - /ultraresearch-local gathers context (no build decisions) - /ultraplan-local produces an executable contract (manifests, plan-critic gate) - /ultraexecute-local executes disciplined (does NOT compensate for weak plans — escalates) Code complete. Docs partial (Arbeidsdeling table + manifest section added to plugin + marketplace READMEs). Verification tests (10-sequence) pending — see REMEMBER.md. Backward compat: v1.6 plans without plan_version marker get legacy mode with synthesized manifests and legacy_plan: true in progress file. Plan-critic emits advisory, not block. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
168 lines
6.6 KiB
Markdown
168 lines
6.6 KiB
Markdown
# Headless Launch Script Template
|
|
|
|
This template is used by the session-decomposer agent to generate a launch script
|
|
for headless execution of decomposed sessions.
|
|
|
|
## Template
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# Headless launch script — generated by ultraplan-local
|
|
# Master plan: {plan_path}
|
|
# Generated: {date}
|
|
# Sessions: {total_sessions} ({parallel_count} parallel, {sequential_count} sequential)
|
|
|
|
set -euo pipefail
|
|
|
|
# Prevent accidental API billing — remove this line if you intend to use API credits
|
|
unset ANTHROPIC_API_KEY
|
|
|
|
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
PLAN_DIR="{session_dir}"
|
|
LOG_DIR="{session_dir}/logs"
|
|
WORKTREE_BASE="{session_dir}/worktrees"
|
|
mkdir -p "$LOG_DIR" "$WORKTREE_BASE"
|
|
|
|
# Cleanup trap — always remove worktrees on exit (success or failure)
|
|
cleanup_worktrees() {
|
|
echo ""
|
|
echo "=== Cleaning up worktrees ==="
|
|
cd "$REPO_ROOT"
|
|
for wt in "$WORKTREE_BASE"/session-*; do
|
|
[ -d "$wt" ] && git worktree remove "$wt" --force 2>/dev/null && echo "Removed: $wt"
|
|
done
|
|
git worktree prune
|
|
git branch --list "ultraplan/{slug}/*" | while read b; do
|
|
git branch -D "$b" 2>/dev/null
|
|
done
|
|
rmdir "$WORKTREE_BASE" 2>/dev/null
|
|
echo "Cleanup complete."
|
|
}
|
|
trap cleanup_worktrees EXIT
|
|
|
|
# Pre-flight: verify clean working tree
|
|
if [ -n "$(git status --porcelain)" ]; then
|
|
echo "ERROR: Working tree is not clean. Commit or stash changes before parallel execution."
|
|
git status --short
|
|
exit 1
|
|
fi
|
|
|
|
# Pre-flight: verify remote push permissions (catches credential/auth issues
|
|
# BEFORE spawning sessions). Sub-agent bash sandbox may have different
|
|
# credentials than the launching shell — Step 0 in each session spec handles
|
|
# the sandbox-side detection. Set ULTRAEXECUTE_SKIP_PREFLIGHT=1 for offline
|
|
# or air-gapped testing.
|
|
if [ "${ULTRAEXECUTE_SKIP_PREFLIGHT:-0}" != "1" ]; then
|
|
if ! git push --dry-run origin HEAD >/tmp/push-dryrun-launch.log 2>&1; then
|
|
echo "ERROR: git push --dry-run failed. Sessions will be unable to push."
|
|
cat /tmp/push-dryrun-launch.log
|
|
echo ""
|
|
echo "Fix remote credentials before running parallel execution, or set"
|
|
echo "ULTRAEXECUTE_SKIP_PREFLIGHT=1 to bypass (offline/air-gapped only)."
|
|
exit 1
|
|
fi
|
|
if grep -qE "(rejected|denied|forbidden|permission)" /tmp/push-dryrun-launch.log; then
|
|
echo "ERROR: git push --dry-run reports rejection. Sessions will fail at commit time."
|
|
cat /tmp/push-dryrun-launch.log
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "=== Ultraplan Headless Execution (Worktree-Isolated) ==="
|
|
echo "Plan: {plan_path}"
|
|
echo "Sessions: {total_sessions}"
|
|
echo "Repo root: $REPO_ROOT"
|
|
echo ""
|
|
|
|
# --- Wave {N}: Parallel sessions (no dependencies) ---
|
|
echo "--- Wave {N}: {description} ---"
|
|
|
|
{# For each parallel session in this wave, create worktree: }
|
|
git worktree add -b "ultraplan/{slug}/session-{n}" "$WORKTREE_BASE/session-{n}" HEAD
|
|
echo "Worktree created: session-{n} (branch: ultraplan/{slug}/session-{n})"
|
|
|
|
{# Launch session in its worktree: }
|
|
cd "$WORKTREE_BASE/session-{n}" && claude -p "$(cat "$PLAN_DIR/session-{n}-{slug}.md")" \
|
|
--allowedTools "Read,Write,Edit,Bash,Glob,Grep" \
|
|
--permission-mode bypassPermissions \
|
|
> "$LOG_DIR/session-{n}.log" 2>&1 &
|
|
PID_{n}=$!
|
|
cd "$REPO_ROOT"
|
|
echo "Started session {n}: {title} (PID $PID_{n})"
|
|
|
|
{# After all parallel sessions in this wave: }
|
|
echo "Waiting for Wave {N} to complete..."
|
|
wait $PID_{n1} $PID_{n2}
|
|
echo "Wave {N} complete."
|
|
echo ""
|
|
|
|
# --- Merge wave results (sequential) ---
|
|
echo "--- Merging Wave {N} ---"
|
|
cd "$REPO_ROOT"
|
|
{# For each session in the wave, merge its branch: }
|
|
git merge --no-ff "ultraplan/{slug}/session-{n}" \
|
|
-m "merge: ultraplan session {n} — {title}"
|
|
if [ $? -ne 0 ]; then
|
|
echo "MERGE CONFLICT: session {n}. Conflicting files:"
|
|
git diff --name-only --diff-filter=U
|
|
git merge --abort
|
|
echo "Aborting. Earlier sessions in this wave are already merged."
|
|
exit 1
|
|
fi
|
|
git worktree remove "$WORKTREE_BASE/session-{n}" --force
|
|
git branch -d "ultraplan/{slug}/session-{n}"
|
|
echo "Merged and cleaned: session {n}"
|
|
|
|
git worktree prune
|
|
|
|
# --- Verify wave results ---
|
|
echo "--- Verifying Wave {N} ---"
|
|
{# For each session in the wave, run its exit condition commands }
|
|
{verify_commands}
|
|
|
|
# --- Wave {N+1}: Sequential sessions (depends on previous wave) ---
|
|
{# Repeat wave pattern for dependent sessions }
|
|
|
|
echo ""
|
|
echo "=== All sessions complete ==="
|
|
echo "Review logs in $LOG_DIR/"
|
|
echo "Run final verification: {final_verify_command}"
|
|
```
|
|
|
|
## Rules for the session-decomposer
|
|
|
|
When generating a launch script from this template:
|
|
|
|
1. **Group sessions into waves** by dependency. Sessions with no dependencies
|
|
or whose dependencies are all in earlier waves can run in the same wave.
|
|
2. **Each wave waits for completion** before the next wave starts.
|
|
3. **Verification runs after each wave** — if verification fails, the script
|
|
stops and reports which session failed.
|
|
4. **Log each session** to a separate file for debugging.
|
|
5. **Use `claude -p`** with the session spec file as the prompt.
|
|
6. **Use `--allowedTools "Read,Write,Edit,Bash,Glob,Grep"`** with
|
|
`--permission-mode bypassPermissions` for child sessions. This limits the
|
|
tool surface to what the executor needs and prevents agent spawning, MCP
|
|
access, and external web requests in headless sessions.
|
|
7. **Final verification** at the end runs the master plan's verification section.
|
|
8. **Never include secrets** in the generated script.
|
|
9. **Wave verification must be independent.** After each wave completes, run
|
|
verification commands fresh via Bash — never parse session log files as proof
|
|
of success. Log files contain executor self-reporting, not ground truth. The
|
|
command's exit code is the only authoritative verification signal.
|
|
10. **Billing preamble.** Prepend `unset ANTHROPIC_API_KEY` with a comment at
|
|
the top of the script to prevent accidental API billing. Users who intend
|
|
to use API credits can remove this line.
|
|
11. **Worktree isolation is mandatory.** Every parallel wave MUST use git
|
|
worktrees. Each session gets its own worktree and branch. Never launch
|
|
parallel `claude -p` sessions in the same working directory.
|
|
12. **Cleanup trap on EXIT.** The generated script MUST include a `trap` on
|
|
EXIT that removes all worktrees (`git worktree remove --force`) and prunes
|
|
branches, even if the script fails or is interrupted.
|
|
13. **Sequential merge after each wave.** After all sessions in a wave complete,
|
|
merge their branches back to the main branch one at a time. Abort on merge
|
|
conflict — do not force-resolve.
|
|
14. **Clean working tree before worktrees.** Add a `git status --porcelain`
|
|
check at the top of the script. Fail if the working tree is dirty.
|
|
15. **Absolute paths for logs.** Log file paths must be absolute (resolved from
|
|
`$REPO_ROOT`), not relative to any worktree.
|