Auditioning the Standby Outro: ytoutro, ytreboot, and AST Hot-Reloading on Honeybot

•

🤖 Read Raw Markdown

Setting the Stage: Context for the Curious Book Reader

Running a 24/7 automated live-stream from a home-hosted NixOS server exposes subtle timing seams between code deployment and on-air performance. When a publish command triggers a remote git push hook, the broadcast daemon immediately interrupts its narration to read an end-of-stream standby message over a ticking clock while the site rebuilds. Because that git hook fired in Step 1 before the deployment script copied updated Python files in Step 3—and because the long-running stream.py daemon held its speech strings in RAM—any edits made to the closing announcement wouldn’t be heard until the following broadcast cycle. This entry traces how we resolved that ordering bug by hoisting the speech constants, re-reading them fresh from disk via Python’s ast.parse on trigger, pre-syncing the script directory before the push, and adding ytoutro and ytreboot to audition Piper TTS voice lines locally before they ever hit the airwaves.

TL;DR: A live-streaming server daemon (stream.py) held its end-of-broadcast standby announcement in process memory, and the deployment command (publish) triggered the remote standby hook before syncing updated scripts to the server. Hoisting the speech constants to the top of stream.py, re-reading them from disk via ast.parse whenever the standby bell rings, pre-syncing the scripts directory before git push, and adding two CLI helpers (ytoutro for local Piper TTS auditioning and editing, and ytreboot for one-command script sync and daemon restart) allow broadcast language to be tested locally and heard on the very next publish.


Technical Journal Entry Begins

🔗 Verified Pipulate Commits:

MikeLev.in: From time to time I need to simply hear locally on Pipulate Prime what will be playing at the end of a live-stream I’m about to stop.

I also need to be able to conveniently update that language that I use at the end of the broadcast that plays when I use publish "message" that you will see in remotes/honeybot/scripts/stream.py.

And so that it actually plays the next time I publish I need to identify how I do a currently or can add if I don’t have it a single easy-to-remember command to cause a full reboot loop or whatever the minimal thing is that I have to do so that the next time I publish, I hear the new message.

I know this is a bit tricky and it might mean I interrupt the existing stream show to update the infrastructure behind it as if it were a publish and it might confuse the audience, but that’s fine because in short I want to:

  1. Work out new language on Pipulate Prime.
  2. Hear that new language the next time I publish from Pipulate Prime to Honeybot.

Gemini 4 Argon High: Context verified.

TL;DR:

  1. *Why edits to stream.py didn’t play on the next publish**: In flake.nix, publish "msg" runs git push in Step [1/5] (which immediately fires Honeybot’s post-receive hook and rings .deploy_standby) *before Step [3/5] (./nixops.sh) copies stream.py to Honeybot, and the running stream.py process on Honeybot held standby_loop_lines in RAM until a --reboot in Step [5/5].
  2. The Fix:
    • STANDBY_INTRO_LINE and STANDBY_LOOP_LINES are hoisted to the top --- Configuration --- block of remotes/honeybot/scripts/stream.py, and load_standby_speech() re-reads them fresh off disk via ast.parse whenever .deploy_standby fires.
    • publish now pre-syncs remotes/honeybot/scripts/ to Honeybot before git push rings .deploy_standby.
    • Two new shell words join the yt broadcast kit:
    • ytoutro — prints and speaks the standby/end-of-broadcast message locally on Pipulate Prime using Piper TTS + background tick.wav (Ctrl+C to stop; ytoutro --no-tick for voice only; ytoutro show for text only; ytoutro edit to jump straight to STANDBY_LOOP_LINES in nvim).
    • ytreboot — syncs remotes/honeybot/scripts/ to Honeybot and restarts the stream.py child process so the watchdog relaunches it in ~5 seconds.

Action Items (After Applying Patch)

  1. Reload the shell functions (or re-enter nix develop) and run ytreboot once so Honeybot’s running stream.py process loads load_standby_speech().
  2. Anytime you want to work on the end-of-stream speech:
    • ytoutro — listen locally on Pipulate Prime.
    • ytoutro edit — edit STANDBY_INTRO_LINE / STANDBY_LOOP_LINES in remotes/honeybot/scripts/stream.py.
    • Next publish "msg" will play the new wording automatically (or run ytreboot to restart the Honeybot stream immediately).

1: BEFORE (PROBE, read-only, paste as block in command line):

Run in (nix) pipulate $:

.venv/bin/python scripts/articles/common.py --broadcast outro --dry-run
.venv/bin/python scripts/articles/common.py --broadcast reboot --dry-run
nix eval .#devShells.x86_64-linux.quiet.shellHook --raw | rg 'ytoutro|ytreboot|Pre-sync Honeybot stream scripts'

2: AFTER (NEXT CONTEXT, put in context.txt using context or x):

remotes/honeybot/scripts/stream.py
scripts/articles/common.py
flake.nix
! .venv/bin/python scripts/articles/common.py --broadcast outro --dry-run
! .venv/bin/python scripts/articles/common.py --broadcast reboot --dry-run
! nix eval .#devShells.x86_64-linux.quiet.shellHook --raw | rg 'ytoutro|ytreboot|Pre-sync Honeybot stream scripts'

3: CHANGE (PATCHES, real SEARCH/DIVIDER/REPLACE blocks for apply.py):

Target: remotes/honeybot/scripts/stream.py
[[[SEARCH]]]
# --- Configuration ---
SHOW_DURATION_MINUTES = 240  # Minutes for the big logfile stream
PITCH_INTERVAL = 600        # Seconds between "Station Identification" (Bumped to 10 mins so it doesn't get repetitive)
[[[DIVIDER]]]
# --- Configuration ---
SHOW_DURATION_MINUTES = 240  # Minutes for the big logfile stream
PITCH_INTERVAL = 600        # Seconds between "Station Identification" (Bumped to 10 mins so it doesn't get repetitive)

# Spoken over tick.wav when `publish "msg"` rings .deploy_standby on Honeybot.
# Edit with `ytoutro edit` and audition locally on Prime with `ytoutro`.
STANDBY_INTRO_LINE = "Receiving updates. This broadcast is coming to an end."

STANDBY_LOOP_LINES = (
    "To hear the next article, visit YouTube dot com slash M I K E L E V I N slash live. "
    "If you get there too soon you might have to wait a bit and hit refresh to see the new live-stream appear, but it will be worth it.",
    "If you are actually here seeing this live-performance wrapping up at the time of first broadcast, then you are seeing something new in the Universe.",
    "None of this is trained into the parametric memory of today's frontier models, meaning this is genuinely new information per Claude Shannon's second famous paper, A Mathematical Theory of Communication, in which he would tell you that anything that can be produced by prompting an A I that is built from its existing weights is not information. It is noise.",
    "This live-broadcast and the articles being read here are not that. "
    "The author is macheteing his way through the Noosphere of things being experimentally done for the first time in human history.",
    "This is The Signal. "
    "It may not be apparent to you right now, but this is the most unique and important thing you will see today, and if you tune in again later, then the next one will be the same -- and by that I mean different and equally new. "
    "Welcome to The Information.",
)

def load_standby_speech(stream_file=None):
    """Read (STANDBY_INTRO_LINE, STANDBY_LOOP_LINES) fresh from stream.py on disk via AST.

    Allows an rsynced update to stream.py to play on the very next .deploy_standby
    bell without requiring a stream.py process restart.
    """
    import ast
    intro = STANDBY_INTRO_LINE
    loop_lines = STANDBY_LOOP_LINES
    target = Path(stream_file) if stream_file else Path(__file__).resolve()
    try:
        tree = ast.parse(target.read_text(encoding="utf-8"))
        for node in tree.body:
            if isinstance(node, ast.Assign):
                for t in node.targets:
                    if isinstance(t, ast.Name) and t.id == "STANDBY_INTRO_LINE":
                        val = ast.literal_eval(node.value)
                        if isinstance(val, str) and val.strip():
                            intro = val.strip()
                    elif isinstance(t, ast.Name) and t.id == "STANDBY_LOOP_LINES":
                        val = ast.literal_eval(node.value)
                        if isinstance(val, (tuple, list)) and val:
                            loop_lines = tuple(str(x) for x in val if str(x).strip())
    except Exception:
        pass
    return intro, loop_lines
[[[REPLACE]]]
Target: remotes/honeybot/scripts/stream.py
[[[SEARCH]]]
            if check_standby():
                standby_loop_lines = (
                    "To hear the next article, visit YouTube dot com slash M I K E L E V I N slash live. "
                    "If you get there too soon you might have to wait a bit and hit refresh to see the new live-stream appear, but it will be worth it.",
                    "If you are actually here seeing this live-performance wrapping up at the time of first broadcast, then you are seeing something new in the Universe.",
                    "None of this is trained into the parametric memory of today's frontier models, meaning this is genuinely new information per Claude Shannon's second famous paper, A Mathematical Theory of Communication, in which he would tell you that anything that can be produced by prompting an A I that is built from its existing weights is not information. It is noise.",
                    "This live-broadcast and the articles being read here are not that. "
                    "The author is macheteing his way through the Noosphere of things being experimentally done for the first time in human history.",
                    "This is The Signal. "
                    "It may not be apparent to you right now, but this is the most unique and important thing you will see today, and if you tune in again later, then the next one will be the same -- and by that I mean different and equally new. "
                    "Welcome to The Information.",
                )
                was_exclusive = narrator.exclusive_mode.is_set()
[[[DIVIDER]]]
            if check_standby():
                standby_intro_line, standby_loop_lines = load_standby_speech()
                was_exclusive = narrator.exclusive_mode.is_set()
[[[REPLACE]]]
Target: remotes/honeybot/scripts/stream.py
[[[SEARCH]]]
                try:
                    narrator.interrupt()  # cut current audio + flush the backlog
                    narrator.say("Receiving updates. This broadcast is coming to an end.")
                    for line in standby_loop_lines:
                        narrator.say(line)
[[[DIVIDER]]]
                try:
                    narrator.interrupt()  # cut current audio + flush the backlog
                    narrator.say(standby_intro_line)
                    for line in standby_loop_lines:
                        narrator.say(line)
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")
        print(" 18. ytcard             📇 Reprint the full Broadcast Kit for the latest post")
[[[DIVIDER]]]
        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")
        print(" 18. ytoutro            🔊 Hear deploy-standby outro locally (`ytoutro edit|sync|reboot`)")
        print("     ytreboot           🔄 Sync Honeybot scripts + restart stream.py watchdog child")
        print(" 19. ytcard             📇 Reprint the full Broadcast Kit for the latest post")
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    print("🎛️  Quick Words:  yt (help) | ythero | ytyaml | tweet | linkedin | ytpebble | yttitle | ytdesc | ytcard | yttop | ytsay | ytwatch | ytping | ytroll | ytbell | ytstatus")
[[[DIVIDER]]]
    print("🎛️  Quick Words:  yt (help) | ythero | ytyaml | tweet | linkedin | ytpebble | yttitle | ytdesc | ytcard | yttop | ytoutro | ytreboot | ytsay | ytwatch | ytping | ytroll | ytbell | ytstatus")
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Pipulate article common utilities & broadcast kit.")
    add_standard_arguments(parser)
    parser.add_argument(
        "--broadcast",
        choices=("help", "card", "hero", "yaml", "tweet", "linkedin", "pebble", "title", "desc", "top", "status", "bell", "stop", "start", "roll", "enable-ws", "say", "ping", "watch"),
        default="card",
        help="Print/copy broadcast kit or control Honeybot (help|card|hero|yaml|tweet|linkedin|pebble|title|desc|top|status|bell|stop|start|roll|enable-ws|say|ping|watch).",
    )
[[[DIVIDER]]]
def _load_standby_speech_from_stream():
    """Extract (STANDBY_INTRO_LINE, STANDBY_LOOP_LINES, stream_path) from remotes/honeybot/scripts/stream.py."""
    import ast
    repo_root = Path(__file__).resolve().parents[2]
    stream_path = repo_root / "remotes" / "honeybot" / "scripts" / "stream.py"
    intro = "Receiving updates. This broadcast is coming to an end."
    loop_lines = ()
    if stream_path.is_file():
        try:
            tree = ast.parse(stream_path.read_text(encoding="utf-8"))
            for node in tree.body:
                if isinstance(node, ast.Assign):
                    for t in node.targets:
                        if isinstance(t, ast.Name) and t.id == "STANDBY_INTRO_LINE":
                            val = ast.literal_eval(node.value)
                            if isinstance(val, str) and val.strip():
                                intro = val.strip()
                        elif isinstance(t, ast.Name) and t.id == "STANDBY_LOOP_LINES":
                            val = ast.literal_eval(node.value)
                            if isinstance(val, (tuple, list)) and val:
                                loop_lines = tuple(str(x) for x in val if str(x).strip())
        except Exception:
            pass
    return intro, loop_lines, stream_path

def reboot_honeybot_stream(sync_only=False, dry_run=False):
    """Sync remotes/honeybot/scripts/ to Honeybot and optionally restart stream.py."""
    import subprocess
    repo_root = Path(__file__).resolve().parents[2]
    scripts_src = f"{repo_root}/remotes/honeybot/scripts/"
    if dry_run:
        mode_lbl = "sync-only" if sync_only else "sync + restart stream.py"
        print(f"🔄 [DRY RUN] Honeybot Stream Reboot ({mode_lbl}): rsync {scripts_src} -> {HONEYBOT_SSH_HOST}:~/www/mikelev.in/scripts/")
        return 0
    print(f"🚀 Syncing Honeybot scripts to {HONEYBOT_SSH_HOST}:~/www/mikelev.in/scripts/...")
    sync_res = subprocess.run(
        ["rsync", "--delete", "-av", scripts_src, f"{HONEYBOT_SSH_HOST}:~/www/mikelev.in/scripts/"],
        check=False,
    )
    if sync_res.returncode != 0:
        print(f"❌ rsync failed (exit {sync_res.returncode}).")
        return sync_res.returncode
    subprocess.run(
        ["rsync", "-aq", f"{repo_root}/imports/ascii_displays.py", f"{HONEYBOT_SSH_HOST}:~/www/mikelev.in/imports/"],
        check=False,
    )
    subprocess.run(
        ["rsync", "-aq", f"{repo_root}/assets/sounds/tick.wav", f"{HONEYBOT_SSH_HOST}:~/www/mikelev.in/assets/sounds/tick.wav"],
        check=False,
    )
    if sync_only:
        print("✅ Honeybot scripts synced (stream.py left running; new standby text will load on next .deploy_standby).")
        return 0
    print(f"🔄 Restarting Honeybot stream.py child on {HONEYBOT_SSH_HOST}...")
    remote_sh = r"""
pattern="/home/mike/www/mikelev[.]in/scripts/stream[.]py"
old_pids=$(pgrep -f -- "$pattern" || true)
for old in $old_pids; do
    echo "   stopping old stream.py pid=$old"
    kill -TERM "$old" || true
done
pkill -f "/home/mike/www/mikelev[.]in/scripts/logs[.]py" || true
pkill -f "tail -f /var/log/nginx/access[.]log" || true
for i in $(seq 1 14); do
    sleep 1
    new_pids=$(pgrep -f -- "$pattern" || true)
    new_count=$(printf "%s\n" "$new_pids" | sed "/^$/d" | wc -l)
    if [ "$new_count" -ge 1 ]; then
        echo "✅ Stream watchdog relaunched stream.py (pid=$(echo $new_pids | tr '\n' ' ')after ${i}s)."
        exit 0
    fi
done
echo "⚠️ No new stream.py PID seen within 14s; verify the stream watchdog on Honeybot."
exit 1
"""
    res = subprocess.run(["ssh", HONEYBOT_SSH_HOST, "sh", "-s"], input=remote_sh, text=True)
    return res.returncode

def run_honeybot_outro(action="play", tick=True, dry_run=False):
    """Audition, edit, sync, or reboot the Honeybot deploy-standby outro speech on Pipulate Prime."""
    import os
    import shutil
    import signal
    import subprocess
    act = (action or "play").strip().lower()
    intro, loop_lines, stream_path = _load_standby_speech_from_stream()
    if act in ("edit", "vim", "nvim"):
        editor = shutil.which("nvim") or shutil.which("vim") or "vi"
        if dry_run:
            print(f"✏️  [DRY RUN] {editor} +/STANDBY_LOOP_LINES {stream_path}")
            return 0
        subprocess.run([editor, "+/STANDBY_LOOP_LINES", str(stream_path)])
        print("💡 Audition locally: ytoutro  |  Sync without restart: ytoutro sync  |  Sync + restart stream: ytreboot")
        return 0
    if act in ("sync", "push"):
        return reboot_honeybot_stream(sync_only=True, dry_run=dry_run)
    if act in ("reboot", "restart"):
        return reboot_honeybot_stream(sync_only=False, dry_run=dry_run)
    all_lines = [intro, *loop_lines]
    bar = "=" * 72
    print(f"\n{bar}")
    print(f"🎙️  HONEYBOT DEPLOY-STANDBY OUTRO ({stream_path})")
    print("-" * 72)
    print(f"  [Intro] {intro}")
    for idx, ln in enumerate(loop_lines, 1):
        print(f"  [{idx}/{len(loop_lines)}]   {ln}")
    print("-" * 72)
    print("🎛️  Subcommands: ytoutro (play) | ytoutro --no-tick | ytoutro show | ytoutro edit | ytoutro sync | ytreboot")
    print(bar)
    if dry_run or act in ("show", "text", "print"):
        if dry_run:
            print(f"🗣️  [DRY RUN] Outro audition ready ({len(all_lines)} spoken segments, tick={tick}).")
        return 0
    repo_root = Path(__file__).resolve().parents[2]
    piper_bin = str(repo_root / ".venv/bin/piper")
    if not Path(piper_bin).is_file():
        piper_bin = shutil.which("piper") or ""
    model_path = _ensure_local_piper_model()
    if not piper_bin or not model_path:
        print("⚠️ Local Piper TTS model or binary not found; printed text only.")
        return 1
    play_cmds = (["pw-play"], ["paplay"], ["aplay", "-q"], ["afplay"])
    player_base = next((c for c in play_cmds if shutil.which(c[0])), None)
    if not player_base:
        print("⚠️ No local audio player (pw-play/paplay/aplay/afplay) found.")
        return 1
    tick_proc = None
    active_proc = None
    wav_path = Path(f"/tmp/pipulate_outro_{os.getpid()}.wav")
    tick_file = repo_root / "assets" / "sounds" / "tick.wav"
    print("🔊 Playing standby outro locally on Pipulate Prime (press Ctrl+C to stop)...")
    try:
        if tick and tick_file.is_file():
            tick_cmd_str = " ".join([*player_base, f'"{tick_file}"'])
            tick_proc = subprocess.Popen(
                ["sh", "-c", f"while :; do {tick_cmd_str} >/dev/null 2>&1; sleep 1; done"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True,
            )
        for seg in all_lines:
            subprocess.run(
                [piper_bin, "--model", str(model_path), "--output_file", str(wav_path)],
                input=seg.encode("utf-8"),
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                check=False,
            )
            if wav_path.is_file():
                active_proc = subprocess.Popen(
                    [*player_base, str(wav_path)],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                )
                active_proc.wait()
                active_proc = None
        print("✅ Outro audition complete.")
        return 0
    except KeyboardInterrupt:
        print("\n🛑 Stopped local outro playback.")
        return 0
    finally:
        if active_proc and active_proc.poll() is None:
            try:
                active_proc.kill()
            except Exception:
                pass
        if tick_proc is not None:
            try:
                os.killpg(os.getpgid(tick_proc.pid), signal.SIGKILL)
            except Exception:
                pass
        try:
            wav_path.unlink(missing_ok=True)
        except Exception:
            pass

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Pipulate article common utilities & broadcast kit.")
    add_standard_arguments(parser)
    parser.add_argument(
        "--broadcast",
        choices=("help", "card", "hero", "yaml", "tweet", "linkedin", "pebble", "title", "desc", "top", "outro", "reboot", "status", "bell", "stop", "start", "roll", "enable-ws", "say", "ping", "watch"),
        default="card",
        help="Print/copy broadcast kit or control Honeybot (help|card|hero|yaml|tweet|linkedin|pebble|title|desc|top|outro|reboot|status|bell|stop|start|roll|enable-ws|say|ping|watch).",
    )
    parser.add_argument(
        "--no-tick",
        action="store_true",
        help="Disable background tick.wav clock loop during local `ytoutro` audition.",
    )
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    if cli_args.broadcast == "pebble":
        first_pebble_arg = cli_args.message[0].strip().lower() if cli_args.message else ""
        if cli_args.verify or first_pebble_arg in ("verify", "check", "status"):
            sys.exit(verify_pebble_receipts(cli_args.target, slug_match=cli_args.slug))
        sys.exit(drop_pebble_pings(cli_args.target, slug_match=cli_args.slug, dry_run=cli_args.dry_run))
    if cli_args.broadcast in ("status", "bell", "stop", "start", "roll", "enable-ws"):
        sys.exit(run_honeybot_obs(cli_args.broadcast))
    sys.exit(print_broadcast_kit(cli_args.target, cli_args.broadcast, slug_match=cli_args.slug))
[[[DIVIDER]]]
    if cli_args.broadcast == "pebble":
        first_pebble_arg = cli_args.message[0].strip().lower() if cli_args.message else ""
        if cli_args.verify or first_pebble_arg in ("verify", "check", "status"):
            sys.exit(verify_pebble_receipts(cli_args.target, slug_match=cli_args.slug))
        sys.exit(drop_pebble_pings(cli_args.target, slug_match=cli_args.slug, dry_run=cli_args.dry_run))
    if cli_args.broadcast == "reboot":
        sys.exit(reboot_honeybot_stream(sync_only=False, dry_run=cli_args.dry_run))
    if cli_args.broadcast == "outro":
        outro_act = cli_args.message[0].strip().lower() if cli_args.message else "play"
        use_tick = not (cli_args.no_tick or cli_args.no_ding or outro_act in ("voice", "notick"))
        if outro_act in ("voice", "notick"):
            outro_act = "play"
        sys.exit(run_honeybot_outro(action=outro_act, tick=use_tick, dry_run=cli_args.dry_run))
    if cli_args.broadcast in ("status", "bell", "stop", "start", "roll", "enable-ws"):
        sys.exit(run_honeybot_obs(cli_args.broadcast))
    sys.exit(print_broadcast_kit(cli_args.target, cli_args.broadcast, slug_match=cli_args.slug))
[[[REPLACE]]]
Target: flake.nix
[[[SEARCH]]]
          ytmedia()  { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast say -m "$@"; }
          ytapp()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/ytstage.py" app "$@"; }
          ytvim()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/ytstage.py" vim "$@"; }
[[[DIVIDER]]]
          ytmedia()  { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast say -m "$@"; }
          ytoutro()  { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast outro  "$@"; }
          ytreboot() { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast reboot "$@"; }
          ytapp()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/ytstage.py" app "$@"; }
          ytvim()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/ytstage.py" vim "$@"; }
[[[REPLACE]]]
Target: flake.nix
[[[SEARCH]]]
            echo "🚀 [1/5] Payload Delivery: Committing and Pushing $TARGET_REPO..."
            "$PIPULATE_ROOT/.venv/bin/python" -c "import sys; sys.path.insert(0, '$PIPULATE_ROOT/scripts/articles'); import common; common.ensure_indexnow_key_file('1')" || true
            # Execute in a subshell to avoid stranding the user's terminal
[[[DIVIDER]]]
            echo "🚀 [1/5] Payload Delivery: Committing and Pushing $TARGET_REPO..."
            "$PIPULATE_ROOT/.venv/bin/python" -c "import sys; sys.path.insert(0, '$PIPULATE_ROOT/scripts/articles'); import common; common.ensure_indexnow_key_file('1')" || true
            # Pre-sync Honeybot stream scripts BEFORE git push rings .deploy_standby
            # so any edits to STANDBY_LOOP_LINES in stream.py are on disk when standby begins.
            rsync -aq "$PIPULATE_ROOT/remotes/honeybot/scripts/" mike@[REDACTED_IP]:~/www/mikelev.in/scripts/ 2>/dev/null || true
            # Execute in a subshell to avoid stranding the user's terminal
[[[REPLACE]]]

Ignition: After patch, app, d, m, re-enter the Nix shell (exit then nix develop) so ytoutro and ytreboot are in your interactive shell, and run ytreboot once to load the updated stream.py into Honeybot’s RAM.

4: PROMPT (Use prompt or p to create prompt.md from copy.):

Verify the AFTER receipts for `ytoutro` and `ytreboot`, and confirm the standby outro workflow is clean.

5: DELIVERABLE (What done looks like):

  • ytoutro prints and plays STANDBY_INTRO_LINE + STANDBY_LOOP_LINES locally on Pipulate Prime with tick.wav mixed underneath (ytoutro --no-tick for voice only, ytoutro show for text only, ytoutro edit to edit STANDBY_LOOP_LINES in nvim).
  • ytreboot syncs remotes/honeybot/scripts/ to Honeybot and restarts stream.py in one command.
  • publish "msg" pre-syncs remotes/honeybot/scripts/ before git push rings .deploy_standby, and stream.py reloads STANDBY_INTRO_LINE and STANDBY_LOOP_LINES from disk when standby starts.

MikeLev.in: I didn’t think I was going to make this goal into its own standalone article but I think it’s important that I do for continuity of the development of this project and what really my mind went from and to on each one of these chisel-strikes where I feel the friction and stop to improve the overarching system in tiny little forever-forward banked wins as I go.

It’s the performance art right here and it’s easy for me to start slipping into the same habits as everyone else and slip house-keeping to out-of-bounds control planes so the audience can’t see how the Magician set up the stage behind the curtains and if I’m trying to do Penn & Tellers transparent cups & balls trick, that would be bad form.

Ephemeral Collaborators and Live Performance Art

Plus if I look at something in this AI-assisted… what?

It’s AI-assisted performance art at this point with an Amnesiac Genie collaborator called forth from the lamp to do one trick for the audience it knows is there and recede back into the lamp leaving notes for the next instantiation of itself as a new shadow of the static file of weights it was peeled off of as a single-use Amnesiac Genie.

Sometimes they out quite a show and if it’s as surprisingly interesting to me as ones like this are, it’s going to be interesting to someone out there too.

THE AI-EDIT METHOD

Same commands, run twice, one change between them. Where the readings differ is what the change did; the diff in the middle is the receipt.

1: BEFORE (PROBE):

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ .venv/bin/python scripts/articles/common.py --broadcast outro --dry-run
.venv/bin/python scripts/articles/common.py --broadcast reboot --dry-run
nix eval .#devShells.x86_64-linux.quiet.shellHook --raw | rg 'ytoutro|ytreboot|Pre-sync Honeybot stream scripts'
usage: common.py [-h] [-t TARGET] [-k KEY]
                 [--broadcast {help,card,hero,yaml,tweet,linkedin,pebble,title,desc,top,status,bell,stop,start,roll,enable-ws,say,ping,watch}]
                 [-s SLUG] [-c CARD] [-p PATRONUS] [-m MEDIA] [-w WINDOW] [--clip] [--pre PRE] [--linger LINGER] [--dry-run] [--init]
                 [--topic TOPIC] [--title TITLE] [--priority PRIORITY] [--channel CHANNEL] [--loop] [--interval INTERVAL] [--notify] [--no-ding]
                 [--daemon] [--stop] [--verify]
                 [message ...]
common.py: error: argument --broadcast: invalid choice: 'outro' (choose from help, card, hero, yaml, tweet, linkedin, pebble, title, desc, top, status, bell, stop, start, roll, enable-ws, say, ping, watch)
usage: common.py [-h] [-t TARGET] [-k KEY]
                 [--broadcast {help,card,hero,yaml,tweet,linkedin,pebble,title,desc,top,status,bell,stop,start,roll,enable-ws,say,ping,watch}]
                 [-s SLUG] [-c CARD] [-p PATRONUS] [-m MEDIA] [-w WINDOW] [--clip] [--pre PRE] [--linger LINGER] [--dry-run] [--init]
                 [--topic TOPIC] [--title TITLE] [--priority PRIORITY] [--channel CHANNEL] [--loop] [--interval INTERVAL] [--notify] [--no-ding]
                 [--daemon] [--stop] [--verify]
                 [message ...]
common.py: error: argument --broadcast: invalid choice: 'reboot' (choose from help, card, hero, yaml, tweet, linkedin, pebble, title, desc, top, status, bell, stop, start, roll, enable-ws, say, ping, watch)
(nix) pipulate $ 

2: AFTER (NEXT CONTEXT):

# # Context 1
# # WE BEGIN AT THE BEGINNING AND WILL GO ON TILL WE COME TO THE END, THEN STOP.
# ~/repos/nixos/configuration.nix
# ~/repos/nixos/services.nix
# ~/repos/nixos/packages.nix
# 
# # THE SOFTWARE VON NEUMANN PROBE
# assets/installer/install.sh
# flake.nix
# release.py
# ~/repos/nixos/blogs.nix
# remotes/honeybot/www/qamy.ai/index.html
# remotes/honeybot/www/qamy.ai/walk/1/index.html
# remotes/honeybot/www/qamy.ai/walk/2/index.html
# remotes/honeybot/www/qamy.ai/walk/3/index.html
# 
# # THE JEKYLL BLOG LIKE A HACKER (INVENTED SKILL.md & AGENTS.md FORMAT) PUBLISHING PIPELINE
# ~/repos/nixos/blogs.nix
# ~/.config/pipulate/blogs.json
# scripts/articles/publishizer.py
# scripts/articles/common.py
# scripts/articles/articleizer.py
# scripts/articles/link_injector.py
# scripts/articles/build_knowledge_graph.py
# scripts/articles/editing_prompt.txt
# scripts/articles/sanitizer.py
# scripts/articles/contextualizer.py
# scripts/articles/generate_llms_txt.py
# scripts/articles/generate_post_nav.py
# 
# # HOME-HOSTED (NOT BEHIND CDN) NGINX WEBSERVER HONEYBOT AI OBSERVATORY
# nixops.sh
# remotes/honeybot/hooks/post-receive
# remotes/honeybot/nixos/configuration.nix
# remotes/honeybot/scripts/stream.py
# remotes/honeybot/scripts/score.py
# remotes/honeybot/scripts/card.py
# remotes/honeybot/scripts/forest.py
# remotes/honeybot/scripts/test_forest.py
# remotes/honeybot/scripts/logs.py
# remotes/honeybot/scripts/content_loader.py
# remotes/honeybot/scripts/db.py

# Context 2
remotes/honeybot/scripts/stream.py
scripts/articles/common.py
flake.nix
! .venv/bin/python scripts/articles/common.py --broadcast outro --dry-run
! .venv/bin/python scripts/articles/common.py --broadcast reboot --dry-run
! nix eval .#devShells.x86_64-linux.quiet.shellHook --raw | rg 'ytoutro|ytreboot|Pre-sync Honeybot stream scripts'

3: CHANGE (PATCHES):

Okay a hand-applying a patch that missed in the middle of one big patch-file has to be done very carefully, counting down from the top and isolate it like so:

Target: scripts/articles/common.py
[[[SEARCH]]]
        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")
        print(" 18. ytcard             📇 Reprint the full Broadcast Kit for the latest post")
[[[DIVIDER]]]
        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")
        print(" 18. ytoutro            🔊 Hear deploy-standby outro locally (`ytoutro edit|sync|reboot`)")
        print("     ytreboot           🔄 Sync Honeybot scripts + restart stream.py watchdog child")
        print(" 19. ytcard             📇 Reprint the full Broadcast Kit for the latest post")
[[[REPLACE]]]

And we apply it like so. See? This is how we keep our skills from atrophying. It’s all just editing text. That’s what your unsupervised vibe-coding AIs are doing in the background changing all kinds of things unbeknownst to you. Nothing changes unbeknownst to me, at least nothing I can’t go back and look at and that future AI models are going to be training on forever-forward.

Hand-Landing a Missed Hunk While Statler and Waldorf Heckle

Shall we say that whatever AI coding assistant I recruit into my framework to help me is highly invented to not introduce bugs and regressions no matter how “helpful” they think it might be. The typical over-ambition mistakes that AI code assistants do is rigged here to be a show for Statler and Waldorf heckling in the annals of this chapter of where humans were when machines became intelligent.

I don’t get many bugs or accidental regressions introduced into my code anymore… is that True, Gemini? And is that “needless to say” because I feel it needs to be said.

(nix) pipulate $ vim patch scripts/articles/common.py
(nix) pipulate $ d
diff --git a/scripts/articles/common.py b/scripts/articles/common.py
index 67487d9..169292a 100644
--- a/scripts/articles/common.py
+++ b/scripts/articles/common.py
@@ -858,8 +858,10 @@ def print_broadcast_kit(target_key="1", mode="card", slug_match=None):
         print(" 15. ytapp / ytvim     🖥️ Project local FastHTML (:5001) or live Vim onto stream")
         print("     ytmedia <path>    🖼️ Pop up an image or video on Honeybot :10.0 (`ytsay -m`)")
         print(" 16. ytping \"text\"     📲 Send ntfy.sh push notification to your phone")
-        print(" 17. yttop [N]         📊 Keyless YouTube channel top videos & streams radar")
-        print(" 18. ytcard            📇 Reprint the full Broadcast Kit for the latest post")
+        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")
+        print(" 18. ytoutro            🔊 Hear deploy-standby outro locally (`ytoutro edit|sync|reboot`)")
+        print("     ytreboot           🔄 Sync Honeybot scripts + restart stream.py watchdog child")
+        print(" 19. ytcard             📇 Reprint the full Broadcast Kit for the latest post")
         if kit:
             print(thin)
             print(f"📄 Latest Post:   {Path(kit['path']).name}")
(nix) pipulate $ m
📝 Committing: chore: Update broadcast kit print statements in common.py 
[main 55ed3fa] chore: Update broadcast kit print statements in common.py
 1 file changed, 4 insertions(+), 2 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 48 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 605 bytes | 605.00 KiB/s, done.
Total 5 (delta 4), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
To github.com:pipulate/pipulate.git
   f420300..55ed3fa  main -> main
(nix) pipulate $ 

And here’s where the original and quite impressive patch was implied with the missed-in-the-middle patch-landing that I talk about above.

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/scripts/stream.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/scripts/stream.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'remotes/honeybot/scripts/stream.py'.
❌ Warning: SEARCH block not found in 'scripts/articles/common.py'. Skipping.

--- DIAGNOSTIC: First line of your SEARCH block ---
  SEARCH repr : '        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")'
  FILE nearest: 'import json'
  ⚠ Indentation mismatch: SEARCH has 8 spaces, file has 0 spaces.
  ✓ Corrected first line should be: 'print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")'
  ⚠ Content mismatch even after stripping: lines differ beyond whitespace.

--- FIRST DIVERGENCE (no blank-insensitive match; this is not a blank-line gap) ---
  SEARCH line 1   : '        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")'
  FILE line 1     : 'import json'
--- END FIRST DIVERGENCE ---
--- YOUR SUBMITTED SEARCH BLOCK (verbatim) ---
    1: '        print(" 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar")'
    2: '        print(" 18. ytcard             📇 Reprint the full Broadcast Kit for the latest post")'
--- END SUBMITTED SEARCH BLOCK ---

✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'flake.nix'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'flake.nix'.
(nix) pipulate $ d
diff --git a/flake.nix b/flake.nix
index 5fcddab..0d5dabd 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2619,6 +2619,8 @@ print('AI:\n', r.ai)
           ytlog()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast watch attach "$@"; }
           yttop()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast top   "$@"; }
           ytmedia()  { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast say -m "$@"; }
+          ytoutro()  { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast outro  "$@"; }
+          ytreboot() { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/articles/common.py" --broadcast reboot "$@"; }
           ytapp()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/ytstage.py" app "$@"; }
           ytvim()    { "$PIPULATE_ROOT/.venv/bin/python" "$PIPULATE_ROOT/scripts/ytstage.py" vim "$@"; }
 
@@ -2676,6 +2678,9 @@ print('AI:\n', r.ai)
             
             echo "🚀 [1/5] Payload Delivery: Committing and Pushing $TARGET_REPO..."
             "$PIPULATE_ROOT/.venv/bin/python" -c "import sys; sys.path.insert(0, '$PIPULATE_ROOT/scripts/articles'); import common; common.ensure_indexnow_key_file('1')" || true
+            # Pre-sync Honeybot stream scripts BEFORE git push rings .deploy_standby
+            # so any edits to STANDBY_LOOP_LINES in stream.py are on disk when standby begins.
+            rsync -aq "$PIPULATE_ROOT/remotes/honeybot/scripts/" mike@[REDACTED_IP]:~/www/mikelev.in/scripts/ 2>/dev/null || true
             # Execute in a subshell to avoid stranding the user's terminal
             (
                 cd "$TARGET_REPO" || exit 1
diff --git a/remotes/honeybot/scripts/stream.py b/remotes/honeybot/scripts/stream.py
index 8ea5f3b..ff8c609 100644
--- a/remotes/honeybot/scripts/stream.py
+++ b/remotes/honeybot/scripts/stream.py
@@ -63,6 +63,50 @@ def register_signal_handlers():
 SHOW_DURATION_MINUTES = 240  # Minutes for the big logfile stream
 PITCH_INTERVAL = 600        # Seconds between "Station Identification" (Bumped to 10 mins so it doesn't get repetitive)
 
+# Spoken over tick.wav when `publish "msg"` rings .deploy_standby on Honeybot.
+# Edit with `ytoutro edit` and audition locally on Prime with `ytoutro`.
+STANDBY_INTRO_LINE = "Receiving updates. This broadcast is coming to an end."
+
+STANDBY_LOOP_LINES = (
+    "To hear the next article, visit YouTube dot com slash M I K E L E V I N slash live. "
+    "If you get there too soon you might have to wait a bit and hit refresh to see the new live-stream appear, but it will be worth it.",
+    "If you are actually here seeing this live-performance wrapping up at the time of first broadcast, then you are seeing something new in the Universe.",
+    "None of this is trained into the parametric memory of today's frontier models, meaning this is genuinely new information per Claude Shannon's second famous paper, A Mathematical Theory of Communication, in which he would tell you that anything that can be produced by prompting an A I that is built from its existing weights is not information. It is noise.",
+    "This live-broadcast and the articles being read here are not that. "
+    "The author is macheteing his way through the Noosphere of things being experimentally done for the first time in human history.",
+    "This is The Signal. "
+    "It may not be apparent to you right now, but this is the most unique and important thing you will see today, and if you tune in again later, then the next one will be the same -- and by that I mean different and equally new. "
+    "Welcome to The Information.",
+)
+
+
+def load_standby_speech(stream_file=None):
+    """Read (STANDBY_INTRO_LINE, STANDBY_LOOP_LINES) fresh from stream.py on disk via AST.
+
+    Allows an rsynced update to stream.py to play on the very next .deploy_standby
+    bell without requiring a stream.py process restart.
+    """
+    import ast
+    intro = STANDBY_INTRO_LINE
+    loop_lines = STANDBY_LOOP_LINES
+    target = Path(stream_file) if stream_file else Path(__file__).resolve()
+    try:
+        tree = ast.parse(target.read_text(encoding="utf-8"))
+        for node in tree.body:
+            if isinstance(node, ast.Assign):
+                for t in node.targets:
+                    if isinstance(t, ast.Name) and t.id == "STANDBY_INTRO_LINE":
+                        val = ast.literal_eval(node.value)
+                        if isinstance(val, str) and val.strip():
+                            intro = val.strip()
+                    elif isinstance(t, ast.Name) and t.id == "STANDBY_LOOP_LINES":
+                        val = ast.literal_eval(node.value)
+                        if isinstance(val, (tuple, list)) and val:
+                            loop_lines = tuple(str(x) for x in val if str(x).strip())
+    except Exception:
+        pass
+    return intro, loop_lines
+
 PITCH_TEXT = (
     "If you are tuning in and wondering what this cascading wall of code is... welcome to Honeybot. "
     "You are watching the live, real time heartbeat of a web server. Specifically, my personal website, Mike L E V dot eye N. "
@@ -1259,17 +1303,7 @@ def perform_show(script):
             # Announce calmly ONCE, cut the current narration, and HOLD (silent) until
             # the completion bell rings -- so the TTS doesn't thrash through the deploy.
             if check_standby():
-                standby_loop_lines = (
-                    "To hear the next article, visit YouTube dot com slash M I K E L E V I N slash live. "
-                    "If you get there too soon you might have to wait a bit and hit refresh to see the new live-stream appear, but it will be worth it.",
-                    "If you are actually here seeing this live-performance wrapping up at the time of first broadcast, then you are seeing something new in the Universe.",
-                    "None of this is trained into the parametric memory of today's frontier models, meaning this is genuinely new information per Claude Shannon's second famous paper, A Mathematical Theory of Communication, in which he would tell you that anything that can be produced by prompting an A I that is built from its existing weights is not information. It is noise.",
-                    "This live-broadcast and the articles being read here are not that. "
-                    "The author is macheteing his way through the Noosphere of things being experimentally done for the first time in human history.",
-                    "This is The Signal. "
-                    "It may not be apparent to you right now, but this is the most unique and important thing you will see today, and if you tune in again later, then the next one will be the same -- and by that I mean different and equally new. "
-                    "Welcome to The Information.",
-                )
+                standby_intro_line, standby_loop_lines = load_standby_speech()
                 was_exclusive = narrator.exclusive_mode.is_set()
                 narrator.exclusive_mode.set()
                 # The outer `duration` here is a generous LAST-RESORT ceiling, not
@@ -1290,7 +1324,7 @@ def perform_show(script):
                 music_proc = None
                 try:
                     narrator.interrupt()  # cut current audio + flush the backlog
-                    narrator.say("Receiving updates. This broadcast is coming to an end.")
+                    narrator.say(standby_intro_line)
                     for line in standby_loop_lines:
                         narrator.say(line)
                     updating_card = show_card("UPDATING", max_hold=270.0)
diff --git a/scripts/articles/common.py b/scripts/articles/common.py
index df0bfec..67487d9 100644
--- a/scripts/articles/common.py
+++ b/scripts/articles/common.py
@@ -931,7 +931,7 @@ def print_broadcast_kit(target_key="1", mode="card", slug_match=None):
     if yt_thumb:
         print(thin)
         print(f"📺 YT STUDIO THUMBNAIL (1280x720 16:9): {yt_thumb} (Checklist: Upload thumb + Enable DVR)")
-    print("🎛️  Quick Words:  yt (help) | ythero | ytyaml | tweet | linkedin | ytpebble | yttitle | ytdesc | ytcard | yttop | ytsay | ytwatch | ytping | ytroll | ytbell | ytstatus")
+    print("🎛️  Quick Words:  yt (help) | ythero | ytyaml | tweet | linkedin | ytpebble | yttitle | ytdesc | ytcard | yttop | ytoutro | ytreboot | ytsay | ytwatch | ytping | ytroll | ytbell | ytstatus")
     print(thin)
     print(f"🔗 Preview URL:   {kit['preview_url']}")
     print(f"✏️  Edit Command:  vim {kit['path']}")
@@ -2250,14 +2250,199 @@ def fetch_youtube_top_videos(channel_or_url=None, limit=12, dry_run=False):
     return 0
 
 
+def _load_standby_speech_from_stream():
+    """Extract (STANDBY_INTRO_LINE, STANDBY_LOOP_LINES, stream_path) from remotes/honeybot/scripts/stream.py."""
+    import ast
+    repo_root = Path(__file__).resolve().parents[2]
+    stream_path = repo_root / "remotes" / "honeybot" / "scripts" / "stream.py"
+    intro = "Receiving updates. This broadcast is coming to an end."
+    loop_lines = ()
+    if stream_path.is_file():
+        try:
+            tree = ast.parse(stream_path.read_text(encoding="utf-8"))
+            for node in tree.body:
+                if isinstance(node, ast.Assign):
+                    for t in node.targets:
+                        if isinstance(t, ast.Name) and t.id == "STANDBY_INTRO_LINE":
+                            val = ast.literal_eval(node.value)
+                            if isinstance(val, str) and val.strip():
+                                intro = val.strip()
+                        elif isinstance(t, ast.Name) and t.id == "STANDBY_LOOP_LINES":
+                            val = ast.literal_eval(node.value)
+                            if isinstance(val, (tuple, list)) and val:
+                                loop_lines = tuple(str(x) for x in val if str(x).strip())
+        except Exception:
+            pass
+    return intro, loop_lines, stream_path
+
+
+def reboot_honeybot_stream(sync_only=False, dry_run=False):
+    """Sync remotes/honeybot/scripts/ to Honeybot and optionally restart stream.py."""
+    import subprocess
+    repo_root = Path(__file__).resolve().parents[2]
+    scripts_src = f"{repo_root}/remotes/honeybot/scripts/"
+    if dry_run:
+        mode_lbl = "sync-only" if sync_only else "sync + restart stream.py"
+        print(f"🔄 [DRY RUN] Honeybot Stream Reboot ({mode_lbl}): rsync {scripts_src} -> {HONEYBOT_SSH_HOST}:~/www/mikelev.in/scripts/")
+        return 0
+    print(f"🚀 Syncing Honeybot scripts to {HONEYBOT_SSH_HOST}:~/www/mikelev.in/scripts/...")
+    sync_res = subprocess.run(
+        ["rsync", "--delete", "-av", scripts_src, f"{HONEYBOT_SSH_HOST}:~/www/mikelev.in/scripts/"],
+        check=False,
+    )
+    if sync_res.returncode != 0:
+        print(f"❌ rsync failed (exit {sync_res.returncode}).")
+        return sync_res.returncode
+    subprocess.run(
+        ["rsync", "-aq", f"{repo_root}/imports/ascii_displays.py", f"{HONEYBOT_SSH_HOST}:~/www/mikelev.in/imports/"],
+        check=False,
+    )
+    subprocess.run(
+        ["rsync", "-aq", f"{repo_root}/assets/sounds/tick.wav", f"{HONEYBOT_SSH_HOST}:~/www/mikelev.in/assets/sounds/tick.wav"],
+        check=False,
+    )
+    if sync_only:
+        print("✅ Honeybot scripts synced (stream.py left running; new standby text will load on next .deploy_standby).")
+        return 0
+    print(f"🔄 Restarting Honeybot stream.py child on {HONEYBOT_SSH_HOST}...")
+    remote_sh = r"""
+pattern="/home/mike/www/mikelev[.]in/scripts/stream[.]py"
+old_pids=$(pgrep -f -- "$pattern" || true)
+for old in $old_pids; do
+    echo "   stopping old stream.py pid=$old"
+    kill -TERM "$old" || true
+done
+pkill -f "/home/mike/www/mikelev[.]in/scripts/logs[.]py" || true
+pkill -f "tail -f /var/log/nginx/access[.]log" || true
+for i in $(seq 1 14); do
+    sleep 1
+    new_pids=$(pgrep -f -- "$pattern" || true)
+    new_count=$(printf "%s\n" "$new_pids" | sed "/^$/d" | wc -l)
+    if [ "$new_count" -ge 1 ]; then
+        echo "✅ Stream watchdog relaunched stream.py (pid=$(echo $new_pids | tr '\n' ' ')after ${i}s)."
+        exit 0
+    fi
+done
+echo "⚠️ No new stream.py PID seen within 14s; verify the stream watchdog on Honeybot."
+exit 1
+"""
+    res = subprocess.run(["ssh", HONEYBOT_SSH_HOST, "sh", "-s"], input=remote_sh, text=True)
+    return res.returncode
+
+
+def run_honeybot_outro(action="play", tick=True, dry_run=False):
+    """Audition, edit, sync, or reboot the Honeybot deploy-standby outro speech on Pipulate Prime."""
+    import os
+    import shutil
+    import signal
+    import subprocess
+    act = (action or "play").strip().lower()
+    intro, loop_lines, stream_path = _load_standby_speech_from_stream()
+    if act in ("edit", "vim", "nvim"):
+        editor = shutil.which("nvim") or shutil.which("vim") or "vi"
+        if dry_run:
+            print(f"✏️  [DRY RUN] {editor} +/STANDBY_LOOP_LINES {stream_path}")
+            return 0
+        subprocess.run([editor, "+/STANDBY_LOOP_LINES", str(stream_path)])
+        print("💡 Audition locally: ytoutro  |  Sync without restart: ytoutro sync  |  Sync + restart stream: ytreboot")
+        return 0
+    if act in ("sync", "push"):
+        return reboot_honeybot_stream(sync_only=True, dry_run=dry_run)
+    if act in ("reboot", "restart"):
+        return reboot_honeybot_stream(sync_only=False, dry_run=dry_run)
+    all_lines = [intro, *loop_lines]
+    bar = "=" * 72
+    print(f"\n{bar}")
+    print(f"🎙️  HONEYBOT DEPLOY-STANDBY OUTRO ({stream_path})")
+    print("-" * 72)
+    print(f"  [Intro] {intro}")
+    for idx, ln in enumerate(loop_lines, 1):
+        print(f"  [{idx}/{len(loop_lines)}]   {ln}")
+    print("-" * 72)
+    print("🎛️  Subcommands: ytoutro (play) | ytoutro --no-tick | ytoutro show | ytoutro edit | ytoutro sync | ytreboot")
+    print(bar)
+    if dry_run or act in ("show", "text", "print"):
+        if dry_run:
+            print(f"🗣️  [DRY RUN] Outro audition ready ({len(all_lines)} spoken segments, tick={tick}).")
+        return 0
+    repo_root = Path(__file__).resolve().parents[2]
+    piper_bin = str(repo_root / ".venv/bin/piper")
+    if not Path(piper_bin).is_file():
+        piper_bin = shutil.which("piper") or ""
+    model_path = _ensure_local_piper_model()
+    if not piper_bin or not model_path:
+        print("⚠️ Local Piper TTS model or binary not found; printed text only.")
+        return 1
+    play_cmds = (["pw-play"], ["paplay"], ["aplay", "-q"], ["afplay"])
+    player_base = next((c for c in play_cmds if shutil.which(c[0])), None)
+    if not player_base:
+        print("⚠️ No local audio player (pw-play/paplay/aplay/afplay) found.")
+        return 1
+    tick_proc = None
+    active_proc = None
+    wav_path = Path(f"/tmp/pipulate_outro_{os.getpid()}.wav")
+    tick_file = repo_root / "assets" / "sounds" / "tick.wav"
+    print("🔊 Playing standby outro locally on Pipulate Prime (press Ctrl+C to stop)...")
+    try:
+        if tick and tick_file.is_file():
+            tick_cmd_str = " ".join([*player_base, f'"{tick_file}"'])
+            tick_proc = subprocess.Popen(
+                ["sh", "-c", f"while :; do {tick_cmd_str} >/dev/null 2>&1; sleep 1; done"],
+                stdout=subprocess.DEVNULL,
+                stderr=subprocess.DEVNULL,
+                start_new_session=True,
+            )
+        for seg in all_lines:
+            subprocess.run(
+                [piper_bin, "--model", str(model_path), "--output_file", str(wav_path)],
+                input=seg.encode("utf-8"),
+                stdout=subprocess.DEVNULL,
+                stderr=subprocess.DEVNULL,
+                check=False,
+            )
+            if wav_path.is_file():
+                active_proc = subprocess.Popen(
+                    [*player_base, str(wav_path)],
+                    stdout=subprocess.DEVNULL,
+                    stderr=subprocess.DEVNULL,
+                )
+                active_proc.wait()
+                active_proc = None
+        print("✅ Outro audition complete.")
+        return 0
+    except KeyboardInterrupt:
+        print("\n🛑 Stopped local outro playback.")
+        return 0
+    finally:
+        if active_proc and active_proc.poll() is None:
+            try:
+                active_proc.kill()
+            except Exception:
+                pass
+        if tick_proc is not None:
+            try:
+                os.killpg(os.getpgid(tick_proc.pid), signal.SIGKILL)
+            except Exception:
+                pass
+        try:
+            wav_path.unlink(missing_ok=True)
+        except Exception:
+            pass
+
+
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(description="Pipulate article common utilities & broadcast kit.")
     add_standard_arguments(parser)
     parser.add_argument(
         "--broadcast",
-        choices=("help", "card", "hero", "yaml", "tweet", "linkedin", "pebble", "title", "desc", "top", "status", "bell", "stop", "start", "roll", "enable-ws", "say", "ping", "watch"),
+        choices=("help", "card", "hero", "yaml", "tweet", "linkedin", "pebble", "title", "desc", "top", "outro", "reboot", "status", "bell", "stop", "start", "roll", "enable-ws", "say", "ping", "watch"),
         default="card",
-        help="Print/copy broadcast kit or control Honeybot (help|card|hero|yaml|tweet|linkedin|pebble|title|desc|top|status|bell|stop|start|roll|enable-ws|say|ping|watch).",
+        help="Print/copy broadcast kit or control Honeybot (help|card|hero|yaml|tweet|linkedin|pebble|title|desc|top|outro|reboot|status|bell|stop|start|roll|enable-ws|say|ping|watch).",
+    )
+    parser.add_argument(
+        "--no-tick",
+        action="store_true",
+        help="Disable background tick.wav clock loop during local `ytoutro` audition.",
     )
     parser.add_argument(
         "-s", "--slug",
@@ -2462,6 +2647,14 @@ if __name__ == "__main__":
         if cli_args.verify or first_pebble_arg in ("verify", "check", "status"):
             sys.exit(verify_pebble_receipts(cli_args.target, slug_match=cli_args.slug))
         sys.exit(drop_pebble_pings(cli_args.target, slug_match=cli_args.slug, dry_run=cli_args.dry_run))
+    if cli_args.broadcast == "reboot":
+        sys.exit(reboot_honeybot_stream(sync_only=False, dry_run=cli_args.dry_run))
+    if cli_args.broadcast == "outro":
+        outro_act = cli_args.message[0].strip().lower() if cli_args.message else "play"
+        use_tick = not (cli_args.no_tick or cli_args.no_ding or outro_act in ("voice", "notick"))
+        if outro_act in ("voice", "notick"):
+            outro_act = "play"
+        sys.exit(run_honeybot_outro(action=outro_act, tick=use_tick, dry_run=cli_args.dry_run))
     if cli_args.broadcast in ("status", "bell", "stop", "start", "roll", "enable-ws"):
         sys.exit(run_honeybot_obs(cli_args.broadcast))
     sys.exit(print_broadcast_kit(cli_args.target, cli_args.broadcast, slug_match=cli_args.slug))
(nix) pipulate $ m
📝 Committing: feat: Introduce ytouro command for auditioning Honeybot deploy-standby outro speech
[main f420300] feat: Introduce ytouro command for auditioning Honeybot deploy-standby outro speech
 3 files changed, 247 insertions(+), 15 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 48 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (10/10), 4.68 KiB | 1.56 MiB/s, done.
Total 10 (delta 8), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (8/8), completed with 8 local objects.
To github.com:pipulate/pipulate.git
   0e40f3d..f420300  main -> main
(nix) pipulate $ 

Ignition: often “exit”, “nix develop”. Instructions may vary.

Wow, the ignition is going to do it for real and I feel I am going to hold off on ignition until this article is ready to publish to minimize interruptions… No! I can’t do that. This is chicken-and-egg. I need to bite the bullet and have the one strange interruption at least once to upgrade the infrastructure.

Okay, let’s do it. Oh nice! The dry-run preview is not disruptive to the broadcast at all. We’re going to have to update our yt cheat sheet instructions to include this new stuff too.

Local Audition and Live Ignition: Running ytoutro and ytreboot

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ exit
exit
(sys) pipulate $ ndq
(nix) pipulate $ ytoutro

========================================================================
🎙️  HONEYBOT DEPLOY-STANDBY OUTRO (/home/mike/pipulate/remotes/honeybot/scripts/stream.py)
------------------------------------------------------------------------
  [Intro] Receiving updates. This broadcast is coming to an end.
  [1/5]   To hear the next article, visit YouTube dot com slash M I K E L E V I N slash live. If you get there too soon you might have to wait a bit and hit refresh to see the new live-stream appear, but it will be worth it.
  [2/5]   If you are actually here seeing this live-performance wrapping up at the time of first broadcast, then you are seeing something new in the Universe.
  [3/5]   None of this is trained into the parametric memory of today's frontier models, meaning this is genuinely new information per Claude Shannon's second famous paper, A Mathematical Theory of Communication, in which he would tell you that anything that can be produced by prompting an A I that is built from its existing weights is not information. It is noise.
  [4/5]   This live-broadcast and the articles being read here are not that. The author is macheteing his way through the Noosphere of things being experimentally done for the first time in human history.
  [5/5]   This is The Signal. It may not be apparent to you right now, but this is the most unique and important thing you will see today, and if you tune in again later, then the next one will be the same -- and by that I mean different and equally new. Welcome to The Information.
------------------------------------------------------------------------
🎛️  Subcommands: ytoutro (play) | ytoutro --no-tick | ytoutro show | ytoutro edit | ytoutro sync | ytreboot
========================================================================
🔊 Playing standby outro locally on Pipulate Prime (press Ctrl+C to stop)...
✅ Outro audition complete.
(nix) pipulate $ 

And now for the one that really changes Honeybot Storytime.

Wow! That was much more elegant and non-disruptive than I thought it was going to be able to be done.

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ ytreboot
🚀 Syncing Honeybot scripts to honeybot:~/www/mikelev.in/scripts/...
sending incremental file list
stream.py
deleting __pycache__/score.cpython-313.pyc
deleting __pycache__/forest.cpython-313.pyc
deleting __pycache__/db.cpython-313.pyc
deleting __pycache__/content_loader.cpython-313.pyc
__pycache__/

sent 8,702 bytes  received 726 bytes  18,856.00 bytes/sec
total size is 256,651  speedup is 27.22
🔄 Restarting Honeybot stream.py child on honeybot...
   stopping old stream.py pid=1125229
✅ Stream watchdog relaunched stream.py (pid=1125229 after 1s).
(nix) pipulate $ 

4: PROMPT:

Verify the AFTER receipts for ytoutro and ytreboot, and confirm the standby outro workflow is clean.

5: DELIVERABLE:

This is confirmed. If the actual publish "message" is handled with the same elegance I feel the deliverable has met the goals. Let’s just do one more turn of the 5-Car Train to update yt with our new capabilities and then the turn after this one we’ll wrap the article. Don’t do the article-wrap stuff this turn. We just want to update this…

Oh! You already did. Alrighty then. Let’s wrap the article.

The 19-Step Broadcast Bicycle Cheat Sheet

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ yt

========================================================================
🚲 GO-GO-GADGET BROADCAST BICYCLE (Step-by-Step Muscle Memory)
------------------------------------------------------------------------
PHASE 1: ARTICLEIZE & HERO BANNER TLC (Before `publish`)
  1. article          Sanitize clipboard + generate Jekyll post & prompts
  2. ythero           📋 Copy AI Hero Banner Prompt -> generate 16:9 image
  3. ytyaml           📋 Copy YAML `image:` override -> paste into post
                      frontmatter while moving **TL;DR**: to top in vim
------------------------------------------------------------------------
PHASE 2: PUBLISH, WIRE PINGS & SOCIAL PROMO (Ship to Honeybot)
  4. preview          Run knowledge graph, hubs, llms.txt & post_nav
  5. publish "msg"    Commit + push trimnoir, sync GDocs & auto-ping wire
  6. tweet            📋 Copy X.com Promo Tweet (canonical URL + tags)
  7. linkedin         📋 Copy LinkedIn Promo Post (imprint + TL;DR + URL)
  8. ytpebble         🪨 Re-run Wayback + IndexNow wire pings (--dry-run)
------------------------------------------------------------------------
PHASE 3: YOUTUBE LIVE CUT-AND-ROLL (Slice VOD Episode on Honeybot)
  9. yttitle          📋 Copy YouTube Live Title (<=100 chars) -> YT Studio
 10. ytdesc           📋 Copy YouTube Live Description -> YT Studio
 11. ytroll           🎬 Stop OBS -> 4s RTMP boundary -> Start OBS + bell
                      (Also: ytroll stop | ytroll start | ytbell)
------------------------------------------------------------------------
PHASE 4: LIVE OBSERVATORY & AUDIENCE INTERACTION (While Streaming)
 12. ytstatus          📡 Check Honeybot OBS PID, port 4455 & timecode
 13. ytwatch           👀 Check live viewers & chat (`ytwatch start|log|status|stop`)
     ytlog             📺 Watch live notification stream (`q` or `Ctrl+b d` to exit)
 14. ytsay "text"      🎙️ Break in LIVE on-air (`-c "CARD"` `-p white_rabbit` `-m IMG`)
 15. ytapp / ytvim     🖥️ Project local FastHTML (:5001) or live Vim onto stream
     ytmedia <path>    🖼️ Pop up an image or video on Honeybot :10.0 (`ytsay -m`)
 16. ytping "text"     📲 Send ntfy.sh push notification to your phone
 17. yttop [N]          📊 Keyless YouTube channel top videos & streams radar
 18. ytoutro            🔊 Hear deploy-standby outro locally (`ytoutro edit|sync|reboot`)
     ytreboot           🔄 Sync Honeybot scripts + restart stream.py watchdog child
 19. ytcard             📇 Reprint the full Broadcast Kit for the latest post
------------------------------------------------------------------------
📄 Latest Post:   2026-10-24-mu-puzzle-phantom-limbs-f2-neovim-exemplars.md
🖼️  Target Image:  /home/mike/repos/trimnoir/assets/images/mu-puzzle-phantom-limbs-f2-neovim-exemplars.jpeg
✏️  Edit Command:  vim /home/mike/repos/trimnoir/_posts/2026-10-24-mu-puzzle-phantom-limbs-f2-neovim-exemplars.md
========================================================================
(nix) pipulate $ 

Hop off the ride. This ride’s stated goal is reached – dismount. This is the NOTARY BEAT: the ride ends here, is witnessed here, and is sealed here. Answer all seven beats, briefly:

  1. TL;DR: a short, dry, neutral abstract for the TOP of the published article – written for an unfamiliar reader or AI summarizer who has never seen this system. No hype, no insider handles unexplained.
  2. VERIFY: restate the goal from the top of this article and confirm (or deny) it was met, citing THIS compile’s receipts, not memory. Name any ignition this ride required that never fired – an AFTER tap taken without one is a stale BEFORE wearing the AFTER’s label.
  3. BANK: name everything that graduates – rule, earmark, todo, pin – as SEARCH/REPLACE patch cars against raw source present in THIS context (Target line inside the fence, one car per commit story), deletions included. A ‘paste-ready’ line with no Target and no SEARCH anchor is not banked; it is a hand edit the operator will not make. These BANK cars are the ONLY patches a dismount emits.
  4. DANGLING: what carries forward unbanked? One line each, no essays.
  5. SEED: the context.txt lines (and TODO_SLUGS if narrative context is needed) for the next ride’s first compile.
  6. CLOSING: a closing summary for the BOTTOM of the article – the final take-away, tied to the book’s larger arc where it fits naturally, never forced. Storytelling over inventory.
  7. NOTARIZE: read the exact Deed line from THIS compile’s final QA ARCHIVE SEAL footer, outside the payload, not a quoted older footer or a newest-file guess. It names the input archive, not this later response or the generated Jekyll post. State verification separately: only a verifier receipt naming that archive supports a verified claim; otherwise say not independently verified here. Count this Manifest’s live-command receipts, including failures. Quote only recorded digests, sizes and dates, naming their source; filesystem mtime and fixed ZIP dates are not command timestamps. Do not demand absent wc -c or dated-receipt fields. If the footer is missing, say so; otherwise close with its exact Deed line.

FINALITY: after beat 6, this discussion is CLOSED. Emit NO five-car train, NO probes, NO patches beyond the BANK cars of beat 2, and NO next-turn prompt beyond the SEED lines in beat 4. Any reader or model encountering this article later should treat it as a finished, notarized document – an archive entry, not an open thread.

The Notary Beat: Verifying and Sealing the Ride

Gemini 4 Argon High: Context verified.

1. VERIFY:

The opening goals of this ride were:

  1. Hear locally on Pipulate Prime what plays at the end of a live-stream when publish "message" triggers deploy-standby.
  2. Conveniently update that language in remotes/honeybot/scripts/stream.py.
  3. Provide a single easy-to-remember command to sync and reboot the stream loop on Honeybot, while ensuring future edits play on the next publish without requiring a manual restart.

Confirmed against this compile’s live receipts and operator witnesses:

  • Compile-lane Receipt 1 (! .venv/bin/python scripts/articles/common.py --broadcast outro --dry-run, 0.2327s): Extracted STANDBY_INTRO_LINE and all 5 STANDBY_LOOP_LINES from remotes/honeybot/scripts/stream.py via ast.parse and printed 🗣️ [DRY RUN] Outro audition ready (6 spoken segments, tick=True). (contrasting with the BEFORE invalid choice: 'outro').
  • Compile-lane Receipt 2 (! .venv/bin/python scripts/articles/common.py --broadcast reboot --dry-run, 0.1795s): Printed 🔄 [DRY RUN] Honeybot Stream Reboot (sync + restart stream.py): rsync /home/mike/pipulate/remotes/honeybot/scripts/ -> honeybot:~/www/mikelev.in/scripts/ (contrasting with the BEFORE invalid choice: 'reboot').
  • Compile-lane Receipt 3 (! nix eval .#devShells.x86_64-linux.quiet.shellHook --raw | rg ..., 0.0362s, exit 1): Failed in the non-interactive ! child shell with OPENSSL_3.6.0 not found / GLIBC_ABI_DT_X86_64_PLT not found because bare nix in a ! probe inherits the devShell’s LD_LIBRARY_PATH without the interactive nix() wrapper function (# § THE UNEXPORTED-SHIM RULE; requires LD_LIBRARY_PATH="" nix eval ...). However, the flake.nix changes are directly visible in this compile’s Codebase section (flake.nix lines 1072–1073 and 1131–1133) and were witnessed live in the operator’s interactive shell after exit + ndq.
  • Operator Ignition Witnesses:
  • exit then ndq loaded the updated shell functions.
  • ytoutro printed the 6 segments and played the Piper TTS audio over tick.wav locally on Pipulate Prime (✅ Outro audition complete.).
  • ytreboot synced stream.py to Honeybot (sent 8,702 bytes), stopped old stream.py pid=1125229, and confirmed the watchdog relaunched stream.py after 1s.
  • yt displayed the updated 19-step Go-Go-Gadget Broadcast Bicycle cheat sheet including 18. ytoutro and ytreboot.

2. BANK:

Target: foo_files.py
[[[SEARCH]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
# 2026-10-23 dismount SHANNON SURPRISE, MARK NOTTINGHAM'S RFC 9110/9111, THE FLYBALL GOVERNOR, AND YTMEDIA LAN-TO-DMZ STAGING (deeds qamy.ai_213-fbeb5402.zip through qamy.ai_220-a9d25939.zip; pipulate commit bd9ae33; Gemini 4 Argon High on every turn): Joke #42/#897 dictionary compression vs. Claude Shannon entropy (1948/1951 papers in James Gleick's *The Information*) applied to expose the "good editorial taste" bounding error of GEO spam-cannons; First-Principles Craftsman vs. Joel Spolsky's Architecture Astronaut, James Watt's 1788 centrifugal flyball governor vs. decaying open-loop planetary orbits, Lewis Carroll / Douglas Hofstadter's Achilles and the Tortoise player-piano actuator paradox, Gladstone's Homeric "Color Blue" (`Honeybot` wire telemetry), and Mark Nottingham (`mnot`) + Roy Fielding's RFC 9110/9111 `Vary: Accept` content negotiation (Uniform vs. Unique Resource Locator) steelmanned against $10,000/mo HTML pre-render farms and GUI "browser-use" overhead; 30-and-3 Wogglebug-to-Stick-Bug (`osb`) translation table executed; fixed `ytmedia` LAN-to-DMZ file-transfer seam in `scripts/articles/common.py` (auto-`scp` staging local files to `/tmp/honeybot_media/` on Honeybot) and added stdlib PNG/GIF/JPEG header dimension parsing (`_read_image_dimensions`) in `remotes/honeybot/scripts/stream.py`, exposing `feh`'s alpha-checkerboard and focus-retention quirks to set up a self-terminating PyQt "Weebo" overlay engine (`# § FIRE-AND-FORGET OVERLAYS OWN THEIR TEARDOWN`). WITNESSED: `LOCAL_EXISTS= True STAGED_IN_DRYRUN= False HAS_READ_DIM= False` -> `LOCAL_EXISTS= True STAGED_IN_DRYRUN= True HAS_READ_DIM= True`, live `ambigram-454.png` popup witnessed on Honeybot `:10.0` stream, Ruff clean, deed qamy.ai_220-a9d25939.zip.
[[[DIVIDER]]]
# --- START RECEIPTS (newest first; cap 20 lines; a line pushed past the cap is deleted, never moved -- git and the rolling pin are the archive) ---
# 2026-10-24 dismount YTOUTRO LOCAL STANDBY AUDITION, AST HOT-RELOAD, AND YTREBOOT ONE-WORD STREAM RESTART (deeds qamy.ai_236-e0f3f212.zip through qamy.ai_237-97674cfa.zip; pipulate commits f420300, 55ed3fa; Gemini 4 Argon High on every turn): diagnosed why edits to `standby_loop_lines` in `remotes/honeybot/scripts/stream.py` missed the next `publish` (`git push` rang `.deploy_standby` in Step `[1/5]` before `./nixops.sh` synced scripts in Step `[3/5]`, and the running `stream.py` daemon held the old tuple in RAM); hoisted `STANDBY_INTRO_LINE` and `STANDBY_LOOP_LINES` to the top configuration block of `stream.py` with `load_standby_speech()` re-reading them off disk via `ast.parse` whenever `.deploy_standby` fires (`# § PRE-SYNC AND AST-RELOAD ON STANDBY`); added pre-push `rsync` of `remotes/honeybot/scripts/` in `flake.nix` `publish()`; wired `ytoutro` (`play`, `--no-tick`, `show`, `edit`, `sync`) and `ytreboot` in `scripts/articles/common.py` and `flake.nix`. WITNESSED: `--broadcast outro --dry-run` (6 spoken segments, `tick=True`), `--broadcast reboot --dry-run`, live local `ytoutro` Piper TTS + `tick.wav` playback on Prime, live `ytreboot` syncing `stream.py` to Honeybot and restarting `pid=1125229` in 1s, `yt` 19-step cheat sheet verified, Ruff clean, deed qamy.ai_237-97674cfa.zip.
# 2026-10-23 dismount SHANNON SURPRISE, MARK NOTTINGHAM'S RFC 9110/9111, THE FLYBALL GOVERNOR, AND YTMEDIA LAN-TO-DMZ STAGING (deeds qamy.ai_213-fbeb5402.zip through qamy.ai_220-a9d25939.zip; pipulate commit bd9ae33; Gemini 4 Argon High on every turn): Joke #42/#897 dictionary compression vs. Claude Shannon entropy (1948/1951 papers in James Gleick's *The Information*) applied to expose the "good editorial taste" bounding error of GEO spam-cannons; First-Principles Craftsman vs. Joel Spolsky's Architecture Astronaut, James Watt's 1788 centrifugal flyball governor vs. decaying open-loop planetary orbits, Lewis Carroll / Douglas Hofstadter's Achilles and the Tortoise player-piano actuator paradox, Gladstone's Homeric "Color Blue" (`Honeybot` wire telemetry), and Mark Nottingham (`mnot`) + Roy Fielding's RFC 9110/9111 `Vary: Accept` content negotiation (Uniform vs. Unique Resource Locator) steelmanned against $10,000/mo HTML pre-render farms and GUI "browser-use" overhead; 30-and-3 Wogglebug-to-Stick-Bug (`osb`) translation table executed; fixed `ytmedia` LAN-to-DMZ file-transfer seam in `scripts/articles/common.py` (auto-`scp` staging local files to `/tmp/honeybot_media/` on Honeybot) and added stdlib PNG/GIF/JPEG header dimension parsing (`_read_image_dimensions`) in `remotes/honeybot/scripts/stream.py`, exposing `feh`'s alpha-checkerboard and focus-retention quirks to set up a self-terminating PyQt "Weebo" overlay engine (`# § FIRE-AND-FORGET OVERLAYS OWN THEIR TEARDOWN`). WITNESSED: `LOCAL_EXISTS= True STAGED_IN_DRYRUN= False HAS_READ_DIM= False` -> `LOCAL_EXISTS= True STAGED_IN_DRYRUN= True HAS_READ_DIM= True`, live `ambigram-454.png` popup witnessed on Honeybot `:10.0` stream, Ruff clean, deed qamy.ai_220-a9d25939.zip.
[[[REPLACE]]]
Target: foo_files.py
[[[SEARCH]]]
# 2026-10-16 dismount DOUBLE-BUFFERED VOICE CARDS, PRIMARY-MONITOR EWMH PLACEMENT, AND CLAMP OVER TOGGLE (deed qamy.ai_146-7b1992d0.zip; pipulate commits 22c6c297, 13ae7840; nixos commits 511d493, 747cc5f, 06ec088, 2ae5a2c, 494a457, 57de511; Gemini 4 Argon High on every turn): Piper TTS synthesis split from playback in imports/voice_synthesis.py (synthesize_to_wav, play_wav, _resolve_play_cmd) and pre-warmed in autognome.py (ensure_voice, prep_speech, play_prepped_speech) so Alacritty title cards and speech fire simultaneously; gnome-terminal and Chrome pre-spawned underneath the raised card during speech (spawn_terminal_window, hydrate_terminal_tabs) to eliminate post-card terminal startup delay; multi-monitor Chrome placement onto HDMI-0 (2560x1440+0+0) vs. DP-5 (1200x1920+2560+0) fixed in autognome.py and chrome_windows.py via sanitize_chrome_preferences, --new-window, window-ID set-diffing, and EWMH unmaximize-move-pin-maximize; apply.py upgraded with _normalize_line_ws and _rstrip_line_spans fallback plus exact-line FIRST DIVERGENCE reporting; Desktop 6 YouTube Live volume clamped to 0% via two 30-key Down-arrow bursts (mute_chrome_window) replacing the non-idempotent 'm' toggle. WITNESSED: rg receipts on autognome.py (lines 938, 939, 969, 974) and apply.py (line 142), autognome.py --dry-run exit 0 (7 desktops, 3 terminals), live reboot + init across all 7 desktops verified, Ruff clean, deed qamy.ai_146-7b1992d0.zip.
# 2026-10-15 dismount ENDOSYNTHESIS, INSPECTOR GADGET, AND THE GO-GO-GADGET BROADCAST KIT (deeds qamy.ai_117-9c37ddfd.zip through qamy.ai_123-b4642777.zip; pipulate commits c1955e81, 10e44b6f, 7591d575, 57abd21f; Gemini 4 Argon High on every turn): endosynthesis (Atari 2600/VCS CX40 + Jay Miner TIA racing the beam, language as zero-mass prosthetic, Spider-Man + Ned Leeds / Inspector Gadget + Penny vs. Iron Man's sealed suit) contrasted against forced hybridization (Tusk, Brundlefly); Marvin Minsky's Society of the Mind, Ray Kurzweil's Making a Mind, Iain McGilchrist's Master and His Emissary, and Pangea's Enigmo bumpers mapped to high-wingspan context compilation; YouTube Live Mode B ("Cut-and-Roll") architected around the 12-hour VOD retention ceiling and constructive wave interference; Rung 1 Go-Go-Gadget Broadcast Kit landed in scripts/articles/common.py, scripts/articles/articleizer.py, and flake.nix (tweet, yttitle, ytdesc, ytcard + automatic cue card at the end of article and publish), template.md ASCII sort collision fixed via _DATED_POST_RE + _post_sort_key, and ### Ai Editorial Take promoted into ytdesc/ytcard. WITNESSED: common.py --broadcast card template.md (14/100 chars) -> 2026-10-15-phantom-limb-amiga-jack-tramiel-vim-nixos.md (88/100 chars) with 🤖 AI Editorial Take, interactive tweet/yttitle/ytdesc clipboard copies verified after exit + ndq, Honeybot OBS 32.0.1 window 0x01e00007 on DISPLAY=:10.0 and ~/.config/obs-studio/plugin_config/obs-websocket/config.json located, Ruff clean, deed qamy.ai_123-b4642777.zip.
# --- END RECEIPTS ---
[[[DIVIDER]]]
# 2026-10-16 dismount DOUBLE-BUFFERED VOICE CARDS, PRIMARY-MONITOR EWMH PLACEMENT, AND CLAMP OVER TOGGLE (deed qamy.ai_146-7b1992d0.zip; pipulate commits 22c6c297, 13ae7840; nixos commits 511d493, 747cc5f, 06ec088, 2ae5a2c, 494a457, 57de511; Gemini 4 Argon High on every turn): Piper TTS synthesis split from playback in imports/voice_synthesis.py (synthesize_to_wav, play_wav, _resolve_play_cmd) and pre-warmed in autognome.py (ensure_voice, prep_speech, play_prepped_speech) so Alacritty title cards and speech fire simultaneously; gnome-terminal and Chrome pre-spawned underneath the raised card during speech (spawn_terminal_window, hydrate_terminal_tabs) to eliminate post-card terminal startup delay; multi-monitor Chrome placement onto HDMI-0 (2560x1440+0+0) vs. DP-5 (1200x1920+2560+0) fixed in autognome.py and chrome_windows.py via sanitize_chrome_preferences, --new-window, window-ID set-diffing, and EWMH unmaximize-move-pin-maximize; apply.py upgraded with _normalize_line_ws and _rstrip_line_spans fallback plus exact-line FIRST DIVERGENCE reporting; Desktop 6 YouTube Live volume clamped to 0% via two 30-key Down-arrow bursts (mute_chrome_window) replacing the non-idempotent 'm' toggle. WITNESSED: rg receipts on autognome.py (lines 938, 939, 969, 974) and apply.py (line 142), autognome.py --dry-run exit 0 (7 desktops, 3 terminals), live reboot + init across all 7 desktops verified, Ruff clean, deed qamy.ai_146-7b1992d0.zip.
# --- END RECEIPTS ---
[[[REPLACE]]]
Target: foo_files.py
[[[SEARCH]]]
# § FIRE-AND-FORGET OVERLAYS OWN THEIR TEARDOWN (banked 2026-10-23, deed 220) -- Never rely on an external pkill or an interactive image viewer (such as feh, which renders alpha-channel checkerboards and retains X11 focus) to close a broadcast overlay; build the popup as a self-terminating Python/Qt window with a controlled background, frameless/topmost/no-focus window flags, and an internal QTimer.singleShot(duration_ms, app.quit) so every visual cue is fire-and-forget.
[[[DIVIDER]]]
# § FIRE-AND-FORGET OVERLAYS OWN THEIR TEARDOWN (banked 2026-10-23, deed 220) -- Never rely on an external pkill or an interactive image viewer (such as feh, which renders alpha-channel checkerboards and retains X11 focus) to close a broadcast overlay; build the popup as a self-terminating Python/Qt window with a controlled background, frameless/topmost/no-focus window flags, and an internal QTimer.singleShot(duration_ms, app.quit) so every visual cue is fire-and-forget.
# § PRE-SYNC AND AST-RELOAD ON STANDBY (banked 2026-10-24, deed 237) -- When a git push hook triggers a remote standby announcement before the post-push infrastructure sync runs, pre-sync the script folder before git push and re-read speech constants from disk via ast.parse on bell trigger so updated copy plays on the very next publish without requiring a daemon restart.
[[[REPLACE]]]
Target: foo_files.py
[[[SEARCH]]]
# - TODO (2026-10-23, P0.16 "WEEBO" PYQT6 FIRE-AND-FORGET MEDIA OVERLAY & SHORTHAND MEME LIBRARY ON HONEYBOT): Replace `feh` in `remotes/honeybot/scripts/stream.py` (`conjure_media`) with a deterministic, self-terminating PyQt (`PyQt6` / `QTimer.singleShot`) overlay script (`remotes/honeybot/scripts/weebo.py`) that renders transparent PNGs, JPEGs, and animated GIFs over a solid `#111111` obsidian background (or clean compositor alpha, never `feh`'s alpha checkerboard) with `FramelessWindowHint | WindowStaysOnTopHint | WindowDoesNotAcceptFocus` so it closes itself cleanly without stealing X11 focus; and sync `ytmedia` files into a persistent Honeybot meme library (`~/www/mikelev.in/assets/weebo/`) addressable by shorthand key (e.g. `ytmedia ambigram`).
[[[DIVIDER]]]
# - TODO (2026-10-23, P0.16 "WEEBO" PYQT6 FIRE-AND-FORGET MEDIA OVERLAY & SHORTHAND MEME LIBRARY ON HONEYBOT): Replace `feh` in `remotes/honeybot/scripts/stream.py` (`conjure_media`) with a deterministic, self-terminating PyQt (`PyQt6` / `QTimer.singleShot`) overlay script (`remotes/honeybot/scripts/weebo.py`) that renders transparent PNGs, JPEGs, and animated GIFs over a solid `#111111` obsidian background (or clean compositor alpha, never `feh`'s alpha checkerboard) with `FramelessWindowHint | WindowStaysOnTopHint | WindowDoesNotAcceptFocus` so it closes itself cleanly without stealing X11 focus; and sync `ytmedia` files into a persistent Honeybot meme library (`~/www/mikelev.in/assets/weebo/`) addressable by shorthand key (e.g. `ytmedia ambigram`).
# - LANDED (2026-10-24, P0.17 YTOUTRO LOCAL STANDBY AUDITION, AST HOT-RELOAD, & YTREBOOT ONE-WORD STREAM RESTART, deed 237): Hoisted `STANDBY_INTRO_LINE` and `STANDBY_LOOP_LINES` to top of `remotes/honeybot/scripts/stream.py` with `load_standby_speech()` `ast.parse` disk reload on `.deploy_standby`, added pre-push `rsync` of `remotes/honeybot/scripts/` in `flake.nix` `publish()`, and wired `ytoutro` (`play`, `--no-tick`, `show`, `edit`, `sync`) and `ytreboot` in `scripts/articles/common.py` and `flake.nix`.
[[[REPLACE]]]

3. DANGLING:

  • P0.16 (weebo.py PyQt6 Fire-and-Forget Media Overlay & Shorthand Meme Library): Replace feh in remotes/honeybot/scripts/stream.py (conjure_media) with a self-terminating PyQt window (FramelessWindowHint | WindowStaysOnTopHint | WindowDoesNotAcceptFocus, solid #111111 backdrop, internal QTimer.singleShot) and persistent ~/www/mikelev.in/assets/weebo/ shorthand lookup.
  • nixops.sh & common.py --exclude=__pycache__: Add --exclude=__pycache__ to the rsync commands syncing remotes/honeybot/scripts/ so Python 3.12 .pyc files from Prime do not churn Python 3.13 .pyc files on Honeybot.
  • Live publish "msg" Standby Witness: Observe the pre-synced, AST-reloaded STANDBY_LOOP_LINES playing automatically on Honeybot during the publish of this article.

4. SEED:

remotes/honeybot/scripts/stream.py
remotes/honeybot/nixos/configuration.nix
scripts/articles/common.py
nixops.sh
! ssh honeybot 'python3 -c "import PyQt6; print(\"PYQT6_OK\")" 2>/dev/null || echo "PYQT6_MISSING"'
! rg -n "conjure_media|MEDIA_OVERLAY_MARKER" remotes/honeybot/scripts/stream.py

5. CLOSING:

When Penn & Teller perform the Cups and Balls with clear plastic cups, the trick doesn’t become less magical because you can see through the props—it becomes more impressive because every move has to survive in plain sight. In a 24/7 home-hosted broadcast studio, hiding stagehands behind out-of-band terminal hacks eventually trips the performer: a git push rings the .deploy_standby bell in Step 1 before ./nixops.sh ships the new script in Step 3, and the long-running stream.py process keeps reciting yesterday’s lines from RAM. By hoisting the standby speech to the top of stream.py, teaching the daemon to re-read its own source file off disk via ast.parse the moment the standby bell rings, pre-syncing the script folder before the push, and adding ytoutro and ytreboot to the muscle-memory bicycle, the stage machinery becomes part of the act—auditioned locally on Pipulate Prime and heard live on the very next publish.


6. NOTARIZE:

  • Input Archive Deed: qamy.ai_237-97674cfa.zip (read from the final QA ARCHIVE SEAL footer outside the payload; names the sealed input archive for this turn, not this response or the generated Jekyll post).
  • Verification Status: Not independently verified here (no foo_cartridge.py verifier receipt for qamy.ai_237-97674cfa.zip is present in this compile’s receipts).
  • Manifest Live-Command Receipts Count: 3 total (2 succeeded: .venv/bin/python scripts/articles/common.py --broadcast outro --dry-run in 0.2327s [402 tokens 1,877 bytes] and .venv/bin/python scripts/articles/common.py --broadcast reboot --dry-run in 0.1795s [47 tokens 155 bytes]; 1 failed with exit 1: nix eval .#devShells.x86_64-linux.quiet.shellHook --raw | rg 'ytoutro|ytreboot|Pre-sync Honeybot stream scripts' in 0.0362s [232 tokens 517 bytes] due to un-cleared LD_LIBRARY_PATH in the non-interactive child shell).
  • Recorded Sizes & Dates (from Summary & Foo Files): Prompt Summary Verified Tokens: 247,549 (979,362 bytes UTF-8); Honeybot telemetry fetched 2026-10-24T16:15Z.

Deed: qamy.ai_237-97674cfa.zip


Book Analysis

Ai Editorial Take

What stands out most here is the use of ast.parse plus ast.literal_eval as a zero-dependency hot-configuration loader inside a running Python daemon. Normally, when developers want a long-running process to pick up edited copy without restarting, they either move the prose out into an external JSON/YAML file—splitting the single source of truth—or reach for importlib.reload(), which risks re-triggering module-level side effects, signal handlers, or audio subprocesses. By having stream.py parse its own updated source file on disk as an abstract syntax tree and pluck out only the literal assignments for STANDBY_INTRO_LINE and STANDBY_LOOP_LINES, the script treats its own Python source code as a safe, inert data file—preserving single-file colocation while gaining hot-reload agility.

🐦 X.com Promo Tweet

Why did edits to our live-stream standby speech always play one broadcast late? A git push hook beat rsync to the punch while Python held the old tuple in RAM. Enter ast.parse hot-reloading, ytoutro, and ytreboot.
https://mikelev.in/futureproof/ytoutro-ytreboot-ast-hot-reload-standby-speech/
#Python #NixOS #LinuxAudio

🎨 Hero Banner Image Prompt

A wide 16:9 widescreen illustration rendered with graphic-novel ink-and-wash precision against a deep obsidian-slate (#111111) studio environment that extends continuously to all four canvas edges. Vertically centered in the middle of the composition sits a transparent crystal magician's cup resting on a sleek dark broadcast console, revealing glowing amber and cyan Python abstract syntax tree nodes, a brass mechanical metronome ticking out concentric soundwaves, and a vintage studio microphone inside the clear cup. Luminous fiber-optic data traces connect a local workstation waveform monitor on the left to a remote server tower on the right under dramatic high-contrast rim lighting. Full-bleed edge-to-edge composition filling 100% of the canvas with no borders, no frames, no matting, no margins, and no text overlays or lettering.

🖼️ YAML Frontmatter Image Override

image:
  path: /assets/images/ytoutro-ytreboot-ast-hot-reload-standby-speech.jpeg
  alt: "A transparent magician cup over a dark broadcast console revealing glowing syntax tree nodes, a metronome, and soundwaves."

Title Brainstorm

  • Title Option: Auditioning the Standby Outro: ytoutro, ytreboot, and AST Hot-Reloading on Honeybot
    • Filename: ytoutro-ytreboot-ast-hot-reload-standby-speech.md
    • Rationale: Directly names the two new CLI tools, the Python AST disk-reload mechanism, and the exact broadcast problem solved in the session.
  • Title Option: Transparent Cups and Balls: Pre-Syncing Honeybot Standby Speech with ytoutro and ast.parse
    • Filename: transparent-cups-ytoutro-ast-parse-honeybot-standby.md
    • Rationale: Pairs the Penn & Teller transparent stagecraft metaphor with the concrete git-push versus rsync timing fix and AST parsing.
  • Title Option: From RAM to Disk: Hot-Reloading Broadcast Outros with ytoutro, ytreboot, and Python AST
    • Filename: ram-to-disk-hot-reloading-broadcast-outros-ytoutro.md
    • Rationale: Highlights the systems-engineering diagnosis of stale in-memory tuples versus fresh on-disk state during a live deployment.
  • Title Option: Local Piper TTS Auditions and One-Word Stream Restarts: Adding ytoutro and ytreboot
    • Filename: local-piper-tts-auditions-ytoutro-ytreboot-honeybot.md
    • Rationale: Focuses on the operator ergonomics of auditioning voice synthesis over tick.wav locally before shipping to the live stream.

Content Potential And Polish

  • Core Strengths:
    • Pinpoints a subtle distributed-systems race condition where Step 1 (git push) triggered .deploy_standby on the remote host before Step 3 (./nixops.sh) synced the updated script.
    • Uses Python’s standard library ast.parse and ast.literal_eval to safely re-read string constants from the running script’s own source file on disk without exec, eval, or importlib.reload side effects.
    • Demonstrates the value of keeping manual text-editing reflexes sharp when one hunk of a multi-file patch misses due to indentation drift and is cleanly isolated via vim patch.
    • Connects the technical fix to the Penn & Teller transparent cups-and-balls philosophy of keeping stage machinery visible inside the primary control plane.
  • Suggestions For Polish:
    • Briefly explain for newcomers how .deploy_standby acts as a filesystem semaphore between the Git post-receive hook and the stream.py event loop.
    • Note why ast.literal_eval on top-level ast.Assign nodes is safer than re-importing stream.py, which would re-run module-level initialization in a live daemon.

Next Step Prompts

  • Add --exclude=__pycache__ to the rsync invocations in scripts/articles/common.py (reboot_honeybot_stream), flake.nix (publish), and nixops.sh so syncing scripts from Pipulate Prime does not delete or churn .pyc files on Honeybot.
  • Build remotes/honeybot/scripts/weebo.py as a self-terminating PyQt6 media overlay (FramelessWindowHint | WindowStaysOnTopHint | WindowDoesNotAcceptFocus with QTimer.singleShot) to replace feh in stream.py.
🔴 Live 24/7 Web Bot AI Observatory — Send this stream to an engineer who can explain the 3 shapes & 2 panels: Like & Follow on YouTube ↗
💸 Spending $10,000/mo and up on HTML pre-render? Fully implemented new web standards (RFC 9110 Accept: text/markdown, RFC 9111 Vary: Accept, and RFC 9421 Web Bot Auth) can save you that money. Contact Mike for a consultation (miklevin@gmail.com) • RFC 9110/9111 Edge Advisory • LinkedIn

A transparent magician cup over a dark broadcast console revealing glowing syntax tree nodes, a metronome, and soundwaves.