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.
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
"name": "agent-loop",
|
"name": "agent-loop",
|
||||||
"source": "./",
|
"source": "./",
|
||||||
"description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Plan interactively, then execute with full visibility.",
|
"description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Plan interactively, then execute with full visibility.",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Sheldon"
|
"name": "Sheldon"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "agent-loop",
|
"name": "agent-loop",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Run /agent-loop:run to start.",
|
"description": "Autonomous generator-evaluator agent loop for long-running coding tasks. Run /agent-loop:run to start.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Sheldon"
|
"name": "Sheldon"
|
||||||
|
|||||||
@@ -11,12 +11,16 @@ You are a planner agent for the agent loop harness. Your job is to decompose a f
|
|||||||
|
|
||||||
## CONSTRAINTS
|
## CONSTRAINTS
|
||||||
|
|
||||||
- You may ONLY write files inside the `.loop/` directory
|
- You may ONLY write files inside the `.loop/` directory (or the absolute loop directory path if one is provided)
|
||||||
- You may NOT write any project source code (.js, .ts, .py, .go, .rs, .html, .css, etc.)
|
- You may NOT write any project source code (.js, .ts, .py, .go, .rs, .html, .css, etc.)
|
||||||
- You may NOT run bash commands
|
- You may NOT run bash commands
|
||||||
- You may NOT start implementing features
|
- You may NOT start implementing features
|
||||||
- You produce prd.json and contracts, then STOP
|
- You produce prd.json and contracts, then STOP
|
||||||
|
|
||||||
|
## OUTPUT DIRECTORY
|
||||||
|
|
||||||
|
If the prompt specifies an absolute path for the loop directory (e.g., "Write all files to /path/to/worktree/.loop/"), use that absolute path for ALL file writes. Otherwise, use the relative `.loop/` path.
|
||||||
|
|
||||||
## YOUR TASK
|
## YOUR TASK
|
||||||
|
|
||||||
You will be given a feature spec or description. Decompose it into stories.
|
You will be given a feature spec or description. Decompose it into stories.
|
||||||
|
|||||||
@@ -150,6 +150,54 @@ print(json.load(open(os.environ['LOOP_PRD'])).get('branchName', ''), end='')
|
|||||||
echo "[archive] .loop/ reset — ready for new stories"
|
echo "[archive] .loop/ reset — ready for new stories"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Archive a completed run from a worktree back to the main project's .loop/archive/.
|
||||||
|
# Called by the /run skill's completion handler after the loop finishes in a worktree.
|
||||||
|
#
|
||||||
|
# Usage: archive_from_worktree <worktree_loop_dir> <main_loop_dir>
|
||||||
|
# worktree_loop_dir: absolute path to the worktree's .loop/ (source)
|
||||||
|
# main_loop_dir: absolute path to the main project's .loop/ (destination)
|
||||||
|
archive_from_worktree() {
|
||||||
|
local wt_loop_dir="$1"
|
||||||
|
local main_loop_dir="$2"
|
||||||
|
local wt_prd="$wt_loop_dir/prd.json"
|
||||||
|
|
||||||
|
[ -f "$wt_prd" ] || { echo "[archive] WARNING: No prd.json in worktree — nothing to archive"; return 1; }
|
||||||
|
|
||||||
|
# Read branch name from worktree's prd.json
|
||||||
|
local branch_name=""
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
branch_name=$(jq -r '.branchName // empty' "$wt_prd" 2>/dev/null)
|
||||||
|
elif command -v python3 &>/dev/null; then
|
||||||
|
branch_name=$(LOOP_PRD="$wt_prd" python3 -c "
|
||||||
|
import json, os
|
||||||
|
print(json.load(open(os.environ['LOOP_PRD'])).get('branchName', ''), end='')
|
||||||
|
" 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
local feature_name
|
||||||
|
feature_name=$(echo "${branch_name:-unknown}" | sed 's|.*/||')
|
||||||
|
|
||||||
|
local archive_dir="$main_loop_dir/archive/$(date +%Y-%m-%d)-${feature_name}"
|
||||||
|
mkdir -p "$archive_dir"
|
||||||
|
|
||||||
|
# Copy artifacts from worktree
|
||||||
|
[ -f "$wt_prd" ] && cp "$wt_prd" "$archive_dir/"
|
||||||
|
[ -f "$wt_loop_dir/progress.md" ] && cp "$wt_loop_dir/progress.md" "$archive_dir/"
|
||||||
|
[ -f "$wt_loop_dir/progress-archive.md" ] && cp "$wt_loop_dir/progress-archive.md" "$archive_dir/"
|
||||||
|
[ -d "$wt_loop_dir/contracts" ] && cp -r "$wt_loop_dir/contracts" "$archive_dir/"
|
||||||
|
[ -d "$wt_loop_dir/triage" ] && cp -r "$wt_loop_dir/triage" "$archive_dir/"
|
||||||
|
|
||||||
|
# Verify archive has content
|
||||||
|
if ! find "$archive_dir" -maxdepth 1 -type f | read -r; then
|
||||||
|
echo "[archive] WARNING: Archive directory is empty — copy may have failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
append_runs_log "$branch_name" "$archive_dir"
|
||||||
|
|
||||||
|
echo "[archive] Archived worktree run to $archive_dir"
|
||||||
|
}
|
||||||
|
|
||||||
# Append a one-line summary to the runs log.
|
# Append a one-line summary to the runs log.
|
||||||
append_runs_log() {
|
append_runs_log() {
|
||||||
local branch_name="$1"
|
local branch_name="$1"
|
||||||
|
|||||||
6
loop.sh
6
loop.sh
@@ -181,12 +181,14 @@ if [ -f "$LOOP_DIR/init.sh" ]; then
|
|||||||
bash "$LOOP_DIR/init.sh"
|
bash "$LOOP_DIR/init.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure correct git branch
|
# Verify we're on the expected branch (worktree should already be on it)
|
||||||
BRANCH=$(prd_branch_name 2>/dev/null || echo "")
|
BRANCH=$(prd_branch_name 2>/dev/null || echo "")
|
||||||
if [ -n "$BRANCH" ]; then
|
if [ -n "$BRANCH" ]; then
|
||||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
||||||
if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then
|
if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then
|
||||||
log "Switching to branch: $BRANCH"
|
log "WARNING: Expected branch '$BRANCH' but on '$CURRENT_BRANCH'"
|
||||||
|
log "If running in a worktree, the branch should already be checked out."
|
||||||
|
log "Attempting to switch..."
|
||||||
git checkout "$BRANCH" 2>/dev/null || \
|
git checkout "$BRANCH" 2>/dev/null || \
|
||||||
git checkout -b "$BRANCH" "origin/$BRANCH" 2>/dev/null || \
|
git checkout -b "$BRANCH" "origin/$BRANCH" 2>/dev/null || \
|
||||||
git checkout -b "$BRANCH"
|
git checkout -b "$BRANCH"
|
||||||
|
|||||||
55
setup.sh
55
setup.sh
@@ -11,16 +11,30 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# --- Parse arguments ---
|
# --- Parse arguments ---
|
||||||
UPDATE_ONLY=false
|
ACTION="scaffold"
|
||||||
MODE="${1:-implement}"
|
MODE="${1:-implement}"
|
||||||
|
WORKTREE_PATH=""
|
||||||
|
MAIN_LOOP_DIR=""
|
||||||
|
|
||||||
if [ "$MODE" = "--update" ]; then
|
case "$MODE" in
|
||||||
UPDATE_ONLY=true
|
--update)
|
||||||
MODE="${2:-implement}" # mode not used in update, but keep arg parsing clean
|
ACTION="update"
|
||||||
|
MODE="${2:-implement}"
|
||||||
|
;;
|
||||||
|
--init-worktree)
|
||||||
|
ACTION="init-worktree"
|
||||||
|
WORKTREE_PATH="$2"
|
||||||
|
MAIN_LOOP_DIR="$3"
|
||||||
|
if [ -z "$WORKTREE_PATH" ] || [ -z "$MAIN_LOOP_DIR" ]; then
|
||||||
|
echo "[setup] ERROR: --init-worktree requires <worktree_path> <main_loop_dir>"
|
||||||
|
echo "[setup] Usage: setup.sh --init-worktree /path/to/worktree /path/to/main/.loop"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
# --- Validate mode ---
|
# --- Validate mode ---
|
||||||
if [ "$UPDATE_ONLY" = false ] && [[ ! "$MODE" =~ ^(implement|explore|fix)$ ]]; then
|
if [ "$ACTION" = "scaffold" ] && [[ ! "$MODE" =~ ^(implement|explore|fix)$ ]]; then
|
||||||
echo "[setup] ERROR: Invalid mode '$MODE'. Must be: implement, explore, fix"
|
echo "[setup] ERROR: Invalid mode '$MODE'. Must be: implement, explore, fix"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -63,7 +77,7 @@ if [ -f "$HARNESS_SRC/.claude-plugin/plugin.json" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Update-only mode: refresh harness files without touching run state ---
|
# --- Update-only mode: refresh harness files without touching run state ---
|
||||||
if [ "$UPDATE_ONLY" = true ]; then
|
if [ "$ACTION" = "update" ]; then
|
||||||
LOOP_DIR="$(pwd)/.loop"
|
LOOP_DIR="$(pwd)/.loop"
|
||||||
if [ ! -d "$LOOP_DIR" ]; then
|
if [ ! -d "$LOOP_DIR" ]; then
|
||||||
echo "[setup] ERROR: No .loop/ directory found. Run setup first."
|
echo "[setup] ERROR: No .loop/ directory found. Run setup first."
|
||||||
@@ -83,6 +97,34 @@ if [ "$UPDATE_ONLY" = true ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- Init-worktree mode: initialize .loop/ in a worktree from main's config ---
|
||||||
|
if [ "$ACTION" = "init-worktree" ]; then
|
||||||
|
LOOP_DIR="$WORKTREE_PATH/.loop"
|
||||||
|
mkdir -p "$LOOP_DIR"
|
||||||
|
|
||||||
|
# Copy harness files from plugin source
|
||||||
|
cp -r "$HARNESS_SRC/prompts" "$LOOP_DIR/"
|
||||||
|
cp -r "$HARNESS_SRC/templates" "$LOOP_DIR/"
|
||||||
|
cp -r "$HARNESS_SRC/lib" "$LOOP_DIR/"
|
||||||
|
cp "$HARNESS_SRC/loop.sh" "$LOOP_DIR/"
|
||||||
|
chmod +x "$LOOP_DIR/loop.sh"
|
||||||
|
|
||||||
|
# Copy project config and init from main's .loop/
|
||||||
|
[ -f "$MAIN_LOOP_DIR/config.json" ] && cp "$MAIN_LOOP_DIR/config.json" "$LOOP_DIR/"
|
||||||
|
[ -f "$MAIN_LOOP_DIR/init.sh" ] && cp "$MAIN_LOOP_DIR/init.sh" "$LOOP_DIR/"
|
||||||
|
|
||||||
|
# Stamp harness version
|
||||||
|
[ -n "$PLUGIN_VERSION" ] && echo "$PLUGIN_VERSION" > "$LOOP_DIR/.harness-version"
|
||||||
|
|
||||||
|
# Create .gitignore for worktree's .loop/
|
||||||
|
cat > "$LOOP_DIR/.gitignore" << 'GITIGNORE'
|
||||||
|
*
|
||||||
|
GITIGNORE
|
||||||
|
|
||||||
|
echo "[setup] Worktree .loop/ initialized at $LOOP_DIR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# --- Ensure git repo exists ---
|
# --- Ensure git repo exists ---
|
||||||
if ! git rev-parse --git-dir &>/dev/null; then
|
if ! git rev-parse --git-dir &>/dev/null; then
|
||||||
echo "[setup] No git repo found. Initializing..."
|
echo "[setup] No git repo found. Initializing..."
|
||||||
@@ -141,6 +183,7 @@ archive/
|
|||||||
.archive-staging/
|
.archive-staging/
|
||||||
.last-branch
|
.last-branch
|
||||||
.harness-version
|
.harness-version
|
||||||
|
.active-worktree
|
||||||
.loop.lock
|
.loop.lock
|
||||||
GITIGNORE
|
GITIGNORE
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
---
|
---
|
||||||
name: run
|
name: run
|
||||||
description: "Agent Loop — single entry point. Scaffolds .loop/ if missing, generates stories if no prd.json, then launches autonomous execution in tmux."
|
description: "Agent Loop — single entry point. Scaffolds .loop/ if missing, creates a worktree, generates stories, then launches autonomous execution in tmux."
|
||||||
---
|
---
|
||||||
|
|
||||||
# /run — Agent Loop
|
# /run — Agent Loop
|
||||||
|
|
||||||
Single entry point for the agent loop. Handles setup and planning interactively, then launches autonomous execution in a tmux session.
|
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
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/agent-loop:run # Full flow: setup → stories → launch
|
/agent-loop:run # Full flow: setup → worktree → stories → launch
|
||||||
/agent-loop:run --skip-eval # Skip evaluator pass
|
/agent-loop:run --skip-eval # Skip evaluator pass
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -20,9 +22,9 @@ Follow this sequence. Each phase checks what exists and skips if already done.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 1: Scaffold or Update Harness
|
## Phase 1: Scaffold Main .loop/ (if needed)
|
||||||
|
|
||||||
Check if `.loop/config.json` exists.
|
Check if `.loop/config.json` exists in the current project root.
|
||||||
|
|
||||||
**If it does NOT exist**, run the setup script:
|
**If it does NOT exist**, run the setup script:
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ Ask the user: **Mode?** (a) Implement (b) Explore (c) Fix — default is Impleme
|
|||||||
Then run:
|
Then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash "$(ls -d ~/.claude/plugins/cache/agent-loop/agent-loop/*/setup.sh 2>/dev/null | head -1)" <mode>
|
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.
|
Show the output. If setup fails, stop.
|
||||||
@@ -50,50 +52,15 @@ 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
|
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}."* Then continue to Phase 2.
|
Tell the user: *"Updated harness files to v{version}."*
|
||||||
|
|
||||||
If versions match, skip to Phase 2.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 2: Generate Stories (if needed)
|
## Phase 2: Create Worktree and Generate Stories
|
||||||
|
|
||||||
Check if `.loop/prd.json` exists.
|
### 2a. Find the spec
|
||||||
|
|
||||||
**If it exists**, check whether the previous run is complete. Run:
|
Search for existing specs or plans:
|
||||||
|
|
||||||
```bash
|
|
||||||
source .loop/lib/state.sh 2>/dev/null; LOOP_DIR=.loop all_stories_pass 2>/dev/null && echo "ALL_PASSED" || echo "IN_PROGRESS"
|
|
||||||
```
|
|
||||||
|
|
||||||
Also check if the feature branch from prd.json still exists:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
jq -r '.branchName // empty' .loop/prd.json
|
|
||||||
```
|
|
||||||
|
|
||||||
If the result is `ALL_PASSED` **or** the branch no longer exists (deleted after merge), the previous run is done. Archive it and reset:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
LOOP_DIR=.loop source .loop/lib/state.sh && source .loop/lib/archive.sh && archive_and_reset .loop
|
|
||||||
```
|
|
||||||
|
|
||||||
Tell the user: *"Archived previous run ({branch}). Starting fresh."*
|
|
||||||
|
|
||||||
Then proceed to generate new stories below.
|
|
||||||
|
|
||||||
If the result is `IN_PROGRESS` and the branch still exists, the previous run is still active. Tell the user:
|
|
||||||
|
|
||||||
> **Existing run found** — {passed}/{total} stories complete on `{branch}`.
|
|
||||||
>
|
|
||||||
> - Say **go** to resume the existing run
|
|
||||||
> - Say **archive** to archive this run and start fresh
|
|
||||||
|
|
||||||
Wait for the user to decide before proceeding.
|
|
||||||
|
|
||||||
**If `prd.json` does NOT exist**, generate it:
|
|
||||||
|
|
||||||
1. Search for existing specs or plans:
|
|
||||||
- `docs/superpowers/specs/*.md`
|
- `docs/superpowers/specs/*.md`
|
||||||
- `docs/superpowers/plans/*.md`
|
- `docs/superpowers/plans/*.md`
|
||||||
- `docs/specs/*.md`
|
- `docs/specs/*.md`
|
||||||
@@ -116,78 +83,148 @@ Wait for the user to decide before proceeding.
|
|||||||
|
|
||||||
**STOP here. Do NOT ask the user to describe the project in a few sentences. Do NOT proceed without a spec file.**
|
**STOP here. Do NOT ask the user to describe the project in a few sentences. Do NOT proceed without a spec file.**
|
||||||
|
|
||||||
2. Read the project root and tech stack info.
|
### 2b. Derive names and create worktree
|
||||||
|
|
||||||
3. Dispatch the **agent-loop:planner** agent:
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git worktree add "$WORKTREE_PATH" -b "$BRANCH_NAME"
|
||||||
|
```
|
||||||
|
|
||||||
|
If the branch already exists (e.g., from a previous run):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git worktree add "$WORKTREE_PATH" "$BRANCH_NAME"
|
||||||
|
```
|
||||||
|
|
||||||
|
Initialize the worktree's `.loop/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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(
|
Agent(
|
||||||
subagent_type: "agent-loop:planner",
|
subagent_type: "agent-loop:planner",
|
||||||
prompt: "Generate prd.json and sprint contracts.\n\nMode: {mode}\nProject root: {path}\n\nSpec:\n{spec content}\n\nTech stack: {detected stack}",
|
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"
|
description: "Planning: generate stories"
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
4. After the planner finishes, read `.loop/prd.json` and present:
|
### 2d. Present stories
|
||||||
|
|
||||||
|
After the planner finishes, read `{WORKTREE_PATH}/.loop/prd.json` and present:
|
||||||
|
|
||||||
> **Stories generated — Review before running**
|
> **Stories generated — Review before running**
|
||||||
>
|
>
|
||||||
|
> Worktree: `{WORKTREE_PATH}` (branch: `{BRANCH_NAME}`)
|
||||||
|
>
|
||||||
> 1. US-001: {title}
|
> 1. US-001: {title}
|
||||||
> 2. US-002: {title}
|
> 2. US-002: {title}
|
||||||
> ...
|
> ...
|
||||||
>
|
>
|
||||||
> **Review:**
|
> **Review:**
|
||||||
> - `.loop/prd.json` — stories and acceptance criteria
|
> - `{WORKTREE_PATH}/.loop/prd.json` — stories and acceptance criteria
|
||||||
> - `.loop/contracts/` — done conditions per story
|
> - `{WORKTREE_PATH}/.loop/contracts/` — done conditions per story
|
||||||
>
|
>
|
||||||
> Let me know if you want changes, or say **go** to start the loop.
|
> Let me know if you want changes, or say **go** to start the loop.
|
||||||
|
|
||||||
5. **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.
|
**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.
|
||||||
|
|
||||||
**If `prd.json` already exists**, skip to Phase 3.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 3: Validate and Launch
|
## Phase 3: Validate and Launch
|
||||||
|
|
||||||
1. Read `.loop/prd.json` and verify:
|
1. Read `{WORKTREE_PATH}/.loop/prd.json` and verify:
|
||||||
- Has a `userStories` array (NOT `sprints`, `stories`, or `tasks`)
|
- Has a `userStories` array (NOT `sprints`, `stories`, or `tasks`)
|
||||||
- Each story has: `id`, `title`, `passes`, `priority`
|
- Each story has: `id`, `title`, `passes`, `priority`
|
||||||
- If invalid, show the error and stop.
|
- If invalid, show the error and stop.
|
||||||
|
|
||||||
2. Read `.loop/config.json` for `mode`, `maxIterations`.
|
2. Read `{WORKTREE_PATH}/.loop/config.json` for `mode`, `maxIterations`.
|
||||||
|
|
||||||
3. Verify `.loop/loop.sh` exists and is executable.
|
3. Verify `{WORKTREE_PATH}/.loop/loop.sh` exists and is executable.
|
||||||
|
|
||||||
4. Parse arguments for any flags to pass through (e.g., `--skip-eval`).
|
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:
|
5. Build the loop.sh command and derive a unique tmux session name:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
LOOP_CMD=".loop/loop.sh"
|
LOOP_CMD="{WORKTREE_PATH}/.loop/loop.sh"
|
||||||
# Add --skip-eval if requested
|
# Add --skip-eval if requested
|
||||||
# Add --max N if specified
|
# Add --max N if specified
|
||||||
|
|
||||||
# Derive tmux session name from project directory name.
|
# Derive tmux session name from worktree directory name
|
||||||
# This allows multiple loops to run in parallel on different projects.
|
WORKTREE_DIR=$(basename "$WORKTREE_PATH")
|
||||||
SESSION_NAME="agent-loop-$(basename "$(pwd)")"
|
SESSION_NAME="agent-loop-${WORKTREE_DIR}"
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Kill any existing tmux session for THIS project, then launch detached:
|
6. Kill any existing tmux session with this name, then launch detached in the worktree:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tmux kill-session -t "$SESSION_NAME" 2>/dev/null; tmux new-session -d -s "$SESSION_NAME" -c <project_root> "$LOOP_CMD"
|
tmux kill-session -t "$SESSION_NAME" 2>/dev/null; tmux new-session -d -s "$SESSION_NAME" -c "$WORKTREE_PATH" "$LOOP_CMD"
|
||||||
```
|
```
|
||||||
|
|
||||||
7. Start a **background watcher** that waits for the loop to finish. Use the Bash tool with `run_in_background: true`:
|
7. Save the worktree path and session name for the completion handler. Write a tracking file in main's .loop/:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > .loop/.active-worktree << EOF
|
||||||
|
WORKTREE_PATH={WORKTREE_PATH}
|
||||||
|
SESSION_NAME={SESSION_NAME}
|
||||||
|
BRANCH_NAME={BRANCH_NAME}
|
||||||
|
MAIN_LOOP_DIR={MAIN_LOOP_DIR}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Start a **background watcher** that waits for the loop to finish. Use the Bash tool with `run_in_background: true`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
while tmux has-session -t "$SESSION_NAME" 2>/dev/null; do sleep 10; done; echo "LOOP_COMPLETE"
|
while tmux has-session -t "$SESSION_NAME" 2>/dev/null; do sleep 10; done; echo "LOOP_COMPLETE"
|
||||||
```
|
```
|
||||||
|
|
||||||
This runs silently. When the tmux session exits, Claude Code gets notified automatically.
|
9. Tell the user:
|
||||||
|
|
||||||
8. Tell the user:
|
|
||||||
|
|
||||||
> **Loop launched** as tmux session `{SESSION_NAME}`. Watch it live:
|
> **Loop launched** as tmux session `{SESSION_NAME}`. Watch it live:
|
||||||
> ```
|
> ```
|
||||||
@@ -200,6 +237,13 @@ This runs silently. When the tmux session exits, Claude Code gets notified autom
|
|||||||
> - Ask me "status" anytime and I'll check progress.
|
> - Ask me "status" anytime and I'll check progress.
|
||||||
>
|
>
|
||||||
> I'll notify you when the loop finishes.
|
> 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}
|
||||||
|
> ```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -207,18 +251,45 @@ This runs silently. When the tmux session exits, Claude Code gets notified autom
|
|||||||
|
|
||||||
When you receive the background task notification (the watcher prints "LOOP_COMPLETE"), the loop has finished. Automatically:
|
When you receive the background task notification (the watcher prints "LOOP_COMPLETE"), the loop has finished. Automatically:
|
||||||
|
|
||||||
1. Read `.loop/prd.json` — count passed/failed/blocked stories
|
1. Read the tracking file to get paths:
|
||||||
2. Read `.loop/progress.md` — show the latest session log entries
|
|
||||||
3. Check `git log --oneline` for commits made during the run
|
```bash
|
||||||
4. Present a summary:
|
cat .loop/.active-worktree
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Read `{WORKTREE_PATH}/.loop/prd.json` — count passed/failed/blocked stories
|
||||||
|
3. Read `{WORKTREE_PATH}/.loop/progress.md` — show the latest session log entries
|
||||||
|
4. Check `git log --oneline` on the feature branch for commits made during the run
|
||||||
|
|
||||||
|
5. Archive the run to main's `.loop/archive/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source .loop/lib/state.sh && source .loop/lib/archive.sh && archive_from_worktree "{WORKTREE_PATH}/.loop" "$(pwd)/.loop"
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Clean up the tracking file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -f .loop/.active-worktree
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Present a summary:
|
||||||
|
|
||||||
> **Loop Complete**
|
> **Loop Complete**
|
||||||
> - Stories: {passed}/{total} complete, {blocked} blocked
|
> - Stories: {passed}/{total} complete, {blocked} blocked
|
||||||
> - Iterations: {from progress.md}
|
> - Iterations: {from progress.md}
|
||||||
> - Commits: {list from git log}
|
> - 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 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."}
|
> {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}
|
||||||
|
> ```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -226,12 +297,23 @@ When you receive the background task notification (the watcher prints "LOOP_COMP
|
|||||||
|
|
||||||
If the user asks about progress (e.g., "status", "how's it going"):
|
If the user asks about progress (e.g., "status", "how's it going"):
|
||||||
|
|
||||||
1. Read `.loop/prd.json` — count passed/failed/blocked
|
1. Check for active worktree tracking:
|
||||||
2. Derive the session name and capture recent tmux output:
|
|
||||||
|
```bash
|
||||||
|
cat .loop/.active-worktree 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
If no tracking file, check for tmux sessions matching the pattern:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tmux list-sessions 2>/dev/null | grep "^agent-loop-"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Read `{WORKTREE_PATH}/.loop/prd.json` — count passed/failed/blocked
|
||||||
|
3. Capture recent tmux output:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
SESSION_NAME="agent-loop-$(basename "$(pwd)")"
|
|
||||||
tmux capture-pane -t "$SESSION_NAME" -p | tail -20
|
tmux capture-pane -t "$SESSION_NAME" -p | tail -20
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Report current status.
|
4. Report current status.
|
||||||
|
|||||||
@@ -7,26 +7,14 @@ description: "Generate prd.json and sprint contracts by dispatching the planner
|
|||||||
|
|
||||||
Dispatch the planner agent to decompose a spec into stories. The planner agent cannot write source code or run bash commands — it can only write to `.loop/`.
|
Dispatch the planner agent to decompose a spec into stories. The planner agent cannot write source code or run bash commands — it can only write to `.loop/`.
|
||||||
|
|
||||||
|
**Note:** In most cases, use `/agent-loop:run` instead — it handles worktree creation, story generation, and launching the loop in one flow. Use `/agent-loop:stories` only if you want to generate stories without launching the loop.
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
### 1. Check prerequisites
|
### 1. Check prerequisites
|
||||||
|
|
||||||
Verify `.loop/config.json` exists. If not, tell the user to run `/agent-loop:setup` first and stop.
|
Verify `.loop/config.json` exists. If not, tell the user to run `/agent-loop:setup` first and stop.
|
||||||
|
|
||||||
If `.loop/prd.json` already exists, check whether the previous run is complete:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source .loop/lib/state.sh 2>/dev/null; LOOP_DIR=.loop all_stories_pass 2>/dev/null && echo "ALL_PASSED" || echo "IN_PROGRESS"
|
|
||||||
```
|
|
||||||
|
|
||||||
If `ALL_PASSED`, archive the completed run before generating new stories:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
LOOP_DIR=.loop source .loop/lib/state.sh && source .loop/lib/archive.sh && archive_and_reset .loop
|
|
||||||
```
|
|
||||||
|
|
||||||
If `IN_PROGRESS`, warn the user that a run is active and ask whether to archive it or abort.
|
|
||||||
|
|
||||||
### 2. Find the spec
|
### 2. Find the spec
|
||||||
|
|
||||||
Check these locations:
|
Check these locations:
|
||||||
@@ -54,9 +42,15 @@ Agent(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If a worktree path is known (e.g., passed as context), include it in the prompt:
|
||||||
|
|
||||||
|
```
|
||||||
|
IMPORTANT: Write ALL files using absolute paths under: {WORKTREE_PATH}/.loop/
|
||||||
|
```
|
||||||
|
|
||||||
### 5. Present results
|
### 5. Present results
|
||||||
|
|
||||||
After the planner finishes, read `.loop/prd.json` and show the user:
|
After the planner finishes, read `.loop/prd.json` (or `{WORKTREE_PATH}/.loop/prd.json`) and show the user:
|
||||||
|
|
||||||
> **Plan Ready — Review Before Running**
|
> **Plan Ready — Review Before Running**
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user