#!/usr/bin/env bash # The agent's turn: one task from its queue, one branch, one pull request. # # Runs on boone's Mac, by hand or from launchd, under his own git and GitHub # credentials: the agent has no login of its own. Its branches start with # agent/, which is what keeps its PRs off the auto-approve path (the pickup # run merges them only after Mike or Jack approves). # # scripts/agent_worker.sh # take the top open task for the agent # scripts/agent_worker.sh --dry-run # say which task and branch; change nothing # # Needs on this Mac: the repo venv, `claude` (Claude Code CLI) and `gh` # (GitHub CLI, logged in as boone10). Log: logs/agent_worker.log. set -euo pipefail REPO="${PIPELINE_REPO:-$(cd "$(dirname "$0")/.." && pwd)}" PY="$REPO/.venv/bin/python" CLI="$PY $REPO/scripts/tracker_cli.py" BASE="${TRACKER_INTEGRATION_BRANCH:-develop}" MAX_TURNS="${AGENT_MAX_TURNS:-80}" LOG="$REPO/logs/agent_worker.log" DRY=0 [[ "${1:-}" == "--dry-run" ]] && DRY=1 for tool in claude gh git; do command -v "$tool" >/dev/null 2>&1 || { echo "ERROR: $tool is not installed or not on PATH" >&2; exit 2; } done [[ -x "$PY" ]] || { echo "ERROR: venv python not found at $PY" >&2; exit 2; } mkdir -p "$REPO/logs" cd "$REPO" AGENT="${TRACKER_AGENT:-$("$PY" -c 'from demoprep_app.tracker import store as S; print(S.agent_username())')}" TASK_JSON="$($CLI next --owner "$AGENT")" if [[ -z "$TASK_JSON" || "$TASK_JSON" == "null" ]]; then echo "no open task for $AGENT" exit 0 fi eval "$("$PY" - "$TASK_JSON" <<'PYEOF' import json, re, shlex, sys t = json.loads(sys.argv[1]) slug = re.sub(r"[^a-z0-9]+", "-", t["title"].lower()).strip("-")[:48].rstrip("-") or "change" print(f"TID={t['id']}; TKEY={shlex.quote(t['task'])}; TSTATUS={t['status']}; " f"TSLUG={slug}; TTITLE={shlex.quote(t['title'])}") PYEOF )" BRANCH="agent/t${TID}-${TSLUG}" WT="$REPO/pipeline/agent/t${TID}" echo "task $TKEY ($TSTATUS): $TTITLE -> $BRANCH" [[ $DRY == 1 ]] && exit 0 git fetch origin --prune if [[ ! -d "$WT/.git" && ! -f "$WT/.git" ]]; then git worktree add -B "$BRANCH" "$WT" "origin/$BASE" fi if [[ "$TSTATUS" == "open" ]]; then $CLI update "$TKEY" --by "$AGENT" --status in_progress --note "worker started on $BRANCH" >/dev/null fi PROMPT="$(cat "$REPO/scripts/agent_worker_prompt.md") REPO ROOT (venv and .env live here): $REPO BRANCH: $BRANCH BASE: $BASE AGENT: $AGENT TASK (JSON): $TASK_JSON" ALLOWED="Read,Edit,Write,Grep,Glob,Bash(git *),Bash($PY *)" echo "=== $(date '+%F %T') $TKEY on $BRANCH ===" >>"$LOG" ( cd "$WT" && claude -p "$PROMPT" --max-turns "$MAX_TURNS" --allowedTools "$ALLOWED" --output-format text ) \ 2>&1 | tee -a "$LOG" || true AHEAD="$(git -C "$WT" rev-list --count "origin/$BASE..HEAD")" if [[ "$AHEAD" == "0" ]]; then $CLI note "$TKEY" --by "$AGENT" --note "worker made no commit on $BRANCH; see logs/agent_worker.log" >/dev/null echo "no commit; task left as is" exit 0 fi git -C "$WT" push -u origin "$BRANCH" ORIGIN_REPO="$(git remote get-url origin | sed -E 's#.*github\.com[:/]##; s#\.git$##')" PR_URL="$(gh pr create --repo "$ORIGIN_REPO" --base "$BASE" --head "$BRANCH" \ --title "$TKEY: $TTITLE" \ --body "Opened by the agent ($AGENT) for $TKEY. Agent branches are never auto-approved: Mike or Jack reviews before the pickup run merges it." \ | tail -1)" $CLI update "$TKEY" --by "$AGENT" --status review --pr-url "$PR_URL" --note "PR opened by the worker" >/dev/null echo "PR: $PR_URL"