From 5a33cc380aab763a181732fe93baf5889c30a3ac Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 14 Jul 2026 09:33:23 -0700 Subject: [PATCH] =?UTF-8?q?cancelok:=20keep=20the=20loop=20running=20?= =?UTF-8?q?=E2=80=94=20cron,=20evolve,=20generate,=20lineage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- cancelok/cron.fish | 43 ++++++++++++++++++++++++++++++++++++------- cancelok/evolve.mjs | 6 +++++- cancelok/generate.mjs | 6 +++++- cancelok/lineage.json | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/cancelok/cron.fish b/cancelok/cron.fish index 60be5e695..288e2b78e 100644 --- a/cancelok/cron.fish +++ b/cancelok/cron.fish @@ -22,11 +22,37 @@ set -l PLIST "$HOME/Library/LaunchAgents/$LABEL.plist" set -l LOG "$HOME/Library/Logs/cancelok.log" set -l CMD $argv[1] -# The loop needs serve-local (the gate measures against it) and `claude` (the -# generator IS claude). launchd starts with a bare PATH, so we hand it the real -# one rather than discovering at 3am that node isn't on it. -set -l NODE (which node) -set -l REALPATH (dirname $NODE):/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin +# launchd starts with a bare PATH — it does not source your shell — so every +# binary has to be handed over as an absolute path it will still be able to find +# after a reboot. +# +# `which node` is a TRAP here: under fnm it resolves to a per-shell temp dir with +# a PID in the name (…/fnm_multishells/36675_…/bin/node). Baking that into a +# launchd plist works today and dies the next time you log in, and the failure is +# invisible because the cron just quietly stops making anything. So we resolve the +# STABLE fnm alias instead, and fall back to homebrew. +set -l NODE +for candidate in "$HOME/.local/share/fnm/aliases/default/bin/node" /opt/homebrew/bin/node /usr/local/bin/node + if test -x $candidate + set NODE $candidate + break + end +end +if test -z "$NODE" + echo "❌ no stable node found — refusing to install a cron that will rot." + exit 1 +end + +# `claude` IS the generator, and it lives in ~/.local/bin, which launchd has never +# heard of. +set -l CLAUDE (command -v claude) +if test -z "$CLAUDE"; set CLAUDE "$HOME/.local/bin/claude"; end +if not test -x $CLAUDE + echo "❌ claude not found at $CLAUDE — the generator cannot run." + exit 1 +end + +set -l REALPATH (dirname $NODE):(dirname $CLAUDE):/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin switch "$CMD" case install @@ -40,10 +66,13 @@ switch "$CMD" /bin/sh -lc - cd $REPO; curl -sf -o /dev/null http://localhost:8899/ || (node marketing/av-reels/bin/serve-local.mjs & sleep 3); node cancelok/loop.mjs --tries 2 --no-ship + cd $REPO; curl -sf -o /dev/null http://localhost:8899/ || ($NODE marketing/av-reels/bin/serve-local.mjs & sleep 3); exec $NODE cancelok/loop.mjs --tries 2 --no-ship EnvironmentVariables - PATH$REALPATH + + PATH$REALPATH + CANCELOK_CLAUDE$CLAUDE + StartInterval3600 RunAtLoad StandardOutPath$LOG diff --git a/cancelok/evolve.mjs b/cancelok/evolve.mjs index bc509de11..e50972af5 100644 --- a/cancelok/evolve.mjs +++ b/cancelok/evolve.mjs @@ -27,6 +27,10 @@ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs"; import { resolve } from "node:path"; import { REPO, crowd, pheromone, lineage, remember } from "./taste.mjs"; +// launchd has never heard of ~/.local/bin, so the cron hands us an absolute +// path. A bare "claude" works in a terminal and ENOENTs at 3am. +const CLAUDE = process.env.CANCELOK_CLAUDE || "claude"; + const DISKS = resolve(REPO, "system/public/aesthetic.computer/disks"); const ENGINE = resolve(REPO, "system/public/aesthetic.computer/lib/pads.mjs"); const dry = process.argv.includes("--dry"); @@ -178,7 +182,7 @@ if (dry) { console.log(`🧬 breeding ${mum.code} — mutation: ${mutation.key}${mutation.dad ? ` × ${mutation.dad}` : ""}`); // A language model, not an agent: `claude --print` can WRITE FILES, and a // generator that writes its own pad collides with itself on the name check. -const raw = execFileSync("claude", ["--print", "--model", "sonnet", "--tools", ""], { +const raw = execFileSync(CLAUDE, ["--print", "--model", "sonnet", "--tools", ""], { input: prompt, encoding: "utf8", maxBuffer: 1024 * 1024 * 8, diff --git a/cancelok/generate.mjs b/cancelok/generate.mjs index 32b2b0612..a8528444b 100644 --- a/cancelok/generate.mjs +++ b/cancelok/generate.mjs @@ -14,6 +14,10 @@ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs"; import { resolve } from "node:path"; import { REPO, pheromone } from "./taste.mjs"; +// launchd has never heard of ~/.local/bin, so the cron hands us an absolute +// path. A bare "claude" works in a terminal and ENOENTs at 3am. +const CLAUDE = process.env.CANCELOK_CLAUDE || "claude"; + const DISKS = resolve(REPO, "system/public/aesthetic.computer/disks"); const ENGINE = resolve(REPO, "system/public/aesthetic.computer/lib/pads.mjs"); const EXAMPLE = resolve(DISKS, "wispo.mjs"); @@ -81,7 +85,7 @@ console.log("🧠 generating…"); // writes its own pad will then collide with itself when we check the name. We // want a language model here, not an agent: it returns text, WE decide what // lands on disk. (This cost a whole generation to learn.) -const raw = execFileSync("claude", ["--print", "--model", "sonnet", "--tools", ""], { +const raw = execFileSync(CLAUDE, ["--print", "--model", "sonnet", "--tools", ""], { input: prompt, encoding: "utf8", maxBuffer: 1024 * 1024 * 8, diff --git a/cancelok/lineage.json b/cancelok/lineage.json index 520d2dafe..99dabc752 100644 --- a/cancelok/lineage.json +++ b/cancelok/lineage.json @@ -5,5 +5,47 @@ "mutation": "cross", "trait": "pixel-sort / datamosh field (from vex) x pendulum-wave bell/pluck voice, scored by just-intonation dissonance (from drolo)", "born": "2026-07-14T06:03:23.577Z" + }, + "wandro": { + "parent": "wexo", + "second": "drolo", + "mutation": "cross", + "trait": "pendulum-row visual (wexo) x just-intonation layered voice (drolo)", + "born": "2026-07-14T06:33:06.854Z" + }, + "welto": { + "parent": "wexo", + "second": null, + "mutation": "push", + "trait": "a pendulum wave you can grab and pull taut x pentatonic scrub + slingshot pluck", + "born": "2026-07-14T08:11:25.899Z" + }, + "vosk": { + "parent": "vex", + "second": null, + "mutation": "voice", + "trait": "pixel-sort/datamosh glitch field x slow glass-flute drone", + "born": "2026-07-14T10:27:57.728Z" + }, + "dravo": { + "parent": "drolo", + "second": "wexo", + "mutation": "cross", + "trait": "pendulum-wave row (drolo) x bottom-center pluck detection (wexo)", + "born": "2026-07-14T12:36:20.665Z" + }, + "glavo": { + "parent": "vex", + "second": "tavo", + "mutation": "cross", + "trait": "pixel-sort/datamosh visual generator (vex) x bubble-bath synth voice (tavo)", + "born": "2026-07-14T13:39:43.347Z" + }, + "vexen": { + "parent": "wexo", + "second": null, + "mutation": "invert", + "trait": "struck pendulum row that decays into stillness x pentatonic pluck fading to hush", + "born": "2026-07-14T14:43:43.424Z" } } -- 2.51.2