Files
loop-loop/skills/run/SKILL.md
Sheldon Finlay ecfbd0bb37 feat: worktree-based run isolation for parallel loops
Each /agent-loop:run now creates a git worktree for the feature branch
before generating stories. This provides full isolation:

- Multiple loops can run in parallel on different specs in the same project
- Main working directory stays on main, always available
- Each worktree has its own .loop/ state, tmux session, and branch
- Completed runs are archived to main's .loop/archive/ with runs.log

Changes:
- setup.sh: add --init-worktree mode for initializing worktree .loop/
- archive.sh: add archive_from_worktree() for cross-directory archiving
- loop.sh: replace branch checkout with validation (worktree is pre-checked-out)
- agents/planner.md: accept absolute path prefix for worktree .loop/ writes
- skills/run/SKILL.md: full rewrite — worktree creation in Phase 2, launch in
  Phase 3, archive on completion, .active-worktree tracking file
- skills/stories/SKILL.md: worktree-aware, defer to /run for full flow

Bump to 0.12.0.
2026-04-02 11:21:17 -04:00

9.6 KiB

name, description
name description
run Agent Loop — single entry point. Scaffolds .loop/ if missing, creates a worktree, generates stories, then launches autonomous execution in tmux.

/run — Agent Loop

Single entry point for the agent loop. Handles setup and planning interactively, then launches autonomous execution in a git worktree via tmux.

Each run gets its own worktree (isolated working directory on a feature branch). Multiple loops can run in parallel on different specs. Completed runs are archived to the main project's .loop/archive/.

Usage

/agent-loop:run                    # Full flow: setup → worktree → stories → launch
/agent-loop:run --skip-eval        # Skip evaluator pass

Instructions

Follow this sequence. Each phase checks what exists and skips if already done.


Phase 1: Scaffold Main .loop/ (if needed)

Check if .loop/config.json exists in the current project root.

If it does NOT exist, run the setup script:

Ask the user: Mode? (a) Implement (b) Explore (c) Fix — default is Implement.

Then run:

bash "$(ls -d ~/.claude/plugins/cache/agent-loop/agent-loop/*/setup.sh 2>/dev/null | tail -1)" <mode>

Show the output. If setup fails, stop.

If it already exists, check if the harness files need updating. Compare the installed harness version against the plugin version:

INSTALLED=$(cat .loop/.harness-version 2>/dev/null || echo "unknown")
PLUGIN=$(jq -r '.version // empty' "$(ls -d ~/.claude/plugins/cache/agent-loop/agent-loop/*/.claude-plugin/plugin.json 2>/dev/null | tail -1)" 2>/dev/null || echo "unknown")
echo "installed=$INSTALLED plugin=$PLUGIN"

If the versions differ (or installed is "unknown"), update the harness files:

bash "$(ls -d ~/.claude/plugins/cache/agent-loop/agent-loop/*/setup.sh 2>/dev/null | tail -1)" --update

Tell the user: "Updated harness files to v{version}."


Phase 2: Create Worktree and Generate Stories

2a. Find the spec

Search for existing specs or plans:

  • docs/superpowers/specs/*.md
  • docs/superpowers/plans/*.md
  • docs/specs/*.md
  • docs/plans/*.md
  • SPEC.md, PRD.md, DESIGN.md, PLAN.md at project root
  • Any markdown file that looks like a feature spec or implementation plan

If found: "I found a spec at {path}. Using it to generate stories."

If NOT found, stop and tell the user:

No spec or plan found. Agent Loop decomposes existing plans into stories — it doesn't create plans from scratch.

Create a plan first, then re-run /agent-loop:run:

  • Describe your idea to Claude and ask it to write a spec
  • Use /plan if available
  • Or create a markdown file at docs/specs/ or SPEC.md

The plan should describe what to build, the tech stack, and key requirements.

STOP here. Do NOT ask the user to describe the project in a few sentences. Do NOT proceed without a spec file.

2b. Derive names and create worktree

Read the spec title or filename to derive a feature slug. Examples:

  • SPEC.md with title "# Enhanced Spikes Editor" → slug: enhanced-spikes-editor
  • docs/specs/auth-system.md → slug: auth-system

Derive paths:

PROJECT_DIR=$(basename "$(pwd)")
FEATURE_SLUG="<derived-slug>"
BRANCH_NAME="loop/${FEATURE_SLUG}"
WORKTREE_PATH="../${PROJECT_DIR}--loop-${FEATURE_SLUG}"
MAIN_LOOP_DIR="$(pwd)/.loop"

Check if the worktree already exists:

if [ -d "$WORKTREE_PATH" ]; then
    echo "WORKTREE_EXISTS"
else
    echo "WORKTREE_NEW"
fi

If worktree exists, check its state:

  • Read {WORKTREE_PATH}/.loop/prd.json — are all stories passed?
  • If all passed: ask user — "Previous run on {BRANCH_NAME} is complete. Archive and start fresh, or resume?"
  • If in progress: ask user — "Run in progress on {BRANCH_NAME} ({passed}/{total}). Resume, or archive and start fresh?"
  • If user says resume: skip to Phase 3 (launch in existing worktree)
  • If user says archive/fresh: archive from worktree to main, remove worktree, then continue below

If worktree is new, create it:

git worktree add "$WORKTREE_PATH" -b "$BRANCH_NAME"

If the branch already exists (e.g., from a previous run):

git worktree add "$WORKTREE_PATH" "$BRANCH_NAME"

Initialize the worktree's .loop/:

bash "$(ls -d ~/.claude/plugins/cache/agent-loop/agent-loop/*/setup.sh 2>/dev/null | tail -1)" --init-worktree "$WORKTREE_PATH" "$MAIN_LOOP_DIR"

Initialize submodules if the project uses them:

git -C "$WORKTREE_PATH" submodule update --init --recursive 2>/dev/null || true

2c. Generate stories

Read the project root listing and tech stack info.

Dispatch the agent-loop:planner agent. Pass the absolute worktree path so the planner writes to the worktree's .loop/:

Agent(
  subagent_type: "agent-loop:planner",
  prompt: "Generate prd.json and sprint contracts.\n\nIMPORTANT: Write ALL files using absolute paths under: {WORKTREE_PATH}/.loop/\n- PRD: {WORKTREE_PATH}/.loop/prd.json\n- Contracts: {WORKTREE_PATH}/.loop/contracts/\n- Progress: {WORKTREE_PATH}/.loop/progress.md\n\nBranch name to use in prd.json: {BRANCH_NAME}\n\nMode: {mode}\nProject root: {WORKTREE_PATH}\n\nSpec:\n{spec content}\n\nTech stack: {detected stack}",
  description: "Planning: generate stories"
)

2d. Present stories

After the planner finishes, read {WORKTREE_PATH}/.loop/prd.json and present:

Stories generated — Review before running

Worktree: {WORKTREE_PATH} (branch: {BRANCH_NAME})

  1. US-001: {title}
  2. US-002: {title} ...

Review:

  • {WORKTREE_PATH}/.loop/prd.json — stories and acceptance criteria
  • {WORKTREE_PATH}/.loop/contracts/ — done conditions per story

Let me know if you want changes, or say go to start the loop.

STOP and wait for the user. Do NOT start the loop automatically. The user must say "go", "start", "run", "looks good", or similar before proceeding to Phase 3.


Phase 3: Validate and Launch

  1. Read {WORKTREE_PATH}/.loop/prd.json and verify:

    • Has a userStories array (NOT sprints, stories, or tasks)
    • Each story has: id, title, passes, priority
    • If invalid, show the error and stop.
  2. Read {WORKTREE_PATH}/.loop/config.json for mode, maxIterations.

  3. Verify {WORKTREE_PATH}/.loop/loop.sh exists and is executable.

  4. Parse arguments for any flags to pass through (e.g., --skip-eval).

  5. Build the loop.sh command and derive a unique tmux session name:

LOOP_CMD="{WORKTREE_PATH}/.loop/loop.sh"
# Add --skip-eval if requested
# Add --max N if specified

# Derive tmux session name from worktree directory name
WORKTREE_DIR=$(basename "$WORKTREE_PATH")
SESSION_NAME="agent-loop-${WORKTREE_DIR}"
  1. Kill any existing tmux session with this name, then launch detached in the worktree:
tmux kill-session -t "$SESSION_NAME" 2>/dev/null; tmux new-session -d -s "$SESSION_NAME" -c "$WORKTREE_PATH" "$LOOP_CMD"
  1. Save the worktree path and session name for the completion handler. Write a tracking file in main's .loop/:
cat > .loop/.active-worktree << EOF
WORKTREE_PATH={WORKTREE_PATH}
SESSION_NAME={SESSION_NAME}
BRANCH_NAME={BRANCH_NAME}
MAIN_LOOP_DIR={MAIN_LOOP_DIR}
EOF
  1. Start a background watcher that waits for the loop to finish. Use the Bash tool with run_in_background: true:
while tmux has-session -t "$SESSION_NAME" 2>/dev/null; do sleep 10; done; echo "LOOP_COMPLETE"
  1. Tell the user:

Loop launched as tmux session {SESSION_NAME}. Watch it live:

! tmux attach -t {SESSION_NAME}

(Type the above — it opens the session right here in your terminal.)

  • Detach (return to Claude Code): Ctrl+B then D
  • Stop the loop: Ctrl+C
  • Ask me "status" anytime and I'll check progress.

I'll notify you when the loop finishes.

When complete, merge with:

git merge {BRANCH_NAME}
git worktree remove {WORKTREE_PATH}
git branch -d {BRANCH_NAME}

When Background Watcher Completes

When you receive the background task notification (the watcher prints "LOOP_COMPLETE"), the loop has finished. Automatically:

  1. Read the tracking file to get paths:
cat .loop/.active-worktree
  1. Read {WORKTREE_PATH}/.loop/prd.json — count passed/failed/blocked stories

  2. Read {WORKTREE_PATH}/.loop/progress.md — show the latest session log entries

  3. Check git log --oneline on the feature branch for commits made during the run

  4. Archive the run to main's .loop/archive/:

source .loop/lib/state.sh && source .loop/lib/archive.sh && archive_from_worktree "{WORKTREE_PATH}/.loop" "$(pwd)/.loop"
  1. Clean up the tracking file:
rm -f .loop/.active-worktree
  1. Present a summary:

Loop Complete

  • Stories: {passed}/{total} complete, {blocked} blocked
  • Iterations: {from progress.md}
  • Commits: {list from git log}
  • Archived to: .loop/archive/{date}-{feature}/

{If any stories blocked: "Some stories need human review. Run /agent-loop:triage for details."} {If all passed: "All stories complete. Review the code and test it."}

When ready to merge:

git merge {BRANCH_NAME}
git worktree remove {WORKTREE_PATH}
git branch -d {BRANCH_NAME}

Monitoring (if user asks mid-run)

If the user asks about progress (e.g., "status", "how's it going"):

  1. Check for active worktree tracking:
cat .loop/.active-worktree 2>/dev/null

If no tracking file, check for tmux sessions matching the pattern:

tmux list-sessions 2>/dev/null | grep "^agent-loop-"
  1. Read {WORKTREE_PATH}/.loop/prd.json — count passed/failed/blocked
  2. Capture recent tmux output:
tmux capture-pane -t "$SESSION_NAME" -p | tail -20
  1. Report current status.