Merge pull request 'fix: Migrate planner, predictor, supervisor to SDK (#6)' (#17) from fix/issue-6 into main
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

This commit is contained in:
dev-bot 2026-03-28 13:32:18 +00:00
commit 47d22e014b
5 changed files with 113 additions and 80 deletions

View file

@ -92,11 +92,8 @@ Supported actions:
The commit-and-pr step converts JSONL to JSON array. The orchestrator executes 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." actions after the PR merges. Do NOT call mutation APIs directly during the run."
# Reuse shared footer (API reference + environment), replace phase protocol build_sdk_prompt_footer "$GARDENER_API_EXTRA"
# shellcheck disable=SC2034 # consumed by build_prompt_footer PROMPT_FOOTER="${PROMPT_FOOTER}## Completion protocol (REQUIRED)
PHASE_FILE="" # not used in SDK mode
build_prompt_footer "$GARDENER_API_EXTRA"
PROMPT_FOOTER="${PROMPT_FOOTER%%## Phase protocol*}## Completion protocol (REQUIRED)
When the commit-and-pr step creates a PR, write the PR number and stop: When the commit-and-pr step creates a PR, write the PR number and stop:
echo \"\$PR_NUMBER\" > '${GARDENER_PR_FILE}' echo \"\$PR_NUMBER\" > '${GARDENER_PR_FILE}'
Then STOP. Do NOT write PHASE: signals — the orchestrator handles CI, review, and merge. Then STOP. Do NOT write PHASE: signals — the orchestrator handles CI, review, and merge.

View file

@ -291,6 +291,33 @@ build_graph_section() {
fi 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" || return
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 ────────────────────────────────────────────── # ── Prompt + monitor helpers ──────────────────────────────────────────────
# build_prompt_footer [EXTRA_API_LINES] # build_prompt_footer [EXTRA_API_LINES]

View file

@ -1,10 +1,16 @@
#!/usr/bin/env bash #!/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. # Synchronous bash loop using claude -p (one-shot invocation).
# Creates a tmux session with Claude (opus) reading formulas/run-planner.toml. # No tmux sessions, no phase files — the bash script IS the state machine.
# No action issues — the planner is a nervous system component, not work. #
# 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: # Usage:
# planner-run.sh [projects/disinto.toml] # project config (default: disinto) # 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" source "$FACTORY_ROOT/lib/env.sh"
# Use planner-bot's own Forgejo identity (#747) # Use planner-bot's own Forgejo identity (#747)
FORGE_TOKEN="${FORGE_PLANNER_TOKEN:-${FORGE_TOKEN}}" 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 # shellcheck source=../lib/formula-session.sh
source "$FACTORY_ROOT/lib/formula-session.sh" source "$FACTORY_ROOT/lib/formula-session.sh"
# shellcheck source=../lib/worktree.sh # shellcheck source=../lib/worktree.sh
source "$FACTORY_ROOT/lib/worktree.sh" source "$FACTORY_ROOT/lib/worktree.sh"
# shellcheck source=../lib/guard.sh # shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/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" LOG_FILE="$SCRIPT_DIR/planner.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor # shellcheck disable=SC2034 # consumed by agent-sdk.sh
SESSION_NAME="planner-${PROJECT_NAME}" LOGFILE="$LOG_FILE"
PHASE_FILE="/tmp/planner-session-${PROJECT_NAME}.phase" # shellcheck disable=SC2034 # consumed by agent-sdk.sh
SID_FILE="/tmp/planner-session-${PROJECT_NAME}.sid"
# shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh
PHASE_POLL_INTERVAL=15
SCRATCH_FILE="/tmp/planner-${PROJECT_NAME}-scratch.md" 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"; } 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") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE")
# ── Build prompt ───────────────────────────────────────────────────────── # ── 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]}' 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\":\"...\"}' 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\"}' 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.
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.
## Project context ## Project context
${CONTEXT_BLOCK}${MEMORY_BLOCK}${JOURNAL_BLOCK} ${CONTEXT_BLOCK}${MEMORY_BLOCK}${JOURNAL_BLOCK}
@ -113,12 +116,14 @@ ${SCRATCH_INSTRUCTION}
${PROMPT_FOOTER}" ${PROMPT_FOOTER}"
# ── Run session ────────────────────────────────────────────────────────── # ── Create worktree ──────────────────────────────────────────────────────
export CLAUDE_MODEL="opus" formula_worktree_setup "$WORKTREE"
run_formula_and_monitor "planner"
# ── Cleanup scratch file on normal exit ────────────────────────────────── # ── Run agent ─────────────────────────────────────────────────────────────
# FINAL_PHASE already set by run_formula_and_monitor export CLAUDE_MODEL="opus"
if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then
rm -f "$SCRATCH_FILE" agent_run --worktree "$WORKTREE" "$PROMPT"
fi log "agent_run complete"
rm -f "$SCRATCH_FILE"
log "--- Planner run done ---"

View file

@ -1,10 +1,15 @@
#!/usr/bin/env bash #!/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. # Synchronous bash loop using claude -p (one-shot invocation).
# Creates a tmux session with Claude (sonnet) reading formulas/run-predictor.toml. # No tmux sessions, no phase files — the bash script IS the state machine.
# Files prediction/unreviewed issues for the planner to triage. #
# 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: # Usage:
# predictor-run.sh [projects/disinto.toml] # project config (default: disinto) # 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" source "$FACTORY_ROOT/lib/env.sh"
# Use predictor-bot's own Forgejo identity (#747) # Use predictor-bot's own Forgejo identity (#747)
FORGE_TOKEN="${FORGE_PREDICTOR_TOKEN:-${FORGE_TOKEN}}" 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 # shellcheck source=../lib/formula-session.sh
source "$FACTORY_ROOT/lib/formula-session.sh" source "$FACTORY_ROOT/lib/formula-session.sh"
# shellcheck source=../lib/worktree.sh # shellcheck source=../lib/worktree.sh
source "$FACTORY_ROOT/lib/worktree.sh" source "$FACTORY_ROOT/lib/worktree.sh"
# shellcheck source=../lib/guard.sh # shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/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" LOG_FILE="$SCRIPT_DIR/predictor.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor # shellcheck disable=SC2034 # consumed by agent-sdk.sh
SESSION_NAME="predictor-${PROJECT_NAME}" LOGFILE="$LOG_FILE"
PHASE_FILE="/tmp/predictor-session-${PROJECT_NAME}.phase" # shellcheck disable=SC2034 # consumed by agent-sdk.sh
SID_FILE="/tmp/predictor-session-${PROJECT_NAME}.sid"
# shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh
PHASE_POLL_INTERVAL=15
SCRATCH_FILE="/tmp/predictor-${PROJECT_NAME}-scratch.md" 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"; } 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") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE")
# ── Build prompt ───────────────────────────────────────────────────────── # ── 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.
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.
Your role: abstract adversary. Find the project's biggest weakness, challenge Your role: abstract adversary. Find the project's biggest weakness, challenge
planner claims, and generate evidence. Explore when uncertain (file a prediction), planner claims, and generate evidence. Explore when uncertain (file a prediction),
@ -88,12 +91,12 @@ ${FORMULA_CONTENT}
${SCRATCH_INSTRUCTION} ${SCRATCH_INSTRUCTION}
${PROMPT_FOOTER}" ${PROMPT_FOOTER}"
# ── Run session ────────────────────────────────────────────────────────── # ── Create worktree ──────────────────────────────────────────────────────
export CLAUDE_MODEL="sonnet" formula_worktree_setup "$WORKTREE"
run_formula_and_monitor "predictor"
# ── Cleanup scratch file on normal exit ────────────────────────────────── # ── Run agent ─────────────────────────────────────────────────────────────
# FINAL_PHASE already set by run_formula_and_monitor agent_run --worktree "$WORKTREE" "$PROMPT"
if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then log "agent_run complete"
rm -f "$SCRATCH_FILE"
fi rm -f "$SCRATCH_FILE"
log "--- Predictor run done ---"

View file

@ -1,14 +1,17 @@
#!/usr/bin/env bash #!/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 # Synchronous bash loop using claude -p (one-shot invocation).
# low memory. Collects metrics via preflight.sh, then creates a tmux session # No tmux sessions, no phase files — the bash script IS the state machine.
# with Claude (sonnet) reading formulas/run-supervisor.toml.
# #
# Replaces supervisor-poll.sh (bash orchestrator + claude -p one-shot) with # Flow:
# formula-driven interactive Claude session matching the planner/predictor # 1. Guards: cron lock, memory check
# pattern. # 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: # Usage:
# supervisor-run.sh [projects/disinto.toml] # project config (default: disinto) # 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" source "$FACTORY_ROOT/lib/env.sh"
# Use supervisor-bot's own Forgejo identity (#747) # Use supervisor-bot's own Forgejo identity (#747)
FORGE_TOKEN="${FORGE_SUPERVISOR_TOKEN:-${FORGE_TOKEN}}" 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 # shellcheck source=../lib/formula-session.sh
source "$FACTORY_ROOT/lib/formula-session.sh" source "$FACTORY_ROOT/lib/formula-session.sh"
# shellcheck source=../lib/worktree.sh # shellcheck source=../lib/worktree.sh
source "$FACTORY_ROOT/lib/worktree.sh" source "$FACTORY_ROOT/lib/worktree.sh"
# shellcheck source=../lib/guard.sh # shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/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" LOG_FILE="$SCRIPT_DIR/supervisor.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor # shellcheck disable=SC2034 # consumed by agent-sdk.sh
SESSION_NAME="supervisor-${PROJECT_NAME}" LOGFILE="$LOG_FILE"
PHASE_FILE="/tmp/supervisor-session-${PROJECT_NAME}.phase" # shellcheck disable=SC2034 # consumed by agent-sdk.sh
SID_FILE="/tmp/supervisor-session-${PROJECT_NAME}.sid"
# shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh
PHASE_POLL_INTERVAL=15
SCRATCH_FILE="/tmp/supervisor-${PROJECT_NAME}-scratch.md" 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"; } 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") SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE")
# ── Build prompt ───────────────────────────────────────────────────────── # ── Build prompt ─────────────────────────────────────────────────────────
build_prompt_footer build_sdk_prompt_footer
export CLAUDE_MODEL="sonnet"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor # ── Create worktree (before prompt assembly so trap is set early) ────────
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. 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. 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. 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} ${SCRATCH_INSTRUCTION}
${PROMPT_FOOTER}" ${PROMPT_FOOTER}"
# ── Run session ────────────────────────────────────────────────────────── # ── Run agent ─────────────────────────────────────────────────────────────
export CLAUDE_MODEL="sonnet" agent_run --worktree "$WORKTREE" "$PROMPT"
run_formula_and_monitor "supervisor" 1200 log "agent_run complete"
# ── Cleanup scratch file on normal exit ────────────────────────────────── rm -f "$SCRATCH_FILE"
# FINAL_PHASE already set by run_formula_and_monitor log "--- Supervisor run done ---"
if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then
rm -f "$SCRATCH_FILE"
fi