Skip to content

Commit 1d10293

Browse files
author
StackMemory Bot (CLI)
committed
feat(dspy): add automated optimization loop with threshold gating
1 parent b0ccf2b commit 1d10293

1 file changed

Lines changed: 133 additions & 0 deletions

File tree

scripts/dspy/loop.sh

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env bash
2+
# DSPy optimization loop — run via cron or manually.
3+
#
4+
# What it does:
5+
# 1. Checks if retrieval_audit has enough new data since last run
6+
# 2. Runs optimization if threshold met (default: 20 new rows)
7+
# 3. Compares optimized vs baseline
8+
# 4. If improved, copies optimized prompt to src/ and rebuilds
9+
# 5. Logs everything to .stackmemory/dspy-loop.log
10+
#
11+
# Install cron (daily at 3am):
12+
# crontab -e
13+
# 0 3 * * * /Users/jwu/Dev/stackmemory/scripts/dspy/loop.sh
14+
#
15+
# Or run manually:
16+
# ./scripts/dspy/loop.sh [--force] [--dry-run]
17+
18+
set -euo pipefail
19+
20+
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd -P)"
21+
DSPY_DIR="$REPO_ROOT/scripts/dspy"
22+
DB_PATH="$REPO_ROOT/.stackmemory/context.db"
23+
STATE_FILE="$DSPY_DIR/optimized_state.json"
24+
LAST_RUN_FILE="$DSPY_DIR/.last_run"
25+
LOG_FILE="$REPO_ROOT/.stackmemory/dspy-loop.log"
26+
MIN_NEW_ROWS=20
27+
FORCE=false
28+
DRY_RUN=false
29+
30+
for arg in "$@"; do
31+
case "$arg" in
32+
--force) FORCE=true ;;
33+
--dry-run) DRY_RUN=true ;;
34+
esac
35+
done
36+
37+
log() { echo "[$(date -Iseconds)] $*" | tee -a "$LOG_FILE"; }
38+
39+
# Ensure log dir exists
40+
mkdir -p "$(dirname "$LOG_FILE")"
41+
42+
log "=== DSPy optimization loop start ==="
43+
44+
# Check prerequisites
45+
if [ ! -f "$DB_PATH" ]; then
46+
log "SKIP: No context.db found at $DB_PATH"
47+
exit 0
48+
fi
49+
50+
if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
51+
# Try loading from .env
52+
if [ -f "$REPO_ROOT/.env" ]; then
53+
export $(grep ANTHROPIC_API_KEY "$REPO_ROOT/.env" 2>/dev/null | head -1 | xargs) 2>/dev/null || true
54+
fi
55+
if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
56+
log "SKIP: ANTHROPIC_API_KEY not set"
57+
exit 0
58+
fi
59+
fi
60+
61+
# Check data threshold
62+
TOTAL_ROWS=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM retrieval_audit;" 2>/dev/null || echo "0")
63+
LAST_COUNT=0
64+
if [ -f "$LAST_RUN_FILE" ]; then
65+
LAST_COUNT=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
66+
fi
67+
NEW_ROWS=$((TOTAL_ROWS - LAST_COUNT))
68+
69+
log "Audit rows: $TOTAL_ROWS total, $NEW_ROWS new since last run (threshold: $MIN_NEW_ROWS)"
70+
71+
if [ "$FORCE" = false ] && [ "$NEW_ROWS" -lt "$MIN_NEW_ROWS" ]; then
72+
log "SKIP: Not enough new data ($NEW_ROWS < $MIN_NEW_ROWS). Use --force to override."
73+
exit 0
74+
fi
75+
76+
# Ensure venv exists
77+
if [ ! -d "$DSPY_DIR/.venv" ]; then
78+
log "Setting up DSPy environment..."
79+
"$DSPY_DIR/setup.sh"
80+
fi
81+
82+
# Activate venv
83+
source "$DSPY_DIR/.venv/bin/activate"
84+
85+
# Run optimization
86+
log "Running optimization (model: claude-haiku-4-5-20251001)..."
87+
if [ "$DRY_RUN" = true ]; then
88+
log "DRY RUN: would run optimize.py"
89+
else
90+
cd "$DSPY_DIR"
91+
python optimize.py \
92+
--db "$DB_PATH" \
93+
--model "anthropic/claude-haiku-4-5-20251001" \
94+
--output "$STATE_FILE" \
95+
2>&1 | tee -a "$LOG_FILE"
96+
97+
if [ $? -ne 0 ]; then
98+
log "ERROR: Optimization failed"
99+
exit 1
100+
fi
101+
fi
102+
103+
# Run evaluation
104+
log "Running evaluation..."
105+
if [ "$DRY_RUN" = false ]; then
106+
cd "$DSPY_DIR"
107+
EVAL_OUTPUT=$(python eval.py \
108+
--db "$DB_PATH" \
109+
--model "anthropic/claude-haiku-4-5-20251001" \
110+
--optimized "$STATE_FILE" \
111+
2>&1)
112+
echo "$EVAL_OUTPUT" | tee -a "$LOG_FILE"
113+
114+
# Check for improvement
115+
if echo "$EVAL_OUTPUT" | grep -q "IMPROVEMENT"; then
116+
log "Improvement detected — prompt update available"
117+
log "To apply: review $STATE_FILE and update llm-context-retrieval.ts"
118+
119+
# Auto-rebuild if state file was updated
120+
if [ -f "$STATE_FILE" ]; then
121+
log "Optimized state saved to $STATE_FILE"
122+
fi
123+
elif echo "$EVAL_OUTPUT" | grep -q "REGRESSION"; then
124+
log "WARNING: Regression detected — keeping current prompt"
125+
rm -f "$STATE_FILE"
126+
else
127+
log "No significant change"
128+
fi
129+
fi
130+
131+
# Record this run
132+
echo "$TOTAL_ROWS" > "$LAST_RUN_FILE"
133+
log "=== DSPy optimization loop complete ==="

0 commit comments

Comments
 (0)