diff --git a/.agents/scripts/supervisor-helper.sh b/.agents/scripts/supervisor-helper.sh index 7481499b12..b02d05548a 100755 --- a/.agents/scripts/supervisor-helper.sh +++ b/.agents/scripts/supervisor-helper.sh @@ -335,6 +335,8 @@ Usage: supervisor-helper.sh dashboard [--batch id] [--interval N] Live TUI dashboard supervisor-helper.sh queue-health [--batch id] Update pinned queue health issue (t1013) supervisor-helper.sh labels [--action X] [--model Y] [--json] Query model usage labels (t1010) + supervisor-helper.sh ai-pipeline [full|dry-run] Run AI reasoning + action pipeline manually + supervisor-helper.sh ai-status Show AI supervisor status and next-run countdown supervisor-helper.sh db [sql] Direct SQLite access supervisor-helper.sh help Show this help @@ -451,6 +453,9 @@ Environment: SUPERVISOR_SELF_MEM_LIMIT MB before supervisor respawns after batch (default: 8192) SUPERVISOR_SKILL_UPDATE_PR Enable skill update PR pipeline in pulse (default: false) SUPERVISOR_SKILL_UPDATE_INTERVAL Seconds between skill update PR runs (default: 86400) + SUPERVISOR_AI_ENABLED Enable AI supervisor reasoning in pulse (default: true) + SUPERVISOR_AI_INTERVAL Pulses between AI reasoning runs (default: 15, ~30min) + AI_MAX_ACTIONS_PER_CYCLE Max actions per AI reasoning cycle (default: 10) AIDEVOPS_SUPERVISOR_DIR Override supervisor data directory Database: ~/.aidevops/.agent-workspace/supervisor/supervisor.db @@ -735,19 +740,43 @@ main() { ;; ai-status) local last_run_ts - last_run_ts=$(db "$SUPERVISOR_DB" "SELECT MAX(timestamp) FROM state_log WHERE task_id = 'ai-supervisor' AND to_state = 'complete';" 2>/dev/null || echo "never") + last_run_ts=$(db "$SUPERVISOR_DB" "SELECT MAX(timestamp) FROM state_log WHERE task_id = 'ai-supervisor' AND to_state = 'complete';" 2>/dev/null || echo "") local run_count run_count=$(db "$SUPERVISOR_DB" "SELECT COUNT(*) FROM state_log WHERE task_id = 'ai-supervisor' AND to_state = 'complete';" 2>/dev/null || echo 0) local action_count action_count=$(db "$SUPERVISOR_DB" "SELECT COUNT(*) FROM state_log WHERE task_id = 'ai-supervisor' AND from_state = 'actions';" 2>/dev/null || echo 0) + + # Read pulse counter and last-run timestamp from files + local ai_pulse_count_file="${SUPERVISOR_DIR}/ai-pulse-count" + local ai_last_run_file="${SUPERVISOR_DIR}/ai-supervisor-last-run" + local ai_log_file="${SUPERVISOR_DIR}/logs/ai-supervisor.log" + local ai_interval="${SUPERVISOR_AI_INTERVAL:-15}" + + local ai_pulse_count=0 + if [[ -f "$ai_pulse_count_file" ]]; then + ai_pulse_count=$(cat "$ai_pulse_count_file" 2>/dev/null || echo 0) + fi + + local ai_last_run="never" + if [[ -f "$ai_last_run_file" ]]; then + ai_last_run=$(cat "$ai_last_run_file" 2>/dev/null || echo "never") + elif [[ -n "$last_run_ts" ]]; then + ai_last_run="$last_run_ts" + fi + + local ai_pulses_remaining=$((ai_interval - ai_pulse_count)) + local ai_minutes_remaining=$((ai_pulses_remaining * 2)) + echo "AI Supervisor Status" - echo " Last run: ${last_run_ts:-never}" - echo " Total reasoning runs: $run_count" - echo " Total action executions: $action_count" - echo " Enabled: ${SUPERVISOR_AI_ENABLED:-true}" - echo " Interval: ${SUPERVISOR_AI_INTERVAL:-15} pulses (~$((${SUPERVISOR_AI_INTERVAL:-15} * 2))min)" + echo " Enabled: ${SUPERVISOR_AI_ENABLED:-true}" + echo " Interval: ${ai_interval} pulses (~$((ai_interval * 2))min)" + echo " Pulse counter: ${ai_pulse_count}/${ai_interval} (${ai_pulses_remaining} pulses / ~${ai_minutes_remaining}min until next run)" + echo " Last run: ${ai_last_run}" + echo " Total runs: $run_count" + echo " Total actions: $action_count" echo " Max actions/cycle: ${AI_MAX_ACTIONS_PER_CYCLE:-10}" - echo " Log dir: ${AI_REASON_LOG_DIR:-$HOME/.aidevops/logs/ai-supervisor}" + echo " Log file: ${ai_log_file}" + echo " Reason log dir: ${AI_REASON_LOG_DIR:-$HOME/.aidevops/logs/ai-supervisor}" ;; help | --help | -h) show_usage ;; *) diff --git a/.agents/scripts/supervisor/pulse.sh b/.agents/scripts/supervisor/pulse.sh index 8bca37a1c9..6a82df5e49 100755 --- a/.agents/scripts/supervisor/pulse.sh +++ b/.agents/scripts/supervisor/pulse.sh @@ -1858,6 +1858,89 @@ RULES: fi fi + # Phase 14: AI Supervisor reasoning + action execution (t1085.5) + # Runs the full AI pipeline (ai-reason.sh -> ai-actions.sh) on a configurable + # pulse interval. Default: every 15 pulses (~30 min at 2-min cron cadence). + # Controlled by SUPERVISOR_AI_ENABLED (default: true) and + # SUPERVISOR_AI_INTERVAL (default: 15 pulses). + # Dedicated log: $SUPERVISOR_DIR/logs/ai-supervisor.log + local ai_enabled="${SUPERVISOR_AI_ENABLED:-true}" + if [[ "$ai_enabled" == "true" ]]; then + local ai_interval="${SUPERVISOR_AI_INTERVAL:-15}" + local ai_pulse_count_file="${SUPERVISOR_DIR}/ai-pulse-count" + local ai_last_run_file="${SUPERVISOR_DIR}/ai-supervisor-last-run" + local ai_log_dir="${SUPERVISOR_DIR}/logs" + local ai_log_file="${ai_log_dir}/ai-supervisor.log" + + # Read and increment pulse counter + local ai_pulse_count=0 + if [[ -f "$ai_pulse_count_file" ]]; then + ai_pulse_count=$(cat "$ai_pulse_count_file" 2>/dev/null || echo 0) + fi + ai_pulse_count=$((ai_pulse_count + 1)) + echo "$ai_pulse_count" >"$ai_pulse_count_file" 2>/dev/null || true + + # Check if it's time to run the AI pipeline + if [[ "$ai_pulse_count" -ge "$ai_interval" ]]; then + # Reset counter + echo "0" >"$ai_pulse_count_file" 2>/dev/null || true + + # Determine repo path + local ai_repo_path="" + ai_repo_path=$(db "$SUPERVISOR_DB" "SELECT DISTINCT repo FROM tasks LIMIT 1;" 2>/dev/null || echo "") + if [[ -z "$ai_repo_path" ]]; then + ai_repo_path="$(pwd)" + fi + + log_info " Phase 14: AI supervisor reasoning + action execution (pulse $ai_pulse_count/$ai_interval)" + + # Ensure log directory exists + mkdir -p "$ai_log_dir" 2>/dev/null || true + + # Record start timestamp + local ai_start_ts + ai_start_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ') + { + echo "" + echo "=== AI Supervisor Run: $ai_start_ts ===" + } >>"$ai_log_file" 2>/dev/null || true + + # Run the full AI pipeline (reasoning -> action execution) + local ai_result="" + local ai_rc=0 + ai_result=$(run_ai_actions_pipeline "$ai_repo_path" "full" 2>>"$ai_log_file") || ai_rc=$? + + # Record completion timestamp + local ai_end_ts + ai_end_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ') + echo "$ai_end_ts" >"$ai_last_run_file" 2>/dev/null || true + + if [[ $ai_rc -eq 0 ]]; then + # Extract summary from result JSON + local ai_executed ai_failed ai_skipped + ai_executed=$(printf '%s' "$ai_result" | jq -r '.executed // 0' 2>/dev/null || echo 0) + ai_failed=$(printf '%s' "$ai_result" | jq -r '.failed // 0' 2>/dev/null || echo 0) + ai_skipped=$(printf '%s' "$ai_result" | jq -r '.skipped // 0' 2>/dev/null || echo 0) + log_success " Phase 14: AI pipeline complete (executed=$ai_executed failed=$ai_failed skipped=$ai_skipped)" + { + echo "Result: executed=$ai_executed failed=$ai_failed skipped=$ai_skipped" + echo "=== End: $ai_end_ts ===" + } >>"$ai_log_file" 2>/dev/null || true + else + log_warn " Phase 14: AI pipeline returned rc=$ai_rc (see $ai_log_file)" + { + echo "Result: rc=$ai_rc (pipeline error)" + echo "=== End: $ai_end_ts ===" + } >>"$ai_log_file" 2>/dev/null || true + fi + else + local ai_pulses_remaining=$((ai_interval - ai_pulse_count)) + log_verbose " Phase 14: AI pipeline skipped (${ai_pulses_remaining} pulses until next run, interval=${ai_interval})" + fi + else + log_verbose " Phase 14: AI pipeline disabled (SUPERVISOR_AI_ENABLED=false)" + fi + # t1052: Clear deferred batch completion flag to avoid leaking state # if the supervisor process is reused for non-pulse commands _PULSE_DEFER_BATCH_COMPLETION=""