fix: feat: active-state files — per-cron guard with self-off semantics (#622)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-23 21:46:59 +00:00
parent e041b20823
commit e535ed776f
10 changed files with 47 additions and 1 deletions

View file

@ -100,6 +100,7 @@ echo "=== 2/2 Function resolution ==="
# lib/secret-scan.sh — sourced by file-action-issue.sh, phase-handler.sh (scan_for_secrets, redact_secrets)
# lib/formula-session.sh — sourced by formula-driven agents (acquire_cron_lock, run_formula_and_monitor, etc.)
# lib/mirrors.sh — sourced by merge sites (mirror_push)
# lib/guard.sh — sourced by all cron entry points (check_active)
#
# Excluded — not sourced inline by agents:
# lib/ci-debug.sh — standalone CLI tool, run directly (not sourced)
@ -110,7 +111,7 @@ echo "=== 2/2 Function resolution ==="
# If a new lib file is added and sourced by agents, add it to LIB_FUNS below
# and add a check_script call for it in the lib files section further down.
LIB_FUNS=$(
for f in lib/agent-session.sh lib/env.sh lib/ci-helpers.sh lib/load-project.sh lib/secret-scan.sh lib/file-action-issue.sh lib/formula-session.sh lib/mirrors.sh; do
for f in lib/agent-session.sh lib/env.sh lib/ci-helpers.sh lib/load-project.sh lib/secret-scan.sh lib/file-action-issue.sh lib/formula-session.sh lib/mirrors.sh lib/guard.sh; do
if [ -f "$f" ]; then get_fns "$f"; fi
done | sort -u
)
@ -181,6 +182,7 @@ check_script lib/file-action-issue.sh lib/secret-scan.sh
check_script lib/formula-session.sh lib/agent-session.sh
check_script lib/load-project.sh
check_script lib/mirrors.sh
check_script lib/guard.sh
# Standalone lib scripts (not sourced by agents; run directly or as services).
# Still checked for function resolution against LIB_FUNS + own definitions.

View file

@ -13,6 +13,9 @@ set -euo pipefail
export PROJECT_TOML="${1:-}"
source "$(dirname "$0")/../lib/env.sh"
# shellcheck source=../lib/guard.sh
source "$(dirname "$0")/../lib/guard.sh"
check_active action
LOGFILE="${FACTORY_ROOT}/action/action-poll-${PROJECT_NAME:-default}.log"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

View file

@ -22,6 +22,9 @@ source "$(dirname "$0")/../lib/env.sh"
source "$(dirname "$0")/../lib/ci-helpers.sh"
# shellcheck source=../lib/mirrors.sh
source "$(dirname "$0")/../lib/mirrors.sh"
# shellcheck source=../lib/guard.sh
source "$(dirname "$0")/../lib/guard.sh"
check_active dev
# Gitea labels API requires []int64 — look up the "underspecified" label ID once
UNDERSPECIFIED_LABEL_ID=$(forge_api GET "/labels" 2>/dev/null \

View file

@ -28,6 +28,8 @@ source "$FACTORY_ROOT/lib/formula-session.sh"
source "$FACTORY_ROOT/lib/ci-helpers.sh"
# shellcheck source=../lib/mirrors.sh
source "$FACTORY_ROOT/lib/mirrors.sh"
# shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/lib/guard.sh"
LOG_FILE="$SCRIPT_DIR/gardener.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
@ -52,6 +54,7 @@ _GARDENER_CRASH_COUNT=0
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
# ── Guards ────────────────────────────────────────────────────────────────
check_active gardener
acquire_cron_lock "/tmp/gardener-run.lock"
check_memory 2000

21
lib/guard.sh Normal file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env bash
# guard.sh — Active-state guard for cron entry points
#
# Each agent checks for a state file before running. If the file
# doesn't exist, the agent logs a skip and exits cleanly.
#
# State files live in $FACTORY_ROOT/state/:
# .dev-active, .reviewer-active, .planner-active, etc.
#
# Presence = permission to run. Absence = skip (factory off by default).
# check_active <agent_name>
# Exit 0 (skip) if the state file is absent.
check_active() {
local agent_name="$1"
local state_file="${FACTORY_ROOT}/state/.${agent_name}-active"
if [ ! -f "$state_file" ]; then
log "${agent_name} not active — skipping"
exit 0
fi
}

View file

@ -22,6 +22,8 @@ source "$FACTORY_ROOT/lib/env.sh"
source "$FACTORY_ROOT/lib/agent-session.sh"
# shellcheck source=../lib/formula-session.sh
source "$FACTORY_ROOT/lib/formula-session.sh"
# shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/lib/guard.sh"
LOG_FILE="$SCRIPT_DIR/planner.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
@ -36,6 +38,7 @@ SCRATCH_FILE="/tmp/planner-${PROJECT_NAME}-scratch.md"
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
# ── Guards ────────────────────────────────────────────────────────────────
check_active planner
acquire_cron_lock "/tmp/planner-run.lock"
check_memory 2000

View file

@ -24,6 +24,8 @@ source "$FACTORY_ROOT/lib/env.sh"
source "$FACTORY_ROOT/lib/agent-session.sh"
# shellcheck source=../lib/formula-session.sh
source "$FACTORY_ROOT/lib/formula-session.sh"
# shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/lib/guard.sh"
LOG_FILE="$SCRIPT_DIR/predictor.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
@ -38,6 +40,7 @@ SCRATCH_FILE="/tmp/predictor-${PROJECT_NAME}-scratch.md"
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
# ── Guards ────────────────────────────────────────────────────────────────
check_active predictor
acquire_cron_lock "/tmp/predictor-run.lock"
check_memory 2000

View file

@ -11,6 +11,9 @@ set -euo pipefail
export PROJECT_TOML="${1:-}"
source "$(dirname "$0")/../lib/env.sh"
source "$(dirname "$0")/../lib/ci-helpers.sh"
# shellcheck source=../lib/guard.sh
source "$(dirname "$0")/../lib/guard.sh"
check_active reviewer
REPO_ROOT="${PROJECT_REPO_ROOT}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

2
state/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Active-state files are runtime state, not committed
.*-active

View file

@ -28,6 +28,8 @@ source "$FACTORY_ROOT/lib/env.sh"
source "$FACTORY_ROOT/lib/agent-session.sh"
# shellcheck source=../lib/formula-session.sh
source "$FACTORY_ROOT/lib/formula-session.sh"
# shellcheck source=../lib/guard.sh
source "$FACTORY_ROOT/lib/guard.sh"
LOG_FILE="$SCRIPT_DIR/supervisor.log"
# shellcheck disable=SC2034 # consumed by run_formula_and_monitor
@ -42,6 +44,7 @@ SCRATCH_FILE="/tmp/supervisor-${PROJECT_NAME}-scratch.md"
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
# ── Guards ────────────────────────────────────────────────────────────────
check_active supervisor
acquire_cron_lock "/tmp/supervisor-run.lock"
check_memory 2000