fix: gardener migration — run-gardener.toml via direct cron, remove legacy scripts (#490)
Rewrite gardener-run.sh as direct cron runner (matching supervisor/planner/ predictor pattern): lock guard, memory check, worktree, tmux session with Claude sonnet + formulas/run-gardener.toml, phase monitoring, cleanup. - Delete gardener-poll.sh and gardener-agent.sh (superseded) - Extract consume_escalation_reply() to lib/formula-session.sh (shared by gardener and supervisor, eliminates duplicate blocks) - Update AGENTS.md, gardener/AGENTS.md, lib/AGENTS.md, CI smoke test, and cross-references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
685ca1034a
commit
b630c6fcc1
11 changed files with 125 additions and 581 deletions
|
|
@ -1,75 +1,103 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# gardener-run.sh — Cron wrapper: files action issue for run-gardener formula
|
||||
# gardener-run.sh — Cron wrapper: gardener execution via Claude + formula
|
||||
#
|
||||
# Runs 2x/day (or on-demand). Guards against concurrent runs and low memory.
|
||||
# Files an action issue referencing formulas/run-gardener.toml; the action-agent
|
||||
# picks it up and executes the gardener steps in an interactive Claude session.
|
||||
# Runs 4x/day (or on-demand). Guards against concurrent runs and low memory.
|
||||
# Creates a tmux session with Claude (sonnet) reading formulas/run-gardener.toml.
|
||||
# No action issues — the gardener is a nervous system component, not work (AD-001).
|
||||
#
|
||||
# Usage:
|
||||
# gardener-run.sh [projects/disinto.toml] # project config (default: disinto)
|
||||
#
|
||||
# Cron: 0 0,6,12,18 * * * cd /home/debian/dark-factory && bash gardener/gardener-run.sh projects/disinto.toml
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
FACTORY_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
FACTORY_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Load shared environment (with optional project TOML override)
|
||||
# Usage: gardener-run.sh [projects/harb.toml]
|
||||
export PROJECT_TOML="${1:-}"
|
||||
# Accept project config from argument; default to disinto
|
||||
export PROJECT_TOML="${1:-$FACTORY_ROOT/projects/disinto.toml}"
|
||||
# shellcheck source=../lib/env.sh
|
||||
source "$FACTORY_ROOT/lib/env.sh"
|
||||
# shellcheck source=../lib/file-action-issue.sh
|
||||
source "$FACTORY_ROOT/lib/file-action-issue.sh"
|
||||
# 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"
|
||||
|
||||
LOG_FILE="$FACTORY_ROOT/gardener/gardener.log"
|
||||
LOCK_FILE="/tmp/gardener-run.lock"
|
||||
LOG_FILE="$SCRIPT_DIR/gardener.log"
|
||||
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
|
||||
SESSION_NAME="gardener-${PROJECT_NAME}"
|
||||
PHASE_FILE="/tmp/gardener-session-${PROJECT_NAME}.phase"
|
||||
|
||||
# shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh
|
||||
PHASE_POLL_INTERVAL=15
|
||||
|
||||
SCRATCH_FILE="/tmp/gardener-${PROJECT_NAME}-scratch.md"
|
||||
RESULT_FILE="/tmp/gardener-result-${PROJECT_NAME}.txt"
|
||||
|
||||
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
||||
|
||||
# ── Lock ──────────────────────────────────────────────────────────────────
|
||||
if [ -f "$LOCK_FILE" ]; then
|
||||
LOCK_PID=$(cat "$LOCK_FILE" 2>/dev/null || true)
|
||||
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
|
||||
log "run: gardener-run running (PID $LOCK_PID)"
|
||||
exit 0
|
||||
fi
|
||||
rm -f "$LOCK_FILE"
|
||||
fi
|
||||
echo $$ > "$LOCK_FILE"
|
||||
trap 'rm -f "$LOCK_FILE"' EXIT
|
||||
|
||||
# ── Memory guard ──────────────────────────────────────────────────────────
|
||||
AVAIL_MB=$(free -m | awk '/Mem:/{print $7}')
|
||||
if [ "${AVAIL_MB:-0}" -lt 2000 ]; then
|
||||
log "run: skipping — only ${AVAIL_MB}MB available (need 2000)"
|
||||
exit 0
|
||||
fi
|
||||
# ── Guards ────────────────────────────────────────────────────────────────
|
||||
acquire_cron_lock "/tmp/gardener-run.lock"
|
||||
check_memory 2000
|
||||
|
||||
log "--- Gardener run start ---"
|
||||
|
||||
# ── File action issue for run-gardener formula ────────────────────────────
|
||||
ISSUE_BODY="---
|
||||
formula: run-gardener
|
||||
model: opus
|
||||
---
|
||||
# ── Consume escalation replies ────────────────────────────────────────────
|
||||
consume_escalation_reply "gardener"
|
||||
|
||||
Periodic gardener housekeeping run. The action-agent reads \`formulas/run-gardener.toml\`
|
||||
and executes the steps: preflight, grooming, blocked-review,
|
||||
AGENTS.md update, and commit-and-pr.
|
||||
# ── Load formula + context ───────────────────────────────────────────────
|
||||
load_formula "$FACTORY_ROOT/formulas/run-gardener.toml"
|
||||
build_context_block AGENTS.md
|
||||
|
||||
Filed automatically by \`gardener-run.sh\`."
|
||||
# ── Read scratch file (compaction survival) ───────────────────────────────
|
||||
SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE")
|
||||
SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE")
|
||||
|
||||
_rc=0
|
||||
file_action_issue "run-gardener" "action: run-gardener — periodic housekeeping" "$ISSUE_BODY" || _rc=$?
|
||||
case "$_rc" in
|
||||
0) ;;
|
||||
1) log "run: open run-gardener action issue already exists — skipping"
|
||||
log "--- Gardener run done ---"
|
||||
exit 0 ;;
|
||||
2) log "ERROR: 'action' label not found — cannot file gardener issue"
|
||||
exit 1 ;;
|
||||
*) log "ERROR: failed to create action issue for run-gardener"
|
||||
exit 1 ;;
|
||||
esac
|
||||
# ── Build prompt (gardener needs extra API endpoints for issue management) ─
|
||||
GARDENER_API_EXTRA="
|
||||
Relabel: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PUT -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/labels' -d '{\"labels\":[LABEL_ID]}'
|
||||
Comment: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X POST -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/comments' -d '{\"body\":\"...\"}'
|
||||
Close: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"state\":\"closed\"}'
|
||||
Edit body: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"body\":\"new body\"}'
|
||||
"
|
||||
build_prompt_footer "$GARDENER_API_EXTRA"
|
||||
|
||||
log "Filed action issue #${FILED_ISSUE_NUM} for run-gardener formula"
|
||||
matrix_send "gardener" "Filed action #${FILED_ISSUE_NUM}: run-gardener — periodic housekeeping" 2>/dev/null || true
|
||||
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
|
||||
PROMPT="You are the issue gardener for ${CODEBERG_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.
|
||||
|
||||
log "--- Gardener run done ---"
|
||||
You have full shell access and --dangerously-skip-permissions.
|
||||
Fix what you can. Escalate what you cannot. Do NOT ask permission — act first, report after.
|
||||
${ESCALATION_REPLY:+
|
||||
## Escalation Reply (from Matrix — human message)
|
||||
${ESCALATION_REPLY}
|
||||
|
||||
Act on this reply during the grooming step.
|
||||
}
|
||||
## Project context
|
||||
${CONTEXT_BLOCK}
|
||||
${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT}
|
||||
}
|
||||
## Result file
|
||||
Write actions and dust items to: ${RESULT_FILE}
|
||||
|
||||
## Formula
|
||||
${FORMULA_CONTENT}
|
||||
|
||||
${SCRATCH_INSTRUCTION}
|
||||
${PROMPT_FOOTER}"
|
||||
|
||||
# ── Reset result file ────────────────────────────────────────────────────
|
||||
rm -f "$RESULT_FILE"
|
||||
touch "$RESULT_FILE"
|
||||
|
||||
# ── Run session ──────────────────────────────────────────────────────────
|
||||
export CLAUDE_MODEL="sonnet"
|
||||
run_formula_and_monitor "gardener" 7200
|
||||
|
||||
# ── 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue