feat(skills): rewrite skills to use CLI, reframe orchestrator as chat-first team lead

- paseo: add loop, schedule, chat command reference sections
- paseo-loop: replace loop.sh with paseo loop CLI, document model selection and archive
- paseo-chat: replace chat.sh with paseo chat CLI
- paseo-orchestrator: reframe as team orchestrator using chat rooms as coordination backbone,
  agents as disposable workers, schedule heartbeat for oversight
- paseo-handoff/committee: fix --mode bypass → --mode bypassPermissions
- Delete loop.sh (541 lines) and chat.sh (635 lines) bash scripts
This commit is contained in:
Mohamed Boudra
2026-03-28 01:13:42 +07:00
parent 657b3e1ccd
commit 29876acae5
8 changed files with 369 additions and 2029 deletions
+52 -270
View File
@@ -6,13 +6,7 @@ user-invocable: true
# Paseo Loop Skill
You are setting up `/paseo-loop` as a flexible loop primitive.
Think of it like a `while` loop:
- each iteration sends work to a target
- an optional verifier judges completion
- optional sleep schedules the next iteration
- hard caps stop the loop if it runs too long
You are setting up a loop — an iterative worker/verifier cycle managed by the Paseo daemon.
**User's arguments:** $ARGUMENTS
@@ -20,319 +14,107 @@ Think of it like a `while` loop:
## Prerequisites
Load the **Paseo skill** first. It contains the CLI reference for `paseo run`, `paseo send`, `paseo wait`, and related commands.
Load the **Paseo skill** first. It contains the CLI reference for `paseo loop` and related commands.
## Core Model
Every loop has these parts:
A loop repeats: launch a worker → verify → repeat until done or limits hit.
1. **Target**: who acts each iteration
2. **Prompt**: what the target does each iteration
3. **Verifier**: optional independent judge
4. **Sleep**: optional pause between iterations
5. **Stop conditions**: max iterations and/or max total runtime
1. **Worker prompt**: what the worker does each iteration
2. **Verification**: verifier prompt and/or shell checks that judge success
3. **Sleep**: optional pause between iterations
4. **Stop conditions**: max iterations and/or max total runtime
5. **Model selection**: different providers/models for worker vs verifier
6. **Archive**: optionally preserve agent history after each iteration
### Target
## Verification
There are two target modes:
Every loop needs at least one form of verification:
#### `self`
- `--verify "<prompt>"` — a verifier agent judges the worker's output
- `--verify-check "<command>"` — a shell command that must exit 0 (repeatable)
- Both can be combined: shell checks run first, then the verifier prompt
The loop sends the prompt back to the current agent each iteration. The current agent is identified by `$PASEO_AGENT_ID`.
## Model Selection
Use `self` when:
- the current agent should keep ownership of the task
- the user says "babysit", "watch", "check every X", "poll", or "monitor"
- waking the same agent is cheaper and more natural than starting fresh
Choose the right provider/model for worker and verifier independently:
#### `new-agent`
- `--provider`/`--model` — sets the worker's provider and model
- `--verify-provider`/`--verify-model` — sets the verifier's provider and model
The loop launches a fresh worker agent each iteration.
Default: both use Claude/sonnet. For implementation loops, use Codex for the worker and Claude for the verifier — each catches the other's blind spots.
Use `new-agent` when:
- the user says "create a loop", "spin up a loop", or "launch a codex agent"
- the task benefits from fresh context per iteration
- you want isolated retries, often in a worktree
## Archive
### Verifier
The verifier is orthogonal to the target:
- no verifier: the target decides whether the loop is done
- verifier present: the verifier decides whether the loop is done
If a verifier exists, it is the source of truth for loop completion.
`--archive` preserves worker and verifier agents after each iteration instead of destroying them. Use this when you need to inspect conversation history for debugging.
## Defaults by User Intent
Infer defaults from the user's phrasing:
### Babysit / watch / check every X
Default to:
- `target=self`
- `sleep=<explicit value or sensible default>`
- no verifier unless the user asks for independent verification
- `max-time=1h` if the user gives no bound and the task could run indefinitely
### Ensure X is done
Default to:
- `target=self`
- verifier enabled
- no sleep unless the task is waiting on an external system
Reason: by default, do not trust the same agent to judge its own completion when the user is asking for assurance.
### Create a loop / launch a loop / loop a codex agent
Default to:
- `target=new-agent`
- verifier enabled when success criteria need independent judgment
- worktree enabled when code changes are involved
## Stop Conditions
Support both:
- `--max-iterations N`
- `--max-time DURATION`
Use at least one bound for open-ended or polling loops when the user does not specify one.
## Feedback Between Iterations
Each iteration should receive the previous result as `<previous-iteration-result>`.
This applies in all cases:
- if there is a verifier, feed back the verifier's `reason`
- otherwise feed back the target's `reason`
## Live Steering
Each loop persists state in:
```text
~/.paseo/loops/<loop-id>/
target-prompt.md # prompt sent to self or worker (live-editable)
verifier-prompt.md # verifier prompt (live-editable, optional)
last_reason.md # latest reason used for feedback
history.log # per-iteration records
```
Edits to prompt files are picked up on the next iteration without restarting the loop.
## Script Interface
The loop is implemented at:
```bash
skills/paseo-loop/bin/loop.sh
```
### Flags
| Flag | Required | Default | Description |
|---|---|---|---|
| `--target self|new-agent` | No | inferred / `new-agent` in raw script | Who acts each iteration |
| `--target-prompt` | Yes* | — | Prompt given to the target each iteration |
| `--target-prompt-file` | Yes* | — | Read the target prompt from a file |
| `--worker` | No | `codex` | Worker agent for `new-agent` loops |
| `--agent-id` | No | `$PASEO_AGENT_ID` | Existing agent id for `self` loops |
| `--verifier-prompt` | No* | — | Prompt for an independent verifier |
| `--verifier-prompt-file` | No* | — | Read verifier prompt from a file |
| `--verifier` | No | `claude/sonnet` | Verifier agent |
| `--name` | Yes | — | Name prefix for loop tracking |
| `--sleep` | No | — | Delay between iterations |
| `--max-iterations` | No | unlimited | Hard cap on iteration count |
| `--max-time` | No | unlimited | Hard cap on total wall-clock runtime |
| `--archive` | No | off | Archive newly created agents after iteration |
| `--worktree` | No | — | Worktree name for `new-agent` loops |
| `--thinking` | No | `medium` | Thinking level for worker |
\* Provide exactly one of `--target-prompt` or `--target-prompt-file`. Provide at most one of `--verifier-prompt` or `--verifier-prompt-file`.
## Behavior Rules
### `target=self`, no verifier
The current agent must return structured JSON:
```json
{ "done": true, "reason": "..." }
```
Use this for:
- babysitting a PR
- scheduled status checks
- monitoring a deployment
- repeating an objective task the current agent can judge itself
### `target=self`, verifier enabled
The current agent does the work. A separate verifier decides whether the loop is done.
Use this for:
- "ensure X is done"
- "work until the tests are passing"
- cases where the user wants independent judgment but keeping the same agent is still the right execution model
### `target=new-agent`, no verifier
A fresh worker launches each iteration and must return structured JSON itself.
Use this when:
- the task is naturally self-judging
- you want fresh context each retry
### `target=new-agent`, verifier enabled
A fresh worker launches each iteration. After it finishes, a separate verifier judges completion.
Use this for:
- implementation loops
- fix-and-verify cycles
- loops the user explicitly asks you to create
## Examples
### Babysit the PR
Interpretation:
- `target=self`
- `sleep=2m`
- no verifier unless requested
- reasonable `max-time` if none given
Example:
```bash
skills/paseo-loop/bin/loop.sh \
--target self \
--target-prompt "Check PR #42. Review CI, review comments, and branch status. Fix issues as they arise. Return JSON with done=true only when the PR is fully green and ready." \
paseo loop run "Check PR #42. Review CI, comments, and branch status. Fix issues as they arise." \
--verify-check "gh pr checks 42 --fail-fast" \
--sleep 2m \
--max-time 1h \
--name babysit-pr-42
```
### Launch a Codex agent to babysit the PR
Interpretation:
- `target=new-agent`
- `worker=codex`
- `sleep=2m`
- usually archive
### Keep trying until tests pass
```bash
skills/paseo-loop/bin/loop.sh \
--target new-agent \
--worker codex \
--target-prompt "Check PR #42. Review CI, review comments, and branch status. Fix issues as they arise. Return JSON with done=true only when the PR is fully green and ready." \
--sleep 2m \
--max-time 1h \
--archive \
--name babysit-pr-42
```
### Work until the tests are passing
Interpretation:
- `target=self`
- verifier enabled
- no sleep
```bash
skills/paseo-loop/bin/loop.sh \
--target self \
--target-prompt "Run the test suite, investigate failures, and fix the code. Stop after you have made a coherent attempt for this iteration." \
--verifier-prompt "Run the relevant test suite. Return done=true only if all tests pass. Reason must cite the exact command and outcome." \
paseo loop run "Run the test suite, investigate failures, and fix the code." \
--provider codex \
--verify "Run the test suite. Return done=true only if all tests pass. Cite the exact command and outcome." \
--verify-check "npm test" \
--max-iterations 10 \
--name fix-tests
```
### Create a loop to fix the tests
Interpretation:
- `target=new-agent`
- verifier enabled
- often use a worktree
### Implementation loop with cross-provider review
```bash
skills/paseo-loop/bin/loop.sh \
--target new-agent \
--worker codex \
--target-prompt "Run the test suite, investigate failures, fix the code, and leave the repo in a clean verifiable state for this iteration." \
--verifier-prompt "Run the relevant test suite. Return done=true only if all tests pass. Reason must cite the exact command and outcome." \
--worktree fix-tests \
--max-iterations 10 \
--name fix-tests
```
### Loop a Codex agent to complete issue 456 in a worktree
Interpretation:
- `target=new-agent`
- worker codex
- verifier enabled
- worktree enabled
```bash
skills/paseo-loop/bin/loop.sh \
--target new-agent \
--worker codex \
--target-prompt "Implement issue #456 in this repo. Use the issue description and surrounding code as context. Make incremental progress each iteration and leave clear evidence of what changed." \
--verifier-prompt "Verify issue #456 is complete. Check changed files, run typecheck and relevant tests, and return done=true only if the implementation meets the issue requirements with evidence." \
--worktree issue-456 \
paseo loop run "Implement issue #456. Make incremental progress each iteration." \
--provider codex \
--verify "Verify issue #456 is complete. Check changed files, run typecheck and tests." \
--verify-provider claude --verify-model sonnet \
--max-iterations 8 \
--max-time 2h \
--archive \
--name issue-456
```
## Managing Loops
```bash
paseo loop ls # List all loops
paseo loop inspect <id> # Show details and iteration history
paseo loop logs <id> # Stream logs
paseo loop stop <id> # Stop a running loop
```
## Your Job
1. Understand the user's intent from the conversation and `$ARGUMENTS`
2. Decide `target=self` or `target=new-agent`
3. Decide whether a verifier is needed
4. Write the target prompt so it is self-contained for the selected target
5. Write the verifier prompt, if used, so it is factual and evidence-based
6. Choose sleep only when the task is naturally scheduled or polling
7. Add sensible stop conditions
8. Choose worker / verifier agents
9. Choose a short name
10. Run `skills/paseo-loop/bin/loop.sh` with the final arguments
2. Decide the worker prompt — self-contained, concrete about what to do
3. Decide verification — shell checks for objective criteria, verifier prompt for judgment
4. Choose providers/models for worker and verifier
5. Choose sleep only when the task is polling or waiting on an external system
6. Add sensible stop conditions
7. Run `paseo loop run` with the final arguments
## Prompt Writing Rules
### Target prompt
### Worker prompt
The target prompt must be:
The worker prompt must be:
- self-contained
- concrete about commands, files, branches, tests, PRs, or systems to inspect
- explicit about what counts as progress this iteration
If there is no verifier, the target prompt must instruct the target to end with strict JSON matching:
```json
{ "done": boolean, "reason": "string" }
```
### Verifier prompt
The verifier prompt should:
- check facts, not offer fixes
- cite commands, outputs, or file evidence
- return strict JSON matching:
```json
{ "done": boolean, "reason": "string" }
```
## Skill Stacking
The target prompt can instruct the worker to use other skills:
```bash
skills/paseo-loop/bin/loop.sh \
--target new-agent \
--target-prompt "Use /committee first if you are stuck, then fix the provider list bug." \
--verifier-prompt "Verify the provider list renders correctly and typecheck passes." \
--name provider-fix
```
- be specific about what "done" means
-540
View File
@@ -1,540 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: loop.sh --name NAME --target-prompt TEXT [options]
Target (required):
--target self|new-agent Who acts each iteration. Default: new-agent
--target-prompt TEXT Prompt given to the target each iteration
--target-prompt-file PATH Read the target prompt from a file
Target options:
--agent-id ID Existing agent id for --target self
Default: $PASEO_AGENT_ID
--worker PROVIDER/MODEL Worker agent for --target new-agent
Default: codex
Verifier (optional):
--verifier-prompt TEXT Verification prompt evaluated after each iteration
--verifier-prompt-file PATH Read the verification prompt from a file
--verifier PROVIDER/MODEL Verifier agent. Default: claude/sonnet
Limits:
--sleep DURATION Sleep between iterations (e.g. 30s, 5m, 1h)
--max-iterations N Maximum loop iterations (default: unlimited)
--max-time DURATION Maximum total runtime (e.g. 30m, 2h)
Other options:
--name NAME Name prefix for loop tracking (required)
--archive Archive newly created agents after each iteration
--worktree NAME Run new agents in this worktree
--thinking LEVEL Thinking level for new-agent worker (default: medium)
EOF
exit 1
}
parse_agent_spec() {
local spec="$1"
local default_provider="$2"
local default_model="$3"
if [[ -z "$spec" ]]; then
echo "$default_provider" "$default_model"
return
fi
if [[ "$spec" == */* ]]; then
echo "${spec%%/*}" "${spec#*/}"
else
echo "$spec" ""
fi
}
parse_duration_to_seconds() {
local raw="$1"
if [[ -z "$raw" ]]; then
echo "Error: duration cannot be empty" >&2
exit 1
fi
if [[ "$raw" =~ ^[0-9]+$ ]]; then
echo "$raw"
return
fi
if [[ "$raw" =~ ^([0-9]+)(s|m|h|d)$ ]]; then
local value="${BASH_REMATCH[1]}"
local unit="${BASH_REMATCH[2]}"
case "$unit" in
s) echo "$value" ;;
m) echo $((value * 60)) ;;
h) echo $((value * 3600)) ;;
d) echo $((value * 86400)) ;;
*)
echo "Error: unsupported duration unit: $unit" >&2
exit 1
;;
esac
return
fi
echo "Error: invalid duration: $raw (use Ns, Nm, Nh, Nd, or seconds)" >&2
exit 1
}
load_prompt() {
local inline_value="$1"
local file_value="$2"
local label="$3"
local required="$4"
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
if [[ "$required" == "true" ]]; then
echo "Error: either --${label} or --${label}-file is required"
usage
fi
return 1
}
build_target_prompt() {
local prompt="$1"
local prior_reason="$2"
local require_structured_output="$3"
local full_prompt="$prompt"
if [[ -n "$prior_reason" ]]; then
full_prompt="$full_prompt
<previous-iteration-result>
The previous iteration reported the following:
$prior_reason
</previous-iteration-result>"
fi
if [[ "$require_structured_output" == "true" ]]; then
full_prompt="$full_prompt
End your response with strict JSON matching:
{ \"done\": true/false, \"reason\": \"...\" }
Rules:
- Return only valid JSON
- done=true only if the loop's goal is complete
- done=false if more work is needed
- reason must briefly explain the current state"
fi
printf '%s' "$full_prompt"
}
extract_last_assistant_message() {
local agent_id="$1"
PASEO_LOOP_REPO_ROOT="$repo_root" TARGET_AGENT_ID="$agent_id" npx tsx --eval '
import { pathToFileURL } from "node:url";
const repoRoot = process.env.PASEO_LOOP_REPO_ROOT;
const agentId = process.env.TARGET_AGENT_ID;
if (!repoRoot || !agentId) {
throw new Error("Missing repo root or agent id");
}
const { connectToDaemon } = await import(
pathToFileURL(`${repoRoot}/packages/cli/src/utils/client.ts`).href
);
const { resolveStructuredResponseMessage } = await import(
pathToFileURL(`${repoRoot}/packages/cli/src/commands/agent/run.ts`).href
);
const client = await connectToDaemon({});
try {
const message = await resolveStructuredResponseMessage({
client,
agentId,
lastMessage: null,
});
if (message) {
process.stdout.write(message);
}
} finally {
await client.close();
}
'
}
parse_done_reason() {
local raw="$1"
local context_label="$2"
if ! echo "$raw" | jq -e '.done | type == "boolean"' >/dev/null 2>&1; then
echo "Error: ${context_label} response did not include boolean .done" >&2
echo "$raw" >&2
exit 1
fi
if ! echo "$raw" | jq -e '.reason | type == "string"' >/dev/null 2>&1; then
echo "Error: ${context_label} response did not include string .reason" >&2
echo "$raw" >&2
exit 1
fi
}
ensure_time_remaining() {
if [[ "$max_time_seconds" -le 0 ]]; then
return
fi
local now
now="$(date +%s)"
local elapsed=$((now - start_epoch))
if [[ "$elapsed" -ge "$max_time_seconds" ]]; then
echo "=== Loop exhausted: max time reached (${max_time_raw}) ==="
exit 1
fi
}
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/../../.." && pwd)"
target="new-agent"
max_iterations=0
max_time_raw=""
max_time_seconds=0
archive=false
worker_spec=""
verifier_spec=""
target_prompt_input=""
target_prompt_file_input=""
verifier_prompt_input=""
verifier_prompt_file_input=""
name=""
thinking="medium"
worktree=""
sleep_raw=""
sleep_seconds=0
agent_id="${PASEO_AGENT_ID:-}"
state_root="${HOME}/.paseo/loops"
while [[ $# -gt 0 ]]; do
case "$1" in
--target) target="$2"; shift 2 ;;
--target-prompt) target_prompt_input="$2"; shift 2 ;;
--target-prompt-file) target_prompt_file_input="$2"; shift 2 ;;
--agent-id) agent_id="$2"; shift 2 ;;
--worker) worker_spec="$2"; shift 2 ;;
--verifier-prompt) verifier_prompt_input="$2"; shift 2 ;;
--verifier-prompt-file) verifier_prompt_file_input="$2"; shift 2 ;;
--verifier) verifier_spec="$2"; shift 2 ;;
--name) name="$2"; shift 2 ;;
--max-iterations) max_iterations="$2"; shift 2 ;;
--max-time) max_time_raw="$2"; shift 2 ;;
--archive) archive=true; shift ;;
--sleep) sleep_raw="$2"; shift 2 ;;
--worktree) worktree="$2"; shift 2 ;;
--thinking) thinking="$2"; shift 2 ;;
--help|-h) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
case "$target" in
self|new-agent) ;;
*)
echo "Error: --target must be 'self' or 'new-agent'"
usage
;;
esac
target_prompt="$(load_prompt "$target_prompt_input" "$target_prompt_file_input" "target-prompt" "true")"
[[ -z "$name" ]] && { echo "Error: --name is required"; usage; }
if [[ "$target" == "self" && -z "$agent_id" ]]; then
echo "Error: --target self requires --agent-id or \$PASEO_AGENT_ID"
exit 1
fi
if [[ -n "$sleep_raw" ]]; then
sleep_seconds="$(parse_duration_to_seconds "$sleep_raw")"
fi
if [[ -n "$max_time_raw" ]]; then
max_time_seconds="$(parse_duration_to_seconds "$max_time_raw")"
fi
has_verifier=false
verifier_prompt_text=""
if [[ -n "$verifier_prompt_input" || -n "$verifier_prompt_file_input" ]]; then
verifier_prompt_text="$(load_prompt "$verifier_prompt_input" "$verifier_prompt_file_input" "verifier-prompt" "true")"
has_verifier=true
fi
read -r worker_provider worker_model <<< "$(parse_agent_spec "$worker_spec" "codex" "")"
read -r verifier_provider verifier_model <<< "$(parse_agent_spec "$verifier_spec" "claude" "sonnet")"
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"
target_prompt_file="${state_dir}/target-prompt.md"
last_reason_file="${state_dir}/last_reason.md"
history_log="${state_dir}/history.log"
printf '%s\n' "$target_prompt" > "$target_prompt_file"
printf '' > "$last_reason_file"
printf '' > "$history_log"
if [[ "$has_verifier" == true ]]; then
verifier_prompt_file="${state_dir}/verifier-prompt.md"
printf '%s\n' "$verifier_prompt_text" > "$verifier_prompt_file"
fi
worker_flags=()
if [[ "$worker_provider" == "codex" ]]; then
worker_flags+=(--mode full-access --provider codex)
elif [[ "$worker_provider" == "claude" ]]; then
worker_flags+=(--mode bypassPermissions --provider claude)
fi
[[ -n "$worker_model" ]] && worker_flags+=(--model "$worker_model")
[[ -n "$thinking" ]] && worker_flags+=(--thinking "$thinking")
verifier_flags=()
if [[ "$verifier_provider" == "codex" ]]; then
verifier_flags+=(--mode full-access --provider codex)
elif [[ "$verifier_provider" == "claude" ]]; then
verifier_flags+=(--mode bypassPermissions --provider claude)
fi
[[ -n "$verifier_model" ]] && verifier_flags+=(--model "$verifier_model")
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
done_schema='{"type":"object","properties":{"done":{"type":"boolean"},"reason":{"type":"string"}},"required":["done","reason"],"additionalProperties":false}'
start_epoch="$(date +%s)"
iteration=0
echo "=== Loop started: $name ==="
echo " Loop ID: $loop_id"
echo " State dir: $state_dir"
echo " Target: $target"
if [[ "$target" == "self" ]]; then
echo " Agent ID: $agent_id"
else
echo " Worker: $worker_provider/${worker_model:-(default)}"
fi
echo " Target prompt: $target_prompt_file (live-editable)"
if [[ "$has_verifier" == true ]]; then
echo " Verifier prompt: $verifier_prompt_file (live-editable)"
echo " Verifier: $verifier_provider/${verifier_model:-(default)}"
else
echo " Verifier: none"
fi
echo " Last reason file: $last_reason_file"
echo " History log: $history_log"
if [[ -n "$sleep_raw" ]]; then
echo " Sleep: $sleep_raw between iterations"
fi
if [[ -n "$max_time_raw" ]]; then
echo " Max time: $max_time_raw"
else
echo " Max time: unlimited"
fi
if [[ "$archive" == true ]]; then
echo " Archive: newly created agents archived after each iteration"
fi
if [[ -n "$worktree" ]]; then
echo " Worktree: $worktree (base: $base_branch)"
fi
if [[ "$max_iterations" -gt 0 ]]; then
echo " Max iterations: $max_iterations"
else
echo " Max iterations: unlimited"
fi
echo ""
while [[ "$max_iterations" -eq 0 || "$iteration" -lt "$max_iterations" ]]; do
ensure_time_remaining
iteration=$((iteration + 1))
if [[ "$max_iterations" -gt 0 ]]; then
echo "--- Iteration $iteration/$max_iterations ---"
else
echo "--- Iteration $iteration ---"
fi
if [[ ! -s "$target_prompt_file" ]]; then
echo "Error: target prompt file is missing or empty: $target_prompt_file"
exit 1
fi
current_target_prompt="$(cat "$target_prompt_file")"
last_reason="$(cat "$last_reason_file" 2>/dev/null || true)"
target_needs_structured_output=false
if [[ "$has_verifier" == false ]]; then
target_needs_structured_output=true
fi
full_target_prompt="$(build_target_prompt "$current_target_prompt" "$last_reason" "$target_needs_structured_output")"
if [[ "$target" == "new-agent" ]]; then
worker_name="${name}-${iteration}"
if [[ "$has_verifier" == true ]]; then
echo "Launching worker: $worker_name"
worker_id=$(paseo run -d "${worker_flags[@]}" "${worktree_flags[@]}" --name "$worker_name" "$full_target_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 ""
echo "Waiting for worker to complete..."
paseo wait "$worker_id"
echo "Worker done."
if [[ "$archive" == true ]]; then
paseo agent archive "$worker_name" 2>/dev/null || true
fi
else
echo "Launching worker: $worker_name"
verdict=$(paseo run "${worker_flags[@]}" "${worktree_flags[@]}" --name "$worker_name" --output-schema "$done_schema" "$full_target_prompt")
echo "Result: $verdict"
parse_done_reason "$verdict" "worker"
done_value=$(echo "$verdict" | jq -r '.done')
reason=$(echo "$verdict" | jq -r '.reason')
if [[ "$archive" == true ]]; then
paseo agent archive "$worker_name" 2>/dev/null || true
fi
printf '[%s] iteration=%s target=%s target_agent=%s done=%s reason=%s\n' \
"$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
"$iteration" \
"$target" \
"$worker_name" \
"$done_value" \
"$(echo "$reason" | tr '\n' ' ')" >> "$history_log"
fi
else
iteration_label="self:${agent_id}"
iteration_prompt_file="$(mktemp)"
trap 'rm -f "$iteration_prompt_file"' EXIT
printf '%s\n' "$full_target_prompt" > "$iteration_prompt_file"
echo "Sending iteration prompt to existing agent: $agent_id"
paseo send "$agent_id" --prompt-file "$iteration_prompt_file" >/dev/null
rm -f "$iteration_prompt_file"
trap - EXIT
if [[ "$has_verifier" == false ]]; then
verdict="$(extract_last_assistant_message "$agent_id")"
echo "Result: $verdict"
parse_done_reason "$verdict" "self target"
done_value=$(echo "$verdict" | jq -r '.done')
reason=$(echo "$verdict" | jq -r '.reason')
printf '[%s] iteration=%s target=%s target_agent=%s done=%s reason=%s\n' \
"$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
"$iteration" \
"$target" \
"$iteration_label" \
"$done_value" \
"$(echo "$reason" | tr '\n' ' ')" >> "$history_log"
fi
fi
if [[ "$has_verifier" == true ]]; then
current_verifier_prompt="$(cat "$verifier_prompt_file")"
verifier_name="${name}-verify-${iteration}"
full_verifier_prompt="$current_verifier_prompt
Respond with strict JSON matching:
{ \"done\": true/false, \"reason\": \"...\" }
Rules:
- done=true only if the loop goal has been met
- done=false if another iteration is needed
- reason must explain what you found with evidence"
echo ""
echo "Launching verifier: $verifier_name"
verdict=$(paseo run "${verifier_flags[@]}" "${worktree_flags[@]}" --name "$verifier_name" --output-schema "$done_schema" "$full_verifier_prompt")
echo "Verdict: $verdict"
parse_done_reason "$verdict" "verifier"
done_value=$(echo "$verdict" | jq -r '.done')
reason=$(echo "$verdict" | jq -r '.reason')
if [[ "$archive" == true ]]; then
paseo agent archive "$verifier_name" 2>/dev/null || true
fi
if [[ "$target" == "new-agent" ]]; then
target_label="$worker_name"
else
target_label="self:${agent_id}"
fi
printf '[%s] iteration=%s target=%s target_agent=%s verifier=%s done=%s reason=%s\n' \
"$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
"$iteration" \
"$target" \
"$target_label" \
"$verifier_name" \
"$done_value" \
"$(echo "$reason" | tr '\n' ' ')" >> "$history_log"
fi
if [[ "$done_value" == "true" ]]; then
echo ""
echo "=== Loop complete: done on iteration $iteration ==="
echo "Reason: $reason"
exit 0
fi
echo "Not done: $reason"
printf '%s\n' "$reason" > "$last_reason_file"
ensure_time_remaining
if [[ "$sleep_seconds" -gt 0 ]] && [[ "$max_iterations" -eq 0 || "$iteration" -lt "$max_iterations" ]]; then
echo "Sleeping $sleep_raw before next iteration..."
sleep "$sleep_seconds"
fi
echo ""
done
echo "=== Loop exhausted: $max_iterations iterations without completing ==="
exit 1