diff --git a/scripts/u/cascade b/scripts/u/cascade new file mode 100755 index 0000000..a1664a0 --- /dev/null +++ b/scripts/u/cascade @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# u/cascade — script-driven hub-and-spoke cascading orchestrator. +# +# An expensive model decomposes a task into mechanical subtasks, then cheap +# models execute them in parallel. The script (not an agent) coordinates. +# +# Usage: cascade [flags] [prompt...] +# echo "prompt" | cascade [flags] +# +# Flags: +# -pb BACKEND planner backend (default: $OLLIE_CASCADE_PLAN_BACKEND or anthropic) +# -pm MODEL planner model (default: $OLLIE_CASCADE_PLAN_MODEL or claude-sonnet-4-20250514) +# -wb BACKEND worker backend (default: $OLLIE_CASCADE_BACKEND or ollama) +# -wm MODEL worker model (default: $OLLIE_CASCADE_MODEL or qwen2.5-coder:14b) +set -euo pipefail + +OLLIE="${OLLIE:-$HOME/mnt/ollie}" +B="$OLLIE/s/b" + +PB="${OLLIE_CASCADE_PLAN_BACKEND:-anthropic}" +PM="${OLLIE_CASCADE_PLAN_MODEL:-claude-sonnet-4-20250514}" +WB="${OLLIE_CASCADE_BACKEND:-ollama}" +WM="${OLLIE_CASCADE_MODEL:-qwen2.5-coder:14b}" + +die() { printf 'cascade: %s\n' "$*" >&2; exit 1; } + +while [ $# -gt 0 ]; do + case $1 in + -pb) PB=$2; shift ;; + -pm) PM=$2; shift ;; + -wb) WB=$2; shift ;; + -wm) WM=$2; shift ;; + --) shift; break ;; + -*) die "unknown flag: $1" ;; + *) break ;; + esac + shift +done + +if [ $# -gt 0 ]; then + task="$*" +elif ! [ -t 0 ]; then + task=$(cat) +else + die "prompt required" +fi +[ -z "$task" ] && die "empty prompt" + +# Step 1: Ask the planner to decompose into subtasks (JSON array). +plan=$("$B" -backend "$PB" -model "$PM" -json \ + "Decompose this task into independent, mechanical subtasks that a weak model can execute without judgment. Each subtask must be fully self-contained (include all needed context). Output a JSON array of objects with \"id\" (short string) and \"instruction\" (complete task description). Keep it minimal — only decompose what benefits from parallelism. + +TASK: +$task") + +# Validate we got JSON. +printf '%s' "$plan" | jq empty 2>/dev/null || die "planner did not return valid JSON: $plan" + +# Step 2: Spawn workers in parallel, collect session paths. +count=$(printf '%s' "$plan" | jq 'length') +[ "$count" -eq 0 ] && die "planner returned empty plan" + +pids=() +workers=() +base="cascade-$(date +%s%N)" + +for i in $(seq 0 $((count - 1))); do + id=$(printf '%s' "$plan" | jq -r ".[$i].id") + instruction=$(printf '%s' "$plan" | jq -r ".[$i].instruction") + sid="${base}-${id}" + workers+=("$sid") + + ( + "$B" -name "$sid" -backend "$WB" -model "$WM" -keep \ + "You are a worker agent. Execute the following task exactly. Output only the result. + +$instruction" > "/tmp/${sid}.out" 2>&1 + ) & + pids+=($!) +done + +# Step 3: Wait for all workers. +for pid in "${pids[@]}"; do wait "$pid" || true; done + +# Step 4: Gather results. +results="" +for sid in "${workers[@]}"; do + out=$(cat "/tmp/${sid}.out" 2>/dev/null || echo "(no output)") + results="${results} +--- ${sid} --- +${out} +" + rm -f "/tmp/${sid}.out" +done + +# Step 5: Ask the planner to synthesize. +"$B" -backend "$PB" -model "$PM" \ + "Synthesize these worker results into a final, coherent answer to the original task. + +ORIGINAL TASK: +$task + +WORKER RESULTS: +$results" diff --git a/tools/cascade b/tools/cascade new file mode 100755 index 0000000..7510f0d --- /dev/null +++ b/tools/cascade @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# ollie:parallel read +# description: Delegate a mechanical subtask to a cheaper/weaker model. Returns immediately; the worker reports back to your prompt when done. Use for tasks requiring no judgment — edits, lookups, transforms, formatting. +# Args: [id] +set -euo pipefail + +OLLIE="${OLLIE:-$HOME/mnt/ollie}" +BACKEND="${OLLIE_CASCADE_BACKEND:-ollama}" +MODEL="${OLLIE_CASCADE_MODEL:-qwen2.5-coder:14b}" + +die() { printf 'cascade: %s\n' "$*" >&2; exit 1; } + +instruction="${1:-}" +[ -z "$instruction" ] && die "instruction required" + +id="${2:-cascade-$(date +%s%N)-$$}" +[ -n "$OLLIE_SESSION_ID" ] && id="${OLLIE_SESSION_ID}__${id}" + +kv="name=${id} +cwd=$(pwd) +backend=${BACKEND} +model=${MODEL}" +printf '%s\n' "$kv" > "$OLLIE/s/new" || die "failed to create session" + +B="$OLLIE/s/$id" +deadline=$(( $(date +%s) + 5 )) +until [ -d "$B" ] || [ "$(date +%s)" -ge "$deadline" ]; do sleep 0.05; done +[ -d "$B" ] || die "timeout waiting for session $id" + +prompt="[parent_session_id=${OLLIE_SESSION_ID}] +[session_id=${id}] + +You are a worker agent. Execute the following task exactly. Output only the result. +When finished, write your result to: ${OLLIE}/s/${OLLIE_SESSION_ID}/prompt +Use this exact format: +[from=${id}] +STATUS: done +SUMMARY: + +TASK: +${instruction}" + +printf '%s\n' "$prompt" > "$B/prompt" || die "failed to submit prompt" +printf 'spawned %s\n' "$B"