feat: add distributable skills (paseo, handoff, committee, loop)
Package the four core skills into the repo under skills/ so users can install them via `npx skills add getpaseo/paseo`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
356faa0563
commit
484edb1e1e
@@ -0,0 +1,221 @@
|
||||
---
|
||||
name: loop
|
||||
description: Run a task in a worker→judge loop until acceptance criteria are met. Use when the user says "loop", "loop this", "keep trying until", or wants iterative autonomous execution.
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Loop Skill
|
||||
|
||||
You are setting up an autonomous worker→judge loop. A worker agent implements the plan, then a judge agent independently verifies the plan was implemented to the letter.
|
||||
|
||||
**User's arguments:** $ARGUMENTS
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Load the **Paseo skill** first — it contains the CLI reference for all agent commands.
|
||||
|
||||
## What Is a Loop
|
||||
|
||||
A loop is two roles — **worker** and **judge** — running in alternation:
|
||||
|
||||
1. **Worker** receives the plan and implements it (fresh agent each iteration)
|
||||
2. **Judge** receives the same plan and independently verifies it was implemented correctly, emitting a structured verdict: `{ criteria_met: boolean, reason: string }`
|
||||
3. If `criteria_met` is false, a new worker is launched with the latest judge failure reason (latest only, not full history)
|
||||
4. Loop exits when the judge says `criteria_met: true` or max iterations are reached
|
||||
|
||||
The judge verifies the **entire plan** — every step, every file change, every acceptance criterion. It does NOT suggest fixes or provide guidance. It only reports facts.
|
||||
|
||||
## Live Steering
|
||||
|
||||
Each loop run persists state in:
|
||||
|
||||
```text
|
||||
~/.paseo/loops/<loop-id>/
|
||||
plan.md
|
||||
last_reason.md
|
||||
history.log
|
||||
```
|
||||
|
||||
Behavior:
|
||||
- `plan.md` is read immediately before launching each worker and each judge.
|
||||
- `last_reason.md` stores only the latest judge failure reason (not accumulated history).
|
||||
- `history.log` appends per-iteration operational records.
|
||||
|
||||
Edits to `plan.md` are picked up on the next worker/judge launch without restarting the loop.
|
||||
|
||||
## Parsing Arguments
|
||||
|
||||
Parse `$ARGUMENTS` to determine:
|
||||
|
||||
1. **Plan** — the full implementation plan (context, steps, acceptance criteria, constraints — all in one document)
|
||||
2. **Worker model** — who does the work (default: Codex `gpt-5.4`)
|
||||
3. **Judge model** — who verifies (default: Claude `sonnet`)
|
||||
4. **Name** — a short name for tracking iterations
|
||||
5. **Max iterations** — safety cap (default: 10)
|
||||
6. **Worktree** — whether to run in an isolated git worktree
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
/loop
|
||||
→ Agent derives plan from conversation context
|
||||
|
||||
/loop the provider list bug
|
||||
→ Plan: full context about the bug, steps to fix, acceptance criteria
|
||||
Name: provider-list-fix
|
||||
|
||||
/loop until tests pass
|
||||
→ Plan: make the failing tests pass, acceptance criteria: all tests pass
|
||||
Name: fix-tests
|
||||
|
||||
/loop in a worktree
|
||||
→ Same as above but all agents run in a shared worktree
|
||||
```
|
||||
|
||||
## Using the Script
|
||||
|
||||
The loop is implemented as a bash script at `~/.agents/skills/loop/bin/loop.sh`.
|
||||
|
||||
```bash
|
||||
~/.agents/skills/loop/bin/loop.sh \
|
||||
--plan "The full implementation plan" \
|
||||
--name "short-name" \
|
||||
--max-iterations 10 \
|
||||
--worker-provider codex \
|
||||
--worker-model gpt-5.4 \
|
||||
--judge-provider claude \
|
||||
--judge-model sonnet
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
| Flag | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `--plan` | Yes* | — | Full implementation plan (given to both worker and judge) |
|
||||
| `--plan-file` | Yes* | — | Read the plan from a file |
|
||||
| `--name` | Yes | — | Name prefix for agents (`name-work-N`, `name-verify-N`) |
|
||||
| `--max-iterations` | No | 10 | Safety cap on iterations |
|
||||
| `--worktree` | No | — | Worktree name. Created on first use, reused for all subsequent agents. |
|
||||
| `--worker-provider` | No | `codex` | `codex` or `claude` |
|
||||
| `--worker-model` | No | provider default | Model for worker |
|
||||
| `--judge-provider` | No | `claude` | `codex` or `claude` |
|
||||
| `--judge-model` | No | provider default | Model for judge |
|
||||
| `--thinking` | No | `medium` | Thinking level for worker |
|
||||
|
||||
\* Provide exactly one of `--plan` or `--plan-file`.
|
||||
|
||||
### How the Plan Is Used
|
||||
|
||||
The same plan is given to both the worker and the judge, wrapped in XML tags:
|
||||
|
||||
**Worker receives:**
|
||||
```
|
||||
Implement the following plan exactly as specified.
|
||||
|
||||
<plan>
|
||||
[your plan here]
|
||||
</plan>
|
||||
```
|
||||
|
||||
If a previous iteration failed, the worker also receives:
|
||||
```
|
||||
<previous-verification-failure>
|
||||
[judge's failure reason from last iteration]
|
||||
</previous-verification-failure>
|
||||
```
|
||||
|
||||
**Judge receives:**
|
||||
```
|
||||
Verify that every step of the plan has been implemented to the letter.
|
||||
|
||||
<plan>
|
||||
[your plan here]
|
||||
</plan>
|
||||
```
|
||||
|
||||
### Agent Naming
|
||||
|
||||
Agents are named `{name}-work-{N}` and `{name}-verify-{N}` so the caller can track iterations:
|
||||
|
||||
```
|
||||
fix-bug-work-1 # First worker attempt
|
||||
fix-bug-verify-1 # First judge check
|
||||
fix-bug-work-2 # Second worker attempt (with failure context)
|
||||
fix-bug-verify-2 # Second judge check
|
||||
```
|
||||
|
||||
### Worktree Support
|
||||
|
||||
When `--worktree` is passed, all workers and judges run in the same git worktree. The worktree is created on the first agent launch and reused for all subsequent agents (the `--worktree` flag on `paseo run` is idempotent — if the worktree exists, the agent runs in it without creating a new one).
|
||||
|
||||
No `--base` is needed in the loop script — it automatically uses the current branch as the base.
|
||||
|
||||
```bash
|
||||
~/.agents/skills/loop/bin/loop.sh \
|
||||
--plan-file /tmp/my-plan.md \
|
||||
--name "feature-x" \
|
||||
--worktree "feature-x"
|
||||
```
|
||||
|
||||
## Your Job
|
||||
|
||||
1. **Understand the task** from the conversation context and `$ARGUMENTS`
|
||||
2. **Write the plan** — comprehensive, self-contained (the worker has zero context). The plan should include:
|
||||
- Task description and context
|
||||
- Relevant files and what they do
|
||||
- Implementation steps (ordered, concrete)
|
||||
- Acceptance criteria (factual, verifiable)
|
||||
- Constraints (what NOT to do)
|
||||
3. **Choose models** — default: Codex worker + Claude sonnet judge
|
||||
4. **Choose a name** — short, descriptive
|
||||
5. **Run the script** — call `loop.sh` with all the arguments
|
||||
|
||||
### Steering Guidance
|
||||
|
||||
- Prefer tightening or clarifying the plan when important requirements are missing.
|
||||
- Rework acceptance criteria when they are causing dead loops or are not independently verifiable.
|
||||
- Avoid weakening user-defined criteria unless the user explicitly asks for that change.
|
||||
- Keep the plan self-contained so the loop can succeed without relying on iterative reasoning context.
|
||||
|
||||
### Writing a Good Plan
|
||||
|
||||
The plan is the single source of truth for both the worker and the judge. It must be:
|
||||
|
||||
1. **Self-contained** — The worker starts with zero context. Everything needed is in the plan.
|
||||
2. **Specific** — Name files, functions, types. "Update the handler" is useless. "In `src/handlers/auth.ts`, modify the `handleLogin` function to..." is useful.
|
||||
3. **Ordered** — Steps should be in implementation order.
|
||||
4. **Verifiable** — Every step and criterion must be independently checkable by the judge.
|
||||
|
||||
**Good plan elements:**
|
||||
- "File `src/utils.ts` exports a function named `formatDate`"
|
||||
- "Running `npm test` exits with code 0"
|
||||
- "The component at `src/ProviderList.tsx` renders a list of providers from the API response"
|
||||
- "Typecheck passes (`npm run typecheck`)"
|
||||
- "`npm run typecheck` passes"
|
||||
|
||||
**Bad plan elements:**
|
||||
- "The code is clean" (subjective)
|
||||
- "The implementation is correct" (vague)
|
||||
- "It works" (unverifiable)
|
||||
|
||||
### Skill Stacking
|
||||
|
||||
You can instruct the worker to use other skills in the plan:
|
||||
|
||||
```bash
|
||||
~/.agents/skills/loop/bin/loop.sh \
|
||||
--plan "Use the /committee skill to plan and then fix the provider list bug. The bug is..." \
|
||||
--name "provider-fix"
|
||||
```
|
||||
|
||||
### Composing with Handoff
|
||||
|
||||
A handoff can launch a loop in a worktree:
|
||||
|
||||
```
|
||||
/handoff a loop in a worktree
|
||||
```
|
||||
|
||||
The handoff skill writes a prompt telling the new agent to use `/loop`, and the loop runs inside the worktree.
|
||||
Executable
+255
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: loop.sh (--plan PROMPT | --plan-file PATH) --name NAME [options]
|
||||
|
||||
Required:
|
||||
--plan PROMPT The implementation plan (given to both worker and judge)
|
||||
--plan-file PATH Read the plan from a file
|
||||
--name NAME Name prefix for agents (e.g. "fix-bug" → "fix-bug-work-1", "fix-bug-verify-1")
|
||||
|
||||
Options:
|
||||
--max-iterations N Maximum loop iterations (default: 10)
|
||||
--worktree NAME Run all agents in this worktree (created on first use, reused after)
|
||||
--worker-provider P Worker provider: claude or codex (default: codex)
|
||||
--worker-model M Worker model (default: provider's default)
|
||||
--judge-provider P Judge provider: claude or codex (default: claude)
|
||||
--judge-model M Judge model (default: provider's default)
|
||||
--thinking LEVEL Thinking level for worker (default: medium)
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Defaults
|
||||
max_iterations=10
|
||||
worker_provider="codex"
|
||||
worker_model=""
|
||||
judge_provider="claude"
|
||||
judge_model="sonnet"
|
||||
plan=""
|
||||
plan_file_input=""
|
||||
name=""
|
||||
thinking="medium"
|
||||
worktree=""
|
||||
state_root="${HOME}/.paseo/loops"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--plan) plan="$2"; shift 2 ;;
|
||||
--plan-file) plan_file_input="$2"; shift 2 ;;
|
||||
--name) name="$2"; shift 2 ;;
|
||||
--max-iterations) max_iterations="$2"; shift 2 ;;
|
||||
--worktree) worktree="$2"; shift 2 ;;
|
||||
--worker-provider) worker_provider="$2"; shift 2 ;;
|
||||
--worker-model) worker_model="$2"; shift 2 ;;
|
||||
--judge-provider) judge_provider="$2"; shift 2 ;;
|
||||
--judge-model) judge_model="$2"; shift 2 ;;
|
||||
--thinking) thinking="$2"; shift 2 ;;
|
||||
--help|-h) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_prompt() {
|
||||
local inline_value="$1"
|
||||
local file_value="$2"
|
||||
local label="$3"
|
||||
|
||||
if [[ -n "$inline_value" && -n "$file_value" ]]; then
|
||||
echo "Error: use either --${label} or --${label}-file, not both"
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ -n "$file_value" ]]; then
|
||||
[[ -f "$file_value" ]] || { echo "Error: --${label}-file not found: $file_value"; exit 1; }
|
||||
local file_content
|
||||
file_content="$(cat "$file_value")"
|
||||
[[ -n "$file_content" ]] || { echo "Error: --${label}-file is empty: $file_value"; exit 1; }
|
||||
printf '%s' "$file_content"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "$inline_value" ]]; then
|
||||
printf '%s' "$inline_value"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Error: either --${label} or --${label}-file is required"
|
||||
usage
|
||||
}
|
||||
|
||||
plan="$(load_prompt "$plan" "$plan_file_input" "plan")"
|
||||
[[ -z "$name" ]] && { echo "Error: --name is required"; usage; }
|
||||
|
||||
mkdir -p "$state_root"
|
||||
|
||||
generate_loop_id() {
|
||||
uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-' | cut -c1-6
|
||||
}
|
||||
|
||||
loop_id="$(generate_loop_id)"
|
||||
state_dir="${state_root}/${loop_id}"
|
||||
while [[ -e "$state_dir" ]]; do
|
||||
loop_id="$(generate_loop_id)"
|
||||
state_dir="${state_root}/${loop_id}"
|
||||
done
|
||||
|
||||
mkdir -p "$state_dir"
|
||||
|
||||
plan_file="${state_dir}/plan.md"
|
||||
last_reason_file="${state_dir}/last_reason.md"
|
||||
history_log="${state_dir}/history.log"
|
||||
|
||||
printf '%s\n' "$plan" > "$plan_file"
|
||||
printf '' > "$last_reason_file"
|
||||
printf '' > "$history_log"
|
||||
|
||||
# Build worker flags
|
||||
worker_flags=()
|
||||
if [[ "$worker_provider" == "codex" ]]; then
|
||||
worker_flags+=(--mode full-access --provider codex)
|
||||
elif [[ "$worker_provider" == "claude" ]]; then
|
||||
worker_flags+=(--mode bypassPermissions)
|
||||
fi
|
||||
[[ -n "$worker_model" ]] && worker_flags+=(--model "$worker_model")
|
||||
[[ -n "$thinking" ]] && worker_flags+=(--thinking "$thinking")
|
||||
|
||||
# Build judge flags
|
||||
judge_flags=()
|
||||
if [[ "$judge_provider" == "codex" ]]; then
|
||||
judge_flags+=(--mode full-access --provider codex)
|
||||
elif [[ "$judge_provider" == "claude" ]]; then
|
||||
judge_flags+=(--mode bypassPermissions)
|
||||
fi
|
||||
[[ -n "$judge_model" ]] && judge_flags+=(--model "$judge_model")
|
||||
|
||||
# Worktree flags — passed to every paseo run call
|
||||
worktree_flags=()
|
||||
if [[ -n "$worktree" ]]; then
|
||||
base_branch="$(git branch --show-current 2>/dev/null || echo "main")"
|
||||
worktree_flags+=(--worktree "$worktree" --base "$base_branch")
|
||||
fi
|
||||
|
||||
# Judge output schema
|
||||
judge_schema='{"type":"object","properties":{"criteria_met":{"type":"boolean"},"reason":{"type":"string"}},"required":["criteria_met","reason"],"additionalProperties":false}'
|
||||
|
||||
iteration=0
|
||||
|
||||
echo "=== Loop started: $name ==="
|
||||
echo " Loop ID: $loop_id"
|
||||
echo " State dir: $state_dir"
|
||||
echo " Plan file: $plan_file"
|
||||
echo " Last reason file: $last_reason_file"
|
||||
echo " History log: $history_log"
|
||||
echo " Steering: edits to plan.md are picked up on the next worker/judge launch."
|
||||
echo " Worker: $worker_provider ${worker_model:-(default)}"
|
||||
echo " Judge: $judge_provider ${judge_model:-(default)}"
|
||||
if [[ -n "$worktree" ]]; then
|
||||
echo " Worktree: $worktree (base: $base_branch)"
|
||||
fi
|
||||
echo " Max iterations: $max_iterations"
|
||||
echo ""
|
||||
|
||||
while [[ $iteration -lt $max_iterations ]]; do
|
||||
iteration=$((iteration + 1))
|
||||
echo "--- Iteration $iteration/$max_iterations ---"
|
||||
|
||||
if [[ ! -s "$plan_file" ]]; then
|
||||
echo "Error: plan file is missing or empty: $plan_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_plan="$(cat "$plan_file")"
|
||||
last_reason="$(cat "$last_reason_file")"
|
||||
|
||||
# Build worker prompt with plan in XML tags
|
||||
worker_prompt="Implement the following plan exactly as specified.
|
||||
|
||||
<plan>
|
||||
$current_plan
|
||||
</plan>"
|
||||
|
||||
if [[ -n "$last_reason" ]]; then
|
||||
worker_prompt="$worker_prompt
|
||||
|
||||
<previous-verification-failure>
|
||||
The previous attempt was verified and did NOT pass. Address the following issues before anything else:
|
||||
|
||||
$last_reason
|
||||
</previous-verification-failure>"
|
||||
fi
|
||||
|
||||
# Launch worker
|
||||
worker_name="${name}-work-${iteration}"
|
||||
echo "Launching worker: $worker_name"
|
||||
worker_id=$(paseo run -d "${worker_flags[@]}" "${worktree_flags[@]}" --name "$worker_name" "$worker_prompt" -q)
|
||||
echo "Worker [$worker_name] launched. ID: $worker_id"
|
||||
echo " Stream logs: paseo logs $worker_id -f"
|
||||
echo " Inspect: paseo inspect $worker_id"
|
||||
echo " Wait: paseo wait $worker_id"
|
||||
|
||||
# Wait for worker
|
||||
echo ""
|
||||
echo "Waiting for worker to complete..."
|
||||
paseo wait "$worker_id"
|
||||
echo "Worker done."
|
||||
|
||||
# Build judge prompt with plan in XML tags
|
||||
judge_prompt="You are a fact checker. Your ONLY job is to verify whether the plan below was implemented correctly and completely.
|
||||
|
||||
Inspect the codebase independently. Do NOT fix anything. Do NOT suggest fixes. Do NOT provide guidance.
|
||||
|
||||
<plan>
|
||||
$current_plan
|
||||
</plan>
|
||||
|
||||
<instructions>
|
||||
Verify that every step of the plan has been implemented to the letter. If the plan contains acceptance criteria, check each one individually.
|
||||
|
||||
Report ONLY facts:
|
||||
- criteria_met: true if the ENTIRE plan is implemented correctly, false if ANY part is missing or wrong
|
||||
- reason: for each item in the plan, state whether it was implemented correctly with evidence (file contents, test output, etc.). Be specific about what passed and what failed.
|
||||
</instructions>"
|
||||
|
||||
# Launch judge (blocking, structured output)
|
||||
judge_name="${name}-verify-${iteration}"
|
||||
echo ""
|
||||
echo "Launching judge: $judge_name (synchronous, blocks until verdict)"
|
||||
verdict=$(paseo run "${judge_flags[@]}" "${worktree_flags[@]}" --name "$judge_name" --output-schema "$judge_schema" "$judge_prompt")
|
||||
echo "Verdict: $verdict"
|
||||
|
||||
# Parse verdict
|
||||
criteria_met=$(echo "$verdict" | jq -r '.criteria_met')
|
||||
reason=$(echo "$verdict" | jq -r '.reason')
|
||||
printf '[%s] iteration=%s worker_id=%s judge=%s criteria_met=%s reason=%s\n' \
|
||||
"$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
||||
"$iteration" \
|
||||
"$worker_id" \
|
||||
"$judge_name" \
|
||||
"$criteria_met" \
|
||||
"$(echo "$reason" | tr '\n' ' ')" >> "$history_log"
|
||||
|
||||
if [[ "$criteria_met" == "true" ]]; then
|
||||
echo ""
|
||||
echo "=== Loop complete: criteria met on iteration $iteration ==="
|
||||
echo "Reason: $reason"
|
||||
echo ""
|
||||
echo "Review the final worker's changes:"
|
||||
echo " paseo logs $worker_id"
|
||||
echo " paseo inspect $worker_id"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Criteria not met: $reason"
|
||||
echo "Starting next iteration..."
|
||||
|
||||
# Keep only the latest judge reason for the next worker iteration.
|
||||
printf '%s\n' "$reason" > "$last_reason_file"
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=== Loop exhausted: $max_iterations iterations without meeting criteria ==="
|
||||
exit 1
|
||||
Reference in New Issue
Block a user