From 4ac560ea1df77b17a7ba04c60811cbef129c299b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 11:35:36 +0000 Subject: [PATCH] fix: Migrate planner, predictor, supervisor to SDK (#6) Replace tmux-based run_formula_and_monitor() with synchronous agent_run() from lib/agent-sdk.sh, matching the pattern established in gardener-run.sh. Key changes per agent: - Drop agent-session.sh, use agent-sdk.sh (SID_FILE, LOGFILE) - Remove SESSION_NAME, PHASE_FILE, PHASE_POLL_INTERVAL (tmux/phase artifacts) - Create explicit worktree with cleanup trap (replaces formula session worktree) - Strip phase protocol from prompt footer (SDK mode needs no phase signals) - Preserve all prompt composition: context blocks, memory, journal, preflight Co-Authored-By: Claude Opus 4.6 (1M context) --- planner/planner-run.sh | 64 +++++++++++++++++++++------------- predictor/predictor-run.sh | 63 ++++++++++++++++++++------------- supervisor/supervisor-run.sh | 67 ++++++++++++++++++++++-------------- 3 files changed, 122 insertions(+), 72 deletions(-) diff --git a/planner/planner-run.sh b/planner/planner-run.sh index 878fdeb..265e6bc 100755 --- a/planner/planner-run.sh +++ b/planner/planner-run.sh @@ -1,10 +1,16 @@ #!/usr/bin/env bash # ============================================================================= -# planner-run.sh — Cron wrapper: direct planner execution via Claude + formula +# planner-run.sh — Cron wrapper: planner execution via SDK + formula # -# Runs daily (or on-demand). Guards against concurrent runs and low memory. -# Creates a tmux session with Claude (opus) reading formulas/run-planner.toml. -# No action issues — the planner is a nervous system component, not work. +# Synchronous bash loop using claude -p (one-shot invocation). +# No tmux sessions, no phase files — the bash script IS the state machine. +# +# Flow: +# 1. Guards: cron lock, memory check +# 2. Load formula (formulas/run-planner.toml) +# 3. Context: VISION.md, AGENTS.md, ops:RESOURCES.md, structural graph, +# planner memory, journal entries +# 4. agent_run(worktree, prompt) → Claude plans, may push knowledge updates # # Usage: # planner-run.sh [projects/disinto.toml] # project config (default: disinto) @@ -20,24 +26,22 @@ export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}" source "$FACTORY_ROOT/lib/env.sh" # Use planner-bot's own Forgejo identity (#747) FORGE_TOKEN="${FORGE_PLANNER_TOKEN:-${FORGE_TOKEN}}" -# shellcheck source=../lib/agent-session.sh -source "$FACTORY_ROOT/lib/agent-session.sh" # shellcheck source=../lib/formula-session.sh source "$FACTORY_ROOT/lib/formula-session.sh" # shellcheck source=../lib/worktree.sh source "$FACTORY_ROOT/lib/worktree.sh" # shellcheck source=../lib/guard.sh source "$FACTORY_ROOT/lib/guard.sh" +# shellcheck source=../lib/agent-sdk.sh +source "$FACTORY_ROOT/lib/agent-sdk.sh" LOG_FILE="$SCRIPT_DIR/planner.log" -# shellcheck disable=SC2034 # consumed by run_formula_and_monitor -SESSION_NAME="planner-${PROJECT_NAME}" -PHASE_FILE="/tmp/planner-session-${PROJECT_NAME}.phase" - -# shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh -PHASE_POLL_INTERVAL=15 - +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +LOGFILE="$LOG_FILE" +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +SID_FILE="/tmp/planner-session-${PROJECT_NAME}.sid" SCRATCH_FILE="/tmp/planner-${PROJECT_NAME}-scratch.md" +WORKTREE="/tmp/${PROJECT_NAME}-planner-run" log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } @@ -92,14 +96,18 @@ SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # ── Build prompt ───────────────────────────────────────────────────────── +# Reuse shared footer (API reference + environment), replace phase protocol +# shellcheck disable=SC2034 # consumed by build_prompt_footer +PHASE_FILE="" # not used in SDK mode build_prompt_footer " Relabel: curl -sf -H \"Authorization: token \${FORGE_TOKEN}\" -X PUT -H 'Content-Type: application/json' '${FORGE_API}/issues/{number}/labels' -d '{\"labels\":[LABEL_ID]}' Comment: curl -sf -H \"Authorization: token \${FORGE_TOKEN}\" -X POST -H 'Content-Type: application/json' '${FORGE_API}/issues/{number}/comments' -d '{\"body\":\"...\"}' Close: curl -sf -H \"Authorization: token \${FORGE_TOKEN}\" -X PATCH -H 'Content-Type: application/json' '${FORGE_API}/issues/{number}' -d '{\"state\":\"closed\"}' " +# Strip phase protocol — not used in SDK mode +PROMPT_FOOTER="${PROMPT_FOOTER%%## Phase protocol*}" -# shellcheck disable=SC2034 # consumed by run_formula_and_monitor -PROMPT="You are the strategic planner for ${FORGE_REPO}. Work through the formula below. You MUST write PHASE:done to '${PHASE_FILE}' when finished — the orchestrator will time you out if you return to the prompt without signalling. +PROMPT="You are the strategic planner for ${FORGE_REPO}. Work through the formula below. ## Project context ${CONTEXT_BLOCK}${MEMORY_BLOCK}${JOURNAL_BLOCK} @@ -113,12 +121,22 @@ ${SCRATCH_INSTRUCTION} ${PROMPT_FOOTER}" -# ── Run session ────────────────────────────────────────────────────────── -export CLAUDE_MODEL="opus" -run_formula_and_monitor "planner" +# ── Create worktree ────────────────────────────────────────────────────── +cd "$PROJECT_REPO_ROOT" +git fetch origin "$PRIMARY_BRANCH" 2>/dev/null || true +worktree_cleanup "$WORKTREE" +git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" --detach 2>/dev/null -# ── Cleanup scratch file on normal exit ────────────────────────────────── -# FINAL_PHASE already set by run_formula_and_monitor -if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then - rm -f "$SCRATCH_FILE" -fi +cleanup() { + worktree_cleanup "$WORKTREE" +} +trap cleanup EXIT + +# ── Run agent ───────────────────────────────────────────────────────────── +export CLAUDE_MODEL="opus" + +agent_run --worktree "$WORKTREE" "$PROMPT" +log "agent_run complete" + +rm -f "$SCRATCH_FILE" +log "--- Planner run done ---" diff --git a/predictor/predictor-run.sh b/predictor/predictor-run.sh index c7921c0..6a48493 100755 --- a/predictor/predictor-run.sh +++ b/predictor/predictor-run.sh @@ -1,10 +1,15 @@ #!/usr/bin/env bash # ============================================================================= -# predictor-run.sh — Cron wrapper: predictor execution via Claude + formula +# predictor-run.sh — Cron wrapper: predictor execution via SDK + formula # -# Runs daily (or on-demand). Guards against concurrent runs and low memory. -# Creates a tmux session with Claude (sonnet) reading formulas/run-predictor.toml. -# Files prediction/unreviewed issues for the planner to triage. +# Synchronous bash loop using claude -p (one-shot invocation). +# No tmux sessions, no phase files — the bash script IS the state machine. +# +# Flow: +# 1. Guards: cron lock, memory check +# 2. Load formula (formulas/run-predictor.toml) +# 3. Context: AGENTS.md, ops:RESOURCES.md, VISION.md, structural graph +# 4. agent_run(worktree, prompt) → Claude analyzes, writes to ops repo # # Usage: # predictor-run.sh [projects/disinto.toml] # project config (default: disinto) @@ -22,24 +27,22 @@ export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}" source "$FACTORY_ROOT/lib/env.sh" # Use predictor-bot's own Forgejo identity (#747) FORGE_TOKEN="${FORGE_PREDICTOR_TOKEN:-${FORGE_TOKEN}}" -# shellcheck source=../lib/agent-session.sh -source "$FACTORY_ROOT/lib/agent-session.sh" # shellcheck source=../lib/formula-session.sh source "$FACTORY_ROOT/lib/formula-session.sh" # shellcheck source=../lib/worktree.sh source "$FACTORY_ROOT/lib/worktree.sh" # shellcheck source=../lib/guard.sh source "$FACTORY_ROOT/lib/guard.sh" +# shellcheck source=../lib/agent-sdk.sh +source "$FACTORY_ROOT/lib/agent-sdk.sh" LOG_FILE="$SCRIPT_DIR/predictor.log" -# shellcheck disable=SC2034 # consumed by run_formula_and_monitor -SESSION_NAME="predictor-${PROJECT_NAME}" -PHASE_FILE="/tmp/predictor-session-${PROJECT_NAME}.phase" - -# shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh -PHASE_POLL_INTERVAL=15 - +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +LOGFILE="$LOG_FILE" +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +SID_FILE="/tmp/predictor-session-${PROJECT_NAME}.sid" SCRATCH_FILE="/tmp/predictor-${PROJECT_NAME}-scratch.md" +WORKTREE="/tmp/${PROJECT_NAME}-predictor-run" log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } @@ -62,10 +65,14 @@ SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # ── Build prompt ───────────────────────────────────────────────────────── +# Reuse shared footer (API reference + environment), replace phase protocol +# shellcheck disable=SC2034 # consumed by build_prompt_footer +PHASE_FILE="" # not used in SDK mode build_prompt_footer +# Strip phase protocol — not used in SDK mode +PROMPT_FOOTER="${PROMPT_FOOTER%%## Phase protocol*}" -# shellcheck disable=SC2034 # consumed by run_formula_and_monitor -PROMPT="You are the prediction agent (goblin) for ${FORGE_REPO}. Work through the formula below. You MUST write PHASE:done to '${PHASE_FILE}' when finished — the orchestrator will time you out if you return to the prompt without signalling. +PROMPT="You are the prediction agent (goblin) for ${FORGE_REPO}. Work through the formula below. Your role: abstract adversary. Find the project's biggest weakness, challenge planner claims, and generate evidence. Explore when uncertain (file a prediction), @@ -88,12 +95,22 @@ ${FORMULA_CONTENT} ${SCRATCH_INSTRUCTION} ${PROMPT_FOOTER}" -# ── Run session ────────────────────────────────────────────────────────── -export CLAUDE_MODEL="sonnet" -run_formula_and_monitor "predictor" +# ── Create worktree ────────────────────────────────────────────────────── +cd "$PROJECT_REPO_ROOT" +git fetch origin "$PRIMARY_BRANCH" 2>/dev/null || true +worktree_cleanup "$WORKTREE" +git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" --detach 2>/dev/null -# ── Cleanup scratch file on normal exit ────────────────────────────────── -# FINAL_PHASE already set by run_formula_and_monitor -if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then - rm -f "$SCRATCH_FILE" -fi +cleanup() { + worktree_cleanup "$WORKTREE" +} +trap cleanup EXIT + +# ── Run agent ───────────────────────────────────────────────────────────── +export CLAUDE_MODEL="sonnet" + +agent_run --worktree "$WORKTREE" "$PROMPT" +log "agent_run complete" + +rm -f "$SCRATCH_FILE" +log "--- Predictor run done ---" diff --git a/supervisor/supervisor-run.sh b/supervisor/supervisor-run.sh index d32bd79..5ffbbc9 100755 --- a/supervisor/supervisor-run.sh +++ b/supervisor/supervisor-run.sh @@ -1,14 +1,17 @@ #!/usr/bin/env bash # ============================================================================= -# supervisor-run.sh — Cron wrapper: supervisor execution via Claude + formula +# supervisor-run.sh — Cron wrapper: supervisor execution via SDK + formula # -# Runs every 20 minutes (or on-demand). Guards against concurrent runs and -# low memory. Collects metrics via preflight.sh, then creates a tmux session -# with Claude (sonnet) reading formulas/run-supervisor.toml. +# Synchronous bash loop using claude -p (one-shot invocation). +# No tmux sessions, no phase files — the bash script IS the state machine. # -# Replaces supervisor-poll.sh (bash orchestrator + claude -p one-shot) with -# formula-driven interactive Claude session matching the planner/predictor -# pattern. +# Flow: +# 1. Guards: cron lock, memory check +# 2. Housekeeping: clean up stale crashed worktrees +# 3. Collect pre-flight metrics (supervisor/preflight.sh) +# 4. Load formula (formulas/run-supervisor.toml) +# 5. Context: AGENTS.md, preflight metrics, structural graph +# 6. agent_run(worktree, prompt) → Claude monitors, may clean up # # Usage: # supervisor-run.sh [projects/disinto.toml] # project config (default: disinto) @@ -26,24 +29,22 @@ export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}" source "$FACTORY_ROOT/lib/env.sh" # Use supervisor-bot's own Forgejo identity (#747) FORGE_TOKEN="${FORGE_SUPERVISOR_TOKEN:-${FORGE_TOKEN}}" -# shellcheck source=../lib/agent-session.sh -source "$FACTORY_ROOT/lib/agent-session.sh" # shellcheck source=../lib/formula-session.sh source "$FACTORY_ROOT/lib/formula-session.sh" # shellcheck source=../lib/worktree.sh source "$FACTORY_ROOT/lib/worktree.sh" # shellcheck source=../lib/guard.sh source "$FACTORY_ROOT/lib/guard.sh" +# shellcheck source=../lib/agent-sdk.sh +source "$FACTORY_ROOT/lib/agent-sdk.sh" LOG_FILE="$SCRIPT_DIR/supervisor.log" -# shellcheck disable=SC2034 # consumed by run_formula_and_monitor -SESSION_NAME="supervisor-${PROJECT_NAME}" -PHASE_FILE="/tmp/supervisor-session-${PROJECT_NAME}.phase" - -# shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh -PHASE_POLL_INTERVAL=15 - +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +LOGFILE="$LOG_FILE" +# shellcheck disable=SC2034 # consumed by agent-sdk.sh +SID_FILE="/tmp/supervisor-session-${PROJECT_NAME}.sid" SCRATCH_FILE="/tmp/supervisor-${PROJECT_NAME}-scratch.md" +WORKTREE="/tmp/${PROJECT_NAME}-supervisor-run" log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } @@ -75,10 +76,14 @@ SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # ── Build prompt ───────────────────────────────────────────────────────── +# Reuse shared footer (API reference + environment), replace phase protocol +# shellcheck disable=SC2034 # consumed by build_prompt_footer +PHASE_FILE="" # not used in SDK mode build_prompt_footer +# Strip phase protocol — not used in SDK mode +PROMPT_FOOTER="${PROMPT_FOOTER%%## Phase protocol*}" -# shellcheck disable=SC2034 # consumed by run_formula_and_monitor -PROMPT="You are the supervisor agent for ${FORGE_REPO}. Work through the formula below. You MUST write PHASE:done to '${PHASE_FILE}' when finished — the orchestrator will time you out if you return to the prompt without signalling. +PROMPT="You are the supervisor agent for ${FORGE_REPO}. Work through the formula below. You have full shell access and --dangerously-skip-permissions. Fix what you can. File vault items for what you cannot. Do NOT ask permission — act first, report after. @@ -97,12 +102,22 @@ ${FORMULA_CONTENT} ${SCRATCH_INSTRUCTION} ${PROMPT_FOOTER}" -# ── Run session ────────────────────────────────────────────────────────── -export CLAUDE_MODEL="sonnet" -run_formula_and_monitor "supervisor" 1200 +# ── Create worktree ────────────────────────────────────────────────────── +cd "$PROJECT_REPO_ROOT" +git fetch origin "$PRIMARY_BRANCH" 2>/dev/null || true +worktree_cleanup "$WORKTREE" +git worktree add "$WORKTREE" "origin/${PRIMARY_BRANCH}" --detach 2>/dev/null -# ── Cleanup scratch file on normal exit ────────────────────────────────── -# FINAL_PHASE already set by run_formula_and_monitor -if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then - rm -f "$SCRATCH_FILE" -fi +cleanup() { + worktree_cleanup "$WORKTREE" +} +trap cleanup EXIT + +# ── Run agent ───────────────────────────────────────────────────────────── +export CLAUDE_MODEL="sonnet" + +agent_run --worktree "$WORKTREE" "$PROMPT" +log "agent_run complete" + +rm -f "$SCRATCH_FILE" +log "--- Supervisor run done ---"