fffiloni commited on
Commit
7839aa3
·
verified ·
1 Parent(s): a2fe79c

Upload 6 files

Browse files
Files changed (2) hide show
  1. src/jobs.py +53 -0
  2. src/worker_payload.py +800 -0
src/jobs.py CHANGED
@@ -13,6 +13,7 @@ from .worker_payload import (
13
  encoded_pi_model_card_worker_script,
14
  encoded_runtime_recommender_worker_script,
15
  encoded_longcat_article_worker_script,
 
16
  encoded_pi_space_worker_script,
17
  encoded_worker_script,
18
  python_decode_and_run_command,
@@ -305,6 +306,58 @@ def launch_longcat_article_job(
305
  },
306
  )
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  def inspect_job_safe(job_id: str, token: str | None = None) -> dict[str, Any]:
309
  if not job_id:
310
  return {"error": "Missing job_id"}
 
13
  encoded_pi_model_card_worker_script,
14
  encoded_runtime_recommender_worker_script,
15
  encoded_longcat_article_worker_script,
16
+ encoded_universal_model_card_worker_script,
17
  encoded_pi_space_worker_script,
18
  encoded_worker_script,
19
  python_decode_and_run_command,
 
306
  },
307
  )
308
 
309
+
310
+ def launch_universal_model_card_job(
311
+ *,
312
+ token: str,
313
+ username: str,
314
+ target_slug: str | None = None,
315
+ model_id: str | None = None,
316
+ pi_model: str | None = None,
317
+ preferred_space_hardware: str | None = None,
318
+ fallback_space_hardware: str | None = None,
319
+ allow_fixed_gpu_fallback: bool = True,
320
+ implementation_mode: str | None = None,
321
+ run_id: str | None = None,
322
+ ) -> dict[str, Any]:
323
+ """Launch Phase 10: universal model-card → private Space builder with Pi and gated validation."""
324
+ if not token:
325
+ raise ValueError("Missing OAuth token. Please sign in with Hugging Face first.")
326
+ safe_run_id = validate_run_id(run_id) if run_id else make_run_id("universal")
327
+ target_space_id = normalize_target_space(username=username, target_slug=target_slug, run_id=safe_run_id)
328
+ clean_model_id = (model_id or "").strip().replace("https://huggingface.co/", "").strip("/")
329
+ if "/" not in clean_model_id:
330
+ raise ValueError("Model ID must look like owner/model-name or a Hugging Face model URL.")
331
+
332
+ env = _base_env(
333
+ run_id=safe_run_id,
334
+ username=username,
335
+ worker_script_b64=encoded_universal_model_card_worker_script(),
336
+ )
337
+ env["TARGET_SPACE_ID"] = target_space_id
338
+ env["MODEL_ID"] = clean_model_id
339
+ env["PI_MODEL"] = (pi_model or "Qwen/Qwen3-Coder-Next").strip()
340
+ env["PREFERRED_SPACE_HARDWARE"] = (preferred_space_hardware or "cpu-basic").strip()
341
+ env["FALLBACK_SPACE_HARDWARE"] = (fallback_space_hardware or "l40sx1").strip()
342
+ env["ALLOW_FIXED_GPU_FALLBACK"] = "true" if allow_fixed_gpu_fallback else "false"
343
+ env["IMPLEMENTATION_MODE"] = (implementation_mode or "full-inference-gated").strip()
344
+ job = _launch_job(token=token, env=env, timeout="60m")
345
+ return _job_result(
346
+ job,
347
+ run_id=safe_run_id,
348
+ kind="universal_model_card_builder",
349
+ extra={
350
+ "target_space": target_space_id,
351
+ "target_space_url": f"https://huggingface.co/spaces/{target_space_id}",
352
+ "model_id": clean_model_id,
353
+ "pi_model": env["PI_MODEL"],
354
+ "preferred_space_hardware": env["PREFERRED_SPACE_HARDWARE"],
355
+ "fallback_space_hardware": env["FALLBACK_SPACE_HARDWARE"],
356
+ "allow_fixed_gpu_fallback": allow_fixed_gpu_fallback,
357
+ "implementation_mode": env["IMPLEMENTATION_MODE"],
358
+ },
359
+ )
360
+
361
  def inspect_job_safe(job_id: str, token: str | None = None) -> dict[str, Any]:
362
  if not job_id:
363
  return {"error": "Missing job_id"}
src/worker_payload.py CHANGED
@@ -1346,6 +1346,801 @@ if __name__ == "__main__":
1346
  '''
1347
 
1348
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1349
  def encoded_worker_script() -> str:
1350
  """Return the base64-encoded Phase 1 hello worker script."""
1351
  return _encode(HELLO_WORKER_SCRIPT)
@@ -1381,6 +2176,11 @@ def encoded_longcat_article_worker_script() -> str:
1381
  """Return the base64-encoded Phase 9 LongCat article reproduction worker script."""
1382
  return _encode(LONGCAT_ARTICLE_WORKER_SCRIPT)
1383
 
 
 
 
 
 
1384
  def python_decode_and_run_command() -> list[str]:
1385
  """Command list for `run_job`.
1386
 
 
1346
  '''
1347
 
1348
 
1349
+ UNIVERSAL_MODEL_CARD_WORKER_SCRIPT = r'''
1350
+
1351
+ import json
1352
+ import os
1353
+ import re
1354
+ import shutil
1355
+ import subprocess
1356
+ import sys
1357
+ import time
1358
+ from datetime import datetime, timezone
1359
+ from pathlib import Path
1360
+ from textwrap import dedent
1361
+
1362
+ TARGET_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{1,95}/[A-Za-z0-9][A-Za-z0-9._-]{1,95}$")
1363
+ GIST_URL = "https://gist.github.com/gary149/2aba2962375fa9ca56bb9ef53f00b73d"
1364
+ DEFAULT_MODEL_ID = "sshleifer/tiny-gpt2"
1365
+
1366
+
1367
+ def now():
1368
+ return datetime.now(timezone.utc).isoformat()
1369
+
1370
+
1371
+ def write_json(path: Path, payload: dict):
1372
+ path.parent.mkdir(parents=True, exist_ok=True)
1373
+ path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
1374
+
1375
+
1376
+ def append_event(path: Path, step: str, status: str, message: str, data: dict | None = None):
1377
+ path.parent.mkdir(parents=True, exist_ok=True)
1378
+ event = {"ts": now(), "step": step, "status": status, "message": message, "data": data or {}}
1379
+ line = json.dumps(event, ensure_ascii=False)
1380
+ with path.open("a", encoding="utf-8") as f:
1381
+ f.write(line + "\n")
1382
+ print(line, flush=True)
1383
+
1384
+
1385
+ def redact_text(text: str | None) -> str:
1386
+ if not text:
1387
+ return ""
1388
+ value = text
1389
+ for secret_name in ["HF_TOKEN", "HUGGING_FACE_HUB_TOKEN"]:
1390
+ secret = os.environ.get(secret_name)
1391
+ if secret:
1392
+ value = value.replace(secret, "[REDACTED]")
1393
+ value = re.sub(r"Bearer\s+[A-Za-z0-9_\-.=]+", "Bearer [REDACTED]", value)
1394
+ value = re.sub(r"hf_[A-Za-z0-9_\-]{10,}", "hf_[REDACTED]", value)
1395
+ return value
1396
+
1397
+
1398
+ def safe_details(details: dict | None) -> dict:
1399
+ if not details:
1400
+ return {}
1401
+ try:
1402
+ return json.loads(redact_text(json.dumps(details, ensure_ascii=False)))
1403
+ except Exception:
1404
+ return {"redacted_details": redact_text(str(details))[-4000:]}
1405
+
1406
+
1407
+ def fail(run_dir: Path, events_path: Path, message: str, details: dict | None = None, status: str = "failed"):
1408
+ safe = safe_details(details)
1409
+ append_event(events_path, "failure", "failed", message, safe)
1410
+ write_json(run_dir / "state.json", {
1411
+ "run_id": os.environ.get("RUN_ID"),
1412
+ "kind": "universal_model_card_builder",
1413
+ "status": status,
1414
+ "message": message,
1415
+ "updated_at": now(),
1416
+ "details": safe,
1417
+ })
1418
+ report = f"""# Agentic Space Factory — model Article Reproduction Report
1419
+
1420
+ Status: **{status}**
1421
+
1422
+ {message}
1423
+
1424
+ ```json
1425
+ {json.dumps(safe, indent=2, ensure_ascii=False)}
1426
+ ```
1427
+ """
1428
+ (run_dir / "report.md").write_text(report, encoding="utf-8")
1429
+ raise SystemExit(1)
1430
+
1431
+
1432
+ def run_cmd(cmd: list[str], *, cwd: Path | None = None, env: dict | None = None, timeout: int = 600):
1433
+ result = subprocess.run(cmd, cwd=str(cwd) if cwd else None, env=env, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=timeout)
1434
+ return result.returncode, redact_text(result.stdout)
1435
+
1436
+
1437
+ def install_python_deps(events_path: Path):
1438
+ append_event(events_path, "dependencies", "started", "Installing Python worker dependencies")
1439
+ code, out = run_cmd([sys.executable, "-m", "pip", "install", "-q", "--upgrade", "huggingface_hub>=1.0.0", "gradio_client>=2.0.0", "requests>=2.31.0"], timeout=600)
1440
+ if code != 0:
1441
+ append_event(events_path, "dependencies", "failed", "Python dependency installation failed", {"output_tail": out[-4000:]})
1442
+ raise RuntimeError(out)
1443
+ append_event(events_path, "dependencies", "success", "Python worker dependencies installed")
1444
+
1445
+
1446
+ def ensure_node(events_path: Path):
1447
+ node = shutil.which("node")
1448
+ npm = shutil.which("npm")
1449
+ if node and npm:
1450
+ _, node_v = run_cmd([node, "--version"], timeout=30)
1451
+ _, npm_v = run_cmd([npm, "--version"], timeout=30)
1452
+ append_event(events_path, "node", "success", "Node/npm already available", {"node": node_v.strip(), "npm": npm_v.strip()})
1453
+ return
1454
+ append_event(events_path, "node", "started", "Installing nodejs/npm through apt-get")
1455
+ code, out = run_cmd(["bash", "-lc", "apt-get update -qq && apt-get install -y -qq nodejs npm"], timeout=600)
1456
+ if code != 0:
1457
+ append_event(events_path, "node", "failed", "Could not install nodejs/npm", {"output_tail": out[-4000:]})
1458
+ raise RuntimeError(out)
1459
+ append_event(events_path, "node", "success", "Installed nodejs/npm")
1460
+
1461
+
1462
+ def install_pi(events_path: Path):
1463
+ ensure_node(events_path)
1464
+ append_event(events_path, "pi_install", "started", "Installing Pi coding agent from npm")
1465
+ code, out = run_cmd(["npm", "install", "-g", "@mariozechner/pi-coding-agent"], timeout=900)
1466
+ if code != 0:
1467
+ append_event(events_path, "pi_install", "failed", "Pi npm installation failed", {"output_tail": out[-4000:]})
1468
+ raise RuntimeError(out)
1469
+ code, version = run_cmd(["pi", "--version"], timeout=60)
1470
+ append_event(events_path, "pi_install", "success", "Pi installed", {"version_output": version.strip()[-300:]})
1471
+
1472
+
1473
+ def configure_pi(events_path: Path, model: str):
1474
+ pi_dir = Path.home() / ".pi" / "agent"
1475
+ pi_dir.mkdir(parents=True, exist_ok=True)
1476
+ (pi_dir / "auth.json").write_text(json.dumps({"huggingface": {"type": "api_key", "key": os.environ.get("HF_TOKEN", "")}}, indent=2), encoding="utf-8")
1477
+ (pi_dir / "settings.json").write_text(json.dumps({"model": model, "provider": "huggingface", "autoRun": True, "autoApply": True}, indent=2), encoding="utf-8")
1478
+ append_event(events_path, "pi_config", "success", "Configured Pi", {"model": model})
1479
+
1480
+
1481
+ def collect_pi_traces(run_dir: Path, events_path: Path):
1482
+ traces_dir = Path.home() / ".pi" / "agent" / "sessions"
1483
+ raw_dir = run_dir / "traces" / "raw"
1484
+ redacted_dir = run_dir / "traces" / "redacted"
1485
+ raw_dir.mkdir(parents=True, exist_ok=True)
1486
+ redacted_dir.mkdir(parents=True, exist_ok=True)
1487
+ count = 0
1488
+ if traces_dir.exists():
1489
+ for path in traces_dir.rglob("*.jsonl"):
1490
+ rel = path.relative_to(traces_dir)
1491
+ target_raw = raw_dir / rel
1492
+ target_raw.parent.mkdir(parents=True, exist_ok=True)
1493
+ text = path.read_text(encoding="utf-8", errors="ignore")
1494
+ target_raw.write_text(text, encoding="utf-8")
1495
+ target_redacted = redacted_dir / rel
1496
+ target_redacted.parent.mkdir(parents=True, exist_ok=True)
1497
+ target_redacted.write_text(redact_text(text), encoding="utf-8")
1498
+ count += 1
1499
+ append_event(events_path, "traces", "success", "Collected Pi traces", {"count": count})
1500
+ return count
1501
+
1502
+
1503
+ def sanitize_model_id(model_id: str) -> str:
1504
+ model_id = (model_id or DEFAULT_MODEL_ID).strip().replace("https://huggingface.co/", "")
1505
+ model_id = model_id.split("?", 1)[0].strip("/")
1506
+ if not re.match(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$", model_id):
1507
+ raise ValueError("MODEL_ID must look like owner/model-name")
1508
+ return model_id
1509
+
1510
+
1511
+ def make_gradio_client(target_space_id: str, token: str):
1512
+ import inspect
1513
+ from gradio_client import Client
1514
+ params = inspect.signature(Client).parameters
1515
+ if "token" in params:
1516
+ return Client(target_space_id, token=token)
1517
+ if "hf_token" in params:
1518
+ return Client(target_space_id, hf_token=token)
1519
+ if "api_key" in params:
1520
+ return Client(target_space_id, api_key=token)
1521
+ if "headers" in params:
1522
+ return Client(target_space_id, headers={"Authorization": f"Bearer {token}"})
1523
+ return Client(target_space_id)
1524
+
1525
+
1526
+ def api_names_from_schema(schema) -> list[str]:
1527
+ names: list[str] = []
1528
+ if isinstance(schema, dict):
1529
+ endpoints = schema.get("named_endpoints") or schema.get("endpoints") or {}
1530
+ if isinstance(endpoints, dict):
1531
+ for key, value in endpoints.items():
1532
+ if isinstance(key, str) and key.startswith("/"):
1533
+ names.append(key)
1534
+ if isinstance(value, dict):
1535
+ api_name = value.get("api_name")
1536
+ if isinstance(api_name, str) and api_name.startswith("/"):
1537
+ names.append(api_name)
1538
+ if isinstance(schema.get("dependencies"), list):
1539
+ for dep in schema["dependencies"]:
1540
+ if isinstance(dep, dict):
1541
+ api_name = dep.get("api_name")
1542
+ if isinstance(api_name, str):
1543
+ names.append(api_name if api_name.startswith("/") else f"/{api_name}")
1544
+ return list(dict.fromkeys(names))
1545
+
1546
+
1547
+ def space_subdomain_url(target_space_id: str) -> str:
1548
+ owner, name = target_space_id.split("/", 1)
1549
+ # This matches the common Spaces app URL pattern. Keep conservative: our
1550
+ # generated slugs are ASCII and hyphen-friendly.
1551
+ return f"https://{owner}-{name}.hf.space".replace("_", "-").lower()
1552
+
1553
+
1554
+ def runtime_to_dict(runtime) -> dict:
1555
+ payload = {}
1556
+ for attr in ["stage", "hardware", "requested_hardware", "sleep_time", "storage", "gc_timeout"]:
1557
+ value = getattr(runtime, attr, None)
1558
+ payload[attr] = getattr(value, "value", value)
1559
+ return {k: str(v) if v is not None else None for k, v in payload.items()}
1560
+
1561
+
1562
+ def write_space_runtime(api, target_space_id: str, token: str, run_dir: Path, events_path: Path, attempt: int | None = None) -> dict:
1563
+ try:
1564
+ runtime = api.get_space_runtime(repo_id=target_space_id, token=token)
1565
+ payload = runtime_to_dict(runtime)
1566
+ payload["attempt"] = attempt
1567
+ write_json(run_dir / "space_runtime.json", payload)
1568
+ return payload
1569
+ except Exception as exc:
1570
+ payload = {"error": str(exc)[:2000], "attempt": attempt}
1571
+ write_json(run_dir / "space_runtime.json", payload)
1572
+ append_event(events_path, "space_runtime", "warning", "Could not fetch Space runtime", payload)
1573
+ return payload
1574
+
1575
+
1576
+ def collect_space_logs(target_space_id: str, token: str, run_dir: Path, events_path: Path):
1577
+ logs_dir = run_dir / "logs"
1578
+ logs_dir.mkdir(parents=True, exist_ok=True)
1579
+ env = os.environ.copy()
1580
+ env["HF_TOKEN"] = token
1581
+ commands = {
1582
+ "space_logs_runtime.txt": ["hf", "spaces", "logs", target_space_id],
1583
+ "space_logs_build.txt": ["hf", "spaces", "logs", target_space_id, "--build"],
1584
+ }
1585
+ written = []
1586
+ for filename, cmd in commands.items():
1587
+ try:
1588
+ code, out = run_cmd(cmd, env=env, timeout=75)
1589
+ (logs_dir / filename).write_text(out, encoding="utf-8")
1590
+ written.append({"file": filename, "returncode": code, "tail": out[-1000:]})
1591
+ except Exception as exc:
1592
+ written.append({"file": filename, "error": str(exc)[:1000]})
1593
+ append_event(events_path, "space_logs", "success", "Collected best-effort Space logs", {"files": written})
1594
+ return written
1595
+
1596
+
1597
+ def validate_http_health(target_space_id: str, token: str, run_dir: Path, events_path: Path, attempt: int):
1598
+ import requests
1599
+ base_url = space_subdomain_url(target_space_id)
1600
+ url = base_url.rstrip("/") + "/health"
1601
+ headers = {"Authorization": f"Bearer {token}", "Accept": "application/json,text/plain,*/*"}
1602
+ response = requests.get(url, headers=headers, timeout=20)
1603
+ payload = {
1604
+ "status": "success" if response.ok else "failed",
1605
+ "attempt": attempt,
1606
+ "url": url,
1607
+ "status_code": response.status_code,
1608
+ "content_type": response.headers.get("content-type"),
1609
+ "text": response.text[:2000],
1610
+ }
1611
+ if response.ok:
1612
+ try:
1613
+ payload["json"] = response.json()
1614
+ except Exception:
1615
+ pass
1616
+ write_json(run_dir / "tests" / "http_health.json", payload)
1617
+ write_json(run_dir / "tests" / "test_result.json", payload | {"validator": "http_get_health"})
1618
+ append_event(events_path, "api_validation", "success", "HTTP /health validation passed", {"attempt": attempt, "url": url, "status_code": response.status_code})
1619
+ return payload | {"validator": "http_get_health"}
1620
+ raise RuntimeError(f"HTTP /health returned {response.status_code}: {response.text[:500]}")
1621
+
1622
+
1623
+ def validate_gradio_api(target_space_id: str, token: str, run_dir: Path, events_path: Path, attempt: int):
1624
+ client = make_gradio_client(target_space_id, token)
1625
+ schema = client.view_api(return_format="dict")
1626
+ write_json(run_dir / "tests" / "api_schema.json", schema if isinstance(schema, dict) else {"schema": str(schema)})
1627
+ discovered = api_names_from_schema(schema)
1628
+ candidates = []
1629
+ for name in ["/health", "/predict", "/greet"] + discovered:
1630
+ if name not in candidates:
1631
+ candidates.append(name)
1632
+ errors = []
1633
+ for api_name in candidates:
1634
+ try:
1635
+ if api_name == "/greet":
1636
+ result = client.predict("Agentic Space Factory", api_name=api_name)
1637
+ else:
1638
+ result = client.predict(api_name=api_name)
1639
+ payload = {"status": "success", "attempt": attempt, "api_name": api_name, "discovered_api_names": discovered, "result_repr": repr(result)[:2000], "validator": "gradio_client"}
1640
+ write_json(run_dir / "tests" / "test_result.json", payload)
1641
+ append_event(events_path, "api_validation", "success", "Gradio API validation passed", {"attempt": attempt, "api_name": api_name, "discovered_api_names": discovered})
1642
+ return payload
1643
+ except Exception as exc:
1644
+ errors.append({"api_name": api_name, "error": str(exc)[:1000]})
1645
+ raise RuntimeError("; ".join(f"{e['api_name']}: {e['error']}" for e in errors[:5]) or "No callable API endpoints found")
1646
+
1647
+
1648
+ def validate_live_api(api, target_space_id: str, token: str, run_dir: Path, events_path: Path, timeout_s: int = 900):
1649
+ append_event(events_path, "api_validation", "started", "Waiting for live HTTP /health or Gradio API to become available")
1650
+ deadline = time.time() + timeout_s
1651
+ attempt = 0
1652
+ last_error = None
1653
+ runtime_error_count = 0
1654
+ while time.time() < deadline:
1655
+ attempt += 1
1656
+ runtime_payload = write_space_runtime(api, target_space_id, token, run_dir, events_path, attempt)
1657
+ stage = str(runtime_payload.get("stage") or "").upper()
1658
+ if "RUNTIME_ERROR" in stage:
1659
+ runtime_error_count += 1
1660
+ collect_space_logs(target_space_id, token, run_dir, events_path)
1661
+ last_error = f"Space runtime stage is {stage}"
1662
+ if runtime_error_count >= 2:
1663
+ raise RuntimeError(f"Space is in RUNTIME_ERROR. See logs/space_logs_runtime.txt and logs/space_logs_build.txt. Last runtime: {runtime_payload}")
1664
+ try:
1665
+ return validate_http_health(target_space_id, token, run_dir, events_path, attempt)
1666
+ except Exception as exc:
1667
+ last_error = f"HTTP /health failed: {exc}"
1668
+ try:
1669
+ return validate_gradio_api(target_space_id, token, run_dir, events_path, attempt)
1670
+ except Exception as exc:
1671
+ last_error = (last_error or "") + f"; Gradio API failed: {exc}"
1672
+ append_event(events_path, "api_validation", "waiting", "Live health/API not ready yet", {"attempt": attempt, "runtime": runtime_payload, "error": last_error[-1500:] if last_error else None})
1673
+ time.sleep(30)
1674
+ collect_space_logs(target_space_id, token, run_dir, events_path)
1675
+ raise RuntimeError(f"Live health/API validation did not pass before timeout: {last_error}")
1676
+
1677
+
1678
+ def request_hardware(api, target_space_id: str, hardware: str, token: str, events_path: Path, step: str, retries: int = 4):
1679
+ """Best-effort hardware request.
1680
+
1681
+ OAuth tokens can create/write Spaces but may still be unable to trigger
1682
+ hardware changes, especially paid GPU upgrades. Treat 401/auth/billing
1683
+ failures as manual-action-required instead of burning retries.
1684
+ """
1685
+ if not hardware:
1686
+ return {"requested": False, "hardware": hardware, "ok": False, "error": "empty hardware"}
1687
+ last_error = None
1688
+ for attempt in range(1, retries + 1):
1689
+ try:
1690
+ runtime = api.request_space_hardware(repo_id=target_space_id, hardware=hardware, token=token)
1691
+ payload = {
1692
+ "requested": True,
1693
+ "hardware": hardware,
1694
+ "ok": True,
1695
+ "attempt": attempt,
1696
+ "runtime_stage": getattr(getattr(runtime, "stage", None), "value", str(getattr(runtime, "stage", None))),
1697
+ "requested_hardware": getattr(runtime, "requested_hardware", None),
1698
+ "hardware_current": getattr(runtime, "hardware", None),
1699
+ }
1700
+ append_event(events_path, step, "success", f"Requested Space hardware {hardware}", payload)
1701
+ return payload
1702
+ except Exception as exc:
1703
+ last_error = str(exc)[:2000]
1704
+ auth_like = any(marker in last_error for marker in ["401", "Invalid username or password", "Unauthorized", "Repository Not Found"])
1705
+ payload = {"attempt": attempt, "hardware": hardware, "error": last_error, "manual_action_required": auth_like}
1706
+ append_event(events_path, step, "failed" if auth_like or attempt == retries else "waiting", f"Could not request Space hardware {hardware}", payload)
1707
+ if auth_like:
1708
+ return {"requested": True, "hardware": hardware, "ok": False, "attempts": attempt, "error": last_error, "manual_action_required": True}
1709
+ if attempt < retries:
1710
+ time.sleep(8 * attempt)
1711
+ return {"requested": True, "hardware": hardware, "ok": False, "attempts": retries, "error": last_error, "manual_action_required": False}
1712
+
1713
+
1714
+ def create_initial_workspace(workspace: Path, model_id: str, target_space_id: str, preferred_hardware: str, fallback_hardware: str, allow_fallback: bool, implementation_mode: str, model_analysis: dict | None = None):
1715
+ workspace.mkdir(parents=True, exist_ok=True)
1716
+ model_analysis = model_analysis or {}
1717
+ pipeline_tag = model_analysis.get("pipeline_tag")
1718
+ library_name = model_analysis.get("library_name")
1719
+ tags = model_analysis.get("tags", [])[:40]
1720
+ siblings = model_analysis.get("siblings", [])[:60]
1721
+ app_py = f"""import gradio as gr
1722
+ from huggingface_hub import model_info, list_repo_files
1723
+
1724
+ MODEL_ID = {model_id!r}
1725
+ TARGET_SPACE_ID = {target_space_id!r}
1726
+
1727
+
1728
+ def health():
1729
+ return {{
1730
+ "status": "booted",
1731
+ "model_id": MODEL_ID,
1732
+ "target_space_id": TARGET_SPACE_ID,
1733
+ "stage": "initial-scaffold",
1734
+ "note": "Pi should replace this scaffold with a model-specific demo while preserving a cheap health endpoint.",
1735
+ }}
1736
+
1737
+
1738
+ def placeholder(*args):
1739
+ return "Initial scaffold. Pi should replace this with a model-specific inference path, or write TECHNICAL_BLOCKERS.json."
1740
+
1741
+ with gr.Blocks(title="Generated Model Space — Agentic Space Factory") as demo:
1742
+ gr.Markdown("# Generated Model Space — Agentic Space Factory")
1743
+ gr.Markdown(f"Private generated Space for `{MODEL_ID}`.")
1744
+ gr.JSON(label="Health", value=health(), every=None)
1745
+ gr.Button("Health check").click(fn=health, inputs=None, outputs=gr.JSON(), api_name="health")
1746
+ gr.Textbox(label="Input", value="Hello from Agentic Space Factory").submit(fn=placeholder, inputs=None, outputs=gr.Textbox(), api_name="predict")
1747
+ gr.Button("Run placeholder").click(fn=placeholder, inputs=None, outputs=gr.Textbox(), api_name="predict")
1748
+
1749
+ if __name__ == "__main__":
1750
+ demo.launch()
1751
+ """
1752
+ (workspace / "app.py").write_text(app_py, encoding="utf-8")
1753
+ req = """gradio>=5.0.0
1754
+ huggingface_hub>=0.34.0,<1.0.0
1755
+ spaces
1756
+ transformers>=4.45.0
1757
+ diffusers
1758
+ accelerate
1759
+ safetensors
1760
+ torch
1761
+ kernels
1762
+ pillow
1763
+ numpy
1764
+ requests
1765
+ """
1766
+ (workspace / "requirements.txt").write_text(req, encoding="utf-8")
1767
+ readme = f"""---
1768
+ title: Generated Model Space
1769
+ sdk: gradio
1770
+ app_file: app.py
1771
+ python_version: "3.10"
1772
+ suggested_hardware: {preferred_hardware or fallback_hardware or "cpu-basic"}
1773
+ short_description: "Agent-built model demo"
1774
+ ---
1775
+
1776
+ # Generated Model Space — Agentic Space Factory
1777
+
1778
+ Private generated Space for `{model_id}`.
1779
+
1780
+ This Space is created by Phase 10. It should remain private until manually reviewed.
1781
+ """
1782
+ (workspace / "README.md").write_text(readme, encoding="utf-8")
1783
+ analysis_json = json.dumps({"pipeline_tag": pipeline_tag, "library_name": library_name, "tags": tags, "siblings": siblings}, indent=2, ensure_ascii=False)
1784
+ goal = f"""You are Pi running inside a Hugging Face Job for Agentic Space Factory Phase 10.
1785
+
1786
+ Goal: build the best possible private Hugging Face Space demo for an arbitrary model card.
1787
+
1788
+ MODEL_ID: {model_id}
1789
+ TARGET_SPACE_ID: {target_space_id}
1790
+ IMPLEMENTATION_MODE: {implementation_mode}
1791
+ MODEL_METADATA:
1792
+ ```json
1793
+ {analysis_json}
1794
+ ```
1795
+
1796
+ First read and follow the operational rules from this gist:
1797
+ {GIST_URL}
1798
+
1799
+ Non-negotiable safety and product constraints:
1800
+ - The target Space must remain private.
1801
+ - Do not delete any user resources.
1802
+ - Do not print secrets or tokens.
1803
+ - Work only inside the current workspace.
1804
+ - The wrapper will create the private Space, request hardware best-effort, upload files, and validate the live app. Do not create/delete repos yourself in this Phase 10 worker.
1805
+ - Preserve a cheap health endpoint named `health` with `api_name="health"`. It must not load weights, run GPU work, or download large files.
1806
+ - Keep the huggingface_hub pin in requirements.txt: huggingface_hub>=0.34.0,<1.0.0.
1807
+ - README.md frontmatter must remain valid; if it uses short_description, it must be 60 characters or fewer.
1808
+
1809
+ Implementation contract:
1810
+ - If IMPLEMENTATION_MODE is `full-inference-gated`, you are not allowed to silently replace generation with a placeholder and call it success.
1811
+ - Try to implement the closest real inference path for the model card using evidence from README, model metadata, config files, and repo files.
1812
+ - You may choose an appropriate Gradio UI for the task: text, image, audio, video, multimodal, embeddings, classification, etc.
1813
+ - If the model is standard and feasible, implement a real generate/predict function and expose it as a Gradio endpoint.
1814
+ - If the model requires GPU, add ZeroGPU-compatible `@spaces.GPU(...)` only around the inference function. Do not decorate health.
1815
+ - If the model requires special dependencies, include them only when needed and document risks.
1816
+ - Investigate compatibility fallbacks before declaring a blocker: PyTorch SDPA, xformers, HF Kernels where relevant, CPU/offload/lazy loading, smaller resolution/steps, safe smoke-test inputs.
1817
+ - If real inference is impossible or unsafe in a Space, write TECHNICAL_BLOCKERS.json with concrete evidence for every blocker.
1818
+
1819
+ Deliverables:
1820
+ - app.py must boot on Hugging Face Spaces.
1821
+ - app.py must expose health/api_name="health".
1822
+ - If real generation is implemented, generate/predict must attempt a real model call, not only return a textual diagnostic.
1823
+ - If real generation is not implemented, write TECHNICAL_BLOCKERS.json with: full_inference_implemented=false, blockers[], evidence[], minimum_runtime, and suggested_next_step.
1824
+ - Write INFERENCE_CONTRACT.json with: full_inference_implemented, health_endpoint, primary_api_name, expected_output_type, validation_level, requires_gpu, estimated_vram, and blockers_count.
1825
+ - README.md must explain the runtime strategy, task, limitations, and how to test.
1826
+ - Write a concise PI_SUMMARY.md with what you changed and whether full inference is implemented.
1827
+ """
1828
+ (workspace / "GOAL.md").write_text(goal, encoding="utf-8")
1829
+ return ["app.py", "requirements.txt", "README.md", "GOAL.md"]
1830
+
1831
+
1832
+ def sanitize_readme_metadata(workspace: Path, events_path: Path):
1833
+ readme_path = workspace / "README.md"
1834
+ if not readme_path.exists():
1835
+ return
1836
+ text = readme_path.read_text(encoding="utf-8", errors="ignore")
1837
+ if not text.startswith("---"):
1838
+ return
1839
+ parts = text.split("---", 2)
1840
+ if len(parts) < 3:
1841
+ return
1842
+ _, frontmatter, body = parts
1843
+ changed = False
1844
+ sanitized_lines = []
1845
+ for line in frontmatter.splitlines():
1846
+ if line.strip().startswith("short_description:"):
1847
+ value = "model video avatar demo"
1848
+ sanitized_lines.append(f"short_description: {value}")
1849
+ changed = True
1850
+ else:
1851
+ sanitized_lines.append(line)
1852
+ # If Pi added other unexpectedly long one-line metadata values, leave them alone:
1853
+ # the known Hub validation blocker for this run was short_description > 60 chars.
1854
+ if changed:
1855
+ new_text = "---\n" + "\n".join(sanitized_lines).strip() + "\n---" + body
1856
+ readme_path.write_text(new_text, encoding="utf-8")
1857
+ append_event(events_path, "metadata_sanitize", "success", "Sanitized README metadata", {"short_description": "model video avatar demo"})
1858
+
1859
+
1860
+ def upload_workspace(api, workspace: Path, target_space_id: str, token: str, run_dir: Path, events_path: Path):
1861
+ sanitize_readme_metadata(workspace, events_path)
1862
+ append_event(events_path, "upload_files", "started", "Uploading generated universal model-card workspace recursively")
1863
+ gen_dir = run_dir / "generated"
1864
+ if gen_dir.exists():
1865
+ shutil.rmtree(gen_dir)
1866
+ shutil.copytree(workspace, gen_dir, ignore=shutil.ignore_patterns(".git", "node_modules", "__pycache__", "*.pyc"))
1867
+ for filename in ["app.py", "README.md", "requirements.txt"]:
1868
+ if not (workspace / filename).exists():
1869
+ raise RuntimeError(f"Missing required generated file: {filename}")
1870
+ api.upload_folder(
1871
+ folder_path=str(workspace),
1872
+ repo_id=target_space_id,
1873
+ repo_type="space",
1874
+ token=token,
1875
+ ignore_patterns=[".git/*", "node_modules/*", "__pycache__/*", "*.pyc", "GOAL.md"],
1876
+ )
1877
+ uploaded_files = sorted(str(p.relative_to(workspace)) for p in workspace.rglob("*") if p.is_file() and "node_modules" not in p.parts and "__pycache__" not in p.parts)
1878
+ append_event(events_path, "upload_files", "success", "Uploaded generated workspace folder", {"file_count": len(uploaded_files), "files_sample": uploaded_files[:50]})
1879
+
1880
+
1881
+ def load_json_if_exists(path: Path) -> dict:
1882
+ if not path.exists():
1883
+ return {}
1884
+ try:
1885
+ return json.loads(path.read_text(encoding="utf-8", errors="replace"))
1886
+ except Exception as exc:
1887
+ return {"parse_error": str(exc), "raw_tail": path.read_text(encoding="utf-8", errors="replace")[-2000:]}
1888
+
1889
+
1890
+ def infer_generation_gate(workspace: Path, implementation_mode: str, validation: dict, run_dir: Path, events_path: Path) -> dict:
1891
+ """Classify the run separately from process success.
1892
+
1893
+ /health passing means the Space boots. It does not mean the generated Space
1894
+ performs model inference. In full-inference-gated mode we require either
1895
+ an actual implementation signal or a machine-readable blocker report.
1896
+ """
1897
+ app_text = (workspace / "app.py").read_text(encoding="utf-8", errors="ignore") if (workspace / "app.py").exists() else ""
1898
+ summary_text = (workspace / "PI_SUMMARY.md").read_text(encoding="utf-8", errors="ignore") if (workspace / "PI_SUMMARY.md").exists() else ""
1899
+ req_text = (workspace / "requirements.txt").read_text(encoding="utf-8", errors="ignore") if (workspace / "requirements.txt").exists() else ""
1900
+ blockers_path = workspace / "TECHNICAL_BLOCKERS.json"
1901
+ blockers = load_json_if_exists(blockers_path)
1902
+
1903
+ combined = (app_text + "\n" + summary_text).lower()
1904
+ blocked_markers = [
1905
+ "full generation is not implemented",
1906
+ "full generation is intentionally not wired",
1907
+ "full inference is blocked",
1908
+ "returns a detailed diagnostic",
1909
+ "diagnostic report instead",
1910
+ "placeholder generator",
1911
+ "placeholder generation",
1912
+ "info-only",
1913
+ "not implemented",
1914
+ "cannot run in this environment",
1915
+ "out of scope",
1916
+ ]
1917
+ blocker_detected = bool(blockers) or any(m in combined for m in blocked_markers)
1918
+ implementation_signals = {
1919
+ "has_spaces_gpu": "@spaces.GPU" in app_text,
1920
+ "has_torch": "torch" in req_text or "import torch" in app_text,
1921
+ "has_diffusers": "diffusers" in req_text or "diffusers" in app_text,
1922
+ "has_video_output_hint": any(x in app_text.lower() for x in ["gr.video", "video", ".mp4", "ffmpeg"]),
1923
+ "health_passed": validation.get("method") in {"http_health", "gradio"},
1924
+ }
1925
+
1926
+ if blocker_detected:
1927
+ status = "technical_blocker"
1928
+ message = "Space boots, but full model inference was not implemented. See TECHNICAL_BLOCKERS.json / PI_SUMMARY.md."
1929
+ elif implementation_mode in {"full-inference-gated", "full-inference-attempt"}:
1930
+ # Without a video smoke test, do not claim real inference success.
1931
+ status = "full_inference_candidate_health_passed"
1932
+ message = "Space boots and contains inference signals, but no generation smoke test has validated a real video output."
1933
+ else:
1934
+ status = "health_only"
1935
+ message = "Safe scaffold health validation passed. Full inference was not requested."
1936
+
1937
+ if blocker_detected and not blockers:
1938
+ blockers = {
1939
+ "full_inference_implemented": False,
1940
+ "source": "worker_heuristic_from_PI_SUMMARY_or_app.py",
1941
+ "blockers": [
1942
+ {
1943
+ "type": "agent_declared_or_detected_blocker",
1944
+ "claim": "Pi-generated artifacts state that full inference is blocked/not implemented or generation returns diagnostics/placeholders.",
1945
+ "evidence": "See PI_SUMMARY.md and app.py in generated artifacts.",
1946
+ "severity": "blocking",
1947
+ }
1948
+ ],
1949
+ "required_investigations_for_next_run": [
1950
+ "Check whether PyTorch SDPA can replace flash-attn calls.",
1951
+ "Check whether HF Kernels flash-attn2/3/4 can replace required flash-attn APIs.",
1952
+ "Verify whether 2-GPU context parallelism is strictly required or can be reduced to a single-GPU smoke test.",
1953
+ ],
1954
+ }
1955
+ (workspace / "TECHNICAL_BLOCKERS.json").write_text(json.dumps(blockers, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
1956
+ (run_dir / "generated" / "TECHNICAL_BLOCKERS.json").write_text(json.dumps(blockers, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
1957
+
1958
+ gate = {
1959
+ "status": status,
1960
+ "message": message,
1961
+ "implementation_mode": implementation_mode,
1962
+ "blocker_detected": blocker_detected,
1963
+ "implementation_signals": implementation_signals,
1964
+ "validation_method": validation.get("method"),
1965
+ "blockers": blockers,
1966
+ }
1967
+ write_json(run_dir / "inference_gate.json", gate)
1968
+ append_event(events_path, "inference_gate", status, message, gate)
1969
+ return gate
1970
+
1971
+
1972
+ def main():
1973
+ run_id = os.environ["RUN_ID"]
1974
+ hf_username = os.environ.get("HF_USERNAME", "unknown")
1975
+ bucket_source = os.environ.get("BUCKET_SOURCE", "unknown")
1976
+ output_root = Path(os.environ.get("OUTPUT_ROOT", "/output"))
1977
+ target_space_id = os.environ.get("TARGET_SPACE_ID", "")
1978
+ model_id = sanitize_model_id(os.environ.get("MODEL_ID", DEFAULT_MODEL_ID))
1979
+ pi_model = os.environ.get("PI_MODEL", "Qwen/Qwen3-Coder-Next")
1980
+ preferred_hardware = os.environ.get("PREFERRED_SPACE_HARDWARE", "zero-a10g")
1981
+ fallback_hardware = os.environ.get("FALLBACK_SPACE_HARDWARE", "l40sx1")
1982
+ allow_fixed_gpu_fallback = os.environ.get("ALLOW_FIXED_GPU_FALLBACK", "true").lower() in {"1", "true", "yes", "on"}
1983
+ implementation_mode = os.environ.get("IMPLEMENTATION_MODE", "full-inference-attempt")
1984
+ token = os.environ.get("HF_TOKEN")
1985
+
1986
+ run_dir = output_root / "runs" / run_id
1987
+ events_path = run_dir / "events.jsonl"
1988
+ state_path = run_dir / "state.json"
1989
+ workspace = Path("/tmp/universal_workspace")
1990
+
1991
+ append_event(events_path, "bootstrap", "started", "Universal model-card builder worker started", {"model_id": model_id, "target_space_id": target_space_id})
1992
+ write_json(state_path, {"run_id": run_id, "kind": "universal_model_card_builder", "status": "running", "message": "Attempting Universal model-card builderd Space creation", "model_id": model_id, "target_space": target_space_id, "created_by": hf_username, "bucket_source": bucket_source, "created_at": now(), "updated_at": now()})
1993
+ if not token:
1994
+ fail(run_dir, events_path, "HF_TOKEN is missing from Job secrets")
1995
+ if not TARGET_RE.match(target_space_id):
1996
+ fail(run_dir, events_path, "Invalid TARGET_SPACE_ID", {"target_space_id": target_space_id})
1997
+
1998
+ try:
1999
+ install_python_deps(events_path)
2000
+ from huggingface_hub import HfApi
2001
+ api = HfApi(token=token)
2002
+ whoami = api.whoami(token=token)
2003
+ append_event(events_path, "auth", "success", "Authenticated inside Job", {"whoami_name": whoami.get("name")})
2004
+
2005
+ append_event(events_path, "model_analysis", "started", "Fetching model metadata", {"model_id": model_id})
2006
+ info = api.model_info(model_id, token=token, files_metadata=True)
2007
+ siblings = [getattr(s, "rfilename", "") for s in (info.siblings or [])]
2008
+ analysis = {"model_id": model_id, "pipeline_tag": getattr(info, "pipeline_tag", None), "library_name": getattr(info, "library_name", None), "tags": list(getattr(info, "tags", []) or [])[:100], "siblings": siblings[:160], "default_model_target": model_id == DEFAULT_MODEL_ID, "preferred_hardware": preferred_hardware, "fallback_hardware": fallback_hardware, "allow_fixed_gpu_fallback": allow_fixed_gpu_fallback, "implementation_mode": implementation_mode}
2009
+ write_json(run_dir / "model_analysis.json", analysis)
2010
+ append_event(events_path, "model_analysis", "success", "Model metadata fetched", {"pipeline_tag": analysis["pipeline_tag"], "library_name": analysis["library_name"]})
2011
+
2012
+ create_initial_workspace(workspace, model_id, target_space_id, preferred_hardware, fallback_hardware, allow_fixed_gpu_fallback, implementation_mode, analysis)
2013
+ append_event(events_path, "workspace", "success", "Prepared universal model-card workspace", {"files": sorted(p.name for p in workspace.iterdir())})
2014
+
2015
+ install_pi(events_path)
2016
+ configure_pi(events_path, pi_model)
2017
+ append_event(events_path, "pi_run", "started", "Running Pi on universal model-card workspace", {"model": pi_model})
2018
+ code, pi_out = run_cmd(["pi", "-p", (workspace / "GOAL.md").read_text(encoding="utf-8")], cwd=workspace, timeout=2400)
2019
+ (run_dir / "logs").mkdir(parents=True, exist_ok=True)
2020
+ (run_dir / "logs" / "pi_output.txt").write_text(pi_out, encoding="utf-8")
2021
+ if code != 0:
2022
+ append_event(events_path, "pi_run", "failed", "Pi returned a non-zero exit code", {"returncode": code, "output_tail": pi_out[-4000:]})
2023
+ collect_pi_traces(run_dir, events_path)
2024
+ fail(run_dir, events_path, "Pi failed before Space upload", {"returncode": code, "output_tail": pi_out[-4000:]})
2025
+ append_event(events_path, "pi_run", "success", "Pi completed universal model-card workspace pass", {"output_tail": pi_out[-2000:]})
2026
+ if not (workspace / "PI_SUMMARY.md").exists():
2027
+ (workspace / "PI_SUMMARY.md").write_text("# Pi Summary\n\nPi did not create a PI_SUMMARY.md. See logs/pi_output.txt.\n", encoding="utf-8")
2028
+
2029
+ app_text = (workspace / "app.py").read_text(encoding="utf-8", errors="ignore")
2030
+ if "/health" not in app_text and "api_name=\"health\"" not in app_text and "api_name='health'" not in app_text:
2031
+ append_event(events_path, "pi_verification", "failed", "app.py does not appear to expose /health; injecting safe health endpoint is not implemented")
2032
+ fail(run_dir, events_path, "Pi output did not preserve a /health endpoint")
2033
+ append_event(events_path, "pi_verification", "success", "Pi output preserved health validation endpoint")
2034
+
2035
+ append_event(events_path, "create_space", "started", "Creating private target Space", {"target_space": target_space_id})
2036
+ api.create_repo(repo_id=target_space_id, repo_type="space", space_sdk="gradio", private=True, exist_ok=False, token=token)
2037
+ append_event(events_path, "create_space", "success", "Private target Space created", {"target_space": target_space_id})
2038
+
2039
+ # Upload before requesting hardware. Newly created private Spaces may not be
2040
+ # immediately available on the hardware endpoint; uploading first also ensures
2041
+ # the repo has valid Space metadata before any restart is triggered.
2042
+ upload_workspace(api, workspace, target_space_id, token, run_dir, events_path)
2043
+
2044
+ hardware_attempts = []
2045
+ preferred = request_hardware(api, target_space_id, preferred_hardware, token, events_path, "hardware_preferred")
2046
+ hardware_attempts.append(preferred)
2047
+ selected_hardware = preferred_hardware if preferred.get("ok") else None
2048
+ if not selected_hardware and allow_fixed_gpu_fallback and fallback_hardware:
2049
+ fallback = request_hardware(api, target_space_id, fallback_hardware, token, events_path, "hardware_fallback")
2050
+ hardware_attempts.append(fallback)
2051
+ selected_hardware = fallback_hardware if fallback.get("ok") else None
2052
+ if not selected_hardware:
2053
+ append_event(events_path, "hardware", "warning", "Could not request preferred/fallback hardware; Space may remain on default CPU", {"attempts": hardware_attempts})
2054
+ selected_hardware = "default-cpu-or-existing"
2055
+ write_json(run_dir / "hardware_attempts.json", {"selected_hardware": selected_hardware, "attempts": hardware_attempts})
2056
+
2057
+ validation = validate_live_api(api, target_space_id, token, run_dir, events_path, timeout_s=1200)
2058
+ inference_gate = infer_generation_gate(workspace, implementation_mode, validation, run_dir, events_path)
2059
+ collect_pi_traces(run_dir, events_path)
2060
+
2061
+ final_state = {
2062
+ "run_id": run_id,
2063
+ "kind": "universal_model_card_builder",
2064
+ "status": inference_gate["status"],
2065
+ "message": inference_gate["message"],
2066
+ "model_id": model_id,
2067
+ "target_space": target_space_id,
2068
+ "target_space_url": f"https://huggingface.co/spaces/{target_space_id}",
2069
+ "selected_hardware": selected_hardware,
2070
+ "hardware_attempts": hardware_attempts,
2071
+ "validation": validation,
2072
+ "inference_gate": inference_gate,
2073
+ "updated_at": now(),
2074
+ "created_by": hf_username,
2075
+ "bucket_source": bucket_source,
2076
+ }
2077
+ write_json(state_path, final_state)
2078
+ report = f"""# Agentic Space Factory — Universal Model-Card Builder Report
2079
+
2080
+ Run ID: `{run_id}`
2081
+
2082
+ Status: **{inference_gate['status']}**
2083
+
2084
+ {inference_gate['message']}
2085
+
2086
+ Target Space: https://huggingface.co/spaces/{target_space_id}
2087
+
2088
+ Model: `{model_id}`
2089
+
2090
+ ## Hardware
2091
+
2092
+ Selected/requested hardware: `{selected_hardware}`
2093
+
2094
+ Hardware changes are best-effort with OAuth. If requests fail with 401/auth/billing errors, set the Space hardware manually and rerun validation.
2095
+
2096
+ ```json
2097
+ {json.dumps(hardware_attempts, indent=2, ensure_ascii=False)}
2098
+ ```
2099
+
2100
+ ## Health validation
2101
+
2102
+ The wrapper validated the live Space using HTTP `/health` first, with Gradio Client as fallback. This only proves bootability.
2103
+
2104
+ ```json
2105
+ {json.dumps(validation, indent=2, ensure_ascii=False)}
2106
+ ```
2107
+
2108
+ ## Full-inference gate
2109
+
2110
+ ```json
2111
+ {json.dumps(inference_gate, indent=2, ensure_ascii=False)}
2112
+ ```
2113
+
2114
+ ## Pi summary
2115
+
2116
+ {(workspace / 'PI_SUMMARY.md').read_text(encoding='utf-8', errors='ignore') if (workspace / 'PI_SUMMARY.md').exists() else 'No PI_SUMMARY.md was produced.'}
2117
+
2118
+ ## Safety
2119
+
2120
+ - The target Space was created private.
2121
+ - No public publication was attempted.
2122
+ - Raw traces should remain private; redacted traces are stored separately.
2123
+ - If fallback fixed GPU was used or selected manually, review billing/hardware settings manually after the run.
2124
+ """
2125
+ (run_dir / "report.md").write_text(report, encoding="utf-8")
2126
+ append_event(events_path, "report_write", "success", "Wrote report.md")
2127
+ append_event(events_path, "done", inference_gate["status"], "Universal model-card builder completed", {"target_space": target_space_id, "selected_hardware": selected_hardware, "gate_status": inference_gate["status"]})
2128
+ except SystemExit:
2129
+ raise
2130
+ except Exception as exc:
2131
+ try:
2132
+ collect_pi_traces(run_dir, events_path)
2133
+ except Exception:
2134
+ pass
2135
+ fail(run_dir, events_path, "Universal model-card builder worker failed", {"error": str(exc)})
2136
+
2137
+
2138
+ if __name__ == "__main__":
2139
+ main()
2140
+
2141
+ '''
2142
+
2143
+
2144
  def encoded_worker_script() -> str:
2145
  """Return the base64-encoded Phase 1 hello worker script."""
2146
  return _encode(HELLO_WORKER_SCRIPT)
 
2176
  """Return the base64-encoded Phase 9 LongCat article reproduction worker script."""
2177
  return _encode(LONGCAT_ARTICLE_WORKER_SCRIPT)
2178
 
2179
+
2180
+ def encoded_universal_model_card_worker_script() -> str:
2181
+ """Return the base64-encoded Phase 10 universal model-card builder worker script."""
2182
+ return _encode(UNIVERSAL_MODEL_CARD_WORKER_SCRIPT)
2183
+
2184
  def python_decode_and_run_command() -> list[str]:
2185
  """Command list for `run_job`.
2186