github-actions[bot] commited on
Commit
e8e26ee
·
1 Parent(s): da64bf3

Deploy from GitHub Actions (ad47ba2f9b2bd4acb980f4d33e508ea187f7960d)

Browse files
README.md CHANGED
@@ -390,7 +390,7 @@ GitHub **variables** (not secrets — visible in logs, fine for non-sensitive co
390
  |----------|----------|-------|
391
  | `HF_USERNAME` | ✅ | e.g. `abhay1704` |
392
  | `HF_SPACE_ID` | ✅ | e.g. `abhay1704/content-generator-rsi` |
393
- | `MUTATOR_MODEL` | optional | Head of the coder cascade, e.g. `nim/z-ai/glm-5.2` (`nim/*` = NVIDIA NIM, `openrouter/*`, `gemini/*`) |
394
  | `RESEARCH_MODEL` | optional | Head of the reasoner cascade, default `nim/nvidia/nemotron-3-ultra-550b-a55b` |
395
  | `MONGO_DATABASE` | optional | default `content_generator` |
396
  | `AUTONOMOUS_MERGE` | optional | `false` (default) or `true` |
 
390
  |----------|----------|-------|
391
  | `HF_USERNAME` | ✅ | e.g. `abhay1704` |
392
  | `HF_SPACE_ID` | ✅ | e.g. `abhay1704/content-generator-rsi` |
393
+ | `MUTATOR_MODEL` | optional | Head of the coder cascade, e.g. `nim/nvidia/nemotron-3-ultra-550b-a55b` (`nim/*` = NVIDIA NIM, `openrouter/*`, `gemini/*`) |
394
  | `RESEARCH_MODEL` | optional | Head of the reasoner cascade, default `nim/nvidia/nemotron-3-ultra-550b-a55b` |
395
  | `MONGO_DATABASE` | optional | default `content_generator` |
396
  | `AUTONOMOUS_MERGE` | optional | `false` (default) or `true` |
harness/researcher.py CHANGED
@@ -34,6 +34,7 @@ import datetime as _dt
34
  import json
35
  import os
36
  import re
 
37
  from pathlib import Path
38
 
39
  import requests
@@ -58,9 +59,11 @@ GENOME_PATH = Path(__file__).resolve().parent / "genome.py"
58
  # were removed from this cascade.
59
  DEFAULT_PRIMARY = "nim/nvidia/nemotron-3-ultra-550b-a55b"
60
  DEFAULT_FALLBACKS = [
 
 
61
  "nim/nvidia/nemotron-3-super-120b-a12b",
62
- "nim/z-ai/glm-5.2",
63
  "gemini/gemini-flash-latest",
 
64
  "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free",
65
  ]
66
 
@@ -164,56 +167,62 @@ def _call_openai_compatible(provider: str, model: str, system: str, user: str) -
164
  if provider == "openrouter":
165
  headers["HTTP-Referer"] = "https://github.com"
166
  headers["X-Title"] = "content-generator-rsi-mutator"
167
- try:
168
- resp = requests.post(
169
- url,
170
- headers=headers,
171
- json={
172
- "model": model,
173
- "messages": [
174
- {"role": "system", "content": system},
175
- {"role": "user", "content": user},
176
- ],
177
- "temperature": 0.7,
178
- "max_tokens": _MAX_OUTPUT_TOKENS,
179
- "stream": True,
180
- },
181
- timeout=_HTTP_TIMEOUT,
182
- stream=True,
183
- )
184
- resp.raise_for_status()
185
- content: list[str] = []
186
- reasoning: list[str] = []
187
- for line in resp.iter_lines(decode_unicode=True):
188
- if not line or not line.startswith("data:"):
189
- continue
190
- chunk = line[5:].strip()
191
- if chunk == "[DONE]":
192
- break
193
- try:
194
- event = json.loads(chunk)
195
- except json.JSONDecodeError:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  continue
197
- for choice in event.get("choices") or []:
198
- delta = choice.get("delta") or {}
199
- if delta.get("content"):
200
- content.append(delta["content"])
201
- thought = delta.get("reasoning_content") or delta.get("reasoning")
202
- if thought:
203
- reasoning.append(thought)
204
- text = _THINK_BLOCK.sub("", "".join(content)).strip()
205
- # A reasoning model that spends its whole budget thinking leaves content empty; its
206
- # draft plan usually lives in reasoning_content, which beats cascading to a weaker model.
207
- text = text or "".join(reasoning).strip()
208
- if not text:
209
- # Say so out loud: an empty 200 is the quietest way this loop fails, and a silent
210
- # None here reads identically to "no API key configured" in the run log.
211
  print(f"::warning::{provider} returned an EMPTY completion model={model}")
212
  return None
213
- return text
214
- except Exception as error: # network, auth, model unavailable, bad payload
215
- print(f"::warning::{provider} call failed model={model} error={error}")
216
- return None
 
 
217
 
218
 
219
  def _call_gemini(model: str, system: str, user: str) -> str | None:
 
34
  import json
35
  import os
36
  import re
37
+ import time
38
  from pathlib import Path
39
 
40
  import requests
 
59
  # were removed from this cascade.
60
  DEFAULT_PRIMARY = "nim/nvidia/nemotron-3-ultra-550b-a55b"
61
  DEFAULT_FALLBACKS = [
62
+ "nim/nvidia/llama-3.3-nemotron-super-49b-v1",
63
+ "nim/minimaxai/minimax-m3",
64
  "nim/nvidia/nemotron-3-super-120b-a12b",
 
65
  "gemini/gemini-flash-latest",
66
+ "openrouter/z-ai/glm-5.3",
67
  "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free",
68
  ]
69
 
 
167
  if provider == "openrouter":
168
  headers["HTTP-Referer"] = "https://github.com"
169
  headers["X-Title"] = "content-generator-rsi-mutator"
170
+
171
+ for attempt in range(1, 3):
172
+ try:
173
+ resp = requests.post(
174
+ url,
175
+ headers=headers,
176
+ json={
177
+ "model": model,
178
+ "messages": [
179
+ {"role": "system", "content": system},
180
+ {"role": "user", "content": user},
181
+ ],
182
+ "temperature": 0.7,
183
+ "max_tokens": _MAX_OUTPUT_TOKENS,
184
+ "stream": True,
185
+ },
186
+ timeout=_HTTP_TIMEOUT,
187
+ stream=True,
188
+ )
189
+ resp.raise_for_status()
190
+ content: list[str] = []
191
+ reasoning: list[str] = []
192
+ for line in resp.iter_lines(decode_unicode=True):
193
+ if not line or not line.startswith("data:"):
194
+ continue
195
+ chunk = line[5:].strip()
196
+ if chunk == "[DONE]":
197
+ break
198
+ try:
199
+ event = json.loads(chunk)
200
+ except json.JSONDecodeError:
201
+ continue
202
+ for choice in event.get("choices") or []:
203
+ delta = choice.get("delta") or {}
204
+ if delta.get("content"):
205
+ content.append(delta["content"])
206
+ thought = delta.get("reasoning_content") or delta.get("reasoning")
207
+ if thought:
208
+ reasoning.append(thought)
209
+ text = _THINK_BLOCK.sub("", "".join(content)).strip()
210
+ # A reasoning model that spends its whole budget thinking leaves content empty; its
211
+ # draft plan usually lives in reasoning_content, which beats cascading to a weaker model.
212
+ text = text or "".join(reasoning).strip()
213
+ if text:
214
+ return text
215
+ if attempt < 2:
216
+ time.sleep(1.5)
217
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  print(f"::warning::{provider} returned an EMPTY completion model={model}")
219
  return None
220
+ except Exception as error: # network, auth, model unavailable, bad payload
221
+ if attempt < 2:
222
+ time.sleep(1.5)
223
+ continue
224
+ print(f"::warning::{provider} call failed model={model} error={error}")
225
+ return None
226
 
227
 
228
  def _call_gemini(model: str, system: str, user: str) -> str | None:
scripts/aider_llm_proxy.py CHANGED
@@ -73,13 +73,12 @@ PROVIDERS: dict[str, tuple[str, tuple[str, ...]]] = {
73
  PROVIDER_ALIASES = {"nvidia_nim": "nim", "nvidia": "nim"}
74
 
75
  # The workflow overrides this list; this default is the safe standalone one.
76
- # Both deepseek-v4 ids reached end-of-life on 2026-08-07 (permanent 410 Gone) and are dropped.
77
  DEFAULT_MODELS = [
78
- "nim/z-ai/glm-5.2",
79
- "nim/nvidia/nemotron-3-super-120b-a12b",
80
- "nim/openai/gpt-oss-120b",
81
- "nim/minimaxai/minimax-m3",
82
  "nim/nvidia/nemotron-3-ultra-550b-a55b",
 
 
 
 
83
  ]
84
 
85
  _THINK_BLOCK = re.compile(r"^\s*<(think|thinking|reasoning)>.*?</\1>\s*", re.DOTALL | re.IGNORECASE)
@@ -227,6 +226,11 @@ class Handler(BaseHTTPRequestHandler):
227
  "failed model=%s attempt=%s status=%s after=%.1fs error=%s",
228
  spec, attempt, status, time.time() - started, last_error,
229
  )
 
 
 
 
 
230
  # 429 = rate limited; 502/503/504 = the provider gateway gave up on a queue that
231
  # never got scheduled; 599 = our own socket timeout. All mean "this model is
232
  # busy" -> bench it. Only a 429 is worth an immediate retry: re-queueing behind
 
73
  PROVIDER_ALIASES = {"nvidia_nim": "nim", "nvidia": "nim"}
74
 
75
  # The workflow overrides this list; this default is the safe standalone one.
 
76
  DEFAULT_MODELS = [
 
 
 
 
77
  "nim/nvidia/nemotron-3-ultra-550b-a55b",
78
+ "nim/nvidia/llama-3.3-nemotron-super-49b-v1",
79
+ "nim/minimaxai/minimax-m3",
80
+ "nim/nvidia/nemotron-3-super-120b-a12b",
81
+ "nim/deepseek-ai/deepseek-v4-flash-0731",
82
  ]
83
 
84
  _THINK_BLOCK = re.compile(r"^\s*<(think|thinking|reasoning)>.*?</\1>\s*", re.DOTALL | re.IGNORECASE)
 
226
  "failed model=%s attempt=%s status=%s after=%.1fs error=%s",
227
  spec, attempt, status, time.time() - started, last_error,
228
  )
229
+ # 404 / 410 = model is retired or not found on provider -> bench permanently (24h)
230
+ if status in {404, 410}:
231
+ self.log_message("model=%s permanently unavailable (status %s); benching indefinitely", spec, status)
232
+ _start_cooldown(spec, 86400)
233
+ break
234
  # 429 = rate limited; 502/503/504 = the provider gateway gave up on a queue that
235
  # never got scheduled; 599 = our own socket timeout. All mean "this model is
236
  # busy" -> bench it. Only a 429 is worth an immediate retry: re-queueing behind
scripts/check_models.py CHANGED
@@ -7,7 +7,7 @@ the cycle records a phantom "mutation" with no code change. One cheap ping per m
7
  that into a line in the run log you can read at a glance.
8
 
9
  python scripts/check_models.py # checks the default cascades
10
- python scripts/check_models.py --models nim/z-ai/glm-5.2,gemini/gemini-flash-latest
11
  python scripts/check_models.py --require 2 # exit 1 if fewer than 2 answered
12
 
13
  Never blocks by default (exit 0 even if everything is down) — pass --require to gate on it.
@@ -27,25 +27,20 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
27
  from harness.researcher import DEFAULT_FALLBACKS, DEFAULT_PRIMARY, _complete # noqa: E402
28
 
29
  # Coder cascade — keep in sync with AIDER_PROXY_MODELS in .github/workflows/mutator.yml.
30
- # The deepseek-v4 pair was removed on 2026-08-07 (end-of-life, permanent 410).
31
  IMPL_MODELS = [
32
- "nim/z-ai/glm-5.2",
33
- "nim/nvidia/nemotron-3-super-120b-a12b",
34
- "nim/openai/gpt-oss-120b",
35
- "nim/minimaxai/minimax-m3",
36
  "nim/nvidia/nemotron-3-ultra-550b-a55b",
 
 
 
 
37
  ]
38
 
39
  # Not in any cascade — probed only so the preflight table tells us whether they are entitled to
40
- # this account and how fast they schedule. glm-5.2's queue keeps growing, so we need a shortlist
41
- # of replacements measured under real conditions before promoting one. All four are present in
42
- # NIM's public /v1/models catalog as of 2026-08-07; entitlement is a separate question, and a
43
- # model that 404s/410s here costs the preflight ~0.1s. Promote anything that answers fast.
44
  CANDIDATE_MODELS = [
45
- "nim/moonshotai/kimi-k2.6",
46
  "nim/stepfun-ai/step-3.7-flash",
47
- "nim/poolside/laguna-xs-2.1",
48
- "nim/deepseek-ai/deepseek-v4-flash-0731",
49
  ]
50
 
51
  PING = "Reply with exactly: OK"
 
7
  that into a line in the run log you can read at a glance.
8
 
9
  python scripts/check_models.py # checks the default cascades
10
+ python scripts/check_models.py --models nim/nvidia/nemotron-3-ultra-550b-a55b,gemini/gemini-flash-latest
11
  python scripts/check_models.py --require 2 # exit 1 if fewer than 2 answered
12
 
13
  Never blocks by default (exit 0 even if everything is down) — pass --require to gate on it.
 
27
  from harness.researcher import DEFAULT_FALLBACKS, DEFAULT_PRIMARY, _complete # noqa: E402
28
 
29
  # Coder cascade — keep in sync with AIDER_PROXY_MODELS in .github/workflows/mutator.yml.
 
30
  IMPL_MODELS = [
 
 
 
 
31
  "nim/nvidia/nemotron-3-ultra-550b-a55b",
32
+ "nim/nvidia/llama-3.3-nemotron-super-49b-v1",
33
+ "nim/minimaxai/minimax-m3",
34
+ "nim/nvidia/nemotron-3-super-120b-a12b",
35
+ "nim/deepseek-ai/deepseek-v4-flash-0731",
36
  ]
37
 
38
  # Not in any cascade — probed only so the preflight table tells us whether they are entitled to
39
+ # this account and how fast they schedule.
 
 
 
40
  CANDIDATE_MODELS = [
41
+ "nim/nvidia/nemotron-3.5-lightning-30b-a3b",
42
  "nim/stepfun-ai/step-3.7-flash",
43
+ "openrouter/z-ai/glm-5.3",
 
44
  ]
45
 
46
  PING = "Reply with exactly: OK"
tests/test_orchestrator.py CHANGED
@@ -1,235 +1,240 @@
1
- """Tests for harness/orchestrator.run_day with fully faked I/O."""
2
-
3
- import json
4
- from dataclasses import dataclass
5
-
6
- from harness import orchestrator
7
- from harness.genome import VideoArtifact, MANIFEST_FILENAME
8
- from harness.scoreboard import ScoreRow
9
-
10
-
11
- @dataclass
12
- class FakePub:
13
- video_id: str
14
- upload_date: str
15
-
16
-
17
- def _make_variants(tmp_path, ids):
18
- root = tmp_path / "variants"
19
- for vid in ids:
20
- d = root / vid
21
- d.mkdir(parents=True)
22
- (d / MANIFEST_FILENAME).write_text(json.dumps({"variant_id": vid, "genome": {"k": vid}}), encoding="utf-8")
23
- return root
24
-
25
-
26
- def _artifact(i=1):
27
- return VideoArtifact(video_path=f"/tmp/v{i}.mp4", title=f"title {i}", description="desc",
28
- music_name="m", music_attribution="attr")
29
-
30
-
31
- def test_run_day_allocates_produces_publishes_attributes(tmp_path):
32
- root = _make_variants(tmp_path, ["A", "B"])
33
- rows = [ScoreRow("v1", "2026-06-12", "A", "h", "seed", 90, 0.9, 9.0)] # A scored, B newborn
34
-
35
- recorded = []
36
- produce_calls = []
37
-
38
- def produce(manifest, n):
39
- produce_calls.append((manifest.variant_id, n))
40
- return [_artifact(i) for i in range(n)]
41
-
42
- def record(**kw):
43
- recorded.append(kw)
44
-
45
- counter = {"n": 0}
46
-
47
- def publish(manifest, artifact):
48
- counter["n"] += 1
49
- return FakePub(video_id=f"vid{counter['n']}", upload_date="2026-06-18")
50
-
51
- report = orchestrator.run_day(
52
- scoreboard_rows=rows,
53
- produce=produce,
54
- publish=publish,
55
- record_attribution=record,
56
- budget=3,
57
- variants_dir=root,
58
- )
59
-
60
- assert report.slots == {"A": 2, "B": 1} # A scored → soaks taper; B newborn → floor of 1
61
- assert report.published_count == 3
62
- assert ("A", 2) in produce_calls and ("B", 1) in produce_calls
63
- # every published video was attributed to a real variant + its genome hash
64
- assert len(recorded) == 3
65
- assert all(r["variant_id"] in {"A", "B"} and r["genome_hash"] for r in recorded)
66
-
67
-
68
- def test_run_day_isolates_publish_failure(tmp_path):
69
- root = _make_variants(tmp_path, ["A"])
70
- rows = [ScoreRow("v1", "2026-06-12", "A", "h", "seed", 90, 0.9, 9.0)]
71
-
72
- def publish(manifest, artifact):
73
- raise RuntimeError("youtube down")
74
-
75
- report = orchestrator.run_day(
76
- scoreboard_rows=rows,
77
- produce=lambda m, n: [_artifact(i) for i in range(n)],
78
- publish=publish,
79
- record_attribution=lambda **kw: None,
80
- budget=2, variants_dir=root,
81
- )
82
- assert report.published_count == 0
83
- assert any("publish" in e for e in report.errors)
84
-
85
-
86
- def test_run_day_isolates_produce_failure(tmp_path):
87
- root = _make_variants(tmp_path, ["A", "B"])
88
- rows = [] # both newborns → floor 1 each
89
-
90
- def produce(manifest, n):
91
- if manifest.variant_id == "A":
92
- raise RuntimeError("render exploded")
93
- return [_artifact(1)]
94
-
95
- report = orchestrator.run_day(
96
- scoreboard_rows=rows,
97
- produce=produce,
98
- publish=lambda m, a: FakePub("vidB", "2026-06-18"),
99
- record_attribution=lambda **kw: None,
100
- budget=2, variants_dir=root,
101
- )
102
- # A's failure is isolated; B still publishes.
103
- assert report.published_count == 1
104
- assert any("A: produce" in e for e in report.errors)
105
-
106
-
107
- def test_run_day_empty_produce_is_reported(tmp_path):
108
- root = _make_variants(tmp_path, ["A"])
109
- report = orchestrator.run_day(
110
- scoreboard_rows=[],
111
- produce=lambda m, n: [],
112
- publish=lambda m, a: FakePub("x", "y"),
113
- record_attribution=lambda **kw: None,
114
- budget=2, variants_dir=root,
115
- )
116
- assert report.published_count == 0
117
- assert any("no videos rendered" in e for e in report.errors)
118
-
119
-
120
- def test_run_day_parallel_produce_publishes_all(tmp_path):
121
- import threading
122
-
123
- root = _make_variants(tmp_path, ["A", "B"])
124
- seen: list[str] = []
125
- lock = threading.Lock()
126
-
127
- def produce(manifest, n):
128
- with lock:
129
- seen.append(manifest.variant_id)
130
- return [_artifact(1)]
131
-
132
- report = orchestrator.run_day(
133
- scoreboard_rows=[], # both newborns → 1 slot each
134
- produce=produce,
135
- publish=lambda m, a: FakePub("vid", "2026-06-18"),
136
- record_attribution=lambda **kw: None,
137
- budget=2, variants_dir=root, max_workers=2,
138
- )
139
- assert set(seen) == {"A", "B"} # both variants produced
140
- assert report.published_count == 2 # publishing stays correct under parallel produce
141
-
142
-
143
- def test_run_day_tracks_extinction_streak(tmp_path):
144
- root = _make_variants(tmp_path, ["A", "B"])
145
- rows = [ScoreRow("v1", "2026-06-12", "A", "h", "seed", 90, 0.9, 9.0)] # A scored, B newborn
146
-
147
- saved: dict[str, int] = {}
148
-
149
- def save(streak):
150
- saved.clear()
151
- saved.update(streak)
152
-
153
- report = orchestrator.run_day(
154
- scoreboard_rows=rows,
155
- produce=lambda m, n: [_artifact(1)],
156
- publish=lambda m, a: FakePub("vid", "2026-06-18"),
157
- record_attribution=lambda **kw: None,
158
- budget=1, variants_dir=root, # budget 1 → newborn B takes the floor slot, A starves
159
- load_streak=lambda: {"A": 11},
160
- save_streak=save,
161
- extinction_k=12,
162
- )
163
- assert report.slots == {"A": 0, "B": 1} # A got 0 slots this run
164
- assert saved["A"] == 12 and saved["B"] == 0 # A's zero-streak ticks to 12; B resets
165
- assert report.extinct == ["A"] # crossed the threshold → flagged
166
-
167
-
168
- class _Readability:
169
- def __init__(self, score, issues=()):
170
- self.score = score
171
- self.issues = list(issues)
172
- @property
173
- def assessed(self):
174
- return self.score >= 0.0
175
-
176
-
177
- def test_run_day_skips_unreadable_and_records_score(tmp_path):
178
- root = _make_variants(tmp_path, ["A"])
179
- recorded = []
180
-
181
- report = orchestrator.run_day(
182
- scoreboard_rows=[],
183
- produce=lambda m, n: [_artifact(i) for i in range(n)], # 1 newborn → 1 slot → 1 video
184
- publish=lambda m, a: FakePub("vidA", "2026-06-18"),
185
- record_attribution=lambda **kw: recorded.append(kw),
186
- budget=1, variants_dir=root,
187
- assess_readability=lambda art: _Readability(0.1, ["text clipped"]),
188
- readability_floor=0.35,
189
- )
190
- assert report.published_count == 0 # skipped: 0.1 < floor 0.35
191
- assert report.unreadable and "readability 0.10" in report.unreadable[0]
192
- assert recorded == [] # nothing published → nothing attributed
193
-
194
-
195
- def test_run_day_publishes_readable_and_carries_readability(tmp_path):
196
- root = _make_variants(tmp_path, ["A"])
197
- recorded = []
198
-
199
- report = orchestrator.run_day(
200
- scoreboard_rows=[],
201
- produce=lambda m, n: [_artifact(1)],
202
- publish=lambda m, a: FakePub("vidA", "2026-06-18"),
203
- record_attribution=lambda **kw: recorded.append(kw),
204
- budget=1, variants_dir=root,
205
- assess_readability=lambda art: _Readability(0.9, []),
206
- readability_floor=0.35,
207
- )
208
- assert report.published_count == 1
209
- assert recorded[0]["readability"] == 0.9 # score rode into attribution
210
- assert report.published[0].readability == 0.9
211
-
212
-
213
- def test_run_day_readability_fails_open_on_not_assessed(tmp_path):
214
- root = _make_variants(tmp_path, ["A"])
215
- report = orchestrator.run_day(
216
- scoreboard_rows=[],
217
- produce=lambda m, n: [_artifact(1)],
218
- publish=lambda m, a: FakePub("vidA", "2026-06-18"),
219
- record_attribution=lambda **kw: None,
220
- budget=1, variants_dir=root,
221
- assess_readability=lambda art: _Readability(-1.0), # not assessed → must still publish
222
- readability_floor=0.99,
223
- )
224
- assert report.published_count == 1 # non-assessment never blocks
225
-
226
-
227
- def test_run_day_no_variants(tmp_path):
228
- root = tmp_path / "empty"
229
- root.mkdir()
230
- report = orchestrator.run_day(
231
- scoreboard_rows=[],
232
- produce=lambda m, n: [], publish=lambda m, a: FakePub("", ""),
233
- record_attribution=lambda **kw: None, budget=3, variants_dir=root,
234
- )
235
- assert report.errors == ["no living variants"]
 
 
 
 
 
 
1
+ """Tests for harness/orchestrator.run_day with fully faked I/O."""
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from datetime import datetime, timedelta, timezone
6
+
7
+ from harness import orchestrator
8
+ from harness.genome import VideoArtifact, MANIFEST_FILENAME
9
+ from harness.scoreboard import ScoreRow
10
+
11
+
12
+ def _recent_date(days_ago: int = 5) -> str:
13
+ return (datetime.now(timezone.utc) - timedelta(days=days_ago)).date().isoformat()
14
+
15
+
16
+ @dataclass
17
+ class FakePub:
18
+ video_id: str
19
+ upload_date: str
20
+
21
+
22
+ def _make_variants(tmp_path, ids):
23
+ root = tmp_path / "variants"
24
+ for vid in ids:
25
+ d = root / vid
26
+ d.mkdir(parents=True)
27
+ (d / MANIFEST_FILENAME).write_text(json.dumps({"variant_id": vid, "genome": {"k": vid}}), encoding="utf-8")
28
+ return root
29
+
30
+
31
+ def _artifact(i=1):
32
+ return VideoArtifact(video_path=f"/tmp/v{i}.mp4", title=f"title {i}", description="desc",
33
+ music_name="m", music_attribution="attr")
34
+
35
+
36
+ def test_run_day_allocates_produces_publishes_attributes(tmp_path):
37
+ root = _make_variants(tmp_path, ["A", "B"])
38
+ rows = [ScoreRow("v1", _recent_date(5), "A", "h", "seed", 90, 0.9, 9.0)] # A scored, B newborn
39
+
40
+ recorded = []
41
+ produce_calls = []
42
+
43
+ def produce(manifest, n):
44
+ produce_calls.append((manifest.variant_id, n))
45
+ return [_artifact(i) for i in range(n)]
46
+
47
+ def record(**kw):
48
+ recorded.append(kw)
49
+
50
+ counter = {"n": 0}
51
+
52
+ def publish(manifest, artifact):
53
+ counter["n"] += 1
54
+ return FakePub(video_id=f"vid{counter['n']}", upload_date="2026-06-18")
55
+
56
+ report = orchestrator.run_day(
57
+ scoreboard_rows=rows,
58
+ produce=produce,
59
+ publish=publish,
60
+ record_attribution=record,
61
+ budget=3,
62
+ variants_dir=root,
63
+ )
64
+
65
+ assert report.slots == {"A": 2, "B": 1} # A scored → soaks taper; B newborn → floor of 1
66
+ assert report.published_count == 3
67
+ assert ("A", 2) in produce_calls and ("B", 1) in produce_calls
68
+ # every published video was attributed to a real variant + its genome hash
69
+ assert len(recorded) == 3
70
+ assert all(r["variant_id"] in {"A", "B"} and r["genome_hash"] for r in recorded)
71
+
72
+
73
+ def test_run_day_isolates_publish_failure(tmp_path):
74
+ root = _make_variants(tmp_path, ["A"])
75
+ rows = [ScoreRow("v1", _recent_date(5), "A", "h", "seed", 90, 0.9, 9.0)]
76
+
77
+ def publish(manifest, artifact):
78
+ raise RuntimeError("youtube down")
79
+
80
+ report = orchestrator.run_day(
81
+ scoreboard_rows=rows,
82
+ produce=lambda m, n: [_artifact(i) for i in range(n)],
83
+ publish=publish,
84
+ record_attribution=lambda **kw: None,
85
+ budget=2, variants_dir=root,
86
+ )
87
+ assert report.published_count == 0
88
+ assert any("publish" in e for e in report.errors)
89
+
90
+
91
+ def test_run_day_isolates_produce_failure(tmp_path):
92
+ root = _make_variants(tmp_path, ["A", "B"])
93
+ rows = [] # both newborns → floor 1 each
94
+
95
+ def produce(manifest, n):
96
+ if manifest.variant_id == "A":
97
+ raise RuntimeError("render exploded")
98
+ return [_artifact(1)]
99
+
100
+ report = orchestrator.run_day(
101
+ scoreboard_rows=rows,
102
+ produce=produce,
103
+ publish=lambda m, a: FakePub("vidB", "2026-06-18"),
104
+ record_attribution=lambda **kw: None,
105
+ budget=2, variants_dir=root,
106
+ )
107
+ # A's failure is isolated; B still publishes.
108
+ assert report.published_count == 1
109
+ assert any("A: produce" in e for e in report.errors)
110
+
111
+
112
+ def test_run_day_empty_produce_is_reported(tmp_path):
113
+ root = _make_variants(tmp_path, ["A"])
114
+ report = orchestrator.run_day(
115
+ scoreboard_rows=[],
116
+ produce=lambda m, n: [],
117
+ publish=lambda m, a: FakePub("x", "y"),
118
+ record_attribution=lambda **kw: None,
119
+ budget=2, variants_dir=root,
120
+ )
121
+ assert report.published_count == 0
122
+ assert any("no videos rendered" in e for e in report.errors)
123
+
124
+
125
+ def test_run_day_parallel_produce_publishes_all(tmp_path):
126
+ import threading
127
+
128
+ root = _make_variants(tmp_path, ["A", "B"])
129
+ seen: list[str] = []
130
+ lock = threading.Lock()
131
+
132
+ def produce(manifest, n):
133
+ with lock:
134
+ seen.append(manifest.variant_id)
135
+ return [_artifact(1)]
136
+
137
+ report = orchestrator.run_day(
138
+ scoreboard_rows=[], # both newborns → 1 slot each
139
+ produce=produce,
140
+ publish=lambda m, a: FakePub("vid", "2026-06-18"),
141
+ record_attribution=lambda **kw: None,
142
+ budget=2, variants_dir=root, max_workers=2,
143
+ )
144
+ assert set(seen) == {"A", "B"} # both variants produced
145
+ assert report.published_count == 2 # publishing stays correct under parallel produce
146
+
147
+
148
+ def test_run_day_tracks_extinction_streak(tmp_path):
149
+ root = _make_variants(tmp_path, ["A", "B"])
150
+ rows = [ScoreRow("v1", _recent_date(5), "A", "h", "seed", 90, 0.9, 9.0)] # A scored, B newborn
151
+
152
+ saved: dict[str, int] = {}
153
+
154
+ def save(streak):
155
+ saved.clear()
156
+ saved.update(streak)
157
+
158
+ report = orchestrator.run_day(
159
+ scoreboard_rows=rows,
160
+ produce=lambda m, n: [_artifact(1)],
161
+ publish=lambda m, a: FakePub("vid", "2026-06-18"),
162
+ record_attribution=lambda **kw: None,
163
+ budget=1, variants_dir=root, # budget 1 → newborn B takes the floor slot, A starves
164
+ load_streak=lambda: {"A": 11},
165
+ save_streak=save,
166
+ extinction_k=12,
167
+ )
168
+ assert report.slots == {"A": 0, "B": 1} # A got 0 slots this run
169
+ assert saved["A"] == 12 and saved["B"] == 0 # A's zero-streak ticks to 12; B resets
170
+ assert report.extinct == ["A"] # crossed the threshold → flagged
171
+
172
+
173
+ class _Readability:
174
+ def __init__(self, score, issues=()):
175
+ self.score = score
176
+ self.issues = list(issues)
177
+ @property
178
+ def assessed(self):
179
+ return self.score >= 0.0
180
+
181
+
182
+ def test_run_day_skips_unreadable_and_records_score(tmp_path):
183
+ root = _make_variants(tmp_path, ["A"])
184
+ recorded = []
185
+
186
+ report = orchestrator.run_day(
187
+ scoreboard_rows=[],
188
+ produce=lambda m, n: [_artifact(i) for i in range(n)], # 1 newborn → 1 slot → 1 video
189
+ publish=lambda m, a: FakePub("vidA", "2026-06-18"),
190
+ record_attribution=lambda **kw: recorded.append(kw),
191
+ budget=1, variants_dir=root,
192
+ assess_readability=lambda art: _Readability(0.1, ["text clipped"]),
193
+ readability_floor=0.35,
194
+ )
195
+ assert report.published_count == 0 # skipped: 0.1 < floor 0.35
196
+ assert report.unreadable and "readability 0.10" in report.unreadable[0]
197
+ assert recorded == [] # nothing published → nothing attributed
198
+
199
+
200
+ def test_run_day_publishes_readable_and_carries_readability(tmp_path):
201
+ root = _make_variants(tmp_path, ["A"])
202
+ recorded = []
203
+
204
+ report = orchestrator.run_day(
205
+ scoreboard_rows=[],
206
+ produce=lambda m, n: [_artifact(1)],
207
+ publish=lambda m, a: FakePub("vidA", "2026-06-18"),
208
+ record_attribution=lambda **kw: recorded.append(kw),
209
+ budget=1, variants_dir=root,
210
+ assess_readability=lambda art: _Readability(0.9, []),
211
+ readability_floor=0.35,
212
+ )
213
+ assert report.published_count == 1
214
+ assert recorded[0]["readability"] == 0.9 # score rode into attribution
215
+ assert report.published[0].readability == 0.9
216
+
217
+
218
+ def test_run_day_readability_fails_open_on_not_assessed(tmp_path):
219
+ root = _make_variants(tmp_path, ["A"])
220
+ report = orchestrator.run_day(
221
+ scoreboard_rows=[],
222
+ produce=lambda m, n: [_artifact(1)],
223
+ publish=lambda m, a: FakePub("vidA", "2026-06-18"),
224
+ record_attribution=lambda **kw: None,
225
+ budget=1, variants_dir=root,
226
+ assess_readability=lambda art: _Readability(-1.0), # not assessed → must still publish
227
+ readability_floor=0.99,
228
+ )
229
+ assert report.published_count == 1 # non-assessment never blocks
230
+
231
+
232
+ def test_run_day_no_variants(tmp_path):
233
+ root = tmp_path / "empty"
234
+ root.mkdir()
235
+ report = orchestrator.run_day(
236
+ scoreboard_rows=[],
237
+ produce=lambda m, n: [], publish=lambda m, a: FakePub("", ""),
238
+ record_attribution=lambda **kw: None, budget=3, variants_dir=root,
239
+ )
240
+ assert report.errors == ["no living variants"]
variants/variant_1/manifest.json CHANGED
@@ -14,6 +14,6 @@
14
  "keep_top_n": 2,
15
  "boldness": 0.9,
16
  "pipeline_version": 1,
17
- "planner_prompt_version": 6
18
  }
19
  }
 
14
  "keep_top_n": 2,
15
  "boldness": 0.9,
16
  "pipeline_version": 1,
17
+ "planner_prompt_version": 5
18
  }
19
  }