Flow Auto-Pilot banks: compile sheets into 11 attach-grouped prompt banks + runbook

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 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-20 05:07:22 +10:00
parent 4f7d21df2b
commit 91930f820b
2 changed files with 132 additions and 0 deletions

View File

@ -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 <raw> <shot_id> <action|death>``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.

93
tools/make_flow_bank.py Normal file
View File

@ -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()