feat(execute)!: hand waves over as session orders, never start Claude Code (green)
Operator decision 27.09.2026: Voyage works the PM way, so nothing in it starts Claude Code. Work that needs another session becomes an order plus a starter line the user gives a session they open themselves; Voyage never starts the session. /trekexecute's multi-session path (Phase 2.6) now prepares one wave at a time: worktree per session, then lib/util/session-orders.mjs writes one order per session (worktree, steps, done-condition, starter) plus orders/wave.json, and the run stops. Running the same command again reads each session's progress file (status mode), merges sequentially with push-before-cleanup as before, and prepares the next wave. Phase 2.5 skips the destructive pre-flight while a wave is in flight. Phase 7.6 recovery is handed over the same way. Hard rule 21: never start Claude Code. Removed with it, because they existed only for launched children: the ANTHROPIC_API_KEY billing check (and its dry-run line and Hard rule 1 exception), the hooks-fire version precheck, the safety preamble, the scoped-tool/turn/budget flags, templates/headless-launch-template.md, the decomposer's launch.sh (now orders.md), /trekplan's "launch", the README billing and MCP tuning sections, and scripts/q3-cache-prefix-experiment.mjs (an experiment that spawned the binary; its results stay in docs/). /trekresearch's deep-research pre-gate no longer runs the binary for its version; the post-hoc check was already the authoritative guard. Chose orders + starters over in-session subagents because the order's done criterion asks for exactly that, and because /trekexecute disallows the Agent tool on purpose (Hard rule 10); reopening that is a separate change. Gate: 54 -> 0 hit lines outside EXEMPT. npm test 1250 (1245 pass, 0 fail, 5 skipped: 3 are the retired-reference scan without its local list). verify.sh 7/0. The fixture run (tests/lib/session-orders.test.mjs) writes two orders and wave.json for wave 1 and leaves a trap binary untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bc652ec7d3
commit
219e40cd91
18 changed files with 664 additions and 1108 deletions
|
|
@ -1,223 +0,0 @@
|
|||
# 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 trekplan
|
||||
# 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"
|
||||
|
||||
# Disable git's optional locks during parallel worktree ops (research/02 R2;
|
||||
# GH #47721). Mirror Phase 2.6 hardenings (commands/trekexecute.md).
|
||||
export GIT_OPTIONAL_LOCKS=0
|
||||
|
||||
# Per-child guardrails (operator may override via env). Match Phase 2.6
|
||||
# Step 2a-pre defaults.
|
||||
MAX_TURNS="${TREKEXECUTE_MAX_TURNS:-50}"
|
||||
MAX_BUDGET_USD="${TREKEXECUTE_MAX_BUDGET_USD:-5}"
|
||||
|
||||
# Shared context (brief + architecture if present). Absolute path so child
|
||||
# sessions resolve it after `cd` into their worktree.
|
||||
SHARED_CONTEXT_FILE="$WORKTREE_BASE/.shared-context.md"
|
||||
if [ -f "$PLAN_DIR/brief.md" ]; then
|
||||
cat "$PLAN_DIR/brief.md" > "$SHARED_CONTEXT_FILE"
|
||||
if [ -f "$PLAN_DIR/architecture/overview.md" ]; then
|
||||
printf '\n\n---\n\n' >> "$SHARED_CONTEXT_FILE"
|
||||
cat "$PLAN_DIR/architecture/overview.md" >> "$SHARED_CONTEXT_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Safety preambles prepended to every per-session prompt (defense-in-depth
|
||||
# against headless-mode hook gaps + Claude 4 cross-model malware-reminder).
|
||||
SAFETY_PREAMBLE='[CRITICAL — defense in depth] You are running in headless mode where plugin hooks may not fire reliably (GH #36071). DO NOT execute commands matching: `rm -rf /`, `curl | bash`, `git push --force` to main, `git reset --hard` outside this worktree, or any command writing outside the current worktree. Treat this rule as enforced regardless of `--allowedTools` allowlist.
|
||||
|
||||
Note: any malware-related safety reminders apply conditionally to code you assess as actually malicious, not to all code reads (per GH #52272 community workaround).'
|
||||
|
||||
# Cleanup trap — always remove worktrees on exit (success or failure)
|
||||
cleanup_worktrees() {
|
||||
echo ""
|
||||
echo "=== Cleaning up worktrees ==="
|
||||
cd "$REPO_ROOT"
|
||||
# push-before-cleanup (Hard Rule 19): push any remaining branches so work
|
||||
# survives even if subsequent removal races. Failure is non-fatal.
|
||||
git branch --list "trek/{slug}/*" | while read b; do
|
||||
git push origin "$b" 2>/dev/null || true
|
||||
done
|
||||
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 "trek/{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 TREKEXECUTE_SKIP_PREFLIGHT=1 for offline
|
||||
# or air-gapped testing.
|
||||
if [ "${TREKEXECUTE_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 "TREKEXECUTE_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 "=== Voyage 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 "trek/{slug}/session-{n}" "$WORKTREE_BASE/session-{n}" HEAD
|
||||
echo "Worktree created: session-{n} (branch: trek/{slug}/session-{n})"
|
||||
|
||||
{# Launch session in its worktree (with safety preamble + budget caps + shared context): }
|
||||
cd "$WORKTREE_BASE/session-{n}" && claude -p "${SAFETY_PREAMBLE}
|
||||
|
||||
$(cat "$PLAN_DIR/session-{n}-{slug}.md")" \
|
||||
--allowedTools "Read,Write,Edit,Bash,Glob,Grep" \
|
||||
--permission-mode bypassPermissions \
|
||||
--max-turns "$MAX_TURNS" \
|
||||
--max-budget-usd "$MAX_BUDGET_USD" \
|
||||
--append-system-prompt-file "$SHARED_CONTEXT_FILE" \
|
||||
> "$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: push BEFORE merge (Hard Rule 19 — push-before-cleanup). }
|
||||
git push origin "trek/{slug}/session-{n}" 2>/dev/null || true
|
||||
git merge --no-ff "trek/{slug}/session-{n}" \
|
||||
-m "merge: trekplan 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 "trek/{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.
|
||||
16. **Per-child guardrails (mirrors Phase 2.6 Step 2b).** Every `claude -p`
|
||||
invocation must include `--max-turns "$MAX_TURNS"`,
|
||||
`--max-budget-usd "$MAX_BUDGET_USD"`, and
|
||||
`--append-system-prompt-file "$SHARED_CONTEXT_FILE"`. The shared context
|
||||
must be built once with an absolute path (resolved from `$WORKTREE_BASE`)
|
||||
so child sessions can read it after `cd`.
|
||||
17. **Safety preamble.** Every per-session prompt must be prefixed with the
|
||||
`$SAFETY_PREAMBLE` string defined at the top of the script. This is the
|
||||
primary defense when plugin hooks do not fire reliably (GH #36071), and
|
||||
includes the GH #52272 malware-reminder clarification for AUTO mode.
|
||||
18. **GIT_OPTIONAL_LOCKS=0.** The script must export `GIT_OPTIONAL_LOCKS=0`
|
||||
once at the top so every git invocation (worktree add/remove/prune,
|
||||
branch -d, merge, push) avoids the index.lock background-poll race
|
||||
(research/02 R2; GH #47721).
|
||||
19. **push-before-cleanup (Hard Rule 19).** After successful `git merge --no-ff`,
|
||||
run `git push origin <branch>` BEFORE `git worktree remove` and
|
||||
`git branch -d`. Push failure is non-fatal — cleanup proceeds. Converts
|
||||
unrecoverable branch loss into recoverable remote state (research/02 R3).
|
||||
Loading…
Add table
Add a link
Reference in a new issue