Cyclotron

Editorial intelligence for publications.

Recording catalog

scripts/recordings.py keeps every recorded editorial run as a separate, described recording and publishes only the few that explain something. No model calls, no keys, no .env reads.

A recording is <catalog>/<id>/recording.json (schema cyclotron-recording/v1) plus fixture.json, the schema-2 playback export made by scripts/export_editorial_run_fixture.py. recording.json holds the title, summary, "what to notice" bullets, tags, status (draft, published, retired), featured, the setup read from the run's protocol.json, per-window accuracy (cyclotron, decision model alone, majority baseline, all as fractions), the first rubric cycle, cost, fixture_sha256, and a local-only source block (run directory, replicate set) that export drops.

python scripts/recordings.py add RUN_DIR --id blank-start-first-run --title "..." --summary "..." \
    --notice "..." --notice "..." --tag blank-start --tag jev \
    --corpus-manifest corpus_v3_manifest.json --corpus corpus_v3_operational.jsonl \
    --cycles 280 --window-size 70 --optimizer-price gpt-6-luna --decision-model-price jev \
    --engine-commit SHA --catalog recordings/
python scripts/recordings.py list --catalog recordings/ [--status draft] [--tag jev]
python scripts/recordings.py compare ID1 ID2 --catalog recordings/
python scripts/recordings.py publish ID [--featured] --catalog recordings/
python scripts/recordings.py export-published --to ../site/recordings --catalog recordings/

--catalog may be replaced by $CYCLOTRON_RECORDINGS_CATALOG. Other commands: show, unpublish (back to draft), retire, unfeature.

Defaults

Publishing

publish refuses a recording without: a title and summary; at least one notice bullet; label provenance; a cost total and price basis; a majority baseline on every window; a fixture_sha256 that matches fixture.json. Publishing with --featured clears featured on every other recording; unpublishing or retiring clears it too. A retired recording must be unpublished before it can be published again.

export-published validates again, then writes each published recording (without source) and its fixture to DEST/<id>/ and DEST/index.json ({"schema": "cyclotron-recordings-index/v1", "recordings": [...]} with id, title, summary, tags, featured; featured first, then newest created, then id). Output is sorted-key JSON so re-running changes nothing. It writes a .cyclotron-recordings marker; only when that marker already existed does it remove directories holding a recording.json that are no longer published. An unmarked directory is never cleaned.

setup.review records how much of the run a person reviewed: {policy, summary, stories_reviewed, stories_total}. policy is the run's feedback mode (all, reject_half, casual_ten_percent, or any other recorded mode), read from the exported scenario's feedback policy, then the protocol's single modes entry, then the mode folder name. stories_reviewed counts the recorded cycles whose label was revealed to the learner; stories_total counts the recorded cycles. The block is optional: older recordings without it mean every story was reviewed. list shows the policy and count.

Comparison-ready recordings

New recordings (and old ones after upgrade) carry what is needed to compare two decision models rigorously. Every field below is optional, so older recordings still validate, list and publish.

compare

python scripts/recordings.py compare A B --catalog recordings/ [--disagreements N] [--force]

It refuses (nonzero exit) unless both recordings have the same benchmark.id, listing the benchmark inputs that differ. When comparable it prints the setup differences, per-window final / raw / baseline accuracy, cost, overall final and raw accuracy, final and raw publish recall and precision, and a paired exact McNemar test (two-sided, exact binomial on the discordant counts) on the correctness of final decisions and, separately, of raw answers, with the number of stories where the two answers differ. Stories are paired by id. --disagreements N lists the first N stories whose final decisions differ, with each answer and the gold label. --force compares different benchmarks anyway, with a loud warning.

upgrade

python scripts/recordings.py upgrade ID [--run-dir DIR] --catalog recordings/

Backfills the fields above from the recording's source.run_dir (or --run-dir) and regenerates predictions.jsonl from its fixture. Missing story ids are taken from the run's results.json; if they cannot be found, benchmark.comparable is false with a reason. It never changes accuracy numbers, notices, status, cost or the fixture, and running it again changes nothing.