diff --git a/gardener/gardener-run.sh b/gardener/gardener-run.sh index 6c964dc..31aa8c0 100755 --- a/gardener/gardener-run.sh +++ b/gardener/gardener-run.sh @@ -92,11 +92,8 @@ Supported actions: The commit-and-pr step converts JSONL to JSON array. The orchestrator executes actions after the PR merges. Do NOT call mutation APIs directly during the run." -# 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 "$GARDENER_API_EXTRA" -PROMPT_FOOTER="${PROMPT_FOOTER%%## Phase protocol*}## Completion protocol (REQUIRED) +build_sdk_prompt_footer "$GARDENER_API_EXTRA" +PROMPT_FOOTER="${PROMPT_FOOTER}## Completion protocol (REQUIRED) When the commit-and-pr step creates a PR, write the PR number and stop: echo \"\$PR_NUMBER\" > '${GARDENER_PR_FILE}' Then STOP. Do NOT write PHASE: signals — the orchestrator handles CI, review, and merge. diff --git a/lib/formula-session.sh b/lib/formula-session.sh index 670da95..7f200c5 100644 --- a/lib/formula-session.sh +++ b/lib/formula-session.sh @@ -291,6 +291,33 @@ build_graph_section() { fi } +# ── SDK helpers ─────────────────────────────────────────────────────────── + +# build_sdk_prompt_footer [EXTRA_API_LINES] +# Like build_prompt_footer but omits the phase protocol section (SDK mode). +# Sets PROMPT_FOOTER. +build_sdk_prompt_footer() { + # shellcheck disable=SC2034 # consumed by build_prompt_footer + PHASE_FILE="" # not used in SDK mode + build_prompt_footer "${1:-}" + PROMPT_FOOTER="${PROMPT_FOOTER%%## Phase protocol*}" +} + +# formula_worktree_setup WORKTREE +# Creates an isolated worktree for synchronous formula execution. +# Fetches primary branch, cleans stale worktree, creates new one, and +# sets an EXIT trap for cleanup. +# Requires globals: PROJECT_REPO_ROOT, PRIMARY_BRANCH. +formula_worktree_setup() { + local worktree="$1" + 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 + # shellcheck disable=SC2064 # expand worktree now, not at trap time + trap "worktree_cleanup '$worktree'" EXIT +} + # ── Prompt + monitor helpers ────────────────────────────────────────────── # build_prompt_footer [EXTRA_API_LINES] diff --git a/planner/planner-run.sh b/planner/planner-run.sh index 878fdeb..313f6ef 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,13 @@ SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # ── Build prompt ───────────────────────────────────────────────────────── -build_prompt_footer " +build_sdk_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\"}' " -# 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 +116,14 @@ ${SCRATCH_INSTRUCTION} ${PROMPT_FOOTER}" -# ── Run session ────────────────────────────────────────────────────────── -export CLAUDE_MODEL="opus" -run_formula_and_monitor "planner" +# ── Create worktree ────────────────────────────────────────────────────── +formula_worktree_setup "$WORKTREE" -# ── 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 +# ── 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..fb9bf51 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,10 @@ SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # ── Build prompt ───────────────────────────────────────────────────────── -build_prompt_footer +build_sdk_prompt_footer +export CLAUDE_MODEL="sonnet" -# 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 +91,12 @@ ${FORMULA_CONTENT} ${SCRATCH_INSTRUCTION} ${PROMPT_FOOTER}" -# ── Run session ────────────────────────────────────────────────────────── -export CLAUDE_MODEL="sonnet" -run_formula_and_monitor "predictor" +# ── Create worktree ────────────────────────────────────────────────────── +formula_worktree_setup "$WORKTREE" -# ── 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 +# ── Run agent ───────────────────────────────────────────────────────────── +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..129666f 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,13 @@ SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # ── Build prompt ───────────────────────────────────────────────────────── -build_prompt_footer +build_sdk_prompt_footer +export CLAUDE_MODEL="sonnet" -# 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. +# ── Create worktree (before prompt assembly so trap is set early) ──────── +formula_worktree_setup "$WORKTREE" + +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 +101,9 @@ ${FORMULA_CONTENT} ${SCRATCH_INSTRUCTION} ${PROMPT_FOOTER}" -# ── Run session ────────────────────────────────────────────────────────── -export CLAUDE_MODEL="sonnet" -run_formula_and_monitor "supervisor" 1200 +# ── Run agent ───────────────────────────────────────────────────────────── +agent_run --worktree "$WORKTREE" "$PROMPT" +log "agent_run complete" -# ── 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 +rm -f "$SCRATCH_FILE" +log "--- Supervisor run done ---"