add cascade tool and script-driven orchestrator
tools/cascade: fire-and-forget delegation to cheap model; worker reports back to parent prompt when done. Parallelizable. scripts/u/cascade: script-driven hub-and-spoke orchestrator for terminal use — planner decomposes, workers execute in parallel, planner synthesizes.
This commit is contained in:
parent
a80bb79e4f
commit
b4be3a01d9
|
|
@ -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"
|
||||
|
|
@ -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: <instruction> [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: <your result>
|
||||
|
||||
TASK:
|
||||
${instruction}"
|
||||
|
||||
printf '%s\n' "$prompt" > "$B/prompt" || die "failed to submit prompt"
|
||||
printf 'spawned %s\n' "$B"
|
||||
Loading…
Reference in New Issue