From 91930f820b62245919d44b7a6249448aa479c39c Mon Sep 17 00:00:00 2001 From: type-two Date: Mon, 20 Jul 2026 05:07:22 +1000 Subject: [PATCH] Flow Auto-Pilot banks: compile sheets into 11 attach-grouped prompt banks + runbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 82 remaining shots x2 takes = 164 gens (~4,160 cr of 9,080). Banks live in ~/Documents/flowext/banks/rsg/, grouped by (tier, attach-set) since the extension types text only — composer attachments/model set once per batch. Co-Authored-By: Claude Fable 5 --- production/flow/AUTOPILOT_RUNBOOK.md | 39 ++++++++++++ tools/make_flow_bank.py | 93 ++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 production/flow/AUTOPILOT_RUNBOOK.md create mode 100644 tools/make_flow_bank.py diff --git a/production/flow/AUTOPILOT_RUNBOOK.md b/production/flow/AUTOPILOT_RUNBOOK.md new file mode 100644 index 0000000..62d8477 --- /dev/null +++ b/production/flow/AUTOPILOT_RUNBOOK.md @@ -0,0 +1,39 @@ +# RSG × Flow Auto-Pilot runbook (2026-07-20) + +The sheets are compiled into 11 prompt banks at `~/Documents/flowext/banks/rsg/` +(built by `tools/make_flow_bank.py` — rerun it after editing any sheet). Each +bank = one (model tier, attach-set) combo, 2 takes per shot, ~4,160 credits +total. The extension types prompt text only, so per batch YOU set the composer +state once; then it drains unattended. + +## Per-batch loop +1. Brave → the RECORD STORE GUY Flow project (not djsim/shop!). +2. Composer: attach the bank's characters (in the bank filename), model per + tier — `fast__*` = Veo 3.1 Fast, `quality__*` = Veo 3.1 Quality — outputs 1x, + Agent settings "Confirm before generating: Never". +3. Enqueue the bank (N = the number prefix): + `python3 ~/Documents/recordstoreguy/tools/make_flow_bank.py --enqueue N` +4. Extension popup: tick Auto-start. Walk away. Downloads land in + `~/Downloads/flowrinse/rsg/`. +5. Between banks: swap attachments/model, enqueue the next N. + +## Bank order (biggest first) +01 fast Dez ×120 gens · 02 fast Dez+NameDropper ×6 · 03 fast Dez+BoringGuy ×4 · +04 fast DeadWax ×2 · 05 fast no-chars ×2 · 06 fast BoringGuy ×2 · +07 QUALITY Dez+DeadWax ×14 · 08 QUALITY Dez ×8 · 09 QUALITY Mildred ×2 · +10 QUALITY Dez+Postman ×2 · 11 QUALITY Dez+Accountant ×2 + +## Gotchas +- 96 stale djsim wardrobe IMAGE tasks are still pending in the live queue. + Videos are served first so RSG preempts them, but when the videos drain the + images will fire into whatever tab is open — untick Auto-start when the rsg + category stops growing, or strip them from + `~/Library/Application Support/flowrinse/queue.jsonl` (backup first; ids not + in results.jsonl are the pending ones). They're also stashed-by-copy at + `~/Documents/flowext/banks/djsim_wardrobe_pending_20260720.jsonl`. +- Keepers: postprocess into the engine — + `tools/postprocess.sh ` → `engine/clips/`, + then tune windows in `?debug=1`. Clip filenames already match scenes.json. +- mf1 prompts end with a different lettering clause on purpose (the letters are + the hazard). Everything else ends with the standard no-lettering sentence. +- Log keepers in production/SHOTLOG.csv per the sheets. diff --git a/tools/make_flow_bank.py b/tools/make_flow_bank.py new file mode 100644 index 0000000..9f325f6 --- /dev/null +++ b/tools/make_flow_bank.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Compile production/flow/*_SHOTS.md into Flow Auto-Pilot prompt banks. + +The flowext extension types prompt TEXT only — attached characters and the +model/output-count are composer state set by hand once per batch. So shots are +grouped into one bank per (tier, attach-set); John opens the RSG Flow project, +attaches that bank's characters, sets the model (Fast/Quality) and 1x, then the +bank is enqueued and drains unattended. 2 takes per shot per the spend doctrine. + +Usage: + make_flow_bank.py # write banks to ~/Documents/flowext/banks/rsg/ + make_flow_bank.py --enqueue N # POST bank number N to localhost:8017/enqueue +""" +import json +import re +import sys +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SHEETS = ["TRACK1", "TRACK2", "TRACK3", "TRACK4", "TRACK5", "TRACK6", "TRACK7", + "SHIFTS", "EXTRAS"] +BANKS = Path.home() / "Documents/flowext/banks/rsg" +SKIP = {"Logging", "t1_whitelabel", "attract_loop", "intro_qual"} # done or cut +TAKES = 2 +SERVER = "http://localhost:8017" + + +def parse(): + shots = [] # (id, chars tuple, quality bool, prompt) + for name in SHEETS: + text = (ROOT / f"production/flow/{name}_SHOTS.md").read_text() + for m in re.finditer(r"^## (.+?)\n(.*?)(?=^## |\Z)", text, re.M | re.S): + header, body = m.group(1), m.group(2) + sid = header.split()[0] + if sid in SKIP: + continue + quality = "QUALITY" in header + am = re.search(r"\[attach: ([^\]]+)\]", header) or \ + re.search(r"\[([^\]]+)\]", header) + chars = () if not am or am.group(1) == "no characters" else \ + tuple(c.strip() for c in am.group(1).split(",")) + lines = [l.strip() for l in body.strip().splitlines() if l.strip()] + if lines and lines[0].startswith("(") and lines[0].endswith(")"): + lines = lines[1:] # production note, not paste text + prompt = re.sub(r"\s+", " ", " ".join(lines)).strip() + if len(prompt) < 80: # header wrap fragments / non-prompt sections + print(f" !! skipping {sid}: body too short ({len(prompt)} ch)") + continue + shots.append((sid, chars, quality, prompt)) + return shots + + +def slug(chars): + return "+".join(re.sub(r"^the ", "", c.lower()).replace(" ", "") for c in chars) or "nochars" + + +def main(): + shots = parse() + groups = {} # (quality, chars) -> [shots] — keep sheet order within a group + for s in shots: + groups.setdefault((s[2], s[1]), []).append(s) + # fast banks first (bulk of the game), quality after; big groups first + order = sorted(groups, key=lambda k: (k[0], -len(groups[k]))) + BANKS.mkdir(parents=True, exist_ok=True) + for old in BANKS.glob("*.jsonl"): + old.unlink() + for n, key in enumerate(order, 1): + quality, chars = key + model = "Veo 3.1 - Quality" if quality else "Veo 3.1 - Fast" + path = BANKS / f"{n:02d}__{'quality' if quality else 'fast'}__{slug(chars)}.jsonl" + with open(path, "w") as f: + for sid, _, _, prompt in groups[key]: + for take in range(1, TAKES + 1): + f.write(json.dumps({ + "id": f"rsg_{sid}__t{take}", "kind": "video", "model": model, + "title": f"{sid} take {take}", "prompt": prompt, + "category": "rsg"}) + "\n") + gens = len(groups[key]) * TAKES + cr = gens * (100 if quality else 10) + print(f"{path.name}: {len(groups[key])} shots x{TAKES} = {gens} gens ~{cr} cr" + f" [attach: {', '.join(chars) or 'none'}]") + if len(sys.argv) > 2 and sys.argv[1] == "--enqueue": + bank = sorted(BANKS.glob(f"{int(sys.argv[2]):02d}__*.jsonl"))[0] + for line in bank.read_text().splitlines(): + req = urllib.request.Request(SERVER + "/enqueue", line.encode(), + {"Content-Type": "application/json"}) + urllib.request.urlopen(req, timeout=5).read() + print(f"enqueued {bank.name}") + + +if __name__ == "__main__": + main()