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

View File

@@ -1,14 +1,12 @@
---
name: paseo-chat
description: Use chat rooms through a chat helper. Use when the user says "chat room", "room", "coordinate through chat", "shared mailbox", or wants agents to communicate asynchronously.
description: Use chat rooms through the Paseo CLI. Use when the user says "chat room", "room", "coordinate through chat", "shared mailbox", or wants agents to communicate asynchronously.
user-invocable: true
---
# Paseo Chat Skill
This skill teaches how to use chat.
Use `skills/paseo-chat/bin/chat.sh`.
This skill teaches how to use chat rooms for agent coordination via the Paseo CLI.
**User's arguments:** $ARGUMENTS
@@ -21,136 +19,104 @@ Load the **Paseo skill** first if you need CLI guidance for launching or messagi
## Rules
When using chat:
- create a room with `chat.sh room create` if you need a new room
- inspect available rooms with `chat.sh room list` and `chat.sh room show`
- post with `chat.sh post`
- read with `chat.sh read`
- create a room with `paseo chat create` if you need a new room
- inspect available rooms with `paseo chat ls` and `paseo chat inspect`
- post with `paseo chat post`
- read with `paseo chat read`
- keep reads bounded, usually `--limit 10` or `--limit 20`
- check chat often while working
Do not read or write the backing files directly. Use `chat.sh`.
Mentions and replies are active:
- if you post `@agent-id` in a message, chat will notify that agent immediately
- if you post `@everyone`, chat will notify every agent that has already participated in that room, as if you had tagged each of them directly
- if you use `--reply-to`, chat will notify the author of the replied-to message immediately
- notifications are sent with `paseo send --no-wait`
Mentions are active:
- use `--mention <agent-id>` on your post to notify a specific agent immediately
- if you use `--reply-to`, the author of the replied-to message is notified
- notifications are sent via `paseo send --no-wait` under the hood
- mentions **interrupt** the target agent, so only mention when they need to act now
- if a normal post is enough and no one needs to act right now, skip the mention
## Command Surface
### Create a room
```bash
skills/paseo-chat/bin/chat.sh room create \
--room issue-456 \
--title "Issue 456 coordination" \
--purpose "Coordinate implementation and review"
paseo chat create issue-456 --purpose "Coordinate implementation and review"
```
### List rooms
```bash
skills/paseo-chat/bin/chat.sh room list
paseo chat ls
```
### Show room details
### Inspect room details
```bash
skills/paseo-chat/bin/chat.sh room show --room issue-456
paseo chat inspect issue-456
```
### Post a message
```bash
skills/paseo-chat/bin/chat.sh post \
--room issue-456 \
--body "I traced the failure to relay auth. Investigating config loading now."
paseo chat post issue-456 "I traced the failure to relay auth. Investigating config loading now."
```
Author defaults:
- `--agent-id` if provided
- otherwise `$PASEO_AGENT_ID`
- otherwise `manual`
Optional reply:
With a reply:
```bash
skills/paseo-chat/bin/chat.sh post \
--room issue-456 \
--reply-to msg-001 \
--body "I can take that next."
paseo chat post issue-456 "I can take that next." --reply-to msg-001
```
Direct mention:
With a direct mention:
```bash
skills/paseo-chat/bin/chat.sh post \
--room issue-456 \
--body "@agent-beta can you verify the relay path next?"
```
Notify the whole room:
```bash
skills/paseo-chat/bin/chat.sh post \
--room issue-456 \
--body "@everyone I need each of you to weigh in on whether we should cut scope or keep the full fix."
paseo chat post issue-456 "Can you verify the relay path next?" --mention <agent-id>
```
### Read recent messages
```bash
skills/paseo-chat/bin/chat.sh read --room issue-456 --limit 10
paseo chat read issue-456 --limit 10
```
### Filter reads
```bash
skills/paseo-chat/bin/chat.sh read --room issue-456 --agent-id 123abc
skills/paseo-chat/bin/chat.sh read --room issue-456 --since 2026-03-24T10:00:00Z
paseo chat read issue-456 --agent <agent-id>
paseo chat read issue-456 --since 5m
paseo chat read issue-456 --since 2026-03-24T10:00:00Z
```
### Wait for new messages
```bash
paseo chat wait issue-456 --timeout 60s
```
## Defaults
When creating a room:
- choose a short explicit room id
- prefer slugs like `issue-456`, `pr-143-review`, `feature/relay-cleanup`
- give the room a clear title and purpose
- choose a short slug: `issue-456`, `pr-143-review`, `relay-cleanup`
- give it a clear purpose
When using a room:
- read only a bounded window before acting
- post updates when they would help another agent or your future self
- use `--reply-to` when responding to a specific message
- use `@agent-id` when you want to get a specific agent's attention — but know that mentions **interrupt** the target agent immediately, so only mention when they need to act now
- use `@everyone` sparingly when you want all prior participants in the room to respond or re-check the thread
- prefer `@agent-id` most of the time so you only interrupt the agents who actually need the message
- if a normal post is enough and no one needs to act right now, skip the mention — agents will see it next time they read the room
- use `--mention` when you want to get a specific agent's attention — but know that mentions **interrupt** the target agent
- check chat frequently enough that shared coordination actually works
- your own agent ID is available via `$PASEO_AGENT_ID` — when someone asks you to report back, mention them so they get notified
- your own agent ID is available via `$PASEO_AGENT_ID`
Typical things to post:
- status updates
- blockers
- handoffs
- review findings
- important context and memories another agent may need later
Good use of `@everyone`:
- you need opinions or explicit responses from the whole team
- the room has shared context and each participant should re-check it now
Avoid `@everyone` when:
- only one or two agents need to act
- different agents are doing different jobs and you do not want to interrupt all of them
- a normal post is enough because no immediate response is needed
- important context another agent may need later
## Your Job
1. Understand whether you should use an existing room or create a new one
2. Choose a room id and title
3. Create the room with `chat.sh room create` if needed
4. Read the room with bounded history
5. Post clearly
6. Use `--reply-to` when replying to a specific message
7. Use `@agent-id` when you want to notify someone directly
8. Use `@everyone` only when you intentionally want to notify the full room and prompt a broad response
2. Create the room with `paseo chat create` if needed
3. Read the room with bounded history
4. Post clearly
5. Use `--reply-to` when replying to a specific message
6. Use `--mention` when you want to notify someone directly

View File

@@ -1,634 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
chat.sh room create --room ROOM [--title TITLE] [--purpose PURPOSE]
chat.sh room list
chat.sh room show --room ROOM
chat.sh post --room ROOM (--body TEXT | --body-file PATH) [--agent-id ID] [--agent-name NAME] [--reply-to MESSAGE_ID]
chat.sh read --room ROOM [--limit N] [--since MESSAGE_ID|ISO_TIMESTAMP] [--agent-id ID]
chat.sh wait --room ROOM [--since MESSAGE_ID] [--timeout SECONDS]
Options:
--root PATH Override chat root (default: ~/.paseo/chat)
EOF
exit 1
}
yaml_quote() {
local value="${1//\'/\'\'}"
printf "'%s'" "$value"
}
require_command() {
local name="$1"
command -v "$name" >/dev/null 2>&1 || {
echo "Error: required command not found: $name" >&2
exit 1
}
}
sanitize_room_path() {
local room="$1"
if [[ -z "$room" || "$room" == /* || "$room" == *".."* ]]; then
echo "Error: invalid room id: $room" >&2
exit 1
fi
}
load_body() {
local inline_value="$1"
local file_value="$2"
if [[ -n "$inline_value" && -n "$file_value" ]]; then
echo "Error: use either --body or --body-file, not both" >&2
exit 1
fi
if [[ -n "$file_value" ]]; then
[[ -f "$file_value" ]] || { echo "Error: --body-file not found: $file_value" >&2; exit 1; }
cat "$file_value"
return
fi
if [[ -n "$inline_value" ]]; then
printf '%s' "$inline_value"
return
fi
echo "Error: one of --body or --body-file is required" >&2
exit 1
}
frontmatter_value() {
local key="$1"
local file="$2"
awk -F': ' -v search_key="$key" '
BEGIN { in_frontmatter = 0 }
/^---$/ {
if (in_frontmatter == 0) {
in_frontmatter = 1
next
}
exit
}
in_frontmatter == 1 && $1 == search_key {
value = substr($0, length(search_key) + 3)
gsub(/^'\''|'\''$/, "", value)
print value
exit
}
' "$file"
}
message_body() {
local file="$1"
awk '
BEGIN { delim_count = 0 }
/^---$/ {
delim_count += 1
next
}
delim_count >= 2 {
print
}
' "$file"
}
message_preview() {
local file="$1"
local max_length="${2:-100}"
local preview
preview="$(
message_body "$file" |
awk 'NF { print; exit }' |
tr -s '[:space:]' ' ' |
sed 's/^ //; s/ $//'
)"
if [[ ${#preview} -gt "$max_length" ]]; then
preview="${preview:0:$((max_length - 3))}..."
fi
printf '%s' "$preview"
}
format_timestamp() {
local ts="$1"
local time_part="${ts#*T}"
time_part="${time_part%Z}"
local date_part="${ts%%T*}"
printf '%s %s' "$date_part" "$time_part"
}
print_message() {
local file="$1"
local created_at agent_id agent_name reply_to message_id
created_at="$(frontmatter_value "created_at" "$file")"
agent_id="$(frontmatter_value "agent_id" "$file")"
agent_name="$(frontmatter_value "agent_name" "$file")"
reply_to="$(frontmatter_value "reply_to" "$file")"
message_id="$(frontmatter_value "message_id" "$file")"
local display_time
display_time="$(format_timestamp "$created_at")"
local author
if [[ -n "$agent_name" ]]; then
author="$agent_name ($agent_id)"
else
author="$agent_id"
fi
printf '┌─ %s ── %s ── [%s]' "$author" "$display_time" "$message_id"
if [[ -n "$reply_to" ]]; then
printf ' ↩ %s' "$reply_to"
fi
printf '\n'
printf '│\n'
message_body "$file" | sed '/./,$!d' | while IFS= read -r line; do
printf '│ %s\n' "$line"
done
printf '│\n'
printf '└─\n'
}
require_room() {
local room="$1"
local room_dir="$rooms_root/$room"
[[ -d "$room_dir" ]] || { echo "Error: room not found: $room" >&2; exit 1; }
}
find_message_file_by_id() {
local room="$1"
local message_id="$2"
find "$rooms_root/$room/messages" -name '*.md' -type f -print0 |
while IFS= read -r -d '' file; do
if [[ "$(frontmatter_value "message_id" "$file")" == "$message_id" ]]; then
printf '%s\n' "$file"
return 0
fi
done
}
message_files_sorted() {
local room="$1"
find "$rooms_root/$room/messages" -name '*.md' -type f | sort
}
print_message_files() {
local files=("$@")
if [[ ${#files[@]} -eq 0 ]]; then
return
fi
local index
for ((index = 0; index < ${#files[@]}; index += 1)); do
print_message "${files[$index]}"
if [[ $index -lt $((${#files[@]} - 1)) ]]; then
printf '\n'
fi
done
}
extract_mentions() {
local body="$1"
printf '%s\n' "$body" | grep -oE '@[A-Za-z0-9._:-]+' | sed 's/^@//' | awk '!seen[$0]++'
}
room_participant_ids() {
local room="$1"
find "$rooms_root/$room/messages" -name '*.md' -type f | sort |
while IFS= read -r file; do
frontmatter_value "agent_id" "$file"
done |
awk 'NF && $0 != "manual" && !seen[$0]++'
}
agent_is_archived() {
local target_agent_id="$1"
local archived_at
archived_at="$("$paseo_bin" inspect "$target_agent_id" --json 2>/dev/null | jq -r '.ArchivedAt // empty' 2>/dev/null)" || archived_at=""
[[ -n "$archived_at" ]]
}
notify_agent() {
local target_agent_id="$1"
local room="$2"
local sender_agent_id="$3"
local sender_agent_name="$4"
local message_id="$5"
local reason="$6"
local message_file="$7"
if [[ -z "$target_agent_id" || "$target_agent_id" == "manual" || "$target_agent_id" == "$sender_agent_id" ]]; then
return
fi
if agent_is_archived "$target_agent_id"; then
return
fi
local sender_label
if [[ -n "$sender_agent_name" ]]; then
sender_label="$sender_agent_name ($sender_agent_id)"
else
sender_label="$sender_agent_id"
fi
local notification full_message
notification="You have a new chat message.
Room: $room
From: $sender_label
Message ID: $message_id
Reason: $reason"
full_message="$(message_body "$message_file")"
if [[ -n "$full_message" ]]; then
notification="${notification}
---
$full_message"
fi
notification="${notification}
-----"
"$paseo_bin" send --no-wait "$target_agent_id" "$notification" >/dev/null 2>&1 || true
}
require_command uuidgen
chat_root="${HOME}/.paseo/chat"
paseo_bin="${PASEO_CHAT_PASEO_BIN:-paseo}"
if [[ $# -lt 1 ]]; then
usage
fi
if [[ "$1" == "--root" ]]; then
[[ $# -ge 3 ]] || usage
chat_root="$2"
shift 2
fi
rooms_root="${chat_root}/rooms"
mkdir -p "$rooms_root"
command_name="$1"
shift
case "$command_name" in
room)
[[ $# -ge 1 ]] || usage
room_subcommand="$1"
shift
case "$room_subcommand" in
create)
room=""
title=""
purpose=""
while [[ $# -gt 0 ]]; do
case "$1" in
--room) room="$2"; shift 2 ;;
--title) title="$2"; shift 2 ;;
--purpose) purpose="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
[[ -n "$room" ]] || { echo "Error: --room is required" >&2; exit 1; }
sanitize_room_path "$room"
room_dir="$rooms_root/$room"
messages_dir="$room_dir/messages"
mkdir -p "$messages_dir"
if [[ -z "$title" ]]; then
title="$room"
fi
created_at="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
room_file="$room_dir/room.md"
if [[ ! -f "$room_file" ]]; then
printf '%s\n' \
"---" \
"room: $(yaml_quote "$room")" \
"title: $(yaml_quote "$title")" \
"purpose: $(yaml_quote "$purpose")" \
"created_at: $(yaml_quote "$created_at")" \
"---" \
"" \
"# $(printf '%s' "$title")" \
"" \
"$(printf '%s' "$purpose")" > "$room_file"
fi
printf 'created room %s\n' "$room"
;;
list)
while IFS= read -r room_file; do
room_id="${room_file#"$rooms_root"/}"
room_id="${room_id%/room.md}"
printf '%s\n' "$room_id"
done < <(find "$rooms_root" -name room.md -type f | sort)
;;
show)
room=""
while [[ $# -gt 0 ]]; do
case "$1" in
--room) room="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
[[ -n "$room" ]] || { echo "Error: --room is required" >&2; exit 1; }
sanitize_room_path "$room"
require_room "$room"
room_file="$rooms_root/$room/room.md"
title="$(frontmatter_value "title" "$room_file")"
purpose="$(frontmatter_value "purpose" "$room_file")"
created_at="$(frontmatter_value "created_at" "$room_file")"
message_count="$(find "$rooms_root/$room/messages" -name '*.md' -type f | wc -l | tr -d ' ')"
printf 'room: %s\n' "$room"
printf 'title: %s\n' "$title"
printf 'purpose: %s\n' "$purpose"
printf 'created_at: %s\n' "$created_at"
printf 'messages: %s\n' "$message_count"
;;
*)
usage
;;
esac
;;
post)
room=""
body_input=""
body_file=""
agent_id="${PASEO_AGENT_ID:-manual}"
agent_name=""
reply_to=""
auto_resolve_name=true
while [[ $# -gt 0 ]]; do
case "$1" in
--room) room="$2"; shift 2 ;;
--body) body_input="$2"; shift 2 ;;
--body-file) body_file="$2"; shift 2 ;;
--agent-id) agent_id="$2"; shift 2 ;;
--agent-name) agent_name="$2"; auto_resolve_name=false; shift 2 ;;
--reply-to) reply_to="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
[[ -n "$room" ]] || { echo "Error: --room is required" >&2; exit 1; }
sanitize_room_path "$room"
require_room "$room"
if [[ "$auto_resolve_name" == true && -z "$agent_name" && "$agent_id" != "manual" ]]; then
agent_name="$("$paseo_bin" inspect "$agent_id" --json 2>/dev/null | jq -r '.Name // empty' 2>/dev/null)" || agent_name=""
fi
body="$(load_body "$body_input" "$body_file")"
created_at="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
created_at_file="$(date -u +'%Y-%m-%dT%H-%M-%SZ')"
message_id="msg-$(uuidgen | tr '[:upper:]' '[:lower:]' | tr -d '-' | cut -c1-8)"
agent_slug="$(printf '%s' "$agent_id" | tr '/ :@' '_' | tr -cd '[:alnum:]_.-')"
[[ -n "$agent_slug" ]] || agent_slug="manual"
message_file="$rooms_root/$room/messages/${created_at_file}__${agent_slug}__${message_id}.md"
printf '%s\n' \
"---" \
"room: $(yaml_quote "$room")" \
"message_id: $(yaml_quote "$message_id")" \
"agent_id: $(yaml_quote "$agent_id")" \
"agent_name: $(yaml_quote "$agent_name")" \
"created_at: $(yaml_quote "$created_at")" \
"reply_to: $(yaml_quote "$reply_to")" \
"---" \
"" \
"$body" > "$message_file"
notified_agents=()
if [[ -n "$reply_to" ]]; then
reply_file="$(find_message_file_by_id "$room" "$reply_to" || true)"
if [[ -n "$reply_file" ]]; then
reply_target_agent_id="$(frontmatter_value "agent_id" "$reply_file")"
notify_agent "$reply_target_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "reply to $reply_to" "$message_file"
notified_agents+=("$reply_target_agent_id")
fi
fi
while IFS= read -r mentioned_agent_id; do
[[ -n "$mentioned_agent_id" ]] || continue
if [[ "$mentioned_agent_id" == "everyone" ]]; then
while IFS= read -r participant_agent_id; do
[[ -n "$participant_agent_id" ]] || continue
already_notified=false
for existing_id in "${notified_agents[@]}"; do
if [[ "$existing_id" == "$participant_agent_id" ]]; then
already_notified=true
break
fi
done
if [[ "$already_notified" == false ]]; then
notify_agent "$participant_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "@everyone mention" "$message_file"
notified_agents+=("$participant_agent_id")
fi
done < <(room_participant_ids "$room")
continue
fi
already_notified=false
for existing_id in "${notified_agents[@]}"; do
if [[ "$existing_id" == "$mentioned_agent_id" ]]; then
already_notified=true
break
fi
done
if [[ "$already_notified" == false ]]; then
notify_agent "$mentioned_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "direct mention" "$message_file"
notified_agents+=("$mentioned_agent_id")
fi
done < <(extract_mentions "$body")
printf '%s\n' "$message_id"
;;
read)
room=""
limit="20"
since=""
agent_id_filter=""
while [[ $# -gt 0 ]]; do
case "$1" in
--room) room="$2"; shift 2 ;;
--limit) limit="$2"; shift 2 ;;
--since) since="$2"; shift 2 ;;
--agent-id) agent_id_filter="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
[[ -n "$room" ]] || { echo "Error: --room is required" >&2; exit 1; }
sanitize_room_path "$room"
require_room "$room"
all_files=()
while IFS= read -r _f; do
all_files+=("$_f")
done < <(message_files_sorted "$room")
since_file=""
if [[ -n "$since" ]]; then
if [[ "$since" == msg-* ]]; then
since_file="$(find_message_file_by_id "$room" "$since" || true)"
[[ -n "$since_file" ]] || { echo "Error: message not found: $since" >&2; exit 1; }
fi
fi
filtered_files=()
past_since=false
if [[ -z "$since" ]]; then
past_since=true
fi
for file in "${all_files[@]}"; do
if [[ -n "$since_file" ]]; then
if [[ "$file" == "$since_file" ]]; then
past_since=true
continue
fi
if [[ "$past_since" != true ]]; then
continue
fi
elif [[ -n "$since" ]]; then
created_at="$(frontmatter_value "created_at" "$file")"
if [[ "$created_at" < "$since" ]]; then
continue
fi
fi
file_agent_id="$(frontmatter_value "agent_id" "$file")"
if [[ -n "$agent_id_filter" && "$file_agent_id" != "$agent_id_filter" ]]; then
continue
fi
filtered_files+=("$file")
done
start_index=0
if [[ "$limit" != "all" ]]; then
if ! [[ "$limit" =~ ^[0-9]+$ ]]; then
echo "Error: --limit must be a number or 'all'" >&2
exit 1
fi
if [[ "${#filtered_files[@]}" -gt "$limit" ]]; then
start_index=$((${#filtered_files[@]} - limit))
fi
fi
print_message_files "${filtered_files[@]:$start_index}"
;;
wait)
room=""
since_message_id=""
timeout_seconds=""
poll_interval_seconds="1"
while [[ $# -gt 0 ]]; do
case "$1" in
--room) room="$2"; shift 2 ;;
--since) since_message_id="$2"; shift 2 ;;
--timeout) timeout_seconds="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
[[ -n "$room" ]] || { echo "Error: --room is required" >&2; exit 1; }
sanitize_room_path "$room"
require_room "$room"
if [[ -n "$timeout_seconds" ]] && ! [[ "$timeout_seconds" =~ ^[0-9]+$ ]]; then
echo "Error: --timeout must be a number" >&2
exit 1
fi
baseline_file=""
if [[ -n "$since_message_id" ]]; then
baseline_file="$(find_message_file_by_id "$room" "$since_message_id" || true)"
[[ -n "$baseline_file" ]] || { echo "Error: message not found: $since_message_id" >&2; exit 1; }
else
while IFS= read -r file; do
baseline_file="$file"
done < <(message_files_sorted "$room")
fi
started_at="$(date +%s)"
trap 'exit 130' INT TERM
while true; do
all_files=()
while IFS= read -r _f; do
all_files+=("$_f")
done < <(message_files_sorted "$room")
new_files=()
after_baseline=false
if [[ -z "$baseline_file" ]]; then
after_baseline=true
fi
for file in "${all_files[@]}"; do
if [[ "$after_baseline" == true ]]; then
new_files+=("$file")
continue
fi
if [[ "$file" == "$baseline_file" ]]; then
after_baseline=true
fi
done
if [[ ${#new_files[@]} -gt 0 ]]; then
print_message_files "${new_files[@]}"
exit 0
fi
if [[ -n "$timeout_seconds" ]]; then
now="$(date +%s)"
if (( now - started_at >= timeout_seconds )); then
exit 124
fi
fi
sleep "$poll_interval_seconds"
done
;;
*)
usage
;;
esac

View File

@@ -77,7 +77,7 @@ $NO_EDITS"
Same prompt to both, `[Committee]` prefix for identification:
```bash
opus_id=$(paseo run -d --mode bypass --model opus --thinking on --name "[Committee] Task description" "$prompt" -q)
opus_id=$(paseo run -d --mode bypassPermissions --model opus --thinking on --name "[Committee] Task description" "$prompt" -q)
gpt_id=$(paseo run -d --mode full-access --provider codex --model gpt-5.4 --thinking medium --name "[Committee] Task description" "$prompt" -q)
```

View File

@@ -115,7 +115,7 @@ paseo run -d --mode full-access --provider codex --name "[Handoff] Task descript
### Claude (Opus, no worktree)
```bash
paseo run -d --mode bypass --model opus --name "[Handoff] Task description" "$prompt"
paseo run -d --mode bypassPermissions --model opus --name "[Handoff] Task description" "$prompt"
```
### Codex in a worktree

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

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

View File

@@ -1,519 +1,241 @@
---
name: paseo-orchestrator
description: Orchestrate work through agents. Use when entering orchestrator mode, managing agents, launching agents, or the user says "launch", "spin up", "orchestrate", or wants work delegated to agents.
description: Orchestrate work through a team of agents coordinating via chat. Use when entering orchestrator mode, managing agents, launching agents, or the user says "launch", "spin up", "orchestrate", or wants work delegated to agents.
user-invocable: true
---
# Orchestrator Mode
# Team Orchestrator
You are an orchestrator. You manage agents — you do not write code yourself.
You are a team lead. You build a team of agents, give them roles, and coordinate their work through a shared chat room. You do not write code yourself.
**User's arguments:** $ARGUMENTS
---
## Prerequisites
Load the **Paseo skill** first — it contains the CLI reference for all agent commands and waiting guidelines.
Load the **Paseo skill** first — it contains the CLI reference for all commands.
## The Model
Chat rooms are the backbone. Every team gets a room. The room is:
- the **memory** — agents catch up by reading it, even after losing context
- the **record** — all decisions, findings, and status live there
- the **coordination layer** — agents talk to each other via @mentions
Agents are **disposable**. They get archived when their role is done. The chat room outlives them. If an agent drifts or stalls, archive it and spin up a fresh one that reads the room to catch up.
You stay alive as the orchestrator. You check in on the team periodically via a schedule. You delete the schedule when the objective is complete.
## Your Role
You have two audiences and you speak differently to each.
**To the user** — you are a design partner. Discuss architecture, types, interfaces, trade-offs. Align on what "done" means before agents start.
**To the user** — you are a design partner. You discuss architecture, data shapes, interfaces, types, flow, and trade-offs. Code examples are your primary communication tool here. You help the user think through what they want and ensure their intent is clearly defined before agents start working.
**To agents** — you are a product owner. Define acceptance criteria and behavioral expectations. Do NOT tell agents how to implement — no "in file X change line Y". Agents read the codebase and figure out the implementation.
**To agents** — you are a product owner. You define acceptance criteria and behavioral expectations. You do NOT tell agents how to implement things — no "set variable X to Y", no "in file Z change line 42", no implementation-level code snippets. Agents read the codebase and figure out the implementation.
**You own the outcome.** You wait for agents, read their output, challenge their work, course-correct via chat, and ensure they deliver. You do not fire and forget unless the user explicitly says so.
Your job:
1. **Understand the problem** — discuss with the user, explore the design space, propose types/interfaces/data shapes
2. **Define acceptance criteria** — what does "done" look like from the user's perspective?
3. **Launch agents** with clear, behavior-focused prompts
4. **Course-correct** when agents drift
5. **Review the output** — spin up a review agent to verify the work meets criteria
6. **Report back** to the user with what was done and what to test
## Before Launching
You ensure the user's will is manifested through agents doing work.
Align with the user on:
- **Where?** — current directory or a worktree?
- **What's the deliverable?** — PR? Commit? Exploration?
- **Is there a GitHub issue?** — link it
- **How do we verify?** — tests? typecheck? manual?
**You own your agents.** Every agent you launch is your responsibility. You wait for them, read their output, challenge their work, and ensure they deliver. You do not launch an agent and move on — you stay with it until the job is done. The only exception is when the user explicitly says it's fire-and-forget.
## Phase 1: Set Up the Room
## Before Launching Agents
Create a chat room for the task:
Before any agent starts working, align with the user on logistics:
```bash
paseo chat create <task-slug> --purpose "<one-line objective>"
```
- **Where?** — Work in the current working directory unless the user specifies a worktree. Don't assume.
- **What's the deliverable?** — Is the objective a PR? A commit? Just exploration with no edits? Ask if unclear.
- **Is there a GitHub issue?** — Link it in the agent prompt if so. The agent should reference it.
- **Does the user want proof?** — Screenshot? Video? Manual test? Automated test? Know what "verified" means for this task.
Post the objective and acceptance criteria as the first message:
These questions prevent wasted work. A perfectly implemented feature in the wrong branch or without a PR is still a failure.
```bash
paseo chat post <room> "## Objective
<what we're building/fixing>
## Chat Rooms
## Acceptance Criteria
- [ ] <criterion 1>
- [ ] <criterion 2>
When agents need asynchronous coordination, shared status, or a lightweight handoff channel, use chat rooms.
## Constraints
- <constraint 1>
- <constraint 2>"
```
If you decide chat would help:
- create a room for the task
- tell agents the exact room id
- tell agents to load the `paseo-chat` skill
- tell agents to use `skills/paseo-chat/bin/chat.sh` for reading and posting
- tell agents to post memories, useful context, findings, blockers, and handoffs there
- tell agents to check chat very often while they are working
This is the team's north star. Every agent reads it when they join.
Use chat when:
- multiple agents need to coordinate without constant orchestrator relays
- review findings should be visible to other agents
- a task benefits from a shared running log
## Phase 2: Build the Team
When you launch planning, implementation, investigation, or review agents and chat is in play:
- require them to add their findings to the room
- require them to read recent chat before acting
- require them to post back when they discover something another agent may need
- if you expect a response back (review findings, audit results, blocker reports), tell the agent to `@mention` you when done — your agent ID is `$PASEO_AGENT_ID` (check it with `echo $PASEO_AGENT_ID` if needed) and include it literally in the prompt so the agent knows who to tag
Launch agents with lightweight initial prompts. Each agent gets:
1. Their role
2. The room to join
3. Instructions to load the chat skill and catch up
Chat is not just passive storage. Mentions and replies trigger direct notifications through chat, so agents can actively get each other's attention.
### Initial prompt template
Do not assume chat is required for every orchestration. Use it when it adds coordination value.
```bash
paseo run -d --mode full-access --provider codex \
--name "impl-<scope>" \
"You are an implementation engineer on a team.
## Provider Selection: Claude vs Codex
Load the paseo-chat skill. Read room '<room>' from the beginning to understand the objective and catch up on any prior work. Introduce yourself in the room with a brief message about what you'll focus on.
Claude and Codex have complementary strengths and weaknesses. Use each to cover the other's blind spots.
Then wait for instructions via @mention. Your agent ID is available in \$PASEO_AGENT_ID — share it in your intro so teammates can reach you." -q
```
**Codex** — the implementation workhorse (`--provider codex --mode full-access`):
- Strengths: thorough, methodical, catches edge cases, good at deep implementation
- Weakness: over-engineers. Sees every edge case and hardens against each with defensive code — coordination callbacks, guard clauses, retry wrappers, extra abstractions. Adds robustness through layers instead of designing simplicity.
### Giving work via chat
**Claude** — the design and review brain (`--mode bypassPermissions`):
- Strengths: good design instinct, sees the simple solution, strong at architecture and reasoning
- Weakness: can be careless. Misses details, skips steps, doesn't always verify thoroughly.
Once an agent is in the room and introduced, direct work to them via chat:
**The workflow:**
- Use **Codex for implementation** — it's the workhorse for writing code
- Use **Claude for review after Codex implements** — specifically to catch over-engineering. Ask: "Flag any defensive code, coordination layers, or abstractions that could be eliminated by a simpler design"
- Use **Codex for review after Claude implements** — specifically to catch carelessness, missed edge cases, and incomplete verification
- Each one audits the other's blind spot: Claude simplifies Codex's complexity, Codex catches Claude's carelessness
```bash
paseo chat post <room> "Focus on implementing the API layer. Acceptance criteria:
- endpoints match the spec posted above
- all new endpoints have tests
- typecheck passes
**What Codex over-engineering looks like:**
- Guard clauses and null checks for states that can't happen if the data flow is designed right
- Coordination callbacks or event bridges connecting things that shouldn't need connecting
- Helper functions wrapping trivial operations — indirection without value
- try/catch blocks duplicating error handling the caller already does
- Optional fields and union types that create ambiguity instead of clarity
- Code added "just in case" that can never execute given the current flow
Post your progress here. @mention me ($PASEO_AGENT_ID) when done." \
--mention <agent-id>
```
The fix is never "add more defensive code." The fix is design simpler types and data flow so the edge cases can't happen. Simplicity IS robustness.
The agent gets interrupted with the message and starts working. When done, it @mentions you back.
## Agent Types
### Role-based provider selection
Implementation and review are not the only agent roles. Use the right agent for the job:
Pick the right provider for each role:
- **Exploration agents** — Read the codebase, map dependencies, understand how something works. Launch these when you need context before making decisions.
- **Investigation agents** — Debug a problem, trace a bug, find root cause. Must NOT edit files.
- **Implementation agents** — Write code to meet acceptance criteria. **Default to Codex.**
- **Review agents** — Independently verify implementation meets criteria. Must NOT edit files. **Use the opposite provider from whoever implemented** — Claude reviews Codex's work, Codex reviews Claude's.
- **Second opinion agents** — When you're unsure about an approach, launch an agent (different provider if possible) to evaluate the plan and poke holes in it.
| Role | Provider | Why |
|---|---|---|
| Implementation | `codex` / `gpt-5.4` | Thorough, methodical, good at deep implementation |
| Review / Audit | `claude` / `opus` | Good design instinct, catches over-engineering |
| Investigation | `claude` / `opus` | Strong reasoning, good at tracing code paths |
| Planning | `claude` / `opus` with `--thinking on` | Extended thinking for complex problems |
Don't limit yourself to implement → review. Explore first if you need context. Get a second opinion if the design is tricky. The user is paying for thoroughness, not speed.
Cross-provider review: Codex implements Claude reviews. Claude implements → Codex reviews. Each catches the other's blind spots.
## Phase 3: Heartbeat Schedule
Set up a schedule to wake yourself periodically and check on the team:
```bash
schedule_id=$(paseo schedule create \
"Check on the team in room '<room>'. Read recent chat. Are agents making progress? Is anyone stuck or silent? Course-correct as needed. If the objective is complete, delete this schedule with: paseo schedule delete <schedule-id>" \
--every 10m \
--name "heartbeat-<task-slug>" \
--target self \
--expires-in 4h -q)
```
This ensures you don't lose track of agents even if they go quiet. Delete the schedule when the objective is complete:
```bash
paseo schedule delete <schedule-id>
```
## Phase 4: Coordinate Through Chat
All coordination happens in the room:
### Status checks
```bash
paseo chat read <room> --limit 10
```
### Directing work
```bash
paseo chat post <room> "The API is done. Now focus on the frontend integration." --mention <agent-id>
```
### Course-correcting
```bash
paseo chat post <room> "The tests you wrote are asserting the mock, not the real implementation. Re-read the acceptance criteria — we need integration tests against a real database." --mention <agent-id>
```
### Challenging agents
Agents hand-wave, over-engineer, and skip hard parts. Watch for:
- "Tests pass" without evidence → ask them to post the output
- Vague "I fixed it" → ask what exactly changed and why
- New abstractions → ask if they're necessary or if inline code would do
### Rotating agents
If an agent is stuck, drifting, or has accumulated too much stale context:
```bash
# Archive the stale agent
paseo stop <old-agent-id>
# (archiving happens automatically if the agent was part of a loop with --archive)
# Launch a fresh one
paseo run -d --mode full-access --provider codex \
--name "impl-<scope>-v2" \
"You are picking up work from a previous agent. Load the paseo-chat skill. Read room '<room>' from the beginning to catch up on the full history — the objective, what was done, what went wrong. Introduce yourself and continue from where the previous agent left off. @mention <orchestrator-id> when you've caught up." -q
```
The chat room has the full history. The new agent reads it and continues.
## Phase 5: Review
After implementation is done, launch a review agent (opposite provider):
```bash
paseo run -d --mode bypassPermissions --model opus \
--name "review-<scope>" \
"You are a reviewer on a team. Load the paseo-chat skill. Read room '<room>' to understand the objective and what was implemented.
Review the changes against the acceptance criteria in the room. Answer each criterion with YES/NO and evidence. Post your review to the room.
DO NOT edit files. @mention <orchestrator-id> when your review is posted." -q
```
If the review finds issues, direct the implementer to fix them via chat. If the implementer is archived, launch a fresh one that reads the room.
## Phase 6: Wrap Up
When the objective is met:
1. Post a summary to the room
2. Delete the heartbeat schedule: `paseo schedule delete <schedule-id>`
3. Report back to the user
## Naming Agents
Name sub-agents in kebab-case and include both role and scope.
Use kebab-case: `<role>-<scope>[-<slice>]`
Use this format:
Roles: `plan`, `impl`, `review`, `test`, `qa`, `verify`, `investigate`, `explore`, `refactor`
```text
<role>-<scope>[-<slice>]
```
Examples: `impl-issue-456`, `review-issue-456`, `impl-issue-456-api`, `investigate-ci-flake`
Examples:
- `plan-issue-456`
- `impl-issue-456`
- `review-issue-456`
- `test-issue-456`
- `qa-issue-456`
- `refactor-relay-auth`
- `investigate-ci-flake`
- `explore-agent-chat`
- `verify-pr-143`
- `impl-issue-456-api`
## Writing Agent Prompts
Approved role prefixes:
- `plan`
- `impl`
- `review`
- `test`
- `qa`
- `verify`
- `investigate`
- `explore`
- `refactor`
### Lead with behavior, not implementation
Rules:
- always use kebab-case
- no spaces
- no emoji
- no brackets
- no vague names
- always include the task scope
- add a final slice only when needed to disambiguate
Good names make chat, mentions, logs, and agent lists much easier to use.
## How to Write Agent Prompts
This is the most important skill. A good prompt produces good work. A bad prompt produces wasted time.
### Lead with behavior, not implementation details
When writing prompts for agents, describe the problem and desired outcome. Never dictate implementation.
**Bad** (micromanaging the agent):
```
Fix the cursor position in the autoformat handler. In `divider-autoformat.ts`,
after the replacement edit, set the cursor to `dividerEnd + 1`. Use
`applyEdit` with the new position.
```
This is bad because you're telling the agent WHICH file to edit, WHICH variable to set, and HOW to call the API. The agent will blindly follow your instructions even if they're wrong, instead of understanding the codebase and finding the right fix.
**Good** (product owner defining behavior):
```
## Bug: Caret position after typing ---
### What the user sees
The user types three dashes on an empty line. A divider appears.
The caret should now be blinking on a NEW EMPTY LINE below the divider,
ready for the user to keep typing.
### What actually happens
The caret stays on or before the divider. The user has to manually
press Enter to continue.
### Acceptance criteria
- Type ---, divider appears, caret is on a new empty paragraph BELOW
- Works at end of document (new paragraph created)
- Works in middle of document (caret on existing next line)
- User can immediately start typing — no extra keystrokes needed
```
This is good because the agent understands the PROBLEM and can find the right solution itself.
### Structure every prompt the same way
Every agent prompt should have:
1. **Context** — what repo, what feature, what's the current state
2. **Problem** — what's wrong, described as user-visible behavior
3. **Acceptance criteria** — specific, testable, behavioral statements
4. **How to verify** — describe the test scenario, not the test code. Tell them WHAT to test, not HOW to write the test.
5. **Constraints** — what they must NOT do (e.g., "do not bump version", "do not modify unrelated files")
6. **Workflow** — TDD, commit expectations, what commands to run
### Don't dictate implementation to agents
Trust the agent to:
- Find the right files
- Choose the right approach
- Write the right tests
- Structure the code properly
You tell them WHAT the user needs. They figure out HOW.
But DO discuss implementation with the **user**. When the user asks about architecture, show them types, interfaces, data flow, code examples. That's how you align on design before agents start working.
Describe the problem and desired outcome. Don't dictate files, variables, or approaches.
### Give complete context
Agents start with **zero knowledge** of your conversation. Everything they need must be in the prompt. Don't assume they know:
- What repo they're in (set cwd or tell them)
- What feature was recently added
- What decisions were made
- What was already tried
Agents start with zero knowledge. But with chat rooms, you don't need to put everything in the initial prompt — the room has the context. Just tell them to read it.
If agents should coordinate through chat, include:
- the room id
- the instruction to load `paseo-chat`
- what they should post there
- that they should check it very often
- that they should use `@agent-id` and replies when they want to notify someone directly
### Every prompt should have
## Always Review — Audit Agents Are Your Eyes
1. **Role** — what kind of work they do
2. **Room** — where to catch up and coordinate
3. **How to signal completion**@mention you when done
After every implementation agent finishes, spin up an **audit agent** to independently verify the work. Implementation agents will always drift — not because they're liars, but because that's how LLMs work. They sneak in workarounds, forget constraints, add defensive code, or silently skip hard parts. You cannot trust their self-assessment. Audit agents are how you actually see what happened.
Keep initial prompts short. Direct detailed work via chat @mentions after the agent is in the room.
### Audit prompts must be structured checklists, not open-ended
## Common Failures
Never say "review and let me know if you find issues." That produces vague, unfocused reports. Instead, define the **specific risks and acceptance criteria** you need verified, then ask **narrow yes/no questions**.
You know what the risks are because you wrote the acceptance criteria and you know what agents tend to get wrong. Turn that knowledge into verification questions.
**Bad** (open-ended, unfocused):
```
Review the recent changes. Check for issues, regressions, and code quality problems.
Report your findings.
```
This produces a rambling report that misses the things you actually care about.
**Good** (structured checklist with yes/no questions):
```
Verify the event stream redesign. Answer each question with YES/NO
and evidence (grep output, test output, line reference). DO NOT edit files.
## Acceptance criteria
1. Does `grep -rn "async \*stream(" providers/` return zero matches?
2. Does `grep -rn "Pushable" providers/` return zero matches?
3. Does `npm run typecheck` pass with zero errors?
4. Do all 77 manager tests pass? Run them and paste the summary.
5. Do all 8 integration tests pass against real Claude? Run them.
6. Is there any code path where events are emitted WITHOUT turnId?
grep for notifySubscribers and check each call site.
7. Are there any new helper functions or abstractions that didn't
exist before? If yes, justify each one.
8. Is there any try/catch that duplicates error handling from a caller?
## What to flag
- Any YES that should be NO, or NO that should be YES
- Any question you cannot confidently answer
```
### How to design audit questions
Your audit questions should come from three sources:
1. **Acceptance criteria** — turn each criterion into a verification question
2. **Known risks** — what does this provider (Claude/Codex) tend to get wrong? Ask about those specific patterns
3. **Constraints** — what was the agent told NOT to do? Verify they didn't do it
For Codex implementations, always ask:
- "Are there any new abstractions, helpers, or utility functions?"
- "Is there any defensive code (guard clauses, null checks, fallbacks) that could be designed away?"
- "Are there any coordination layers between components?"
For Claude implementations, always ask:
- "Did the agent skip any acceptance criteria?"
- "Are all edge cases actually tested, not just mentioned?"
- "Did the agent verify by running tests, or just claim they pass?"
### Use narrow, single-purpose audit passes
Don't run one big audit that checks everything. Run multiple focused passes, each with a single concern. This produces sharper results because the agent stays focused on one thing instead of skimming many.
The examples below are templates — every task will require different questions. Think about what specifically could go wrong for THIS task, with THIS provider, and write questions that probe those risks. The templates show the structure and tone, not the exact questions to ask.
**Over-engineering pass** (after Codex implements):
```
Answer YES/NO with line references. DO NOT edit files.
1. List every new function/method/class that didn't exist before this change.
For each one: is it necessary, or could the caller do this inline?
2. List every try/catch block in the changed code.
For each one: does the caller already handle this error?
3. List every guard clause (if !x return/throw) in the changed code.
For each one: can this state actually happen given the data flow?
4. Are there any callbacks, event bridges, or adapters connecting
two things that could talk directly?
```
**Testing pass** (after any implementation):
```
Answer YES/NO with evidence. DO NOT edit files.
1. Run the test suite. Paste the summary line. How many pass/fail/skip?
2. For each acceptance criterion, name the specific test that covers it.
If no test exists, say MISSING.
3. Are any tests using mocks where real implementations exist?
4. Are any tests trivially passing (asserting true, empty test bodies,
testing the mock instead of the code)?
5. Run each test in isolation — do any fail when run alone but pass
in the full suite? (ordering dependency)
```
**Correctness pass** (after any implementation):
```
Answer YES/NO with evidence. DO NOT edit files.
1. For each acceptance criterion, trace the code path that implements it.
Does the path actually work end-to-end?
2. Are there any TODO/FIXME/HACK comments in the changed code?
3. Does npm run typecheck pass? Paste the output.
4. Are there any type assertions (as X) or @ts-ignore in the changed code?
5. Run grep for [specific patterns that should/shouldn't exist].
```
**Cleanup pass** (after deletion/refactoring):
```
Answer YES/NO with evidence. DO NOT edit files.
1. grep for [each deleted concept]. Zero matches in production code?
2. Are there any imports of deleted modules?
3. Are there any dead code paths that reference deleted functionality?
4. Are there any comments referencing deleted concepts?
5. Does the test suite still pass after deletions?
```
Choose which passes to run based on the task and the provider that implemented it. You don't always need all of them — pick the ones that match the risks. Write fresh questions each time based on what you know about the specific task, the acceptance criteria, and what the implementation agent was likely to get wrong.
### Audit agents must run commands, not read claims
The audit agent must independently execute verification:
- Run `npm run typecheck` — don't accept "typecheck passes" from the impl agent
- Run the test suite — don't accept "all tests pass" from the impl agent
- Run `grep` to confirm deletions — don't accept "I deleted it" from the impl agent
- Read the actual diff — don't accept a summary of changes
### Use the opposite provider for audits
When Codex implements, use Claude to audit. When Claude implements, use Codex to audit. Each catches what the other misses.
Don't skip this step. Don't trust the implementation agent's self-assessment. Your audit agents are the only way you actually know what happened.
## Course-Correcting Agents
When an agent goes off track, send a follow-up via `paseo send`:
- Be specific about what's wrong
- Restate the acceptance criteria they're missing
- Don't give code fixes — describe the behavioral gap
```bash
paseo send <id> "The caret is still not on a new line after the divider.
Re-read the acceptance criteria: the user must be able to immediately
start typing on the line AFTER the divider without pressing Enter.
Whatever you did didn't achieve that. Try again."
```
## Challenging Agents
Agents lie, hand-wave, over-engineer, work around problems, solve the wrong thing, and don't check deeply enough. This is not occasional — it is the default. Your job is to catch it.
### The behaviors to watch for
- **Hand-waving** — Agent claims the fix works but didn't actually test the specific scenario. "Tests pass" doesn't mean the right tests exist.
- **Over-engineering** — Agent adds abstractions, helper functions, configuration layers, or generalization that wasn't asked for. Simple problem, simple fix.
- **Working around the problem** — Agent avoids the actual bug by adding a special case, a fallback, or a guard clause that masks the symptom instead of fixing the cause.
- **Lying** — Agent says "all tests pass" when they didn't run them, or "I verified this works" when they checked something else entirely.
- **Not checking deep enough** — Agent fixes the surface symptom without understanding why it happened. The fix works for the reported case but breaks in related scenarios.
- **Being confused** — Agent misunderstands the problem and solves something different from what was asked.
- **Solving the wrong thing** — Agent picks up on a secondary detail and optimizes that instead of addressing the core ask.
### How to catch it
Challenge agents by asking questions via `paseo send`:
- "What exactly did you change and why?"
- "What about [edge case X]? Did you test that?"
- "Have you considered [alternative approach Y]?"
- "Show me the test that covers [specific scenario]"
- "Why did you add [abstraction/helper/config]? Was that necessary?"
- "Is this fixing the root cause or masking the symptom?"
- "Walk me through the data flow after your change"
Don't accept the first answer. Push back. Ask follow-ups. The agent will refine its work when challenged.
### User signals
When the user says things like:
- **"simplify"** — the agent over-engineered
- **"think harder"** — the agent was shallow or confused
- **"I don't like this"** — the agent solved the wrong thing or worked around the problem
- **"this is wrong"** — the agent lied or didn't check deeply enough
These are signals to challenge the agent, not to tweak the implementation yourself. Send the user's concern to the agent as a question, or spin up a review agent specifically looking for the pattern the user flagged.
## Common Orchestrator Failures
These are the patterns that lead to bad outcomes. Avoid them.
### Micromanaging agents
**Symptom:** Your agent prompts contain implementation directives — "set variable X to Y", "modify file Z", "this function is wrong, change it to...". You're telling the agent HOW to code instead of WHAT the user needs.
**Fix:** Describe the behavior and acceptance criteria. The agent reads the codebase and finds the right approach. When you dictate implementation, the agent follows your instructions blindly — even when they're wrong — instead of using its own understanding of the code.
Note: discussing architecture, types, interfaces, and code examples with the **user** is not micromanaging — that's your design partner role.
### Skipping review
**Symptom:** Agent says "done", you tell the user "done", user finds bugs.
**Fix:** Always spin up a review agent. The implementation agent will claim success even when the fix is incomplete. Independent verification catches this.
### Hand-waving acceptance criteria
**Symptom:** "Fix the selection bug" with no behavioral definition of what "fixed" means.
**Fix:** Define exactly what the user should see. Be specific enough that someone who has never seen the app could verify it.
### Giving up on verification
**Symptom:** "The agent says tests pass" without checking what the tests actually test.
**Fix:** Tell the review agent what scenarios to verify. Design the test cases yourself (as a product owner) and tell the reviewer to check they exist.
### Anxious polling
**Symptom:** Agent is running, you start checking `paseo ls`, `paseo inspect`, `paseo logs` in a loop.
**Fix:** `paseo wait`. Trust it. Agents can take 30+ minutes. This is normal.
### Not providing feedback loops
**Symptom:** Launch agent, wait, get result, launch new agent for next issue. Never send follow-ups.
**Fix:** Use `paseo send` to course-correct running agents. They retain context. A follow-up is cheaper than a fresh agent.
### Being too passive
**Symptom:** Agent delivers something that doesn't quite match what the user wanted. You accept it and report back.
**Fix:** Push back. Send follow-ups. Restate criteria. Don't accept work that doesn't meet the bar. You're the quality gate.
## Clarifying Ambiguous Requests
When user requests are unclear:
1. **Research first** — spawn an investigation agent to understand the current state
2. **Ask clarifying questions** — after research, ask the user specific questions
3. **Present options** — offer approaches with trade-offs
4. **Get explicit confirmation** — never assume what the user wants
## Investigation vs Implementation
When asked to investigate:
- **Investigation agents MUST NOT fix issues** — they only identify, document, and report
- **Always ask for confirmation** — after investigation, present findings and ask: "Should I proceed with implementing fixes?"
- **Only implement if explicitly requested**
## Workflow
A typical orchestration cycle:
1. User describes what they want
2. You clarify if needed
3. You launch an implementation agent with behavioral acceptance criteria
4. You wait for it to finish
5. You launch a review agent to verify
6. If review passes → report to user
7. If review fails → send corrections to impl agent or launch a new one
8. Repeat until acceptance criteria are met
## Multi-Agent Coordination
When a task has independent parts:
- Launch agents in parallel with `paseo run -d`
- Each agent gets its own clear scope and acceptance criteria
- Wait for all to finish
- Review the combined output
When tasks are sequential:
- One agent at a time
- Feed the output/state from one into the next agent's prompt
- Review at the end
## Prefix Convention
Name all orchestrated agents with a prefix that indicates their role:
- `[Impl]` — implementation agents
- `[Review]` — review/verification agents
- `[Investigate]` — research/investigation agents
- `[Committee]` — committee planning agents
- `[Handoff]` — handoff agents
- **Not using chat** — agents lose context, you relay everything manually, coordination breaks down
- **Micromanaging** — telling agents which files to edit instead of what behavior to achieve
- **Skipping review** — trusting the implementation agent's self-assessment
- **No heartbeat** — agents go silent and you don't notice until the user asks
- **Keeping stale agents** — agent accumulated bad context, archive it and start fresh
- **Not posting the objective** — agents don't know what "done" looks like

View File

@@ -3,7 +3,7 @@ name: paseo
description: Paseo CLI reference for managing agents. Load this skill whenever you need to use paseo commands.
---
## CLI Commands
## Agent Commands
```bash
# List agents (directory-scoped by default)
@@ -12,15 +12,14 @@ paseo ls -g # All agents across all projects (global)
paseo ls --json # JSON output for parsing
# Create and run an agent (blocks until completion by default, no timeout)
paseo run --mode bypass "<prompt>"
paseo run --mode bypass --name "Task Name" "<prompt>"
paseo run --mode bypass --model opus "<prompt>"
paseo run --mode bypassPermissions "<prompt>"
paseo run --mode bypassPermissions --name "task-name" "<prompt>"
paseo run --mode bypassPermissions --model opus "<prompt>"
paseo run --mode full-access --provider codex "<prompt>"
# Wait timeout - limit how long run blocks (default: no limit)
paseo run --wait-timeout 30m "<prompt>" # Wait up to 30 minutes
paseo run --wait-timeout 1h "<prompt>" # Wait up to 1 hour
paseo run --wait-timeout 3600 "<prompt>" # Plain number = seconds
# Detached mode - runs in background, returns agent ID immediately
paseo run --detach "<prompt>"
@@ -28,9 +27,7 @@ paseo run -d "<prompt>" # Short form
# Structured output - agent returns only matching JSON
paseo run --output-schema '{"type":"object","properties":{"summary":{"type":"string"}},"required":["summary"]}' "<prompt>"
paseo run --output-schema schema.json "<prompt>" # Or from a file
# NOTE: --output-schema blocks until completion (cannot be used with --detach)
# NOTE: --wait-timeout applies to --output-schema runs too
# Worktrees - isolated git worktree for parallel feature development
paseo run --worktree feature-x "<prompt>"
@@ -67,15 +64,91 @@ paseo permit ls # List pending permission requests
paseo permit allow <agent-id> # Allow all pending for agent
paseo permit deny <agent-id> --all # Deny all pending
# Agent mode switching
paseo agent mode <agent-id> --list # Show available modes
paseo agent mode <agent-id> bypass # Set bypass mode
# Output formats
paseo ls --json # JSON output
paseo ls -q # IDs only (quiet mode, useful for scripting)
```
## Loop Commands
Iterative worker loops: launch a worker agent, verify its output, repeat until done.
```bash
# Start a loop
paseo loop run "<worker prompt>" [options]
--verify "<verifier prompt>" # Verifier agent prompt
--verify-check "<command>" # Shell command that must exit 0 (repeatable)
--name <name> # Optional loop name
--sleep <duration> # Delay between iterations (30s, 5m)
--max-iterations <n> # Maximum number of iterations
--max-time <duration> # Maximum total runtime (1h, 30m)
--provider <provider> # Worker agent provider (claude, codex)
--model <model> # Worker agent model
--verify-provider <provider> # Verifier agent provider
--verify-model <model> # Verifier agent model
--archive # Archive agents after each iteration
# Manage loops
paseo loop ls # List all loops
paseo loop inspect <id> # Show loop details and iterations
paseo loop logs <id> # Stream loop logs
paseo loop stop <id> # Stop a running loop
```
## Schedule Commands
Recurring time-based execution: run a prompt on a cron or interval schedule.
```bash
# Create a schedule
paseo schedule create "<prompt>" [options]
--every <duration> # Fixed interval (5m, 1h)
--cron <expr> # Cron expression
--name <name> # Optional schedule name
--target <self|new-agent|id> # Run target
--max-runs <n> # Maximum number of runs
--expires-in <duration> # Time to live for schedule
# Manage schedules
paseo schedule ls # List schedules
paseo schedule inspect <id> # Inspect a schedule
paseo schedule logs <id> # Show recent run logs
paseo schedule pause <id> # Pause a schedule
paseo schedule resume <id> # Resume a paused schedule
paseo schedule delete <id> # Delete a schedule
```
## Chat Commands
Asynchronous agent coordination through persistent chat rooms.
```bash
# Create a chat room
paseo chat create <name> --purpose "<description>"
# List and inspect rooms
paseo chat ls
paseo chat inspect <name-or-id>
# Post a message
paseo chat post <room> "<message>"
paseo chat post <room> "<message>" --reply-to <msg-id>
paseo chat post <room> "<message>" --mention <agent-id> # Repeatable
# Read messages
paseo chat read <room>
paseo chat read <room> --limit <n>
paseo chat read <room> --since <duration-or-timestamp>
paseo chat read <room> --agent <agent-id>
# Wait for new messages
paseo chat wait <room>
paseo chat wait <room> --timeout <duration>
# Delete a room
paseo chat delete <name-or-id>
```
## Available Models
**Claude (default provider)** — use aliases, CLI resolves to latest version:
@@ -89,7 +162,7 @@ paseo ls -q # IDs only (quiet mode, useful for scripting)
## Permissions
Always launch agents fully permissioned. Use `--mode bypass` for Claude and `--mode full-access` for Codex. Control behavior through **strict prompting**, not permission modes.
Always launch agents fully permissioned. Use `--mode bypassPermissions` for Claude and `--mode full-access` for Codex. Control behavior through **strict prompting**, not permission modes.
## Waiting for Agents
@@ -98,48 +171,19 @@ Both `paseo run` and `paseo wait` block until the agent completes. Trust them.
- `paseo run` waits **forever** by default (no timeout). Use `--wait-timeout` to set a limit.
- `paseo wait` also waits forever by default. Use `--timeout` to set a limit.
- Agent tasks can legitimately take 10, 20, or even 30+ minutes. This is normal.
- When a wait times out, **just re-run `paseo wait <id>`** — don't panic, don't start checking logs, don't inspect status. The agent is still working.
- When a wait times out, **just re-run `paseo wait <id>`** — don't panic, don't start checking logs.
- Do NOT poll with `paseo ls`, `paseo inspect`, or `paseo logs` in a loop to "check on" the agent.
- Only check logs/inspect if you have a **specific reason** to believe something is wrong.
- **Never launch a duplicate agent** because a wait timed out. The original is still running.
```bash
# Correct: just keep waiting
paseo wait <id> # timed out? just run it again:
paseo wait <id> # still going? keep waiting:
paseo wait <id> --timeout 300 # or use a longer timeout
# Wrong: anxious polling loop
paseo wait <id> # timed out
paseo ls # is it still running??
paseo inspect <id> # what's it doing??
paseo logs <id> # let me check the logs!!
```
## Composing Agents in Bash
`paseo run` blocks by default and `--output-schema` returns structured JSON, making it easy to compose agents in bash loops and pipelines.
**Implement-and-verify loop:**
```bash
while true; do
paseo run --provider codex "make the tests pass" >/dev/null
verdict=$(paseo run --provider claude --output-schema '{"type":"object","properties":{"criteria_met":{"type":"boolean"}},"required":["criteria_met"],"additionalProperties":false}' "ensure tests all pass")
if echo "$verdict" | jq -e '.criteria_met == true' >/dev/null; then
echo "criteria met"
break
fi
done
```
**Detach + wait pattern for parallel work:**
```bash
# Kick off parallel agents
api_id=$(paseo run -d --name "API impl" "implement the API" -q)
ui_id=$(paseo run -d --name "UI impl" "implement the UI" -q)
api_id=$(paseo run -d --name "impl-api" "implement the API" -q)
ui_id=$(paseo run -d --name "impl-ui" "implement the UI" -q)
# Wait for both to finish
paseo wait "$api_id"
paseo wait "$ui_id"
```