plastic / scripts /export_contract_observatory.py
dmontgomery40's picture
Sync from GitHub via hub-sync (part 2)
c10c824 verified
Raw History Blame Contribute Delete
15.3 kB
"""Export the learning-contract report sets as compact JSON for the observatory's Learning view.
Reads only JSON and Markdown under ``docs/research/results/contract-2026-09-23/``; never weights. Writes one
``index.json`` to ``docs/research/results/learning-observatory/`` and mirrors the same bytes into
``dashboard/public/assets/learning/``, which the dashboard build copies into ``dist/assets`` (the static path the Space
serves). The mirror is a sibling of the Sleep export's ``assets/observatory/``, whose own mirror step replaces that whole
directory; the Sleep export is not touched.
Two kinds of set are recognized by content, not by name:
* a **report set** (``scripts/experiments/transfer_contract.py``): ``manifest.json`` plus one JSON per baseline mode
(``frozen``, ``continued``, ``in_context``). Its learner is ``DynamicsLearner``, whose off-intervention is
``beta_scale=0`` (writes disabled, decay active).
* an **ablation set** (``scripts/experiments/coordinate_ablation.py``): one JSON per variant carrying ``variant``,
``contract`` and its own ``no_adapt_label``. Variants not archived are listed as missing, in the runner's order.
Every value is copied from a source file or computed from it, never invented. Missing values stay ``null``; a rate over
an empty set is ``null``, not zero. A set measured under a contract version other than the current
``plastic.eval.contract.CONTRACT_VERSION`` is marked ``current: false`` (the only staleness the page can show; staleness
of this export against its sources is what ``--check`` reports). Output is deterministic: no wall-clock time, no
absolute paths; it is identified by the sha256 of each source, an archive digest over them, and ``EXPORTER_VERSION``.
Usage
python -m scripts.export_contract_observatory # refresh from the archive
python -m scripts.export_contract_observatory --check # exit 1 if the committed export or its mirror is stale
"""
from __future__ import annotations
import argparse
import hashlib
import re
import shutil
import sys
import tempfile
from pathlib import Path
from typing import Any
from plastic.eval.contract import CONTRACT_VERSION
from scripts.experiments.coordinate_ablation import VARIANTS
from scripts.export_sleep_observatory import dump, mirror, num, read_json, sha256, tree_bytes
EXPORTER_VERSION = "1.0.0"
SCHEMA = "plastic.learning-observatory/1"
ROOT = Path(__file__).resolve().parent.parent
ARCHIVE = ROOT / "docs" / "research" / "results" / "contract-2026-09-23"
OUT = ROOT / "docs" / "research" / "results" / "learning-observatory"
MIRROR = ROOT / "dashboard" / "public" / "assets" / "learning"
MODES = ("frozen", "continued", "in_context")
# The off-intervention of each learner class, as the learner implements it (plastic/eval/contract.py DynamicsLearner:
# adapt=False runs the model with beta_scale=0). Ablation variants record their own label; this is used only for report
# sets, whose producer always builds a DynamicsLearner. tests/ pin it against coordinate_ablation.learner_for.
DYNAMICS_NO_ADAPT = "writes disabled (beta_scale=0)"
# ---------------------------------------------------------------------------------------------------- pieces
def speed_view(s: dict[str, Any] | None) -> dict[str, Any] | None:
"""Adaptation speed. The contract reports ``steps_to_half`` equal to the horizon when the ratio never drops below
one half; that case is exported as ``reached_half: false`` and ``steps_to_half: null``, not as a step."""
if not s:
return None
curve = [v for v in s.get("curve") or [] if isinstance(v, (int, float))]
reached = any(v < 0.5 for v in curve) if curve else None
return {"mean_ratio": num(s.get("area")), "steps_to_half": s.get("steps_to_half") if reached is not False else None,
"reached_half": reached, "horizon": len(curve) or None, "episodes": s.get("episodes")}
def pair(block: dict[str, Any] | None) -> dict[str, Any]:
block = block or {}
return {"adapt": num(block.get("adapt")), "no_adapt": num(block.get("no_adapt")), "elements": block.get("elements")}
def before_view(rep: dict[str, Any]) -> dict[str, Any]:
"""The before-stream block: held-out pairings per held-out policy, the training distribution, adaptation speed."""
transfer = rep.get("transfer") or {}
return {
"policies": [{"policy": p, **pair(v.get("before"))} for p, v in transfer.items()],
"train": {"policies": list((rep.get("spec") or {}).get("train_policies") or []), **pair((rep.get("forgetting") or {}).get("before"))},
"speed": speed_view((rep.get("speed") or {}).get("before")),
}
def rate(value: Any, n: Any) -> float | None:
"""A rate over an empty set is undefined (null), never zero."""
return num(value) if isinstance(n, int) and n > 0 else None
def mode_view(rep: dict[str, Any]) -> dict[str, Any]:
c = rep.get("correction") or {}
acc = rep.get("acceptance") or {}
comp = rep.get("compute") or {}
rev = rep.get("revert") or {}
return {
"mode": rep.get("mode"),
"transfer": [{"policy": p, "delta": num(v.get("delta_mse")), "delta_no_adapt": num(v.get("delta_mse_no_adapt"))}
for p, v in (rep.get("transfer") or {}).items()],
"forgetting_delta": num((rep.get("forgetting") or {}).get("delta_mse")),
"poison_harm_vs_clean": num(c.get("harm")), "poison_harm_vs_start": num(c.get("harm_vs_before")),
"correction_residual": num(c.get("residual")), "correction_residual_vs_start": num(c.get("residual_vs_before")),
"revert": {"ok": rev.get("ok") if isinstance(rev.get("ok"), bool) else None, "gap": num(rev.get("gap")), "tolerance": num(rev.get("tolerance"))},
"acceptance": {"accepted_good": rate(acc.get("accepted_good"), acc.get("n_good")), "n_good": acc.get("n_good"),
"refused_bad": rate(acc.get("refused_bad"), acc.get("n_bad")), "n_bad": acc.get("n_bad")},
"decisions": [{k: d.get(k) for k in ("stream", "beneficial", "accepted")} for d in rep.get("decisions") or []],
"tokens_consumed": comp.get("tokens_consumed"), "tokens_measured": comp.get("tokens_measured"),
"tokens_measured_without_context": comp.get("tokens_measured_without_context"), "parameters": comp.get("parameters"),
}
def readme_tags(archive: Path) -> dict[str, str]:
"""``## `set_id` (tag)`` headings of the archive README: the short name each set is published under."""
readme = archive / "README.md"
if not readme.exists():
return {}
return {m.group(1): m.group(2).strip() for m in re.finditer(r"^##\s+`([^`]+)`\s*\(([^)]+)\)", readme.read_text(encoding="utf-8"), re.M)}
def window_view(w: dict[str, Any] | None) -> dict[str, Any]:
w = w or {}
return {"update_period": w.get("update_period"), "boundaries_per_episode": w.get("boundaries_per_episode"), "checked": bool(w.get("checked"))}
def source_list(paths: list[Path], archive: Path) -> list[dict[str, str]]:
return [{"file": p.relative_to(archive).as_posix(), "sha256": sha256(p)} for p in sorted(paths)]
# ---------------------------------------------------------------------------------------------------- sets
def export_report_set(d: Path, archive: Path, tags: dict[str, str]) -> dict[str, Any]:
m = read_json(d / "manifest.json")
reports = {mode: read_json(d / f"{mode}.json") for mode in MODES if (d / f"{mode}.json").exists()}
if not reports:
raise ValueError(f"{d.name}: a manifest without any mode report")
first = next(iter(reports.values()))
for mode, rep in reports.items():
for key, want in (("contract_version", m.get("contract_version")), ("mode", mode)):
if rep.get(key) != want:
raise ValueError(f"{d.name}/{mode}.json: {key} {rep.get(key)!r} differs from {want!r}")
if (rep.get("split") or {}).get("id") != m.get("split_id") or window_view(rep.get("adaptation_window")) != window_view(m.get("adaptation_window")):
raise ValueError(f"{d.name}/{mode}.json: split or adaptation window differs from the manifest")
if before_view(rep) != before_view(first):
raise ValueError(f"{d.name}/{mode}.json: the before-stream measurement differs between modes")
split = first.get("split") or {}
spec = first.get("spec") or {}
version = m.get("contract_version")
sources = [d / "manifest.json", *(d / f"{mode}.json" for mode in reports)] + sorted(d.glob("*.md"))
return {
"id": d.name, "kind": "report", "tag": tags.get(d.name),
"checkpoint": {"model_id": m.get("model_id"), "digest_prefix": str(m.get("checkpoint_digest") or "")[:16] or None,
"step": (m.get("model_record") or {}).get("step")},
"execution_commit": str(m.get("execution_commit") or "")[:12] or None, "device": m.get("device"), "seed": m.get("seed"),
"contract_version": version, "current": version == CONTRACT_VERSION,
"split": {"id": split.get("id"), "train": split.get("train"), "heldout": split.get("heldout"), "bound_to_learner": split.get("bound_to_learner")},
"adaptation_window": window_view(m.get("adaptation_window")),
"spec": {k: spec.get(k) for k in ("seq_len", "stream_episodes", "heldout_policies", "train_policies", "poison_bias", "revert_tolerance", "eval_batch", "probe_steps")},
"stream": first.get("stream"), "learner": m.get("learner"),
"no_adapt_label": DYNAMICS_NO_ADAPT,
"before": before_view(first),
"modes": [mode_view(reports[mode]) for mode in MODES if mode in reports],
"missing_modes": [mode for mode in MODES if mode not in reports],
"sources": source_list(sources, archive),
}
def variant_view(r: dict[str, Any]) -> dict[str, Any]:
c = r.get("contract") or {}
log = (r.get("train") or {}).get("log") or []
last = log[-1] if log else {}
size = r.get("size") or {}
version = c.get("contract_version")
return {
"variant": r.get("variant"), "config": r.get("config"), "parameters": size.get("parameters"),
"size": {k: size.get(k) for k in ("d_model", "n_heads", "n_layers", "chunk")},
"train_steps": (r.get("train") or {}).get("steps"), "s_per_step": num((r.get("train") or {}).get("s_per_step"), 4),
"final_train_loss": num(last.get("loss"), 4),
"eta_per_layer": [num(e, 4) for e in last["eta"]] if last.get("eta") else None,
"no_adapt_label": r.get("no_adapt_label"),
"contract_version": version, "current": version == CONTRACT_VERSION,
"split_id": (c.get("split") or {}).get("id"), "adaptation_window": window_view(c.get("adaptation_window")),
"before": before_view(c),
}
def export_ablation_set(d: Path, archive: Path, tags: dict[str, str], files: list[Path]) -> dict[str, Any]:
results = {read_json(p)["variant"]: (p, read_json(p)) for p in files}
manifest = read_json(d / "manifest.json") if (d / "manifest.json").exists() else {}
variants = [variant_view(results[v][1]) for v in VARIANTS if v in results]
variants += [variant_view(r) for v, (_, r) in sorted(results.items()) if v not in VARIANTS]
versions = sorted({v["contract_version"] for v in variants if v["contract_version"]})
sources = [p for p, _ in results.values()] + [d / "manifest.json"] * bool(manifest) + sorted(d.glob("*.md"))
return {
"id": d.name, "kind": "ablation", "tag": tags.get(d.name),
"execution_commit": str(manifest.get("execution_commit") or "")[:12] or None,
"contract_version": versions[0] if len(versions) == 1 else None, "contract_versions": versions,
"current": bool(variants) and all(v["current"] for v in variants),
"variants": variants, "missing": [v for v in VARIANTS if v not in results],
"sources": source_list(sources, archive),
}
def classify(d: Path) -> tuple[str, list[Path]] | None:
"""A report set has a manifest and at least one mode report; an ablation set has variant results."""
if (d / "manifest.json").exists() and any((d / f"{mode}.json").exists() for mode in MODES):
return "report", []
variants = [p for p in sorted(d.glob("*.json")) if p.name != "manifest.json" and {"variant", "contract"} <= set(read_json(p))]
return ("ablation", variants) if variants else None
# ---------------------------------------------------------------------------------------------------- driver
def build(out: Path, archive: Path = ARCHIVE) -> dict[str, Any]:
tags = readme_tags(archive)
sets = []
for d in sorted(p for p in archive.iterdir() if p.is_dir()) if archive.exists() else []:
kind = classify(d)
if kind is None:
continue
sets.append(export_report_set(d, archive, tags) if kind[0] == "report" else export_ablation_set(d, archive, tags, kind[1]))
digests = sorted({(f"{s['id']}/{src['file']}", src["sha256"]) for s in sets for src in s["sources"]})
try:
archive_name = archive.resolve().relative_to(ROOT).as_posix()
except ValueError:
archive_name = archive.name
index = {
"schema": SCHEMA, "exporter_version": EXPORTER_VERSION, "current_contract_version": CONTRACT_VERSION,
"archive": archive_name, "archive_digest": hashlib.sha256("".join(f"{f}:{h}\n" for f, h in digests).encode()).hexdigest(),
"sets": sets,
}
out.mkdir(parents=True, exist_ok=True)
(out / "index.json").write_text(dump(index), encoding="utf-8")
return index
def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
ap.add_argument("--out", type=Path, default=OUT, help="export directory (default: docs/research/results/learning-observatory)")
ap.add_argument("--mirror", type=Path, default=MIRROR, help="dashboard copy served under /assets (default: dashboard/public/assets/learning)")
ap.add_argument("--no-mirror", action="store_true", help="write the export only")
ap.add_argument("--check", action="store_true", help="rebuild into a temporary directory and exit 1 if the committed export or its mirror differs")
args = ap.parse_args(argv)
if args.check:
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp) / "export"
build(work)
fresh = tree_bytes(work)
committed = tree_bytes(args.out) if args.out.exists() else {}
mirrored = tree_bytes(args.mirror) if args.mirror.exists() else {}
stale = sorted(k for k in set(fresh) | set(committed) if fresh.get(k) != committed.get(k))
drift = sorted(k for k in set(committed) | set(mirrored) if committed.get(k) != mirrored.get(k))
for k in stale:
print(f"stale: {k}")
for k in drift:
print(f"mirror differs: {k}")
return 1 if stale or drift else 0
if args.out.exists():
shutil.rmtree(args.out)
index = build(args.out)
if not args.no_mirror:
mirror(args.out, args.mirror)
kinds = [s["kind"] for s in index["sets"]]
print(f"exported {kinds.count('report')} report sets and {kinds.count('ablation')} ablation sets -> {args.out}")
return 0
if __name__ == "__main__":
sys.exit(main())