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:
Jesse_Chen
2026-10-13 12:42:49 +08:00
co-authored by Claude Opus 5.5
parent 66de04c6ff
commit cf9793f0c0
24 changed files with 1208 additions and 0 deletions
+64
View File
@@ -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