core/scripts/gsm8k_experience_flywheel.py
Shay 0ca48cc9a3 feat(gsm8k): add bounded experience flywheel for sealed practice
Introduce deterministic practice-memory infrastructure that adapts sealed
scout output into compact, retention-gated ExperienceRecords with family,
hazard, and promotion-candidate summaries. No serving, corpus, pack, or
report.json mutation — measurement-only adapter for future sprint reuse.
2026-06-17 21:05:02 -07:00

109 lines
No EOL
3.2 KiB
Python

#!/usr/bin/env python3
"""GSM8K bounded experience flywheel — deterministic practice memory builder.
Reads sealed scout evidence and emits compact experience artifacts. Never
mutates serving, report.json, packs, teaching corpus, or sealed practice lanes
unless an explicit --out path is provided by the operator.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parents[1]
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
from evals.gsm8k_math.train_sample.v1.experience import (
build_experience_report,
load_compacted_from_report,
write_experience_json,
write_experience_jsonl,
)
from evals.gsm8k_math.train_sample.v1.runner import _CASES_PATH, _load_cases
from evals.gsm8k_math.train_sample.v1.scout import build_scout_summary
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Build bounded GSM8K experience flywheel artifact from scout"
)
parser.add_argument(
"--cases",
type=Path,
default=_CASES_PATH,
help="Path to cases.jsonl (default: train_sample)",
)
parser.add_argument(
"--limit",
type=int,
default=None,
help="Score only the first N cases (sorted by case_id)",
)
parser.add_argument(
"--prior",
type=Path,
default=None,
help="Optional prior experience report JSON for cross-run compaction",
)
parser.add_argument(
"--out",
type=Path,
default=None,
help="Optional JSON output path (never writes repo artifacts by default)",
)
parser.add_argument(
"--jsonl-out",
type=Path,
default=None,
help="Optional JSONL output path for compacted case records",
)
parser.add_argument(
"--include-raw",
action="store_true",
help="Include pre-compaction raw records in JSON output",
)
args = parser.parse_args(argv)
if not args.cases.exists():
print(f"ERROR: cases file not found: {args.cases}", file=sys.stderr)
return 1
cases = _load_cases(args.cases)
if args.limit is not None:
cases = sorted(cases, key=lambda c: c["case_id"])[: args.limit]
scout_summary = build_scout_summary(
cases,
cases_source=str(args.cases),
include_rows=True,
)
prior_compacted = None
if args.prior is not None:
if not args.prior.exists():
print(f"ERROR: prior report not found: {args.prior}", file=sys.stderr)
return 1
prior_payload = json.loads(args.prior.read_text(encoding="utf-8"))
prior_compacted = load_compacted_from_report(prior_payload)
report = build_experience_report(
scout_summary,
cases=cases,
prior_compacted=prior_compacted,
include_raw_records=args.include_raw,
)
print(json.dumps(report, indent=2, sort_keys=True))
if args.out is not None:
write_experience_json(report, args.out)
if args.jsonl_out is not None:
write_experience_jsonl(report, args.jsonl_out)
return 0
if __name__ == "__main__":
raise SystemExit(main())