← Mission Control

Clipping engine, standard operating procedure

Built from SPEC.md v3.1 (2026-09-20). If this file and SPEC.md disagree, SPEC.md wins.

This is two SOPs in one file.

Part A is the machine's SOP: what the engine does on its own, every run.

Part B is your SOP: what to do on each exception, and only on an exception.

Part C is the daily and weekly rhythm.

Part D is the phase gates, as a checklist.

Part E is one table: what "done" means for every stage.

Anything marked Phase 0, in progress is not on disk yet. Another agent is building

those pieces right now under engine/core/. Everything else named here exists today,

at the path given.


PART A: THE MACHINE'S SOP

Preamble: cadence, health check, kill switch, test mode

Cadence. Runs twice a day, 07:15 and 15:15, under the launchd job

com.ralphwest.clipping.daily, which runs engine/daily.py. One full pass through

the 20 stages below. Always finishes. Always sleeps.

Heartbeat watch. A second launchd job, com.ralphwest.clipping.heartbeat,

runs engine/core/watchdog.py every 2 hours. It checks if controls/heartbeat

is older than 14 hours; if so, pages. This detects a stalled loop that silence alone

cannot page on.

Stage 0, Health, runs first, before Discover.

disk space. 3. Read controls/KILL and controls/TEST_MODE. 4. Write

a health record.

heartbeat_missing, since Sleep never gets reached either.

pre-flight check today, it starts straight into polling.

Kill switch, controls/KILL. Present, nothing that touches money or a

platform runs. Discover, Score, Report still run. Remove it, everything resumes.

Test mode, controls/TEST_MODE. Present, publish and submit run the full

path and stop one call short of the real platform, writing what they would have

sent to dryrun/. **The engine ships in test mode and stays there until Phase

1's gate in Part D is met.**

Phase 0, in progress. Neither file exists yet, nor does controls/ itself.

In practice the engine is already test-mode-only today, since Stage 12 has no

working TikTok adapter. Nothing can go out regardless of any flag.


Stage 1: Discover

every non-manual marketplace via engine/campaign_poll.py and

engine/marketplaces.py. 3. Parse raw campaigns (Whop, Vyro, LiquidClips).

  1. Stamp last_seen. 5. Log the count to logs/daily.log.

marketplace this run.

unknown after 5 tries.

engine/daily.py wraps discover-and-score in one try/except today.

Stage 2: Sync

missed polls. 3. Update pool and spend deltas. 4. Recompute burn rate

and runway (already built, engine/campaigns.py).

Burn rate and runway already compute in engine/campaigns.py.

Stage 3: Verify

  1. Capture payout terms. 4. Hash the doc, for change detection.
  2. Give up after 3 tries, write needs_human: rules_doc.

approved_sources[] today.

Stage 4: Score

floor, rating floor. 2. Compute the seven weighted factors. 3. Call

Gemini flash-lite once per campaign, cached forever. 4. Write score,

score_parts{}, eligible, reject[].

and eligible with a reason when false.

Stage 5: Allocate

most 2 per marketplace, skip reliability: bad. 3. Check each pick

has a warm account in its lane, else queue it behind one. 4. Revert any

dropped pick to watching.

each with an account assigned.

Stage 6: Ingest

on that list before any call, refuse otherwise. 3. Download via

engine/ingest.py (yt-dlp, captions first, Whisper fallback). 4. Record a checksum.

in approved_sources[].

Stage 7: Analyze

engine/pick.py, killing sponsor reads and intros. 3. Score each on hook,

payoff, emotion, quotability, one Gemini call. 4. Store timestamped

moments with scores and a prompt version.

unknown if there is no audio at all.

Stage 8: Render

  1. 2. Burn captions,

lay the hook card over the first 2.6s. 3. Render N variants via

engine/variants.py. 4. Write the mp4 files under jobs//.

Stage 9: QA

zone, never over source text. 3. Check a hook in the first 3s.

  1. Check file size under the platform limit. 5. Check no duplicate

hash already on that platform.

naming the failure.

Schedule today. Close this gap before Phase 1.

Stage 10: Schedule

  1. Fill each account's daily allowance from the asset pool. 3. Respect

warm-up stage, minimum gap, no repeated variant on one platform.

  1. Jitter posting time, 8am to 10pm. 5. Write queue.json.

with a reason.

unplaced rows.

Stage 11: Pre-publish

controls/KILL is absent. 3. Check the account and campaign are active.

  1. Check the daily quota is not exhausted. 5. Read controls/TEST_MODE.

and writes due_now.json. Phase 0, in progress: none of the 5 checks run

yet, the controls don't exist on disk.

Stage 12: Publish

the key complete, record the post id or URL. 3. Open publish_failed

with retry_after if the adapter cannot run.

exists (tested 2026-09-19).

what is due. A human, or a Claude session with the Higgsfield MCP tools,

posts it.

Stage 13: Confirm

present, view count reads. 3. Retry hourly for 24h if not confirmed

in 30 min. 4. Escalate after 24h.

Stage 14: Submit

a submission id or screenshot. 3. Open an exception with the URL and

campaign if the adapter cannot submit.

Stage 15: Track

views delta to metrics.json. 3. Skip, don't fail, if the adapter is down.

Stage 16: Reconcile

views x cpm_net. 3. Pull the actual bank or wallet change. 4. Compare

all three, open an exception on any mismatch over 5%.

is missing campaign_id, submitted, approved, paid.

Stage 17: Learn

reliability (3 late payouts flips a marketplace to bad). 3. Re-run

Stage 5. 4. Shift experiment allocation toward the winning arm, 20% floor.

new selection run logged.

score-vs-views correlation today. Phase 0, in progress: the full feedback

loop back into Score and Allocate.

Stage 18: Cleanup

  1. Remove temp dirs under jobs/_src/. 3. Rotate logs/daily.log.

Stage 19: Report

earned yesterday and to date, open escalations, posts

published/confirmed/submitted, active campaigns and their runway, heartbeat

time. 2. Send the heartbeat line to Telegram. 3. Put the exception

count and money numbers in the first five lines.

repeats.

Phase 0, in progress: today's opening lines are new-sources, assets-ready,

scheduled, due-today, not the five spec'd lines. Telegram delivery is not wired.

Stage 20: Sleep

  1. Exit cleanly. 3. Let launchd fire the next run, 07:15 or 15:15.

and a separate watcher pages after 1.5 intervals. See heartbeat_missing

in Part B.

=== daily run end === line in logs/daily.log is the closest thing today.


PART B: THE HUMAN'S SOP

Only ever act here on an open exception. Everything else runs unattended.

needs_human: account:\<lane\>

Mission Control card under "What needs a human."

or Gaming. Phone and a human required, no way around it. 2. Register:

python3 -c "import sys;sys.path.insert(0,'engine');import accounts;accounts.add('','','')".

  1. Tell Clara the handle, platform, lane.

needs_human: rules_doc

Mission Control card with campaign and marketplace.

rules or allowed-content document URL. 3. Paste it into the exception in

Mission Control, or tell Clara.

publish_failed / "publish requires a Claude session"

Control card listing each post: account, platform, file, hook.

due." It uses the Higgsfield MCP tools to post each queued clip.

back into the queue row.

submit not capable

Control card with the post URL and campaign.

into its submit form. 3. Mark it done in Mission Control, or tell Clara.

payout_late

estimate." Mission Control Money card, stated days vs elapsed.

reliability to bad automatically. Chasing it yourself is optional.

reconcile_mismatch

bank $Z." Mission Control shows all three on the campaign's Money card.

wallet or bank deposit. 3. Tell Clara which number is wrong, and why.

which to trust.

throttle_suspected (account paused)

own median for 2 days." Mission Control greys out the account.

own. 2. If it doesn't recover, rebrand it to a different campaign in the

same lane: accounts.rebrand(handle, platform, new_campaign_id).

llm_spend_cap

it under The Engine.

Discovery, tracking, reporting keep running. To spend more, raise the number

in controls/limits.json.

heartbeat_missing

from a separate watcher, since silence can't page on its own.

  1. Check the last run: tail -30 logs/daily.log. 3. Check today's

brief: cat briefs/$(date +%F).md. 4. Reload the plist if unloaded. If

ffmpeg, node, or yt-dlp is missing from PATH, read SYSTEM.md's PATH note

first, the plist PATH is pinned on purpose.

Controls, always available

stops. Discover, Score, Report keep running.

below is met, never before. 1. 3 consecutive clean sandbox days, zero

unknown-class exceptions. 2. At least one account past its cooldown.

  1. Ralph has joined at least one campaign.

PART C: DAILY AND WEEKLY RHYTHM

The 07:15 brief's first five lines

#LineWhat it meansWhat you do

|---|---|---|---|

1Money earned yesterday and to dateThe only number that matters, long runNothing, if it moves up. Flat for days, check Stage 17's reliability flags
2Open escalationsCount of unresolved exceptionsZero, nothing to do. Above zero, go to Part B for the type named
3Posts published / confirmed / submittedToday's throughputA gap between published and confirmed over a day flags a Stage 13 problem
4Active campaigns and runwayThe 3 to 5 selected, days of pool life leftUnder 5 days, it parks itself. No action needed
5Heartbeat timeProof the loop ranNot today's date, see `heartbeat_missing` in Part B

The weekly Learn output

Every week, Stage 17 recomputes reliability flags, refreshes the

historical-performance term, re-runs Stage 5's selector, and shifts experiment

allocation toward whichever hook style, caption style, or post time is

winning. Nothing is required of you. Worth a glance at Mission Control's

Strategy tab to see which arm won and why.

The monthly restore test

The nightly backup is tarred, kept 30 days, one copy off the machine. A

restore is tested once a month and logged. Nothing required, unless the log

shows a failed or skipped test, which is itself an exception to work.


PART D: PHASE GATES

Nothing skips a phase. A phase that regresses, an unknown exception, a

reconciliation miss, a paused account, drops back one phase until clean again.

Phase 0, Sandbox (now)

allocate, ingest, analyze, render, QA, schedule, dry-run publish and submit,

track, reconcile on dry data, report.

Evidence: dryrun/ has an entry for every would-be publish and submit.

controls/heartbeat updates twice a day. A restore-test log exists. Mission

Control requires a login.

Phase 1, First live post

Gate to enter:

What runs: one account, one campaign, one post a day, live publish via the

session path, submit via browser adapter or exception.

Stays human: joining campaigns. Publishing, if the headless-session test fails.

Evidence: audit/YYYY-MM-DD.jsonl shows 3 days with no unknown rows.

accounts.json shows an account aged 3 days or more. A campaign record shows

status: active.

Phase 2, Loop closed

Gate to enter:

What runs: both accounts, 3 campaigns, allowance-driven cadence,

experiments on.

Stays human: account creation, escalations only.

Evidence: a reconciliation record on disk with all three numbers, delta under 5%.

Phase 3, Scale

Gate to enter:

What runs: add accounts and campaigns. Throughput follows the capacity

formula, the sum of each account's allowance for its warm-up stage.

Stays human: account creation, escalations only.

Evidence: 14 days of reconcile records on disk. Exception-to-post ratio

computed and under 5%.


PART E: DEFINITION OF DONE, PER STAGE

#StageDone means, on disk

|---|---|---|

0HealthHealth record written, every dependency checked, kill switch state read
1Discover`last_seen` updated for every pollable marketplace this run
2Sync`burn_rate_per_day`, `runway_days`, `last_seen` current on every campaign
3Verify`approved_sources[]` populated, payout terms captured or marked unknown
4ScoreAll 7 `score_parts` present, `score` and `eligible` written
5Allocate3 to 5 campaigns `selected` or `active`, each with an assigned account
6IngestFile on disk, checksum recorded, URL provably in `approved_sources[]`
7AnalyzeTimestamped moments with scores stored in the job's metadata
8Rendermp4 exists, probe shows 1080x1920, duration in range, audio present
9QAClip marked `qa_passed` with every check recorded, or `qa_failed` with the reason
10Schedule`queue.json` shows every asset `queued` with a slot, or `unplaced` with a reason
11Pre-publishIdempotency key written, all 4 gate checks passed, in `due_now.json`
12PublishPost id or URL recorded against the clip
13ConfirmPost record shows `confirmed: true` with a readable view count
14SubmitURL on file with the campaign, submission id or screenshot stored
15Track`metrics.json` shows updated `views` and `checked` for the post
16ReconcileThree numbers agree within 5%, or an open exception names the mismatch
17Learn`reliability` flags updated, new selection run logged
18CleanupNo source or clip file older than its retention window remains
19ReportToday's brief exists, first 5 lines in the required order
20Sleep`controls/heartbeat` timestamp matches this run's completion