Skip to content

SOPs

Loose Ends Harvester (Retired)

Retired 2026-05-07. The claude-log running session log was archived and removed. Loose ends depended on it as its primary input, so the nightly cron was removed at the same time. The repo and harvest.py script remain on disk for reference but are no longer scheduled. The text below describes the system as it last ran.

Every night at 11:50 PM on the VPS, harvest.py reads three things:

  1. Today’s claude-log file (~/apps/claude-log/2026/April/April 16, 2026 - Thursday.md)
  2. Git commits across every repo under ~/apps/ for the day
  3. The existing store.json (to dedupe against)

It feeds the log and git activity into claude -p --model haiku with a strict prompt: extract unfinished threads only, output JSON. Haiku is the right model for this — pattern extraction, not judgment. It stays on the Max plan, zero API cost.

The returned items get merged into store.json with a fuzzy title dedupe (same thread does not surface twice).

Every morning at 6:30 AM, send-digest.py reads the open items from store.json, renders a dark-themed HTML email grouped by priority, and sends it to James through the existing gmail-notify/send-email.py.

Loose ends are “things that came up” — they are raw and unfiltered. The main to-do list is curated: things James has decided to do. If loose ends flowed straight into the to-do list, they would drown the real priorities in noise.

The digest is a morning review. James scans it, promotes the ones that matter to the real to-do list, and ignores the rest. Items get marked done by editing store.json directly.

~/apps/loose-ends/
├── harvest.py # nightly extraction (11:50 PM VPS cron)
├── send-digest.py # morning email (6:30 AM VPS cron)
├── store.json # running loose-ends state (git-tracked)
├── build.txt
├── CLAUDE.md
└── .gitignore
{
"items": [
{
"title": "Short imperative task",
"context": "1-2 sentences on why it was left unfinished",
"priority": "high|medium|low",
"tags": ["repo_or_topic"],
"discovered": "2026-04-16",
"status": "open|done"
}
]
}

harvest.py does a git pull --rebase --autostash before writing, and git commit + push after writing if any items were added. This keeps the MacBook Pro, Studio, and VPS store in sync automatically. The VPS is the authoritative writer (it runs the cron), but other machines can read the latest state anytime.

50 23 * * * cd /root/apps/loose-ends && /usr/bin/python3 harvest.py >> harvest.log 2>&1
30 6 * * * cd /root/apps/loose-ends && /usr/bin/python3 send-digest.py >> digest.log 2>&1

The 6:30 AM digest fires after the 6:00 AM daily-reflection so they do not compete for attention in the morning inbox.

Haiku 4.5 is the right tool for extraction. Prompt says “find unfinished threads in this log, output JSON” — no deep reasoning required. Sonnet is reserved for the daily reflection, which has to synthesize judgment calls across SOPs, skills, and CLAUDE.md drift. Opus would be overkill here and cost more tokens for the same result.

Both daily-reflection.py and harvest.py run through the Claude Max plan via claude -p, so neither costs paid API tokens.

  • The first VPS run found 5 loose ends from a single day’s log, including the meta one: “complete harvest.py testing and cron setup” — a good sign the extraction prompt is tuned right.
  • Dedupe is fuzzy-title only. Two nearly-identical items (“Nudge matcher paragraph-level fix” vs “Nudge matcher — widen waiting-signal detection”) slipped past on day one. Acceptable for v1; may need semantic dedupe later.
  • No “done” CLI yet. James marks items done by editing store.json. If this becomes tedious, add a mark-done.py helper.
  • Intent Resolution — how Claude decides what James means in the moment
  • Voice Nudges — how Claude calls James back to a waiting session
  • Daily Reflection runs in the same slot (VPS, 6 AM) and covers SOP drift, not unfinished threads