Spaces:
Running
Build Plan β RSI Content Generation Agent
This is the authoritative, phased build plan. The README describes the system as if finished; this document is how we get there without breaking anything, in an order where each phase is independently testable. The guiding rule:
Never refactor working code blind. Copy it intact, wrap it behind a contract, and only physically move shared modules once the wrapper is proven. Every phase ends at a state you can run.
Invariants (true at every phase β violating one is "a mistake")
- The harness grades; the variants are graded.
harness/fitness.py, the scoreboard, and the dispatcher's allocation math are never in the mutator's editable surface. - The agent is blind to credentials. CI reads a sanitized Mongo
fitness_scoreboardvia a role scoped to that collection only. It never getsMONGO_URL, never readsusers. - Merge is gated. A mutation only reaches
mainthrough a PR that (a) has green LOCKED tests and (b) passes the secret-scan. Whether a human or the organism itself clicks merge is theAUTONOMOUS_MERGEtoggle β but a red or secret-touching build never merges, either way. - 3-day leash. Fitness only ever reads analytics for videos uploaded β₯ 3 days ago.
- Death β low score. A suspended/struck channel HALTs the loop; it is never fed into fitness as a number.
- Population β€
MAX_LIVING_VARIANTS(4).
Build status
| Phase | What | State |
|---|---|---|
| 0 | Repo skeleton + seed variant | β done |
| 1 | Variant contract (genome.py) |
β done |
| 2 | Scoreboard (Mongo + CSV, sanitized) | β done |
| 3 | Fitness scorer + live YouTube fetch + /fitness/refresh |
β done (live path needs real keys to validate) |
| 4 | Dispatcher allocation + run_day orchestrator + /run/daily + attribution |
β done (live render/publish needs real keys) |
| 5 | CI: generate / fitness / mutator (scout + self-fix + auto-merge toggle) / deploy | β done |
| 6 | Hoist shared publishers/storage/renderer from variant_1 into harness/ |
β³ deferred (works as-is via import; pure cleanup) |
| 7 | First real mutation & meta-evolution | βΆ runs once deployed with keys |
Everything deterministic is unit-tested (70 tests). What the tests cannot cover without real credentials: the actual YouTube Analytics HTTP calls, MoviePy rendering, and live publishing. Those run only against real services β wired and syntax-clean, validated by mocks.
Phase 0 β Repository skeleton β
content-generator/
βββ README.md # the destination spec
βββ PLAN.md # this file
βββ .gitignore
βββ CODEOWNERS # locks harness/ + fitness + scoreboard to the human
βββ .aiderignore # the mutator's blindfold β everything except variants/**
βββ EXPERIMENTS_LOG.md # genetic memory (seeded)
βββ metrics.csv # scoreboard snapshot (header only, for now)
βββ requirements.txt # root deps (superset)
βββ Dockerfile # HF Space body (port 7860)
βββ harness/ # the locked physics (built in Phases 2β4)
βββ variants/
β βββ variant_1/ # faithful copy of meme-generator (the seed strategy)
βββ .github/workflows/ # fitness.yml + mutator.yml (Phase 5)
variants/variant_1/is a faithful copy ofmeme-generator(minus.git,scratch,queue_state.json). It still runs exactly as the original does. We do not gut it.
Phase 1 β Define the contract (no behaviour change)
Goal: describe what a "variant" is without moving any code yet.
harness/genome.pydefines:VariantManifestβ declared metadata a variant ships invariants/<id>/manifest.json(id, parent, created_at, the mutablegenomedict, the tone/strategy knobs).- The
Variantprotocol: every variant package must exposegenerate_video_plan(budget, *, gemini_api_key) -> list[VideoPlan]and the assets the shared renderer/publisher consume. Variant 1 satisfies this by adapting its existingbackend_service.video_generator_agent.generate_video_plan_bundle.
- Test:
python -c "import harness.genome"imports cleanly; variant_1 unchanged.
Phase 2 β The scoreboard (read/write the ground truth)
Goal: a sanitized, secrets-free Mongo collection + a CSV snapshot.
harness/scoreboard.py:ScoreRowdataclass:video_id, upload_date, variant_id, genome_hash, parent_genome, APV, VSA, fitness, channel_statusβ and nothing else (no tokens, keys, raw tenant id).upsert_rows(rows)β written by the HF body with fullMONGO_URL.read_all()β used by the mutator viaMONGO_FITNESS_READONLY_URL.snapshot_to_csv(path)β dump for committed lineage / Aider input.
- One-time ops: create the
fitnessReadonlyrole +mutator_rouser (snippet in README). - Test: round-trip a fake row through a local/Atlas Mongo; confirm
mutator_rocanfindonfitness_scoreboardand is denied onusers.
Phase 3 β The fitness function (the locked scorer)
Goal: turn YouTube Analytics into fitness, safely. Runs on HF (it needs per-tenant
creds from users).
harness/fitness.py:refresh_scoreboard(*, lab_channel_id, get_channel_credentials, now):- enumerate the lab channel's videos uploaded β₯ 3 days ago (the leash),
- classify
channel_status(active/terminated/suspended/no_data), - on
terminated/suspendedβ raiseChannelHalt(caller pages the operator; no rows written from a dead channel), - fetch APV + VSA via the YouTube Analytics API,
fitness = w_apv * APV + w_vsa * VSA(weights are constants here, not a gene),scoreboard.upsert_rows(...).
- The YouTube Analytics call is isolated behind
_fetch_analytics(...)so it can be mocked.
- HF exposes
POST /fitness/refreshthat callsrefresh_scoreboard. (Or HF self-schedules.) - Test: unit-test the leash (a 1-day-old video is excluded), the HALT path, and the fitness formula with mocked analytics. No live API needed.
Phase 4 β The dispatcher (carrying capacity & lifecycle)
Goal: decide who gets airtime; spawn/retire variants. Pure, deterministic, testable.
harness/dispatcher.py:living_variants()β scanvariants/for valid manifests (cap-aware).allocate_slots(budget, fitness_by_variant, *, newborn_floor):- newborns (no fitness yet) get a guaranteed floor β never starved to 0,
- scored variants split the rest by fitness rank (soft taper, best first β ordering, not noisy magnitudes), and no scored variant is forced to 0 while budget covers them,
- sum == budget exactly. Daily budget defaults to 5.
extinction_candidates(history, K)β variants at 0 slots forKconsecutive days.enforce_cap(MAX_LIVING_VARIANTS).run_day(budget)β for each variant, call itsgenerate_video_plan, hand assets to the shared renderer + publisher, thenrecord_run. This replaces variant 1's standaloneletsDoTodaysJobloop as the top-level entrypoint.
- Test: property-test
allocate_slots(sums to budget, newborn floor honoured, taper by rank); test extinction + cap with synthetic histories.
Phase 5 β The evolutionary engine (CI)
Goal: wire the two GitHub Actions workflows.
.github/workflows/fitness.ymlβ cron every 3 days:curl -XPOST $HF_SPACE_URL/fitness/refresh(optionally withX-Trigger-Token). Holds no secrets beyond the URL/token..github/workflows/mutator.ymlβ cron every 3 days (offset +6h after the refresh):- checkout, set up Python + Aider,
python -m harness.scoreboard --snapshot metrics.csvusingMONGO_FITNESS_READONLY_URL,- run Aider headless with
--messagefromharness/mutator_prompt.md, model$MUTATOR_MODEL(OpenRouter free DeepSeek β Gemini Flash fallback), allowlist enforced by.aiderignore, - run
pytest(liveness gate) inside the container β abort the PR on failure, - secret-scan the diff (
gitleaks+ grep) β label, gh pr createwith the newEXPERIMENTS_LOG.mdentry as the body.
- Test: run the mutator workflow manually (
workflow_dispatch) against a throwaway branch; confirm it opens a PR and cannot merge.
Phase 6 β Hoist shared services (the clean split)
Goal: reach the README's architecture β only now, with tests green.
- Physically move
publishers.py,storage.py, and the MoviePy renderer out ofvariant_1/backend_serviceintoharness/asharness.publishers,harness.storage,harness.video_render. Variant 1 imports them fromharness. - Demote variant 1 to strategy only: idea/critic/template/music prompts + the meme engine.
- Add
.aiderignorecoverage so the hoisted modules are now also off-limits. - Test: full
pytest; one end-to-end dry-run video render withVIDEO_AGENT_DISABLE_LLM=1.
Phase 7 β First mutation & meta-evolution
- Seed
EXPERIMENTS_LOG.mdwith the baseline genome. - Let the mutator spawn
variant_2fromvariant_1with one change. Two deliberately wacky first experiments to "see what it does":- The Subliminal Frame β a 1-frame meme at t=0.5s before the real content (bets on VSA).
- The TTS Auctioneer β
tts_rate=+40%,seconds_per_image=3(bets on rewatch).
- Watch the scoreboard for ~2 windows; merge survivors; let starvation retire losers.
Resolved decisions (locked)
| Decision | Value |
|---|---|
| Evolve cadence | every 3 days |
| Cohorts | multiple, competing for a fixed daily video budget |
| Slot allocation | fitness-proportional + juvenile floor; 0 slots for K=12 days β extinction |
| Population cap | 4 living variants |
| Fitness bridge | HF computes β sanitized Mongo fitness_scoreboard β mutator reads (read-only role) |
| Multi-tenant signal | single lab channel; other tenants excluded from selection |
| Mutator brain | OpenRouter deepseek:free β Gemini Flash fallback (MUTATOR_MODEL) |
| Merge policy | toggle AUTONOMOUS_MERGE: human-in-the-loop (default) or fully autonomous self-merge |
| Secret leak control | .aiderignore blindfold + CODEOWNERS + CI secret-scan tripwire |
Open knobs (tunable, not blocking)
- Fitness weights
w_apv/w_vsa. DAILY_VIDEO_BUDGET(3β5),EXTINCTION_DAYS_K(default 12), juvenile floor size.boldnessgene range.