From 29876acae5af67670a1396e8d131a2847973087e Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 28 Mar 2026 01:13:42 +0700 Subject: [PATCH] feat(skills): rewrite skills to use CLI, reframe orchestrator as chat-first team lead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- skills/paseo-chat/SKILL.md | 116 ++--- skills/paseo-chat/bin/chat.sh | 634 ---------------------------- skills/paseo-committee/SKILL.md | 2 +- skills/paseo-handoff/SKILL.md | 2 +- skills/paseo-loop/SKILL.md | 322 +++----------- skills/paseo-loop/bin/loop.sh | 540 ------------------------ skills/paseo-orchestrator/SKILL.md | 650 +++++++++-------------------- skills/paseo/SKILL.md | 132 ++++-- 8 files changed, 369 insertions(+), 2029 deletions(-) delete mode 100755 skills/paseo-chat/bin/chat.sh delete mode 100755 skills/paseo-loop/bin/loop.sh diff --git a/skills/paseo-chat/SKILL.md b/skills/paseo-chat/SKILL.md index b77d59635..83edc8ef5 100644 --- a/skills/paseo-chat/SKILL.md +++ b/skills/paseo-chat/SKILL.md @@ -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 ` 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 ``` ### 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 +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 diff --git a/skills/paseo-chat/bin/chat.sh b/skills/paseo-chat/bin/chat.sh deleted file mode 100755 index 22aa139d5..000000000 --- a/skills/paseo-chat/bin/chat.sh +++ /dev/null @@ -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 diff --git a/skills/paseo-committee/SKILL.md b/skills/paseo-committee/SKILL.md index de02ca55a..9d8a96f12 100644 --- a/skills/paseo-committee/SKILL.md +++ b/skills/paseo-committee/SKILL.md @@ -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) ``` diff --git a/skills/paseo-handoff/SKILL.md b/skills/paseo-handoff/SKILL.md index dfdadeae7..3e889d4ff 100644 --- a/skills/paseo-handoff/SKILL.md +++ b/skills/paseo-handoff/SKILL.md @@ -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 diff --git a/skills/paseo-loop/SKILL.md b/skills/paseo-loop/SKILL.md index 471889038..ae51fb10c 100644 --- a/skills/paseo-loop/SKILL.md +++ b/skills/paseo-loop/SKILL.md @@ -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 ""` — a verifier agent judges the worker's output +- `--verify-check ""` — 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=` -- 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 ``. - -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// - 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 # Show details and iteration history +paseo loop logs # Stream logs +paseo loop stop # 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 diff --git a/skills/paseo-loop/bin/loop.sh b/skills/paseo-loop/bin/loop.sh deleted file mode 100755 index 43d8a1e81..000000000 --- a/skills/paseo-loop/bin/loop.sh +++ /dev/null @@ -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 - - -The previous iteration reported the following: - -$prior_reason -" - 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 diff --git a/skills/paseo-orchestrator/SKILL.md b/skills/paseo-orchestrator/SKILL.md index da1c8d0a3..adf2a9ba2 100644 --- a/skills/paseo-orchestrator/SKILL.md +++ b/skills/paseo-orchestrator/SKILL.md @@ -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 --purpose "" +``` -- **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 "## Objective + -## Chat Rooms +## Acceptance Criteria +- [ ] +- [ ] -When agents need asynchronous coordination, shared status, or a lightweight handoff channel, use chat rooms. +## Constraints +- +- " +``` -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-" \ + "You are an implementation engineer on a team. -## Provider Selection: Claude vs Codex +Load the paseo-chat skill. Read 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 "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 +``` -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 ''. 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 " \ + --every 10m \ + --name "heartbeat-" \ + --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 +``` + +## Phase 4: Coordinate Through Chat + +All coordination happens in the room: + +### Status checks + +```bash +paseo chat read --limit 10 +``` + +### Directing work + +```bash +paseo chat post "The API is done. Now focus on the frontend integration." --mention +``` + +### Course-correcting + +```bash +paseo chat post "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 +``` + +### 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 +# (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--v2" \ + "You are picking up work from a previous agent. Load the paseo-chat skill. Read 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 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-" \ + "You are a reviewer on a team. Load the paseo-chat skill. Read 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 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 ` +3. Report back to the user ## Naming Agents -Name sub-agents in kebab-case and include both role and scope. +Use kebab-case: `-[-]` -Use this format: +Roles: `plan`, `impl`, `review`, `test`, `qa`, `verify`, `investigate`, `explore`, `refactor` -```text --[-] -``` +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 "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 diff --git a/skills/paseo/SKILL.md b/skills/paseo/SKILL.md index d4f458c2e..c38de3b44 100644 --- a/skills/paseo/SKILL.md +++ b/skills/paseo/SKILL.md @@ -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 "" -paseo run --mode bypass --name "Task Name" "" -paseo run --mode bypass --model opus "" +paseo run --mode bypassPermissions "" +paseo run --mode bypassPermissions --name "task-name" "" +paseo run --mode bypassPermissions --model opus "" paseo run --mode full-access --provider codex "" # Wait timeout - limit how long run blocks (default: no limit) paseo run --wait-timeout 30m "" # Wait up to 30 minutes paseo run --wait-timeout 1h "" # Wait up to 1 hour -paseo run --wait-timeout 3600 "" # Plain number = seconds # Detached mode - runs in background, returns agent ID immediately paseo run --detach "" @@ -28,9 +27,7 @@ paseo run -d "" # Short form # Structured output - agent returns only matching JSON paseo run --output-schema '{"type":"object","properties":{"summary":{"type":"string"}},"required":["summary"]}' "" -paseo run --output-schema schema.json "" # 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 "" @@ -67,15 +64,91 @@ paseo permit ls # List pending permission requests paseo permit allow # Allow all pending for agent paseo permit deny --all # Deny all pending -# Agent mode switching -paseo agent mode --list # Show available modes -paseo agent mode 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 "" [options] + --verify "" # Verifier agent prompt + --verify-check "" # Shell command that must exit 0 (repeatable) + --name # Optional loop name + --sleep # Delay between iterations (30s, 5m) + --max-iterations # Maximum number of iterations + --max-time # Maximum total runtime (1h, 30m) + --provider # Worker agent provider (claude, codex) + --model # Worker agent model + --verify-provider # Verifier agent provider + --verify-model # Verifier agent model + --archive # Archive agents after each iteration + +# Manage loops +paseo loop ls # List all loops +paseo loop inspect # Show loop details and iterations +paseo loop logs # Stream loop logs +paseo loop stop # 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 "" [options] + --every # Fixed interval (5m, 1h) + --cron # Cron expression + --name # Optional schedule name + --target # Run target + --max-runs # Maximum number of runs + --expires-in # Time to live for schedule + +# Manage schedules +paseo schedule ls # List schedules +paseo schedule inspect # Inspect a schedule +paseo schedule logs # Show recent run logs +paseo schedule pause # Pause a schedule +paseo schedule resume # Resume a paused schedule +paseo schedule delete # Delete a schedule +``` + +## Chat Commands + +Asynchronous agent coordination through persistent chat rooms. + +```bash +# Create a chat room +paseo chat create --purpose "" + +# List and inspect rooms +paseo chat ls +paseo chat inspect + +# Post a message +paseo chat post "" +paseo chat post "" --reply-to +paseo chat post "" --mention # Repeatable + +# Read messages +paseo chat read +paseo chat read --limit +paseo chat read --since +paseo chat read --agent + +# Wait for new messages +paseo chat wait +paseo chat wait --timeout + +# Delete a room +paseo chat delete +``` + ## 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 `** — 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 `** — 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 # timed out? just run it again: -paseo wait # still going? keep waiting: -paseo wait --timeout 300 # or use a longer timeout - -# Wrong: anxious polling loop -paseo wait # timed out -paseo ls # is it still running?? -paseo inspect # what's it doing?? -paseo logs # 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" ```