tools(agent-loop): two-agent loop scripts, hooks, prompts, nightly lessons
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
66de04c6ff
commit
cf9793f0c0
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# Control the Claude side of the agent loop on the dev box (no cron/systemd here).
|
||||
# agentctl.sh install copy scripts to ~/.local/share/jyotisha-agent/bin (run from a checkout)
|
||||
# agentctl.sh start start the background loop (tick every 30 min, nightly at 23:30 Beijing)
|
||||
# agentctl.sh stop stop the loop
|
||||
# agentctl.sh status loop pid, last heartbeat, last log lines
|
||||
# agentctl.sh once run one tick now in the foreground
|
||||
# agentctl.sh pause|resume create/remove the STOP file (ticks exit immediately while paused)
|
||||
# The loop runs from the installed copy, because ticks check out and clean the
|
||||
# loop worktree and would otherwise rewrite the scripts under themselves.
|
||||
set -euo pipefail
|
||||
|
||||
BIN="$HOME/.local/share/jyotisha-agent/bin"
|
||||
CONF_DIR="$HOME/.config/jyotisha-agent"
|
||||
STATE_DIR="${AGENT_LOOP_STATE_DIR:-$HOME/.local/state/jyotisha-agent}"
|
||||
PID_FILE="$STATE_DIR/loop.pid"
|
||||
HEARTBEAT="$STATE_DIR/heartbeat"
|
||||
TICK_SECONDS="${AGENT_LOOP_TICK_SECONDS:-1800}"
|
||||
NIGHTLY_AT="${AGENT_LOOP_NIGHTLY_AT:-23:30}" # Asia/Shanghai
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
running() { [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; }
|
||||
|
||||
loop() {
|
||||
echo $$ > "$PID_FILE"
|
||||
while true; do
|
||||
date -u +%FT%TZ > "$HEARTBEAT"
|
||||
"$BIN/claude-tick.sh" || echo "$(date -u +%FT%TZ) tick exited $?" >> "$STATE_DIR/claude-tick.log"
|
||||
today="$(TZ=Asia/Shanghai date +%F)"; now="$(TZ=Asia/Shanghai date +%H:%M)"
|
||||
if [[ ! "$now" < "$NIGHTLY_AT" && "$(cat "$STATE_DIR/nightly.done" 2>/dev/null)" != "$today" ]]; then
|
||||
"$BIN/nightly-review.sh" || true
|
||||
echo "$today" > "$STATE_DIR/nightly.done"
|
||||
fi
|
||||
sleep "$TICK_SECONDS"
|
||||
done
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install)
|
||||
src="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
mkdir -p "$BIN" "$CONF_DIR"
|
||||
rm -rf "$BIN.new" && cp -R "$src" "$BIN.new" && rm -rf "$BIN" && mv "$BIN.new" "$BIN"
|
||||
chmod +x "$BIN"/*.sh "$BIN"/hooks/*/pre-push
|
||||
echo "installed to $BIN from $src ($(git -C "$src" rev-parse --short HEAD 2>/dev/null || echo '?'))"
|
||||
[[ -r "$CONF_DIR/gitea-token" ]] && echo "gitea user: $("$BIN/gitea.sh" whoami)" || echo "WARNING: $CONF_DIR/gitea-token missing" ;;
|
||||
start)
|
||||
running && { echo "already running (pid $(cat "$PID_FILE"))"; exit 0; }
|
||||
[[ -x "$BIN/claude-tick.sh" ]] || { echo "run '$0 install' first"; exit 1; }
|
||||
setsid nohup "$BIN/agentctl.sh" __loop >> "$STATE_DIR/loop.out" 2>&1 < /dev/null &
|
||||
sleep 1; running && echo "started (pid $(cat "$PID_FILE"))" || echo "failed to start, see $STATE_DIR/loop.out" ;;
|
||||
__loop) loop ;;
|
||||
stop)
|
||||
if running; then kill -- -"$(ps -o pgid= "$(cat "$PID_FILE")" | tr -d ' ')" 2>/dev/null || kill "$(cat "$PID_FILE")"; rm -f "$PID_FILE"; echo stopped; else echo "not running"; fi ;;
|
||||
status)
|
||||
running && echo "running (pid $(cat "$PID_FILE"))" || echo "NOT running"
|
||||
[[ -e "$CONF_DIR/STOP" ]] && echo "PAUSED (STOP file present)"
|
||||
echo "last heartbeat: $(cat "$HEARTBEAT" 2>/dev/null || echo never)"
|
||||
echo "--- claude-tick.log"; tail -n 8 "$STATE_DIR/claude-tick.log" 2>/dev/null || true
|
||||
echo "--- nightly.log"; tail -n 3 "$STATE_DIR/nightly.log" 2>/dev/null || true ;;
|
||||
once) "$BIN/claude-tick.sh"; tail -n 5 "$STATE_DIR/claude-tick.log" ;;
|
||||
pause) mkdir -p "$CONF_DIR"; touch "$CONF_DIR/STOP"; echo paused ;;
|
||||
resume) rm -f "$CONF_DIR/STOP"; echo resumed ;;
|
||||
*) sed -n '2,9p' "$0"; exit 64 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user