mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
refactor(paseo-loop): support self and new-agent targets with max-time
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
---
|
||||
name: paseo-loop
|
||||
description: Run a task in a loop until an exit condition is met. Use when the user says "loop", "loop this", "keep trying until", "babysit", "poll", or wants iterative autonomous execution.
|
||||
description: Run an agent loop until an exit condition is met. Use when the user says "loop", "babysit", "keep trying until", "check every X", "watch", or wants iterative autonomous execution.
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Loop Skill
|
||||
# Paseo Loop Skill
|
||||
|
||||
You are setting up an autonomous loop. An agent runs repeatedly until an exit condition is met.
|
||||
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
|
||||
|
||||
**User's arguments:** $ARGUMENTS
|
||||
|
||||
@@ -14,235 +20,319 @@ You are setting up an autonomous loop. An agent runs repeatedly until an exit co
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Load the **Paseo skill** first — it contains the CLI reference for all agent commands.
|
||||
Load the **Paseo skill** first. It contains the CLI reference for `paseo run`, `paseo send`, `paseo wait`, and related commands.
|
||||
|
||||
## What Is a Loop
|
||||
## Core Model
|
||||
|
||||
A loop runs an agent repeatedly until it's done. There are two modes:
|
||||
Every loop has these parts:
|
||||
|
||||
### Self-terminating (no verifier)
|
||||
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
|
||||
|
||||
A single worker agent runs each iteration. It returns `{ done: boolean, reason: string }` via structured output. The loop exits when `done` is true.
|
||||
### Target
|
||||
|
||||
### Worker + Verifier
|
||||
There are two target modes:
|
||||
|
||||
A worker agent runs each iteration (detached, no structured output). After the worker finishes, a separate verifier agent evaluates the verification prompt and returns `{ done: boolean, reason: string }`. The loop exits when `done` is true.
|
||||
#### `self`
|
||||
|
||||
### Feedback between iterations
|
||||
The loop sends the prompt back to the current agent each iteration. The current agent is identified by `$PASEO_AGENT_ID`.
|
||||
|
||||
In both modes, the `reason` from the previous iteration is fed back to the next worker as `<previous-iteration-result>`. This gives the worker context about what happened last time.
|
||||
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
|
||||
|
||||
#### `new-agent`
|
||||
|
||||
The loop launches a fresh worker agent each iteration.
|
||||
|
||||
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
|
||||
|
||||
### 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.
|
||||
|
||||
## 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 run persists state in:
|
||||
Each loop persists state in:
|
||||
|
||||
```text
|
||||
~/.paseo/loops/<loop-id>/
|
||||
worker-prompt.md # worker prompt (live-editable)
|
||||
verifier-prompt.md # verifier prompt (live-editable, only when verifier is used)
|
||||
last_reason.md # latest iteration result
|
||||
history.log # per-iteration records
|
||||
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.
|
||||
|
||||
## Parsing Arguments
|
||||
## Script Interface
|
||||
|
||||
Parse `$ARGUMENTS` to determine:
|
||||
|
||||
1. **Worker prompt** — what the worker does each iteration
|
||||
2. **Verifier prompt** (optional) — what the verifier checks after each worker iteration
|
||||
3. **Worker** — which agent does the work (default: Codex)
|
||||
4. **Verifier** — which agent verifies (default: Claude sonnet)
|
||||
5. **Sleep** (optional) — delay between iterations
|
||||
6. **Name** — a short name for tracking
|
||||
7. **Max iterations** — safety cap (default: unlimited)
|
||||
8. **Archive** — whether to archive agents after each iteration
|
||||
9. **Worktree** — whether to run in an isolated git worktree
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
/loop babysit PR #42 until CI is green, check every 5 minutes
|
||||
→ worker-prompt: "Check the CI status of PR #42 using `gh pr checks 42`. Report done when ALL checks
|
||||
have passed (no pending, no failures). If any checks are still running or have failed, report not
|
||||
done and list which checks are pending or failing."
|
||||
No verifier (self-terminating: the worker inspects CI and reports done when green)
|
||||
sleep: 5m
|
||||
archive: yes
|
||||
name: babysit-pr-42
|
||||
|
||||
/loop implement the auth refactor from the plan in /tmp/plan.md
|
||||
→ worker-prompt-file: /tmp/plan.md
|
||||
verifier-prompt: "Verify every step of the plan was implemented. Check file changes, types, and
|
||||
run `npm run typecheck` and `npm test`. Report each criterion individually with evidence."
|
||||
name: auth-refactor
|
||||
worktree: auth-refactor
|
||||
|
||||
/loop run the test suite until it passes
|
||||
→ worker-prompt: "Run `npm test`. If any tests fail, read the failure output, investigate the root
|
||||
cause in the source code, and fix it. Report done when `npm test` exits with code 0 and all
|
||||
tests pass. Report not done if any test still fails, and explain which tests failed and why."
|
||||
No verifier (self-terminating: worker reports done when tests pass)
|
||||
name: fix-tests
|
||||
|
||||
/loop watch error rates after deploy, check every 10 minutes
|
||||
→ worker-prompt: "Check the error rate for the canary deployment by running `./scripts/check-canary.sh`.
|
||||
Report done when the error rate has been below 0.1% for at least 2 consecutive checks. Report not
|
||||
done with the current error rate and trend."
|
||||
No verifier (self-terminating: worker reports done when error rate is stable)
|
||||
sleep: 10m
|
||||
max-iterations: 30
|
||||
archive: yes
|
||||
name: canary-watch
|
||||
```
|
||||
|
||||
## Using the Script
|
||||
|
||||
The loop is implemented as a bash script at `~/.claude/skills/paseo-loop/bin/loop.sh`.
|
||||
The loop is implemented at:
|
||||
|
||||
```bash
|
||||
# Self-terminating: worker checks PR CI and reports done when all checks pass
|
||||
~/.claude/skills/paseo-loop/bin/loop.sh \
|
||||
--worker-prompt "Check CI status of PR #42 using gh pr checks 42. Report done when ALL checks pass. Report not done with a list of pending/failing checks." \
|
||||
--name "babysit-pr" \
|
||||
--sleep 5m \
|
||||
--archive
|
||||
|
||||
# Worker + verifier: worker implements, verifier independently checks
|
||||
~/.claude/skills/paseo-loop/bin/loop.sh \
|
||||
--worker-prompt "Implement the auth refactor: ..." \
|
||||
--verifier-prompt "Verify the auth refactor is complete. Run npm run typecheck and npm test. Check that all file changes match the plan. Report each criterion with evidence." \
|
||||
--name "auth-refactor" \
|
||||
--worktree "auth-refactor"
|
||||
skills/paseo-loop/bin/loop.sh
|
||||
```
|
||||
|
||||
### Arguments
|
||||
### Flags
|
||||
|
||||
| Flag | Required | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `--worker-prompt` | Yes* | — | Prompt given to the worker each iteration |
|
||||
| `--worker-prompt-file` | Yes* | — | Read the worker prompt from a file |
|
||||
| `--worker` | No | `codex` | Worker agent (`provider/model`, e.g. `codex/gpt-5.4`, `claude/sonnet`) |
|
||||
| `--verifier-prompt` | No* | — | Verification prompt for a separate verifier agent |
|
||||
| `--verifier-prompt-file` | No* | — | Read the verifier prompt from a file |
|
||||
| `--verifier` | No | `claude/sonnet` | Verifier agent (`provider/model`) |
|
||||
| `--name` | Yes | — | Name prefix for agents |
|
||||
| `--sleep` | No | — | Delay between iterations (e.g. `30s`, `5m`, `1h`) |
|
||||
| `--max-iterations` | No | unlimited | Safety cap on iterations |
|
||||
| `--archive` | No | off | Archive agents after each iteration |
|
||||
| `--worktree` | No | — | Worktree name. Created on first use, reused after. |
|
||||
| `--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 `--worker-prompt` or `--worker-prompt-file`. Provide at most one of `--verifier-prompt` or `--verifier-prompt-file`.
|
||||
\* Provide exactly one of `--target-prompt` or `--target-prompt-file`. Provide at most one of `--verifier-prompt` or `--verifier-prompt-file`.
|
||||
|
||||
### Behavior by parameters
|
||||
## Behavior Rules
|
||||
|
||||
| Parameters | Mode | Use case |
|
||||
|---|---|---|
|
||||
| `--worker-prompt` only | Self-terminating worker | Worker does work and decides when done |
|
||||
| `--worker-prompt` + `--verifier-prompt` | Worker + verifier | Worker implements, verifier independently checks |
|
||||
| `--worker-prompt` + `--sleep` | Polling with self-termination | Periodic check until a condition is met |
|
||||
| `--worker-prompt` + `--verifier-prompt` + `--sleep` | Periodic work + verifier | Periodic work with independent verification |
|
||||
### `target=self`, no verifier
|
||||
|
||||
### Agent Naming
|
||||
The current agent must return structured JSON:
|
||||
|
||||
Without verifier: agents are named `{name}-{N}`:
|
||||
```
|
||||
babysit-1 # First iteration
|
||||
babysit-2 # Second iteration
|
||||
```json
|
||||
{ "done": true, "reason": "..." }
|
||||
```
|
||||
|
||||
With verifier: workers are `{name}-{N}`, verifiers are `{name}-verify-{N}`:
|
||||
```
|
||||
feat-1 # First worker
|
||||
feat-verify-1 # First verifier
|
||||
feat-2 # Second worker (with previous result context)
|
||||
feat-verify-2 # Second verifier
|
||||
```
|
||||
Use this for:
|
||||
- babysitting a PR
|
||||
- scheduled status checks
|
||||
- monitoring a deployment
|
||||
- repeating an objective task the current agent can judge itself
|
||||
|
||||
### Worktree Support
|
||||
### `target=self`, verifier enabled
|
||||
|
||||
When `--worktree` is passed, all agents run in the same git worktree. The worktree is created on first launch and reused for all subsequent agents.
|
||||
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
|
||||
~/.claude/skills/paseo-loop/bin/loop.sh \
|
||||
--worker-prompt "Implement the feature..." \
|
||||
--verifier-prompt "Verify the feature works and typecheck passes" \
|
||||
--name "feature-x" \
|
||||
--worktree "feature-x"
|
||||
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." \
|
||||
--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
|
||||
|
||||
```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." \
|
||||
--max-iterations 10 \
|
||||
--name fix-tests
|
||||
```
|
||||
|
||||
### Create a loop to fix the tests
|
||||
|
||||
Interpretation:
|
||||
- `target=new-agent`
|
||||
- verifier enabled
|
||||
- often use a worktree
|
||||
|
||||
```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 \
|
||||
--max-iterations 8 \
|
||||
--max-time 2h \
|
||||
--name issue-456
|
||||
```
|
||||
|
||||
## Your Job
|
||||
|
||||
1. **Understand the task** from the conversation context and `$ARGUMENTS`
|
||||
2. **Decide the mode** — does this need a separate verifier, or can the worker self-terminate?
|
||||
3. **Write the worker prompt** — what the worker does each iteration. Must be self-contained (the agent has zero prior context).
|
||||
4. **Write the verifier prompt** (if needed) — what the verifier checks. Should be factual and verifiable.
|
||||
5. **Choose sleep** — if the task is polling/monitoring, add a sleep duration
|
||||
6. **Choose archive** — use `--archive` for long-running or polling loops to keep the agent list clean
|
||||
7. **Choose agents** — default: `codex` worker + `claude/sonnet` verifier
|
||||
8. **Choose a name** — short, descriptive
|
||||
9. **Run the script** — call `loop.sh` with all the arguments
|
||||
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
|
||||
|
||||
### When to use a verifier vs self-terminating
|
||||
## Prompt Writing Rules
|
||||
|
||||
Use a verifier (`--verifier-prompt`) when:
|
||||
- The worker's job is to implement something and you want independent verification
|
||||
- You want the verification done by a different agent than the worker
|
||||
- The worker should focus on doing work, not on judging its own work
|
||||
### Target prompt
|
||||
|
||||
Use self-terminating (no verifier) when:
|
||||
- The worker is checking/polling an external condition (CI status, deployment health)
|
||||
- The worker can objectively determine when it's done (tests pass, file exists)
|
||||
- You want a single agent doing both the work and the evaluation
|
||||
The target prompt must be:
|
||||
- self-contained
|
||||
- concrete about commands, files, branches, tests, PRs, or systems to inspect
|
||||
- explicit about what counts as progress this iteration
|
||||
|
||||
### Writing a Good Worker Prompt
|
||||
If there is no verifier, the target prompt must instruct the target to end with strict JSON matching:
|
||||
|
||||
The worker prompt is what the agent receives each iteration. It must be:
|
||||
```json
|
||||
{ "done": boolean, "reason": "string" }
|
||||
```
|
||||
|
||||
1. **Self-contained** — The agent starts with zero context. Everything needed is in the prompt.
|
||||
2. **Specific** — Name files, functions, types, URLs. Be concrete.
|
||||
3. **Action-oriented** — Tell the agent what to do, not what to think about.
|
||||
### Verifier prompt
|
||||
|
||||
### Writing a Good Verifier Prompt
|
||||
The verifier prompt should:
|
||||
- check facts, not offer fixes
|
||||
- cite commands, outputs, or file evidence
|
||||
- return strict JSON matching:
|
||||
|
||||
The verifier prompt defines what the verifier checks after each worker iteration. It can range from strict factual checks to qualitative code review:
|
||||
```json
|
||||
{ "done": boolean, "reason": "string" }
|
||||
```
|
||||
|
||||
**Factual / objective checks:**
|
||||
- "Run `npm test` and `npm run typecheck`. Report done only if both pass with zero failures."
|
||||
- "Check that PR #42 has all CI checks green and no unresolved review comments."
|
||||
- "Verify the API responds to `GET /health` with status 200 within 500ms."
|
||||
## Skill Stacking
|
||||
|
||||
**Code quality / style checks:**
|
||||
- "Review the changes for DRY violations. Report done when there is no duplicated logic across files."
|
||||
- "Check that the implementation does not over-engineer. No unnecessary abstractions, no premature generalization, no feature flags for single-use code."
|
||||
- "Verify the code follows the project's conventions: functional style, no classes, explicit types, no `any`."
|
||||
|
||||
**Performance / resource checks:**
|
||||
- "Run the benchmark suite. Report done when p99 latency is under 100ms."
|
||||
- "Check bundle size. Report done when the production build is under 500KB gzipped."
|
||||
|
||||
**Comprehensive verification:**
|
||||
- "Verify every step of the plan was implemented. Check file changes, types, test output. Run `npm run typecheck` and `npm test`. Report each criterion individually with evidence."
|
||||
|
||||
The verifier should report facts with evidence, not suggest fixes.
|
||||
|
||||
### Skill Stacking
|
||||
|
||||
You can instruct the worker to use other skills:
|
||||
The target prompt can instruct the worker to use other skills:
|
||||
|
||||
```bash
|
||||
~/.claude/skills/paseo-loop/bin/loop.sh \
|
||||
--worker-prompt "Use /committee to plan, then fix the provider list bug. The bug is..." \
|
||||
--verifier-prompt "The provider list renders correctly and npm run typecheck passes" \
|
||||
--name "provider-fix"
|
||||
```
|
||||
|
||||
### Composing with Handoff
|
||||
|
||||
A handoff can launch a loop:
|
||||
|
||||
```
|
||||
/handoff a loop in a worktree to babysit PR #42
|
||||
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
|
||||
```
|
||||
|
||||
@@ -3,29 +3,34 @@ set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: loop.sh --worker-prompt TEXT --name NAME [options]
|
||||
Usage: loop.sh --name NAME --target-prompt TEXT [options]
|
||||
|
||||
Worker (required):
|
||||
--worker-prompt TEXT Prompt given to the worker agent each iteration
|
||||
--worker-prompt-file PATH Read the worker prompt from a file
|
||||
--worker PROVIDER/MODEL Worker agent (e.g. codex, codex/gpt-5.4, claude/sonnet). Default: codex
|
||||
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 by a separate agent after each iteration
|
||||
--verifier-prompt-file PATH Read the verification prompt from a file
|
||||
--verifier PROVIDER/MODEL Verifier agent (e.g. claude/sonnet, codex/gpt-5.4). Default: claude/sonnet
|
||||
--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
|
||||
|
||||
When a verifier prompt is provided, a separate verifier agent evaluates the
|
||||
condition after each worker iteration. When omitted, the worker itself returns
|
||||
{ done: boolean, reason: string } and decides when the loop is done.
|
||||
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)
|
||||
|
||||
Options:
|
||||
--name NAME Name prefix for agents (required)
|
||||
--sleep DURATION Sleep between iterations (e.g. 30s, 5m, 1h). Default: no delay.
|
||||
--max-iterations N Maximum loop iterations (default: unlimited)
|
||||
--archive Archive agents after each iteration
|
||||
--worktree NAME Run all agents in this worktree (created on first use, reused after)
|
||||
--thinking LEVEL Thinking level for worker (default: medium)
|
||||
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
|
||||
}
|
||||
@@ -47,39 +52,37 @@ parse_agent_spec() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Defaults
|
||||
max_iterations=0
|
||||
archive=false
|
||||
worker_spec=""
|
||||
verifier_spec=""
|
||||
worker_prompt_input=""
|
||||
worker_prompt_file_input=""
|
||||
verifier_prompt_input=""
|
||||
verifier_prompt_file_input=""
|
||||
name=""
|
||||
thinking="medium"
|
||||
worktree=""
|
||||
sleep_duration=""
|
||||
state_root="${HOME}/.paseo/loops"
|
||||
parse_duration_to_seconds() {
|
||||
local raw="$1"
|
||||
if [[ -z "$raw" ]]; then
|
||||
echo "Error: duration cannot be empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--worker-prompt) worker_prompt_input="$2"; shift 2 ;;
|
||||
--worker-prompt-file) worker_prompt_file_input="$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 ;;
|
||||
--archive) archive=true; shift ;;
|
||||
--sleep) sleep_duration="$2"; shift 2 ;;
|
||||
--worktree) worktree="$2"; shift 2 ;;
|
||||
--thinking) thinking="$2"; shift 2 ;;
|
||||
--help|-h) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
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"
|
||||
@@ -114,10 +117,171 @@ load_prompt() {
|
||||
return 1
|
||||
}
|
||||
|
||||
worker_prompt="$(load_prompt "$worker_prompt_input" "$worker_prompt_file_input" "worker-prompt" "true")"
|
||||
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; }
|
||||
|
||||
# Load verifier prompt if provided (optional)
|
||||
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
|
||||
@@ -125,7 +289,6 @@ if [[ -n "$verifier_prompt_input" || -n "$verifier_prompt_file_input" ]]; then
|
||||
has_verifier=true
|
||||
fi
|
||||
|
||||
# Parse agent specs
|
||||
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")"
|
||||
|
||||
@@ -141,14 +304,13 @@ while [[ -e "$state_dir" ]]; do
|
||||
loop_id="$(generate_loop_id)"
|
||||
state_dir="${state_root}/${loop_id}"
|
||||
done
|
||||
|
||||
mkdir -p "$state_dir"
|
||||
|
||||
prompt_file="${state_dir}/worker-prompt.md"
|
||||
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' "$worker_prompt" > "$prompt_file"
|
||||
printf '%s\n' "$target_prompt" > "$target_prompt_file"
|
||||
printf '' > "$last_reason_file"
|
||||
printf '' > "$history_log"
|
||||
|
||||
@@ -157,135 +319,181 @@ if [[ "$has_verifier" == true ]]; then
|
||||
printf '%s\n' "$verifier_prompt_text" > "$verifier_prompt_file"
|
||||
fi
|
||||
|
||||
# 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)
|
||||
worker_flags+=(--mode bypassPermissions --provider claude)
|
||||
fi
|
||||
[[ -n "$worker_model" ]] && worker_flags+=(--model "$worker_model")
|
||||
[[ -n "$thinking" ]] && worker_flags+=(--thinking "$thinking")
|
||||
|
||||
# Build verifier flags
|
||||
verifier_flags=()
|
||||
if [[ "$verifier_provider" == "codex" ]]; then
|
||||
verifier_flags+=(--mode full-access --provider codex)
|
||||
elif [[ "$verifier_provider" == "claude" ]]; then
|
||||
verifier_flags+=(--mode bypassPermissions)
|
||||
verifier_flags+=(--mode bypassPermissions --provider claude)
|
||||
fi
|
||||
[[ -n "$verifier_model" ]] && verifier_flags+=(--model "$verifier_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
|
||||
|
||||
# Structured output schema
|
||||
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 " Worker prompt: $prompt_file (live-editable)"
|
||||
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 " Mode: worker + verifier"
|
||||
echo " Verifier: $verifier_provider/${verifier_model:-(default)}"
|
||||
else
|
||||
echo " Mode: self-terminating worker"
|
||||
echo " Verifier: none"
|
||||
fi
|
||||
echo " Last reason file: $last_reason_file"
|
||||
echo " History log: $history_log"
|
||||
echo " Worker: $worker_provider/${worker_model:-(default)}"
|
||||
if [[ "$has_verifier" == true ]]; then
|
||||
echo " Verifier: $verifier_provider/${verifier_model:-(default)}"
|
||||
if [[ -n "$sleep_raw" ]]; then
|
||||
echo " Sleep: $sleep_raw between iterations"
|
||||
fi
|
||||
if [[ -n "$sleep_duration" ]]; then
|
||||
echo " Sleep: $sleep_duration between iterations"
|
||||
if [[ -n "$max_time_raw" ]]; then
|
||||
echo " Max time: $max_time_raw"
|
||||
else
|
||||
echo " Max time: unlimited"
|
||||
fi
|
||||
if [[ "$archive" == true ]]; then
|
||||
echo " Archive: agents archived after each iteration"
|
||||
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
|
||||
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
|
||||
while [[ "$max_iterations" -eq 0 || "$iteration" -lt "$max_iterations" ]]; do
|
||||
ensure_time_remaining
|
||||
|
||||
iteration=$((iteration + 1))
|
||||
if [[ $max_iterations -gt 0 ]]; then
|
||||
if [[ "$max_iterations" -gt 0 ]]; then
|
||||
echo "--- Iteration $iteration/$max_iterations ---"
|
||||
else
|
||||
echo "--- Iteration $iteration ---"
|
||||
fi
|
||||
|
||||
if [[ ! -s "$prompt_file" ]]; then
|
||||
echo "Error: worker prompt file is missing or empty: $prompt_file"
|
||||
if [[ ! -s "$target_prompt_file" ]]; then
|
||||
echo "Error: target prompt file is missing or empty: $target_prompt_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current_prompt="$(cat "$prompt_file")"
|
||||
current_target_prompt="$(cat "$target_prompt_file")"
|
||||
last_reason="$(cat "$last_reason_file" 2>/dev/null || true)"
|
||||
|
||||
# Build the full worker prompt
|
||||
full_worker_prompt="$current_prompt"
|
||||
|
||||
if [[ -n "$last_reason" ]]; then
|
||||
full_worker_prompt="$full_worker_prompt
|
||||
|
||||
<previous-iteration-result>
|
||||
The previous iteration reported the following:
|
||||
|
||||
$last_reason
|
||||
</previous-iteration-result>"
|
||||
target_needs_structured_output=false
|
||||
if [[ "$has_verifier" == false ]]; then
|
||||
target_needs_structured_output=true
|
||||
fi
|
||||
|
||||
worker_name="${name}-${iteration}"
|
||||
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
|
||||
# --- Mode: worker + separate verifier ---
|
||||
|
||||
# Worker runs detached (no structured output)
|
||||
echo "Launching worker: $worker_name"
|
||||
worker_id=$(paseo run -d "${worker_flags[@]}" "${worktree_flags[@]}" --name "$worker_name" "$full_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 ""
|
||||
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
|
||||
|
||||
# Re-read verifier prompt for live steering
|
||||
current_verifier_prompt="$(cat "$verifier_prompt_file")"
|
||||
|
||||
# Verifier runs synchronously with structured output
|
||||
verifier_name="${name}-verify-${iteration}"
|
||||
full_verifier_prompt="$current_verifier_prompt
|
||||
|
||||
Respond with { \"done\": true/false, \"reason\": \"...\" }.
|
||||
- done: true if the condition is met, false otherwise
|
||||
- reason: explain what you found with evidence"
|
||||
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')
|
||||
|
||||
@@ -293,33 +501,18 @@ Respond with { \"done\": true/false, \"reason\": \"...\" }.
|
||||
paseo agent archive "$verifier_name" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
printf '[%s] iteration=%s worker=%s verifier=%s done=%s reason=%s\n' \
|
||||
"$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
||||
"$iteration" \
|
||||
"$worker_name" \
|
||||
"$verifier_name" \
|
||||
"$done_value" \
|
||||
"$(echo "$reason" | tr '\n' ' ')" >> "$history_log"
|
||||
|
||||
else
|
||||
# --- Mode: self-terminating worker ---
|
||||
|
||||
# Worker runs synchronously with structured output
|
||||
echo "Launching worker: $worker_name"
|
||||
verdict=$(paseo run "${worker_flags[@]}" "${worktree_flags[@]}" --name "$worker_name" --output-schema "$done_schema" "$full_worker_prompt")
|
||||
echo "Result: $verdict"
|
||||
|
||||
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
|
||||
if [[ "$target" == "new-agent" ]]; then
|
||||
target_label="$worker_name"
|
||||
else
|
||||
target_label="self:${agent_id}"
|
||||
fi
|
||||
|
||||
printf '[%s] iteration=%s worker=%s done=%s reason=%s\n' \
|
||||
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" \
|
||||
"$worker_name" \
|
||||
"$target" \
|
||||
"$target_label" \
|
||||
"$verifier_name" \
|
||||
"$done_value" \
|
||||
"$(echo "$reason" | tr '\n' ' ')" >> "$history_log"
|
||||
fi
|
||||
@@ -334,9 +527,10 @@ Respond with { \"done\": true/false, \"reason\": \"...\" }.
|
||||
echo "Not done: $reason"
|
||||
printf '%s\n' "$reason" > "$last_reason_file"
|
||||
|
||||
if [[ -n "$sleep_duration" ]] && [[ $max_iterations -eq 0 || $iteration -lt $max_iterations ]]; then
|
||||
echo "Sleeping $sleep_duration before next iteration..."
|
||||
sleep "$sleep_duration"
|
||||
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 ""
|
||||
|
||||
Reference in New Issue
Block a user