Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148// loop.mjs — the whole game, once.//// generate → gate → enqueue. Then a human says cancel or ok, and that// verdict is what the NEXT generate reads. That's the entire machine.//// Run it on a cron (hourly) and the queue fills while you sleep; open `cancelok`// and spend a minute emptying it. The loop's only ambition is to never waste// one of those seconds on something that doesn't run.//// node cancelok/loop.mjs # one turn// node cancelok/loop.mjs --tries 3 # keep generating until one passes
import { execFileSync } from "node:child_process";import { readFileSync, writeFileSync, existsSync, rmSync } from "node:fs";import { resolve } from "node:path";import { REPO, QUEUE } from "./taste.mjs";import { gate } from "./gate.mjs";import { ship } from "./ship.mjs";
const DISKS = resolve(REPO, "system/public/aesthetic.computer/disks");const TRIES = parseInt(process.argv[process.argv.indexOf("--tries") + 1] || "1", 10) || 1;const SHIP = !process.argv.includes("--no-ship");
// The colony guards on "is the tree dirty," but that rule would mute this loop// forever — @jeffrey always has something open. The honest guard is narrower:// ship.mjs only ever stages the new pad and bags.json, so the only way it can// step on live work is if HE is mid-edit in bags.json. Everything else in the// tree is none of our business, and `pull --rebase --autostash` keeps it safe.function clean() { const status = execFileSync("git", ["status", "--porcelain", "--", "system/public/aesthetic.computer/bags.json"], { cwd: REPO, encoding: "utf8", }).trim(); return status === "";}
// serve-local is what the gate measures against, so the loop owns its lifetime —// a cron can't assume a terminal left one running.function serving() { try { execFileSync("curl", ["-sf", "-o", "/dev/null", "http://localhost:8899/"], { timeout: 4000 }); return true; } catch { return false; }}
const queue = () => (existsSync(QUEUE) ? JSON.parse(readFileSync(QUEUE, "utf8")) : { items: [] });
function enqueue(item) { const q = queue(); q.items = q.items.filter((i) => i.code !== item.code).concat(item); writeFileSync(QUEUE, JSON.stringify(q, null, 2) + "\n");}
if (!serving()) { console.error("❌ nothing on :8899 — the gate has nothing to measure."); console.error(" start it: node marketing/av-reels/bin/serve-local.mjs"); process.exit(1);}
// Breed or invent. Two thirds of the time we breed from something that already// earned attention; one third we throw the history away and make something with// no parents at all.//// That third is not a hedge, it's the anti-collapse valve. A loop that only ever// breeds from winners walks straight to a local maximum and then spends the rest// of its life congratulating itself there — and it can never notice, because every// generation still scores well against the last one. The only cure is to keep// spending real attention on ideas the history says nothing about.const EVOLVE_ODDS = 0.66;
for (let turn = 1; turn <= TRIES; turn++) { console.log(`\n— turn ${turn}/${TRIES} —`);
const breeding = !process.argv.includes("--fresh") && Math.random() < EVOLVE_ODDS; const tool = breeding ? "cancelok/evolve.mjs" : "cancelok/generate.mjs"; // The non-breeding turn is BLIND — it isn't told what anyone liked. A "fresh" // generation that still reads the pheromone isn't fresh at all: it just invents // another pendulum, politely. That was the bug that let five of them through. const argv = breeding ? [] : ["--blind"];
let made; try { const out = execFileSync("node", [resolve(REPO, tool), ...argv], { encoding: "utf8", cwd: REPO, stdio: ["inherit", "pipe", "inherit"], maxBuffer: 1024 * 1024 * 8, }); made = JSON.parse(out.trim().split("\n").pop()); } catch (e) { // Exit 2 = nothing kept yet (a cold start). Exit 3 = every line is saturated // and breeding would only deepen the rut. Both mean the same thing: stop // asking the history what to do, and go make something it can't predict. if (breeding && (e.status === 2 || e.status === 3)) { console.log( e.status === 3 ? " the gene pool has collapsed — going blind." : " nothing kept yet — inventing from scratch instead.", ); try { const out = execFileSync("node", [resolve(REPO, "cancelok/generate.mjs"), "--blind"], { encoding: "utf8", cwd: REPO, stdio: ["inherit", "pipe", "inherit"], maxBuffer: 1024 * 1024 * 8, }); made = JSON.parse(out.trim().split("\n").pop()); } catch { console.error("❌ generation failed this turn — moving on."); continue; } } else { console.error("❌ generation failed this turn — moving on."); continue; } }
const r = gate(made.code); if (!r.pass) { // A pad that doesn't run never reaches a person. We delete it rather than // queue it: the human tier is for taste, and this was never a taste // question. The failure is still worth saying out loud — it's the only // way the perf contract gets tightened. rmSync(resolve(DISKS, `${made.code}.mjs`), { force: true }); console.log(`❌ ${made.code} rejected by the gate: ${r.why}`); continue; }
enqueue({ ...made, fps: r.fps, born: new Date().toISOString() }); console.log(`✅ ${made.code} passed the gate (${r.fps}fps)`);
if (SHIP) { if (!clean()) { console.log("✋ bags.json is mid-edit — the queen is curating. Left it queued locally."); process.exit(0); } ship(made.code, made.trait); } else { console.log(" (--no-ship) queued locally. open: cancelok"); } process.exit(0);}
console.log("\nno candidate survived the gate this run.");process.exit(1);