← back to Lifestyle Asset Intel
yolo tick #15: launchd-driven overnight autonomous loop
db84b94b7fb43d0ea94881bec3cf096900e115c7 · 2026-05-10 07:21:00 -0700 · Steve Abrams
Steve is going to sleep and wants the lifestyle-asset-intel build
running autonomously overnight. ScheduleWakeup-based loop dies when
the active Claude Code session closes, so set up a more durable
launchd-driven path that fires every 30 minutes regardless of
session state.
Files (all under .yolo/):
tick-prompt.md — self-contained instructions for each fire:
working dir, PG env, pm2 process, standing-rule
guardrails (no DNS, no email, no eBay scrape,
local-only), candidate next-tick items, hard
exit conditions ("≥8 turns elapsed → wrap up").
run-tick.sh — shell wrapper. mkdir-atomic lock (no flock on
macOS), 12-minute stale-lock recovery, claude
CLI invoke with --print --dangerously-skip-
permissions --max-turns 12, append to log.
Unsets ANTHROPIC_API_KEY/AUTH_TOKEN per Steve's
standing rule (Max plan only).
README.md — operator runbook: where the plist lives, how
to pause/resume/tail, why launchd over
ScheduleWakeup, safety knobs.
LaunchAgent: ~/Library/LaunchAgents/com.steve.lai-yolo.plist (not in
this repo — per-machine config). StartCalendarInterval = :15 and :45
of every hour. WorkingDirectory pinned to project root. PATH includes
/Users/stevestudio2/.local/bin so the claude binary resolves
correctly.
.gitignore extended to keep .yolo/yolo-loop.log,
.yolo/launchd-{out,err}.log, and .yolo/.lock.d/ out of the repo;
scripts and prompt stay tracked.
Validated: lock acquires + refuses + releases correctly; launchctl
list shows the entry loaded with LastExitStatus 0; next fire 28
minutes from this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M .gitignoreA .yolo/README.mdA .yolo/run-tick.shA .yolo/tick-prompt.md
Diff
commit db84b94b7fb43d0ea94881bec3cf096900e115c7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 10 07:21:00 2026 -0700
yolo tick #15: launchd-driven overnight autonomous loop
Steve is going to sleep and wants the lifestyle-asset-intel build
running autonomously overnight. ScheduleWakeup-based loop dies when
the active Claude Code session closes, so set up a more durable
launchd-driven path that fires every 30 minutes regardless of
session state.
Files (all under .yolo/):
tick-prompt.md — self-contained instructions for each fire:
working dir, PG env, pm2 process, standing-rule
guardrails (no DNS, no email, no eBay scrape,
local-only), candidate next-tick items, hard
exit conditions ("≥8 turns elapsed → wrap up").
run-tick.sh — shell wrapper. mkdir-atomic lock (no flock on
macOS), 12-minute stale-lock recovery, claude
CLI invoke with --print --dangerously-skip-
permissions --max-turns 12, append to log.
Unsets ANTHROPIC_API_KEY/AUTH_TOKEN per Steve's
standing rule (Max plan only).
README.md — operator runbook: where the plist lives, how
to pause/resume/tail, why launchd over
ScheduleWakeup, safety knobs.
LaunchAgent: ~/Library/LaunchAgents/com.steve.lai-yolo.plist (not in
this repo — per-machine config). StartCalendarInterval = :15 and :45
of every hour. WorkingDirectory pinned to project root. PATH includes
/Users/stevestudio2/.local/bin so the claude binary resolves
correctly.
.gitignore extended to keep .yolo/yolo-loop.log,
.yolo/launchd-{out,err}.log, and .yolo/.lock.d/ out of the repo;
scripts and prompt stay tracked.
Validated: lock acquires + refuses + releases correctly; launchctl
list shows the entry loaded with LastExitStatus 0; next fire 28
minutes from this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
.gitignore | 7 ++++++
.yolo/README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++
.yolo/run-tick.sh | 45 +++++++++++++++++++++++++++++++++++++
.yolo/tick-prompt.md | 53 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 168 insertions(+)
diff --git a/.gitignore b/.gitignore
index e703288..111b69d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,10 @@ build/
tmp/
uploads/
coverage/
+
+# yolo loop runtime artifacts — keep the scripts + prompt, ignore logs/locks
+.yolo/yolo-loop.log
+.yolo/launchd-out.log
+.yolo/launchd-err.log
+.yolo/.lock
+.yolo/.lock.d/
diff --git a/.yolo/README.md b/.yolo/README.md
new file mode 100644
index 0000000..87893f1
--- /dev/null
+++ b/.yolo/README.md
@@ -0,0 +1,63 @@
+# `.yolo/` — overnight autonomous loop
+
+## What this is
+
+A launchd-driven autonomous yolo loop that fires every 30 minutes and runs
+ONE small focused tick on this project. Each tick is an independent
+`claude --print` session — no memory of prior fires; reads `git log` for
+history.
+
+## Files
+
+| Path | Purpose | Tracked |
+|---|---|---|
+| `tick-prompt.md` | What each fire should do — standing rules, candidate tasks, exit conditions | yes |
+| `run-tick.sh` | Shell wrapper: lock + claude CLI + log append | yes |
+| `yolo-loop.log` | Per-fire stdout+stderr + start/end markers | NO (gitignored) |
+| `launchd-out.log` / `launchd-err.log` | launchd-level stdout/err | NO |
+| `.lock.d/` | mkdir-atomic single-fire lock; auto-stolen after 12 min | NO |
+
+## launchd plist
+
+`~/Library/LaunchAgents/com.steve.lai-yolo.plist` (NOT in this repo;
+~/Library is per-machine config).
+
+Fires on `StartCalendarInterval` at minute `15` and `45` of every hour.
+Default cooldown between fires = 30 minutes.
+
+## Operate
+
+```bash
+# Status
+launchctl list com.steve.lai-yolo
+
+# Pause for the night
+launchctl unload ~/Library/LaunchAgents/com.steve.lai-yolo.plist
+
+# Resume
+launchctl load ~/Library/LaunchAgents/com.steve.lai-yolo.plist
+
+# Watch the log live
+tail -f ~/Projects/lifestyle-asset-intel/.yolo/yolo-loop.log
+
+# Force one tick now (respects the lock)
+~/Projects/lifestyle-asset-intel/.yolo/run-tick.sh
+```
+
+## Why launchd over ScheduleWakeup
+
+`ScheduleWakeup` requires the active Claude Code session to be alive —
+closing the terminal kills the loop. launchd survives session close,
+machine reboot (via the LaunchAgent registry), and any pm2 restart.
+Mac sleep still pauses launchd; use `caffeinate -di` if you need
+literal-overnight uninterrupted runs.
+
+## Safety
+
+- `--max-turns 12` per fire caps each tick's compute
+- `unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN` per Steve's standing rule
+ (Claude Max plan only, never the metered API)
+- Lock prevents two fires from racing on git
+- Each tick exits before the next fire window so commits don't pile up
+- `tick-prompt.md` enumerates the standing-rule guardrails (no DNS,
+ no email, no scraping non-public sources, etc.)
diff --git a/.yolo/run-tick.sh b/.yolo/run-tick.sh
new file mode 100755
index 0000000..40407e4
--- /dev/null
+++ b/.yolo/run-tick.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# LAI yolo tick runner — fired by launchd every 30 minutes.
+# Locks via flock so overlapping fires don't race on git.
+set -u
+
+PROJ="/Users/stevestudio2/Projects/lifestyle-asset-intel"
+LOCK="$PROJ/.yolo/.lock"
+LOG="$PROJ/.yolo/yolo-loop.log"
+PROMPT="$PROJ/.yolo/tick-prompt.md"
+
+cd "$PROJ" || { echo "[$(date -u +%FT%TZ)] cwd FAIL" >> "$LOG"; exit 2; }
+
+# Standing rule: NEVER use Anthropic API key — Claude CLI uses Max plan only.
+unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN
+
+# Single-fire lock — bail if a previous tick is still running.
+# macOS has no flock, so we use a mkdir-as-lock pattern (atomic on POSIX).
+LOCK_DIR="$PROJ/.yolo/.lock.d"
+if ! mkdir "$LOCK_DIR" 2>/dev/null; then
+ # Stale lock recovery: if the dir is older than 12 minutes (max-turns 12 +
+ # margin), assume the previous tick is dead and steal the lock.
+ if [ "$(find "$LOCK_DIR" -maxdepth 0 -mmin +12 2>/dev/null | wc -l)" -gt 0 ]; then
+ echo "[$(date -u +%FT%TZ)] STALE lock → steal" >> "$LOG"
+ rmdir "$LOCK_DIR" 2>/dev/null
+ mkdir "$LOCK_DIR" || { echo "[$(date -u +%FT%TZ)] lock steal FAIL" >> "$LOG"; exit 0; }
+ else
+ echo "[$(date -u +%FT%TZ)] SKIP — previous tick still running" >> "$LOG"
+ exit 0
+ fi
+fi
+trap 'rmdir "$LOCK_DIR" 2>/dev/null' EXIT
+
+START=$(date -u +%FT%TZ)
+echo "[$START] tick start" >> "$LOG"
+
+# Pipe the prompt to claude CLI, --print so it exits, --max-turns 12 to cap cost
+/Users/stevestudio2/.local/bin/claude --print --dangerously-skip-permissions --max-turns 12 < "$PROMPT" \
+ >> "$LOG" 2>&1
+EXIT=$?
+
+END=$(date -u +%FT%TZ)
+echo "[$END] tick end exit=$EXIT" >> "$LOG"
+echo "---" >> "$LOG"
+
+exit "$EXIT"
diff --git a/.yolo/tick-prompt.md b/.yolo/tick-prompt.md
new file mode 100644
index 0000000..668eb0b
--- /dev/null
+++ b/.yolo/tick-prompt.md
@@ -0,0 +1,53 @@
+# LAI yolo tick — one focused change, then exit
+
+You are firing inside an autonomous launchd loop on Mac2. Each fire is an
+independent Claude CLI session with NO memory of previous ticks — read the
+git log for history.
+
+WORK DIR: /Users/stevestudio2/Projects/lifestyle-asset-intel
+PG: PGHOST=/tmp PGDATABASE=lifestyle_asset_intel
+PM2: lai-server on :9820 (restart with `pm2 restart lai-server`)
+TESTS: `PGDATABASE=lifestyle_asset_intel npm test` (currently 44/44 green)
+
+## Standing rules (read MEMORY.md if any doubt)
+
+- Local-only ops. NO DNS, NO email, NO scraping eBay (TOS gated), NO outbound
+ comms, NO touching dw_unified or any non-LAI repo.
+- Always stay legal for scraping — auction houses (Sotheby's/Christie's
+ editorial) are public records and OK; aggregators need TOS check first.
+- Author email steve@designerwallcoverings.com on every commit.
+- `git commit` only if `npm test` passes. One logical change = one commit.
+- Reversible only — no `git push --force`, no destructive ops.
+- Per Steve's "build and look anywhere for data" + "always stay legal":
+ Tier-1 auction house editorial > public datasets > nothing.
+
+## What to do this tick
+
+1. `git log --oneline | head -20` — see what's been done. Don't repeat.
+2. Read ROADMAP.md briefly. Pick ONE achievable item.
+3. Implement. Test. If `npm test` green, commit. Else revert and pick a
+ smaller scope.
+4. Restart pm2 if runtime code changed. Don't restart if only docs/SQL.
+5. Write a one-line status to `.yolo/yolo-loop.log` then exit.
+
+## Reasonable next items (pick whichever fits a single tick)
+
+- Christie's editorial scrape — mirror tick #13 pattern, more comps, new source
+- Phillips / Bonhams / Heritage Auctions — same pattern
+- Asset detail "exotic" badge (1-line CSS + EJS conditional)
+- /api/family (no slug) — list all families
+- Server-side sort param on /api/assets (mirrors q param)
+- Condition_facets render on asset detail page
+- Sparkline Y-axis labels
+- Source.weight-aware similarity weight (replace constant 0.18)
+- Add an /admin/audit HTML view over /api/audit/recent
+- Methodology version bump trigger when breakdown weights change
+
+## Exit conditions
+
+- Tests fail and you can't fix in ≤6 turns → revert, log, exit.
+- Project in broken state → fix-first, no new features.
+- ≥8 turns elapsed → wrap up, commit if green, exit.
+- Done with the tick → exit cleanly so launchd can fire again next interval.
+
+KEEP IT SMALL. Many small ticks > one large stuck tick.
← 012be8e yolo tick #14: auto-generate valuation_snapshots from real c
·
back to Lifestyle Asset Intel
·
yolo tick #16: exotic / special-edition badges + audit-test 447537f →