#!/usr/bin/env bash # Run one task through pi in a brand-new, zero-context session. # # Usage: # scripts/run-task.sh 0.1 # scripts/run-task.sh 0.1 --dry-run # print the prompt, run nothing # # Each invocation is a fresh pi session (--no-session). Nothing carries over between # tasks; that is the point. See tasks/AGENT-PROMPT.md. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" TASK_ID="${1:-}" DRY_RUN="${2:-}" if [[ -z "$TASK_ID" ]]; then echo "usage: scripts/run-task.sh [--dry-run]" >&2 echo "example: scripts/run-task.sh 0.1" >&2 exit 2 fi # Resolve the task file from its id prefix. shopt -s nullglob matches=(tasks/"$TASK_ID"-*.md) shopt -u nullglob if [[ ${#matches[@]} -eq 0 ]]; then echo "no task file found for id '$TASK_ID'" >&2 echo "available:" >&2 ls tasks/[0-9]*.md | sed 's|tasks/| |' >&2 exit 1 fi if [[ ${#matches[@]} -gt 1 ]]; then echo "ambiguous task id '$TASK_ID' matches:" >&2 printf ' %s\n' "${matches[@]}" >&2 exit 1 fi TASK_FILE="$(basename "${matches[0]}")" # Extract the prompt body from the fenced block in AGENT-PROMPT.md, then substitute. PROMPT="$( awk '/^```$/{f=!f; next} f' tasks/AGENT-PROMPT.md \ | sed -e "s|{{TASK_ID}}|$TASK_ID|g" -e "s|{{TASK_FILE}}|$TASK_FILE|g" )" if [[ -z "${PROMPT// }" ]]; then echo "failed to extract prompt template from tasks/AGENT-PROMPT.md" >&2 exit 1 fi if [[ "$DRY_RUN" == "--dry-run" ]]; then printf '%s\n' "$PROMPT" exit 0 fi # Default: claude-haiku-4-5. 200K context, reliable tool calling, needs # ANTHROPIC_API_KEY in the environment. # # The two homelab models were both evaluated and rejected as defaults on 2026-08-19: # # reasoning 16384 context, and pi's compaction.reserveTokens defaults to 16384, # so the compaction threshold computes to zero. pi also only evaluates # auto-compaction at run boundaries, never mid-turn (earendil-works/pi # issues 6339, 5512, 2871), so a tool loop grows unchecked until the # provider rejects it. Separately, under tool_choice "auto" it returns # tool_calls: [] and reasons in prose, which stalls an agent loop. # # ornith:35b 131K context and correct auto tool calling -- viable, and the right # local choice. Occupied by other work at time of writing. # # Switch with env vars: # PI_PROVIDER=homelab-ornith PI_MODEL=ornith:35b scripts/run-task.sh 0.1 # PROVIDER="${PI_PROVIDER:-homelab-ornith}" MODEL="${PI_MODEL:-ornith:35b}" # One fresh session file per task. This still guarantees zero context -- each task gets # its own new file and never reads another's -- while leaving the run inspectable: # # pi --session .task-runs/sessions/.jsonl # open it # pi --export .task-runs/sessions/.jsonl out.html # render it # # --no-session would also give zero context, but persists nothing to look at afterwards. SESSION_DIR=".task-runs/sessions" mkdir -p "$SESSION_DIR" SESSION_FILE="$SESSION_DIR/$TASK_ID.jsonl" rm -f "$SESSION_FILE" echo "task : $TASK_ID ($TASK_FILE)" echo "model : $PROVIDER/$MODEL" echo "session : $SESSION_FILE (fresh)" echo # --print is required, not cosmetic. Without it pi starts its interactive TUI; # under run-all.sh stdout is a file and stdin may be /dev/null, so the TUI renders # nothing until exit and can sit waiting for input that never comes. --approve # trusts project-local files (AGENTS.md and friends) for this run, which would # otherwise raise a prompt no one is there to answer. exec pi --provider "$PROVIDER" --model "$MODEL" \ --print --approve \ --session "$SESSION_FILE" "$PROMPT"