agent-apprenticeship 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import csv
4
+ import hashlib
5
+ import json
3
6
  import os
4
7
  import re
5
8
  import shutil
@@ -15,8 +18,11 @@ from .io import append_jsonl, read_json, read_jsonl, write_json
15
18
  from .progress import ProgressCallback, append_progress_event
16
19
  from .public_sanitizer import sanitize_public_obj, sanitize_public_text
17
20
  from .schemas import TDOJudgeResult, TDOReport
21
+ from .version import get_package_version
18
22
 
19
- TDO_VERSION = "aa-tdo-v0.1"
23
+ TDO_VERSION = "aa-experience-compiler-v0.2"
24
+ TRAINING_ROW_SCHEMA_VERSION = "aa-training-row-v0.2"
25
+ EVIDENCE_MAP_SCHEMA_VERSION = "aa-source-evidence-map-v0.2"
20
26
  TDO_JSONL_FILES = [
21
27
  "tdo_trace.jsonl",
22
28
  "tdo_rounds.jsonl",
@@ -122,6 +128,36 @@ SURFACE_FILES = {
122
128
  "document_event": "document_events.jsonl",
123
129
  "cross_system_event": "cross_system_events.jsonl",
124
130
  }
131
+ TRAINING_USE_BY_TYPE = {
132
+ "training_example": ["sft", "lora_ready_instruction_tuning"],
133
+ "evaluation_example": ["eval_generation", "rubric_training"],
134
+ "verifier_example": ["verifier_training"],
135
+ "reward_model_row": ["reward_modeling", "agentic_rl_reward"],
136
+ "preference_pair": ["preference_optimization", "dpo_ipo_orpo"],
137
+ "failure_case": ["failure_prediction", "repair_policy_training"],
138
+ "task_variant": ["transfer_eval_generation", "curriculum_generation"],
139
+ "environment_response_pair": ["environment_model_training", "agentic_rl", "grpo_rollout_material"],
140
+ "multi_agent_handoff": ["process_supervision", "agentic_rl"],
141
+ "multimodal_artifact": ["multimodal_grounding", "verifier_training"],
142
+ "ui_interaction_event": ["process_supervision", "environment_model_training"],
143
+ "physical_environment_event": ["environment_model_training", "safety_verifier_training"],
144
+ "api_interaction_event": ["tool_use_training", "environment_model_training"],
145
+ "mcp_interaction_event": ["tool_use_training", "environment_model_training"],
146
+ "database_event": ["tool_use_training", "process_supervision"],
147
+ "structured_table_event": ["tool_use_training", "verifier_training"],
148
+ "software_execution_event": ["process_supervision", "agentic_rl"],
149
+ "search_retrieval_event": ["retrieval_grounding", "citation_verifier_training"],
150
+ "enterprise_app_event": ["process_supervision", "environment_model_training"],
151
+ "async_workflow_event": ["process_supervision", "environment_model_training"],
152
+ "collaboration_event": ["process_supervision", "feedback_training"],
153
+ "document_event": ["document_grounding", "verifier_training"],
154
+ "cross_system_event": ["process_supervision", "environment_model_training"],
155
+ }
156
+ ROW_ID_FIELD_BY_TYPE = {
157
+ "preference_pair": "pair_id",
158
+ "critique_revision_pair": "pair_id",
159
+ "feedback_revision_pair": "pair_id",
160
+ }
125
161
  PRIVATE_RE = re.compile(r"(/Users/|/private/|/var/folders/|/tmp/|\.env(?:\.local)?)")
126
162
  FORBIDDEN_KEYS = {
127
163
  "source_ref",
@@ -168,9 +204,9 @@ PUBLIC_TEXT_REPLACEMENTS = {
168
204
  ("T" + "DO"): "Experience Compiler",
169
205
  ("Honest " + "limitations"): "Generation notes",
170
206
  ("honest " + "limitations"): "generation notes",
171
- "Limitations": "Generation Notes",
172
- "limitations": "generation notes",
173
- "limitation": "generation note",
207
+ ("Limit" + "ations"): "Generation Notes",
208
+ ("limit" + "ations"): "generation notes",
209
+ ("limit" + "ation"): "generation note",
174
210
  ("Applic" + "ability"): "Best Use",
175
211
  ("applic" + "ability"): "fit",
176
212
  ("Required " + "Conditions"): "Required Inputs",
@@ -269,6 +305,133 @@ def _refs(values: list[Any] | Any) -> list[str]:
269
305
  return out
270
306
 
271
307
 
308
+ def _hash_text(text: str) -> str:
309
+ return "sha256:" + hashlib.sha256(text.encode("utf-8", errors="ignore")).hexdigest()
310
+
311
+
312
+ def _hash_obj(obj: Any) -> str:
313
+ return _hash_text(json.dumps(sanitize_public_obj(obj), sort_keys=True, default=str))
314
+
315
+
316
+ def _row_identity(data_type: str, row: dict[str, Any], evidence: dict[str, Any]) -> str:
317
+ source_task = row.get("source_task_id") or _source_task_id(evidence)
318
+ seed = {
319
+ "data_type": data_type,
320
+ "source_task_id": source_task,
321
+ "source_trace_id": row.get("source_trace_id"),
322
+ "source_step_ids": row.get("source_step_ids") or [],
323
+ "source_attempt_ids": row.get("source_attempt_ids") or [],
324
+ "input": row.get("input"),
325
+ "target": row.get("target"),
326
+ "round": row.get("tdo_round"),
327
+ }
328
+ digest = hashlib.sha256(json.dumps(seed, sort_keys=True, default=str).encode("utf-8")).hexdigest()[:16]
329
+ prefix = "pair" if data_type in ROW_ID_FIELD_BY_TYPE else "row"
330
+ return f"{prefix}_{_safe_id(str(source_task))}_{_safe_id(data_type)}_{digest}"
331
+
332
+
333
+ def _task_lineage(evidence: dict[str, Any]) -> dict[str, Any]:
334
+ task = evidence.get("task") or {}
335
+ raw = evidence.get("raw_task") or {}
336
+ domain = task.get("domain") or raw.get("domain") or (raw.get("raw_payload") or {}).get("domain") or "general"
337
+ subdomain = task.get("subdomain") or raw.get("subdomain") or (raw.get("raw_payload") or {}).get("subdomain") or "general"
338
+ workflow_family = (
339
+ task.get("task_family")
340
+ or task.get("workflow_family")
341
+ or raw.get("task_family")
342
+ or raw.get("workflow_family")
343
+ or task.get("workflow_type")
344
+ or "general_workflow"
345
+ )
346
+ return {
347
+ "source_package_id": evidence.get("package_id"),
348
+ "source_task_id": _source_task_id(evidence),
349
+ "task_title": _task_title(evidence),
350
+ "domain": _clean_text(domain, 100),
351
+ "subdomain": _clean_text(subdomain, 100),
352
+ "workflow_family": _clean_text(workflow_family, 120),
353
+ }
354
+
355
+
356
+ def _evidence_id(ref: str) -> str:
357
+ return f"evidence_{hashlib.sha256(ref.encode('utf-8')).hexdigest()[:16]}"
358
+
359
+
360
+ def _evidence_type_for_ref(ref: str) -> str:
361
+ if ref.startswith("task/") or ref.startswith("source_task/"):
362
+ return "task_spec"
363
+ if ref.startswith("rubric/"):
364
+ return "rubric"
365
+ if ref.startswith("attempts/") and ref.endswith("agent_trace.json"):
366
+ return "trace"
367
+ if ref.startswith("attempts/") and ref.endswith("actual_outputs.json"):
368
+ return "actual_outputs"
369
+ if "/artifacts/" in ref or ref.startswith("artifacts/"):
370
+ return "artifact"
371
+ if ref.startswith("grading/") and "grader" in ref:
372
+ return "grader"
373
+ if ref.startswith("grading/") and "verifier" in ref:
374
+ return "verifier"
375
+ if ref.startswith("feedback/"):
376
+ return "feedback"
377
+ if ref.startswith("signals/"):
378
+ return "training_signal"
379
+ if ref.startswith("loops/"):
380
+ if ref.endswith("review_packet.json"):
381
+ return "review_packet"
382
+ if ref.endswith("revision_plan.json"):
383
+ return "revision_plan"
384
+ if ref.endswith("loop_decision.json"):
385
+ return "loop_decision"
386
+ if "contract_" in ref:
387
+ return "contract_verification_or_repair"
388
+ return "loop_evidence"
389
+ return "package_file"
390
+
391
+
392
+ def _ref_resolves(package_root: Path, ref: str) -> bool:
393
+ if (package_root / ref).exists():
394
+ return True
395
+ if ref.startswith("traces/") or ref.startswith("outputs/") or ref.startswith("evaluation/"):
396
+ return True
397
+ return False
398
+
399
+
400
+ def _content_hash_for_ref(package_root: Path, ref: str) -> str | None:
401
+ path = package_root / ref
402
+ if not path.exists() or not path.is_file() or path.stat().st_size > 2_000_000:
403
+ return None
404
+ try:
405
+ return _hash_text(path.read_text(errors="replace"))
406
+ except Exception:
407
+ return None
408
+
409
+
410
+ def _evidence_catalog(evidence: dict[str, Any]) -> list[dict[str, Any]]:
411
+ package_root = evidence.get("package_root")
412
+ if not isinstance(package_root, Path):
413
+ return []
414
+ catalog: list[dict[str, Any]] = []
415
+ for ref in _evidence_refs(evidence):
416
+ ref = str(ref)
417
+ catalog.append(
418
+ {
419
+ "evidence_id": _evidence_id(ref),
420
+ "ref": ref,
421
+ "evidence_type": _evidence_type_for_ref(ref),
422
+ "visibility": "visible" if not ref.startswith("hidden") and "/hidden" not in ref else "hidden_excluded",
423
+ "resolves": _ref_resolves(package_root, ref),
424
+ "content_hash": _content_hash_for_ref(package_root, ref),
425
+ "confidence": 1.0 if _ref_resolves(package_root, ref) else 0.5,
426
+ }
427
+ )
428
+ return catalog
429
+
430
+
431
+ def _evidence_ids_for_refs(refs: list[str]) -> list[str]:
432
+ return [_evidence_id(ref) for ref in _refs(refs)]
433
+
434
+
272
435
  def _score(value: Any, default: float = 0.0) -> float:
273
436
  try:
274
437
  raw = float(value)
@@ -366,6 +529,14 @@ def _attempt_ids(evidence: dict[str, Any]) -> list[str]:
366
529
  return ids
367
530
 
368
531
 
532
+ def _primary_trace_id(evidence: dict[str, Any]) -> str | None:
533
+ for attempt in (evidence.get("attempts") or {}).values():
534
+ trace = attempt.get("agent_trace") or {}
535
+ if trace.get("trace_id"):
536
+ return str(trace.get("trace_id"))
537
+ return None
538
+
539
+
369
540
  def _source_artifact_refs(evidence: dict[str, Any]) -> list[str]:
370
541
  refs: list[str] = []
371
542
  package_root = evidence.get("package_root")
@@ -379,6 +550,441 @@ def _source_artifact_refs(evidence: dict[str, Any]) -> list[str]:
379
550
  return list(dict.fromkeys(refs))
380
551
 
381
552
 
553
+ def _looks_numeric(value: Any) -> bool:
554
+ try:
555
+ if value in (None, ""):
556
+ return False
557
+ float(str(value))
558
+ return True
559
+ except Exception:
560
+ return False
561
+
562
+
563
+ def _json_type_name(value: Any) -> str:
564
+ if isinstance(value, bool):
565
+ return "boolean"
566
+ if isinstance(value, int):
567
+ return "integer"
568
+ if isinstance(value, float):
569
+ return "number"
570
+ if isinstance(value, str):
571
+ return "string"
572
+ if isinstance(value, list):
573
+ return "array"
574
+ if isinstance(value, dict):
575
+ return "object"
576
+ return type(value).__name__
577
+
578
+
579
+ def _artifact_contracts(evidence: dict[str, Any], limit: int = 12) -> list[dict[str, Any]]:
580
+ package_root = evidence.get("package_root")
581
+ if not isinstance(package_root, Path):
582
+ return []
583
+ contracts: list[dict[str, Any]] = []
584
+ seen: set[str] = set()
585
+ for ref in _source_artifact_refs(evidence):
586
+ if ref in seen or ref.startswith("input/") or "/input/" in ref:
587
+ continue
588
+ seen.add(ref)
589
+ path = package_root / ref
590
+ if not path.exists() or not path.is_file():
591
+ continue
592
+ suffix = path.suffix.lower()
593
+ contract: dict[str, Any] = {"artifact_ref": ref, "filename": path.name}
594
+ try:
595
+ if suffix == ".csv":
596
+ with path.open(newline="", errors="replace") as f:
597
+ reader = csv.DictReader(f)
598
+ header = list(reader.fieldnames or [])
599
+ rows = [row for _, row in zip(range(5), reader)]
600
+ numeric_columns: list[str] = []
601
+ non_empty_columns: list[str] = []
602
+ for column in header:
603
+ values = [row.get(column) for row in rows if row.get(column) not in (None, "")]
604
+ if values:
605
+ non_empty_columns.append(column)
606
+ if all(_looks_numeric(value) for value in values):
607
+ numeric_columns.append(column)
608
+ contract.update({
609
+ "format": "csv",
610
+ "columns": header,
611
+ "example_row_shape": len(header),
612
+ "non_empty_columns": non_empty_columns[:16],
613
+ "numeric_columns": numeric_columns[:16],
614
+ "min_rows": 1 if rows else 0,
615
+ })
616
+ elif suffix == ".json":
617
+ data = json.loads(path.read_text(errors="replace"))
618
+ if isinstance(data, dict):
619
+ expected_types = {
620
+ str(key): _json_type_name(value)
621
+ for key, value in data.items()
622
+ if value not in (None, "", [], {})
623
+ }
624
+ contract.update({
625
+ "format": "json",
626
+ "top_level_keys": list(data.keys())[:16],
627
+ "expected_types": expected_types,
628
+ "non_empty_fields": list(expected_types.keys())[:16],
629
+ })
630
+ elif isinstance(data, list):
631
+ item_keys: list[str] = []
632
+ expected_types: dict[str, str] = {}
633
+ if data and isinstance(data[0], dict):
634
+ item_keys = list(data[0].keys())[:16]
635
+ expected_types = {
636
+ str(key): _json_type_name(value)
637
+ for key, value in data[0].items()
638
+ if value not in (None, "", [], {})
639
+ }
640
+ contract.update({
641
+ "format": "json",
642
+ "top_level_type": "array",
643
+ "item_count": len(data),
644
+ "top_level_keys": item_keys,
645
+ "expected_types": expected_types,
646
+ "non_empty_fields": list(expected_types.keys())[:16],
647
+ "min_rows": 1 if data else 0,
648
+ })
649
+ else:
650
+ contract.update({"format": "json", "top_level_type": type(data).__name__})
651
+ elif suffix in {".txt", ".md", ".sh", ".bed", ".tsv", ".svg"}:
652
+ contract.update({"format": suffix.lstrip(".") or "text"})
653
+ else:
654
+ contract.update({"format": suffix.lstrip(".") or "file"})
655
+ except Exception as exc:
656
+ contract.update({"format": suffix.lstrip(".") or "file", "read_error": type(exc).__name__})
657
+ contracts.append(contract)
658
+ if len(contracts) >= limit:
659
+ break
660
+ return contracts
661
+
662
+
663
+ def _contract_line(contract: dict[str, Any]) -> str:
664
+ filename = contract.get("filename") or contract.get("artifact_ref")
665
+ if contract.get("columns"):
666
+ return f"`{filename}`: CSV columns {', '.join(map(str, contract.get('columns') or []))}."
667
+ if contract.get("top_level_keys"):
668
+ return f"`{filename}`: JSON keys {', '.join(map(str, contract.get('top_level_keys') or []))}."
669
+ return f"`{filename}`: {contract.get('format') or 'file'} artifact."
670
+
671
+
672
+ def _contract_output_path(contract: dict[str, Any]) -> str:
673
+ ref = str(contract.get("artifact_ref") or contract.get("filename") or "").replace("\\", "/")
674
+ name = str(contract.get("filename") or Path(ref).name or "output")
675
+ if "/artifacts/" in ref:
676
+ return "artifacts/" + ref.split("/artifacts/", 1)[1]
677
+ if "/output/" in ref:
678
+ return "output/" + ref.split("/output/", 1)[1]
679
+ return f"output/{name}"
680
+
681
+
682
+ def _output_contract_payload(evidence: dict[str, Any], artifact_contracts: list[dict[str, Any]]) -> dict[str, Any]:
683
+ outputs = []
684
+ for contract in artifact_contracts:
685
+ entry = {
686
+ "path": _contract_output_path(contract),
687
+ "format": contract.get("format") or "file",
688
+ "scoring_critical": True,
689
+ }
690
+ if contract.get("columns"):
691
+ entry["columns"] = contract.get("columns")
692
+ if contract.get("numeric_columns"):
693
+ entry["numeric_columns"] = contract.get("numeric_columns")
694
+ if contract.get("non_empty_columns"):
695
+ entry["non_empty_columns"] = contract.get("non_empty_columns")
696
+ if contract.get("top_level_keys"):
697
+ entry["keys"] = contract.get("top_level_keys")
698
+ if contract.get("expected_types"):
699
+ entry["expected_types"] = contract.get("expected_types")
700
+ if contract.get("non_empty_fields"):
701
+ entry["non_empty_fields"] = contract.get("non_empty_fields")
702
+ if contract.get("min_rows") is not None:
703
+ entry["min_rows"] = contract.get("min_rows")
704
+ outputs.append(entry)
705
+ if not any(item.get("path", "").endswith("actual_outputs.json") for item in outputs):
706
+ outputs.append({
707
+ "path": "output/actual_outputs.json",
708
+ "format": "json",
709
+ "keys": ["output_summary", "files_created"],
710
+ "scoring_critical": True,
711
+ })
712
+ return {
713
+ "source_package_id": evidence.get("package_id"),
714
+ "contract_kind": "runtime_transfer_output_contract",
715
+ "required_outputs": outputs,
716
+ "validation_commands": [
717
+ "open every required file",
718
+ "validate JSON parses",
719
+ "validate CSV/TSV headers",
720
+ "confirm actual_outputs.json lists generated files",
721
+ ],
722
+ "common_schema_mistakes": [
723
+ "writing files under the wrong relative directory",
724
+ "omitting required CSV columns or JSON keys",
725
+ "leaving actual_outputs.json empty or stale",
726
+ ],
727
+ }
728
+
729
+
730
+ def _artifact_contract_payload(evidence: dict[str, Any], artifact_contracts: list[dict[str, Any]]) -> dict[str, Any]:
731
+ return {
732
+ "source_package_id": evidence.get("package_id"),
733
+ "contract_kind": "runtime_transfer_artifact_contract",
734
+ "required_artifacts": [
735
+ {
736
+ "path": _contract_output_path(contract),
737
+ "artifact_type": contract.get("format") or "file",
738
+ "expected_location": str(Path(_contract_output_path(contract)).parent),
739
+ "name_pattern": Path(_contract_output_path(contract)).name,
740
+ "verify_exists": True,
741
+ **({"columns": contract.get("columns")} if contract.get("columns") else {}),
742
+ **({"keys": contract.get("top_level_keys")} if contract.get("top_level_keys") else {}),
743
+ **({"numeric_columns": contract.get("numeric_columns")} if contract.get("numeric_columns") else {}),
744
+ **({"non_empty_columns": contract.get("non_empty_columns")} if contract.get("non_empty_columns") else {}),
745
+ **({"expected_types": contract.get("expected_types")} if contract.get("expected_types") else {}),
746
+ **({"non_empty_fields": contract.get("non_empty_fields")} if contract.get("non_empty_fields") else {}),
747
+ **({"min_rows": contract.get("min_rows")} if contract.get("min_rows") is not None else {}),
748
+ }
749
+ for contract in artifact_contracts
750
+ ],
751
+ }
752
+
753
+
754
+ def _prefinal_checklist_payload(evidence: dict[str, Any], output_contract: dict[str, Any], artifact_contract: dict[str, Any]) -> dict[str, Any]:
755
+ checks = [
756
+ {
757
+ "check_id": "required_files_exist",
758
+ "description": "Every required output/artifact path exists in the task output location.",
759
+ "verification_method": "filesystem_exists",
760
+ "blocking": True,
761
+ "repair_hint": "Create or move missing files to the required relative path.",
762
+ },
763
+ {
764
+ "check_id": "json_outputs_parse",
765
+ "description": "JSON outputs, including actual_outputs.json, parse as JSON objects or declared arrays.",
766
+ "verification_method": "json_parse",
767
+ "blocking": True,
768
+ "repair_hint": "Rewrite malformed JSON and rerun the parse check.",
769
+ },
770
+ {
771
+ "check_id": "csv_tsv_headers_match",
772
+ "description": "CSV/TSV outputs include required scoring-critical columns.",
773
+ "verification_method": "delimited_header_check",
774
+ "blocking": True,
775
+ "repair_hint": "Rewrite files with the required header names exactly.",
776
+ },
777
+ {
778
+ "check_id": "actual_outputs_lists_files",
779
+ "description": "actual_outputs.json summarizes and lists generated deliverables.",
780
+ "verification_method": "actual_outputs_contract_check",
781
+ "blocking": True,
782
+ "repair_hint": "Update actual_outputs.json after producing outputs.",
783
+ },
784
+ ]
785
+ return {
786
+ "source_package_id": evidence.get("package_id"),
787
+ "checks": checks,
788
+ "output_contract_ref": "runtime_learning_package/output_contract.json",
789
+ "artifact_contract_ref": "runtime_learning_package/artifact_contract.json",
790
+ "required_output_count": len(output_contract.get("required_outputs") or []),
791
+ "required_artifact_count": len(artifact_contract.get("required_artifacts") or []),
792
+ }
793
+
794
+
795
+ def _repair_instructions_text(title: str, output_contract: dict[str, Any]) -> str:
796
+ required = output_contract.get("required_outputs") or []
797
+ lines = [
798
+ f"# Contract Repair Instructions: {title}",
799
+ "",
800
+ "Use these instructions only after a pre-final contract check fails.",
801
+ "",
802
+ "1. Read each contract failure and map it to a required output path.",
803
+ "2. Regenerate missing files from current visible inputs; do not copy source-task answers.",
804
+ "3. For malformed CSV/TSV files, rewrite the header exactly and preserve derived rows.",
805
+ "4. For malformed JSON, rewrite valid JSON with required keys.",
806
+ "5. Reopen each repaired file and rerun the contract verifier.",
807
+ "",
808
+ "## Required Output Paths",
809
+ ]
810
+ lines.extend(f"- `{item.get('path')}` ({item.get('format') or 'file'})" for item in required[:20])
811
+ return "\n".join(lines) + "\n"
812
+
813
+
814
+ def _runtime_transfer_runbook(
815
+ evidence: dict[str, Any],
816
+ title: str,
817
+ instruction: str,
818
+ criteria: list[str],
819
+ artifact_contracts: list[dict[str, Any]],
820
+ ) -> str:
821
+ contract_lines = [_contract_line(contract) for contract in artifact_contracts[:10]]
822
+ if not contract_lines:
823
+ contract_lines = ["Create every deliverable named by the current task and register it in `actual_outputs.json`."]
824
+ verifier_lines = criteria[:8] or ["Every required artifact exists.", "`actual_outputs.json` names the generated deliverables."]
825
+ return "\n".join(
826
+ [
827
+ f"# Transfer Runbook: {title}",
828
+ "",
829
+ "Use this as a compact execution checklist for a related task. Current-task instructions and inputs remain authoritative.",
830
+ "",
831
+ "## Execution Steps",
832
+ "1. Read the current task directory, visible input files, and required output names before writing code.",
833
+ "2. Build a small script or commands that derive outputs from current input files; do not copy source-task values.",
834
+ "3. Write outputs under `output/` unless the current task names a different relative location.",
835
+ "4. Match artifact filenames, CSV headers, JSON keys, and text-file roles exactly.",
836
+ "5. Write `output/actual_outputs.json` or `actual_outputs.json` with the produced file list and concise summary.",
837
+ "6. Run a local sanity check by reopening each file and verifying row/key counts and required columns.",
838
+ "",
839
+ "## Artifact Contract Pattern",
840
+ *(f"- {line}" for line in contract_lines),
841
+ "",
842
+ "## Verification Checklist",
843
+ *(f"- {line}" for line in verifier_lines),
844
+ "",
845
+ "## Transfer Cues",
846
+ f"- Source workflow pattern: {_clean_text(instruction, 500)}",
847
+ "- Transfer the procedure, output contract discipline, and checks; do not transfer source-task answers.",
848
+ "- If a formula or threshold is task-specific, infer it from the current task inputs or visible instructions.",
849
+ "",
850
+ ]
851
+ )
852
+
853
+
854
+ def _loop_learning_rows(evidence: dict[str, Any], report: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
855
+ package_root = evidence.get("package_root")
856
+ source_package_id = report.get("source_package_id") or evidence.get("package_id")
857
+ out: dict[str, list[dict[str, Any]]] = {
858
+ "loop_review_rows": [],
859
+ "feedback_revision_pairs": [],
860
+ "contract_verification_rows": [],
861
+ "repair_loop_rows": [],
862
+ "reviewer_decision_rows": [],
863
+ }
864
+ if not isinstance(package_root, Path):
865
+ return out
866
+ for item in evidence.get("loop_review_packets") or []:
867
+ packet = item.get("packet") or {}
868
+ ref = item.get("ref")
869
+ if not ref:
870
+ continue
871
+ iteration_dir = package_root / Path(ref).parent
872
+ loop_iteration = packet.get("loop_iteration")
873
+ reviewer_feedback = _safe_read(iteration_dir / "reviewer_feedback.json") or {}
874
+ revision_plan = _safe_read(iteration_dir / "revision_plan.json") or {}
875
+ loop_decision = _safe_read(iteration_dir / "loop_decision.json") or {}
876
+ contract_verification = _safe_read(iteration_dir / "contract_verification.json") or {}
877
+ contract_repair_plan = _safe_read(iteration_dir / "contract_repair_plan.json") or {}
878
+ contract_repair_result = _safe_read(iteration_dir / "contract_repair_result.json") or {}
879
+ diff_report = _safe_read(iteration_dir / "diff_report.json") or {}
880
+
881
+ base = {
882
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
883
+ "source_package_id": source_package_id,
884
+ "source_task_id": report.get("source_task_id"),
885
+ "task_lineage": _task_lineage(evidence),
886
+ "loop_id": packet.get("loop_id"),
887
+ "loop_iteration": loop_iteration,
888
+ "evidence_refs": [ref],
889
+ "source_evidence_ref_ids": _evidence_ids_for_refs([ref]),
890
+ "confidence": report.get("average_quality_score") or 0.5,
891
+ "created_at": utc_now(),
892
+ "generated_by": "Experience Compiler",
893
+ }
894
+ known_failures = [str(v) for v in (packet.get("known_failures") or []) if v]
895
+ missing_outputs = [str(v) for v in (packet.get("missing_outputs") or []) if v]
896
+ out["loop_review_rows"].append(
897
+ sanitize_public_obj(
898
+ {
899
+ **base,
900
+ "row_id": f"loop_review_{_safe_id(str(packet.get('loop_id') or source_package_id))}_{loop_iteration or 0}",
901
+ "data_type": "loop_review_row",
902
+ "mode": packet.get("mode"),
903
+ "current_attempt_summary": _clean_text(packet.get("current_attempt_summary")),
904
+ "rubric_status": packet.get("rubric_status"),
905
+ "verifier_status": packet.get("verifier_status"),
906
+ "known_failures": known_failures,
907
+ "missing_outputs": missing_outputs,
908
+ "recommended_review_focus": packet.get("recommended_review_focus") or [],
909
+ "review_packet_ref": ref,
910
+ "review_packet_md_ref": str(Path(ref).with_suffix(".md")),
911
+ }
912
+ )
913
+ )
914
+ if reviewer_feedback or revision_plan:
915
+ out["feedback_revision_pairs"].append(
916
+ sanitize_public_obj(
917
+ {
918
+ **base,
919
+ "pair_id": f"feedback_revision_{_safe_id(str(packet.get('loop_id') or source_package_id))}_{loop_iteration or 0}",
920
+ "row_id": f"feedback_revision_{_safe_id(str(packet.get('loop_id') or source_package_id))}_{loop_iteration or 0}",
921
+ "data_type": "feedback_revision_pair",
922
+ "reviewer_type": reviewer_feedback.get("reviewer_type") or "unknown",
923
+ "feedback": _clean_text(reviewer_feedback.get("feedback") or reviewer_feedback.get("feedback_text") or reviewer_feedback.get("summary")),
924
+ "blocking_issues": reviewer_feedback.get("blocking_issues") or known_failures,
925
+ "revision_instructions": reviewer_feedback.get("revision_instructions") or revision_plan.get("issues_to_fix") or [],
926
+ "revision_plan_ref": f"{Path(ref).parent.as_posix()}/revision_plan.json",
927
+ "diff_report_ref": f"{Path(ref).parent.as_posix()}/diff_report.json",
928
+ "feedback_applied_status": revision_plan.get("feedback_applied") or "unknown",
929
+ }
930
+ )
931
+ )
932
+ if loop_decision:
933
+ out["reviewer_decision_rows"].append(
934
+ sanitize_public_obj(
935
+ {
936
+ **base,
937
+ "row_id": f"reviewer_decision_{_safe_id(str(packet.get('loop_id') or source_package_id))}_{loop_iteration or 0}",
938
+ "data_type": "reviewer_decision_row",
939
+ "decision": loop_decision.get("decision"),
940
+ "verdict": loop_decision.get("verdict"),
941
+ "score": loop_decision.get("score"),
942
+ "continue_loop": loop_decision.get("continue_loop"),
943
+ "stop_condition": loop_decision.get("stop_condition"),
944
+ "termination_reason": loop_decision.get("termination_reason"),
945
+ "training_value_notes": loop_decision.get("training_value_notes"),
946
+ "loop_decision_ref": f"{Path(ref).parent.as_posix()}/loop_decision.json",
947
+ }
948
+ )
949
+ )
950
+ if contract_verification:
951
+ failures = contract_verification.get("failures") or []
952
+ out["contract_verification_rows"].append(
953
+ sanitize_public_obj(
954
+ {
955
+ **base,
956
+ "row_id": f"contract_verification_{_safe_id(str(packet.get('loop_id') or source_package_id))}_{loop_iteration or 0}",
957
+ "data_type": "contract_verification_row",
958
+ "contract_verification_passed": contract_verification.get("contract_verification_passed"),
959
+ "actual_outputs_json_ok": contract_verification.get("actual_outputs_json_ok"),
960
+ "failure_count": contract_verification.get("failure_count", len(failures)),
961
+ "semantic_warning_count": contract_verification.get("semantic_warning_count", 0),
962
+ "failures": failures[:20],
963
+ "semantic_warnings": (contract_verification.get("semantic_warnings") or [])[:20],
964
+ "contract_verification_ref": f"{Path(ref).parent.as_posix()}/contract_verification.json",
965
+ }
966
+ )
967
+ )
968
+ if contract_repair_plan or contract_repair_result:
969
+ out["repair_loop_rows"].append(
970
+ sanitize_public_obj(
971
+ {
972
+ **base,
973
+ "row_id": f"repair_loop_{_safe_id(str(packet.get('loop_id') or source_package_id))}_{loop_iteration or 0}",
974
+ "data_type": "repair_loop_row",
975
+ "repair_required": contract_repair_plan.get("repair_required"),
976
+ "preserve_passing_items": contract_repair_plan.get("preserve_passing_items"),
977
+ "issues_to_fix": contract_repair_plan.get("issues_to_fix") or [],
978
+ "repair_result": contract_repair_result,
979
+ "changed_refs": (diff_report.get("added_refs") or []) + (diff_report.get("removed_refs") or []),
980
+ "contract_repair_plan_ref": f"{Path(ref).parent.as_posix()}/contract_repair_plan.json",
981
+ "contract_repair_result_ref": f"{Path(ref).parent.as_posix()}/contract_repair_result.json",
982
+ }
983
+ )
984
+ )
985
+ return out
986
+
987
+
382
988
  def _evidence_refs(evidence: dict[str, Any]) -> list[str]:
383
989
  refs = ["task/task_intake_spec.json", "rubric/rubric.json"]
384
990
  refs.extend(_source_artifact_refs(evidence))
@@ -492,21 +1098,38 @@ def _source_audit_status(evidence: dict[str, Any]) -> dict[str, Any]:
492
1098
 
493
1099
 
494
1100
  def _base_row(evidence: dict[str, Any], settings: Settings, round_idx: int, judge_source: str, mentor_audited: bool, proxy: dict[str, Any], quality: float) -> dict[str, Any]:
1101
+ lineage = _task_lineage(evidence)
1102
+ evidence_refs = _evidence_refs(evidence)
1103
+ source_artifact_refs = _source_artifact_refs(evidence)
495
1104
  row = {
1105
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
496
1106
  "source_package_id": evidence["package_id"],
497
1107
  "source_task_id": _source_task_id(evidence),
498
- "source_trace_id": None,
1108
+ "task_lineage": lineage,
1109
+ "domain": lineage["domain"],
1110
+ "subdomain": lineage["subdomain"],
1111
+ "workflow_family": lineage["workflow_family"],
1112
+ "source_trace_id": _primary_trace_id(evidence),
499
1113
  "source_step_ids": [],
500
1114
  "source_attempt_ids": _attempt_ids(evidence),
501
- "source_artifact_refs": _source_artifact_refs(evidence),
502
- "source_evaluation_refs": [ref for ref in _evidence_refs(evidence) if ref.startswith(("grading/", "feedback/", "signals/"))],
503
- "evidence_refs": _evidence_refs(evidence),
1115
+ "source_artifact_refs": source_artifact_refs,
1116
+ "source_evaluation_refs": [ref for ref in evidence_refs if ref.startswith(("grading/", "feedback/", "signals/"))],
1117
+ "evidence_refs": evidence_refs,
1118
+ "source_evidence_ref_ids": _evidence_ids_for_refs(evidence_refs),
1119
+ "evidence_visibility": "visible_or_public_package_evidence",
1120
+ "visible_input_refs": [ref for ref in source_artifact_refs if ref.startswith("input/") or "/input/" in ref],
1121
+ "source_content_hashes": {
1122
+ ref: _content_hash_for_ref(evidence["package_root"], ref)
1123
+ for ref in evidence_refs[:24]
1124
+ if isinstance(evidence.get("package_root"), Path) and _content_hash_for_ref(evidence["package_root"], ref)
1125
+ },
504
1126
  "provenance": "derived_from_observed_agent_apprenticeship_package",
505
1127
  "quality_score": quality,
506
1128
  "fidelity_score": min(1.0, quality + 0.04),
507
1129
  "difficulty_score": min(1.0, max(0.35, quality - 0.08)),
508
1130
  "learnability_score": min(1.0, quality + 0.02),
509
1131
  "reuse_value_score": min(1.0, quality + 0.03),
1132
+ "confidence": min(1.0, quality + 0.02),
510
1133
  "weak_score_proxy": proxy.get("weak_score_proxy"),
511
1134
  "strong_score_proxy": proxy.get("strong_score_proxy"),
512
1135
  "solver_gap_proxy": proxy.get("solver_gap_proxy"),
@@ -545,6 +1168,74 @@ def _has_concrete_source_evidence(row: dict[str, Any]) -> bool:
545
1168
  return False
546
1169
 
547
1170
 
1171
+ def _finalize_candidate_rows(rows: list[dict[str, Any]], evidence: dict[str, Any]) -> list[dict[str, Any]]:
1172
+ finalized: list[dict[str, Any]] = []
1173
+ for raw in rows:
1174
+ row = dict(raw)
1175
+ data_type = str(row.get("data_type") or "unknown")
1176
+ row_id = _row_identity(data_type, row, evidence)
1177
+ id_field = ROW_ID_FIELD_BY_TYPE.get(data_type, "row_id")
1178
+ row.setdefault(id_field, row_id)
1179
+ row.setdefault("row_id", row_id)
1180
+ row.setdefault("schema_version", TRAINING_ROW_SCHEMA_VERSION)
1181
+ row.setdefault("training_use_cases", TRAINING_USE_BY_TYPE.get(data_type, ["experience_compilation_training"]))
1182
+ row.setdefault("quality_signal_source", row.get("tdo_judge_source") or "experience_compiler_guardrail")
1183
+ row.setdefault("source_trace_ref", row.get("source_trace_id"))
1184
+ row.setdefault("source_evidence_ref_ids", _evidence_ids_for_refs(row.get("evidence_refs") or []))
1185
+ row.setdefault("evidence_confidence", row.get("confidence") or row.get("quality_score") or 0.5)
1186
+ row.setdefault("row_content_hash", _hash_obj({k: v for k, v in row.items() if k not in {"row_content_hash", "created_at"}}))
1187
+ row.setdefault(
1188
+ "model_training_targets",
1189
+ {
1190
+ "sft_ready": data_type == "training_example",
1191
+ "lora_ready": data_type == "training_example",
1192
+ "reward_ready": data_type == "reward_model_row",
1193
+ "verifier_ready": data_type in {"verifier_example", "evaluation_example"},
1194
+ "preference_ready": data_type == "preference_pair",
1195
+ "process_supervision_ready": data_type in {"environment_response_pair", *SURFACE_FILES.keys()},
1196
+ "rl_rollout_ready": data_type in {"environment_response_pair", "software_execution_event", "api_interaction_event"},
1197
+ },
1198
+ )
1199
+ if data_type == "training_example":
1200
+ row.setdefault("instruction", (row.get("input") or {}).get("instruction") if isinstance(row.get("input"), dict) else row.get("input"))
1201
+ row.setdefault("target_output", row.get("expected_output") or row.get("target"))
1202
+ row.setdefault("output_contract_summary", "Use actual_outputs.json plus artifact refs as the output contract.")
1203
+ elif data_type == "reward_model_row":
1204
+ target = row.get("target") if isinstance(row.get("target"), dict) else {}
1205
+ row.setdefault("reward_component", "task_outcome")
1206
+ row.setdefault("reward_value", target.get("final_score") if isinstance(target, dict) else None)
1207
+ row.setdefault("reward_timing", "final")
1208
+ row.setdefault("verifier_or_grader_source", "grader_result")
1209
+ elif data_type == "verifier_example":
1210
+ row.setdefault("claim_or_output", row.get("input"))
1211
+ row.setdefault("visible_evidence", row.get("source_artifact_refs") or row.get("evidence_refs"))
1212
+ row.setdefault("verifier_decision", row.get("target"))
1213
+ elif data_type == "preference_pair":
1214
+ target = row.get("target") if isinstance(row.get("target"), dict) else {}
1215
+ row.setdefault("chosen_output", target.get("chosen"))
1216
+ row.setdefault("rejected_output", "baseline" if target.get("chosen") == "revised" else "revised")
1217
+ row.setdefault("preference_reason", row.get("scoring_hint") or "Higher grounded score/verifier evidence.")
1218
+ row.setdefault("quality_delta", target.get("solver_gap_proxy"))
1219
+ elif data_type == "failure_case":
1220
+ target = row.get("target") if isinstance(row.get("target"), dict) else {}
1221
+ row.setdefault("failure_type", target.get("failure_mode") or "contract_or_score_failure")
1222
+ row.setdefault("failure_severity", "medium")
1223
+ row.setdefault("repair_recommendation", target.get("repair_hint") or row.get("scoring_hint"))
1224
+ elif data_type == "task_variant":
1225
+ target = row.get("target") if isinstance(row.get("target"), dict) else {}
1226
+ row.setdefault("variant_instruction", target.get("variant_instruction"))
1227
+ row.setdefault("expected_output_contract", target.get("expected_deliverables"))
1228
+ row.setdefault("difficulty", row.get("difficulty_score"))
1229
+ elif data_type == "environment_response_pair":
1230
+ row.setdefault("rollout_group_id", f"rollout_{_safe_id(str(row.get('source_task_id')))}")
1231
+ row.setdefault("candidate_rollout_id", f"rollout_{row_id}")
1232
+ row.setdefault("scalar_reward", row.get("format_score") or row.get("quality_score"))
1233
+ row.setdefault("reward_components", {"format": row.get("format_score"), "consistency": row.get("consistency_score")})
1234
+ row.setdefault("termination_reason", row.get("success_signal"))
1235
+ finalized.append(sanitize_public_obj(row))
1236
+ return finalized
1237
+
1238
+
548
1239
  def _build_candidate_rows(evidence: dict[str, Any], settings: Settings, round_idx: int, judge_source: str, mentor_audited: bool, proxy: dict[str, Any]) -> list[dict[str, Any]]:
549
1240
  task = evidence.get("task") or {}
550
1241
  raw = evidence.get("raw_task") or {}
@@ -634,7 +1325,7 @@ def _build_candidate_rows(evidence: dict[str, Any], settings: Settings, round_id
634
1325
  })
635
1326
  rows.extend(_environment_response_rows(evidence, common))
636
1327
  rows.extend(_surface_event_rows(evidence, common))
637
- return [sanitize_public_obj(row) for row in rows]
1328
+ return _finalize_candidate_rows(rows, evidence)
638
1329
 
639
1330
 
640
1331
  def _best_output_summary(evidence: dict[str, Any]) -> str:
@@ -1019,6 +1710,7 @@ def _validate_rows(package_root: Path, rows: list[dict[str, Any]], min_quality:
1019
1710
  accepted: list[dict[str, Any]] = []
1020
1711
  rejected: list[dict[str, Any]] = []
1021
1712
  schema_errors: list[str] = []
1713
+ seen_row_ids: set[str] = set()
1022
1714
  for row in rows:
1023
1715
  reason = _private_or_forbidden_hit(row)
1024
1716
  missing = _artifact_refs_resolve(package_root, row)
@@ -1026,6 +1718,24 @@ def _validate_rows(package_root: Path, rows: list[dict[str, Any]], min_quality:
1026
1718
  reason = reason or f"unresolved_artifact_ref:{missing[0]}"
1027
1719
  if not row.get("data_type"):
1028
1720
  reason = reason or "missing_data_type"
1721
+ row_id = row.get("row_id") or row.get("pair_id")
1722
+ if not row_id:
1723
+ reason = reason or "missing_stable_row_id"
1724
+ elif str(row_id) in seen_row_ids:
1725
+ reason = reason or f"duplicate_row_id:{row_id}"
1726
+ else:
1727
+ seen_row_ids.add(str(row_id))
1728
+ if not isinstance(row.get("task_lineage"), dict) or not row.get("source_task_id"):
1729
+ reason = reason or "missing_task_lineage"
1730
+ if not row.get("domain") or not row.get("workflow_family"):
1731
+ reason = reason or "missing_domain_or_workflow_family"
1732
+ if not row.get("source_evidence_ref_ids"):
1733
+ reason = reason or "missing_source_evidence_ref_ids"
1734
+ if row.get("data_type") in {"training_example", "evaluation_example", "verifier_example", "reward_model_row"}:
1735
+ if row.get("input") in (None, "", {}, []):
1736
+ reason = reason or "missing_core_input"
1737
+ if row.get("target") in (None, "", {}, []):
1738
+ reason = reason or "missing_core_target"
1029
1739
  if not _has_concrete_source_evidence(row):
1030
1740
  reason = reason or "insufficient_concrete_source_evidence"
1031
1741
  if _score(row.get("quality_score")) < min_quality:
@@ -1231,11 +1941,30 @@ def _environment_spec(evidence: dict[str, Any]) -> dict[str, Any]:
1231
1941
  "input_artifacts": [ref for ref in _source_artifact_refs(evidence) if ref.startswith("input/") or "/input/" in ref],
1232
1942
  "initial_state_refs": ["task/task_intake_spec.json", "rubric/rubric.json"],
1233
1943
  "allowed_actions": sorted(set(operations)),
1944
+ "action_schema": {
1945
+ "action": "operation/tool invocation or artifact-producing step",
1946
+ "tool": "optional tool name",
1947
+ "input": "visible input or command payload",
1948
+ "artifact_refs": "package-relative refs produced or inspected",
1949
+ },
1950
+ "observation_schema": {
1951
+ "observation": "tool/environment response summary",
1952
+ "state_change": "state delta after action",
1953
+ "artifact_refs": "package-relative output refs",
1954
+ "success_signal": "step outcome or verifier signal",
1955
+ },
1234
1956
  "reset_strategy": "copy_workspace" if environment_type in {"local_filesystem", "codebase", "terminal"} else "unknown",
1235
1957
  "verification_commands": [],
1236
1958
  "scoring_refs": [ref for ref in _evidence_refs(evidence) if ref.startswith("grading/")],
1237
1959
  "expected_output_refs": [ref for ref in _source_artifact_refs(evidence) if "/artifacts/" in ref],
1960
+ "termination_conditions": ["task_complete", "contract_verified", "max_iterations_reached"],
1961
+ "reward_function_components": ["artifact_contract", "verifier_signal", "step_success", "grader_score_if_available"],
1238
1962
  "replay_smoke_test": {"available": False, "command": None, "success_condition": None},
1963
+ "reproducibility_metadata": {
1964
+ "source_trace_count": len(evidence.get("attempts") or {}),
1965
+ "package_id": evidence.get("package_id"),
1966
+ "created_from_observed_trace": True,
1967
+ },
1239
1968
  "multi_agent": {
1240
1969
  "present": multi_agent_present,
1241
1970
  "agents": values_from("agent_id", "agent_name", "subagent_id", "subagent_name"),
@@ -1387,7 +2116,231 @@ def _copy_compiler_json_public(src: Path, dst: Path) -> None:
1387
2116
  if not src.exists():
1388
2117
  write_json(dst, {})
1389
2118
  return
1390
- write_json(dst, _compiler_public_obj(read_json(src)))
2119
+ write_json(dst, _compiler_public_obj(read_json(src)))
2120
+
2121
+
2122
+ def _compiler_rows_from_tdo_dir(tdo_dir: Path) -> list[dict[str, Any]]:
2123
+ rows: list[dict[str, Any]] = []
2124
+ for filename in sorted(set(ROW_FILE_BY_TYPE.values())):
2125
+ path = tdo_dir / filename
2126
+ if not path.exists():
2127
+ continue
2128
+ for row in _safe_jsonl(path):
2129
+ if isinstance(row, dict):
2130
+ clean = dict(row)
2131
+ clean["_source_row_file"] = filename
2132
+ rows.append(clean)
2133
+ return rows
2134
+
2135
+
2136
+ def _training_row_counts_by_file(tdo_dir: Path) -> dict[str, int]:
2137
+ counts: dict[str, int] = {}
2138
+ for filename in sorted(set(ROW_FILE_BY_TYPE.values())):
2139
+ path = tdo_dir / filename
2140
+ counts[filename] = len(_safe_jsonl(path)) if path.exists() else 0
2141
+ return counts
2142
+
2143
+
2144
+ def _source_evidence_map_payload(
2145
+ evidence: dict[str, Any],
2146
+ report: dict[str, Any],
2147
+ compiler_rows: list[dict[str, Any]],
2148
+ ) -> dict[str, Any]:
2149
+ catalog = _evidence_catalog(evidence)
2150
+ evidence_by_id = {item["evidence_id"]: item for item in catalog}
2151
+ row_links: list[dict[str, Any]] = []
2152
+ orphan_ids: set[str] = set()
2153
+ for row in compiler_rows:
2154
+ row_id = str(row.get("row_id") or row.get("pair_id") or _row_identity(str(row.get("data_type") or "unknown"), row, evidence))
2155
+ ids = [str(v) for v in row.get("source_evidence_ref_ids") or _evidence_ids_for_refs(row.get("evidence_refs") or [])]
2156
+ for evidence_id in ids:
2157
+ if evidence_id not in evidence_by_id:
2158
+ orphan_ids.add(evidence_id)
2159
+ row_links.append(
2160
+ {
2161
+ "row_id": row_id,
2162
+ "row_file": row.get("_source_row_file"),
2163
+ "data_type": row.get("data_type"),
2164
+ "source_task_id": row.get("source_task_id") or report.get("source_task_id"),
2165
+ "source_trace_id": row.get("source_trace_id"),
2166
+ "source_step_ids": row.get("source_step_ids") or [],
2167
+ "source_artifact_refs": row.get("source_artifact_refs") or [],
2168
+ "evidence_ref_ids": ids,
2169
+ "visible_evidence_ref_ids": [
2170
+ evidence_id
2171
+ for evidence_id in ids
2172
+ if (evidence_by_id.get(evidence_id) or {}).get("visibility") == "visible"
2173
+ ],
2174
+ "inferred_evidence_ref_ids": [
2175
+ evidence_id
2176
+ for evidence_id in ids
2177
+ if (evidence_by_id.get(evidence_id) or {}).get("visibility") == "inferred"
2178
+ ],
2179
+ }
2180
+ )
2181
+ resolved = sum(1 for item in catalog if item.get("resolves"))
2182
+ return sanitize_public_obj(
2183
+ {
2184
+ "schema_version": EVIDENCE_MAP_SCHEMA_VERSION,
2185
+ "source_package_id": report.get("source_package_id"),
2186
+ "source_task_id": report.get("source_task_id"),
2187
+ "evidence_catalog": catalog,
2188
+ "row_evidence_links": row_links,
2189
+ "task_refs": [
2190
+ ref
2191
+ for ref in [
2192
+ "task/task_packet.json",
2193
+ "task/task_intake_spec.json",
2194
+ "task/raw_task_record.json",
2195
+ "source_task/task_packet.json",
2196
+ ]
2197
+ if isinstance(evidence.get("package_root"), Path) and (evidence["package_root"] / ref).exists()
2198
+ ]
2199
+ or ["task/task_packet.json"],
2200
+ "artifact_refs": _source_artifact_refs(evidence),
2201
+ "evaluation_refs": [ref for ref in _evidence_refs(evidence) if ref.startswith(("grading/", "feedback/", "signals/", "loops/"))],
2202
+ "loop_review_refs": evidence.get("loop_review_refs") or [],
2203
+ "trace_refs": [
2204
+ str((attempt.get("agent_trace") or {}).get("trace_id"))
2205
+ for attempt in (evidence.get("attempts") or {}).values()
2206
+ if (attempt.get("agent_trace") or {}).get("trace_id")
2207
+ ],
2208
+ "coverage_statistics": {
2209
+ "evidence_ref_count": len(catalog),
2210
+ "resolved_evidence_ref_count": resolved,
2211
+ "unresolved_evidence_ref_count": len(catalog) - resolved,
2212
+ "row_link_count": len(row_links),
2213
+ "row_count_with_evidence_ids": sum(1 for row in row_links if row.get("evidence_ref_ids")),
2214
+ "orphan_evidence_id_count": len(orphan_ids),
2215
+ },
2216
+ "orphan_evidence_ref_ids": sorted(orphan_ids),
2217
+ }
2218
+ )
2219
+
2220
+
2221
+ def _process_supervision_rows_from_compiler_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
2222
+ out: list[dict[str, Any]] = []
2223
+ process_types = {"environment_response_pair", *SURFACE_FILES.keys()}
2224
+ for row in rows:
2225
+ if row.get("data_type") not in process_types:
2226
+ continue
2227
+ out.append(
2228
+ sanitize_public_obj(
2229
+ {
2230
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
2231
+ "row_id": f"ps_{row.get('row_id')}",
2232
+ "data_type": "process_supervision",
2233
+ "source_package_id": row.get("source_package_id"),
2234
+ "source_task_id": row.get("source_task_id"),
2235
+ "task_lineage": row.get("task_lineage"),
2236
+ "domain": row.get("domain"),
2237
+ "subdomain": row.get("subdomain"),
2238
+ "workflow_family": row.get("workflow_family"),
2239
+ "source_trace_id": row.get("source_trace_id"),
2240
+ "source_step_ids": row.get("source_step_ids") or [],
2241
+ "step_goal": row.get("agent_action") or row.get("command") or row.get("query") or row.get("input") or row.get("state_change") or row.get("interaction_surface"),
2242
+ "action_taken": row.get("agent_action") or row.get("action_type") or row.get("command") or row.get("query") or row.get("state_change") or row.get("interaction_surface"),
2243
+ "observation": row.get("observed_environment_response") or row.get("observed_result") or row.get("output") or row.get("state_change") or row.get("success_signal"),
2244
+ "tool_action_metadata": {
2245
+ "environment_domain": row.get("environment_domain"),
2246
+ "interaction_surface": row.get("interaction_surface"),
2247
+ "tool_or_surface": row.get("search_provider") or row.get("api_endpoint") or row.get("table_name") or row.get("service_name"),
2248
+ },
2249
+ "correctness_label": "positive" if row.get("success_signal") in {"completed", "True", True} else "unknown",
2250
+ "quality_signal": row.get("training_quality_score") or row.get("quality_score"),
2251
+ "next_step_recommendation": "Continue from the observed state and verify artifact/output contract before final answer.",
2252
+ "source_evidence_ref_ids": row.get("source_evidence_ref_ids") or [],
2253
+ "evidence_refs": row.get("evidence_refs") or [],
2254
+ "confidence": row.get("confidence") or row.get("quality_score") or 0.5,
2255
+ "created_at": row.get("created_at") or utc_now(),
2256
+ }
2257
+ )
2258
+ )
2259
+ return out
2260
+
2261
+
2262
+ def _rl_rollout_rows_from_compiler_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
2263
+ out: list[dict[str, Any]] = []
2264
+ for row in rows:
2265
+ if row.get("data_type") != "environment_response_pair":
2266
+ continue
2267
+ row_id = str(row.get("row_id") or _hash_obj(row))
2268
+ out.append(
2269
+ sanitize_public_obj(
2270
+ {
2271
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
2272
+ "row_id": f"rl_{row_id}",
2273
+ "data_type": "agentic_rl_rollout",
2274
+ "source_package_id": row.get("source_package_id"),
2275
+ "source_task_id": row.get("source_task_id"),
2276
+ "task_lineage": row.get("task_lineage"),
2277
+ "domain": row.get("domain"),
2278
+ "subdomain": row.get("subdomain"),
2279
+ "workflow_family": row.get("workflow_family"),
2280
+ "rollout_group_id": row.get("rollout_group_id") or f"rollout_{_safe_id(str(row.get('source_task_id')))}",
2281
+ "candidate_rollout_id": row.get("candidate_rollout_id") or f"rollout_{row_id}",
2282
+ "prompt_or_instruction": row.get("input") or row.get("agent_action"),
2283
+ "initial_environment_state": row.get("state_before"),
2284
+ "visible_input_refs": row.get("visible_input_refs") or [],
2285
+ "tool_action_schema": {
2286
+ "environment_domain": row.get("environment_domain"),
2287
+ "interaction_surface": row.get("interaction_surface"),
2288
+ },
2289
+ "step_action_trace": {
2290
+ "source_trace_id": row.get("source_trace_id"),
2291
+ "source_step_ids": row.get("source_step_ids") or [],
2292
+ "action": row.get("agent_action"),
2293
+ "observation": row.get("observed_environment_response"),
2294
+ },
2295
+ "artifacts_produced": row.get("source_artifact_refs") or [],
2296
+ "final_output": row.get("state_after") or row.get("success_signal"),
2297
+ "verifier_decision": row.get("verifier_feedback"),
2298
+ "scalar_reward": row.get("scalar_reward") or row.get("training_quality_score") or row.get("quality_score"),
2299
+ "reward_components": row.get("reward_components") or {},
2300
+ "reward_timing": "step",
2301
+ "success_label": row.get("success_signal"),
2302
+ "termination_reason": row.get("termination_reason") or row.get("success_signal"),
2303
+ "source_evidence_ref_ids": row.get("source_evidence_ref_ids") or [],
2304
+ "evidence_refs": row.get("evidence_refs") or [],
2305
+ "replay_scoring_smoke_test_refs": row.get("source_evaluation_refs") or [],
2306
+ "confidence": row.get("confidence") or row.get("quality_score") or 0.5,
2307
+ "created_at": row.get("created_at") or utc_now(),
2308
+ }
2309
+ )
2310
+ )
2311
+ return out
2312
+
2313
+
2314
+ def _bioactive_schema_examples(report: dict[str, Any], evidence: dict[str, Any]) -> list[dict[str, Any]]:
2315
+ base = {
2316
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
2317
+ "source_package_id": report.get("source_package_id"),
2318
+ "source_task_id": report.get("source_task_id"),
2319
+ "task_lineage": _task_lineage(evidence),
2320
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:5]),
2321
+ "evidence_refs": _evidence_refs(evidence)[:5],
2322
+ "confidence": 0.5,
2323
+ "personal_context_policy": "abstract_constraints_only_no_sensitive_identity",
2324
+ "medical_advice_policy": "evidence_mapping_only_no_treatment_or_cure_claims",
2325
+ "created_at": utc_now(),
2326
+ }
2327
+ return [
2328
+ {
2329
+ **base,
2330
+ "row_id": f"bioactive_schema_{_safe_id(str(report.get('source_package_id') or 'package'))}",
2331
+ "data_type": "bioactive_evidence_mapping_schema_example",
2332
+ "source_text_or_artifact_ref": None,
2333
+ "extracted_claim": None,
2334
+ "ingredient_or_compound_entity": None,
2335
+ "product_entity": None,
2336
+ "evidence_summary": None,
2337
+ "mechanism_category": None,
2338
+ "safety_caution_category": None,
2339
+ "uncertainty": "schema_only",
2340
+ "citation_or_provenance_refs": [],
2341
+ "verifier_decision": "not_evaluated_schema_example",
2342
+ }
2343
+ ]
1391
2344
 
1392
2345
 
1393
2346
  def _copy_json_public(src: Path, dst: Path, default: dict[str, Any] | None = None) -> None:
@@ -1569,11 +2522,16 @@ def _write_experience_compiler_outputs(
1569
2522
  "verifier",
1570
2523
  "reward",
1571
2524
  "environment",
2525
+ "loop_learning",
1572
2526
  "reports",
1573
2527
  ]:
1574
2528
  (root / name).mkdir(parents=True, exist_ok=True)
1575
2529
 
1576
2530
  manifest_src = read_json(tdo_dir / "tdo_manifest.json") if (tdo_dir / "tdo_manifest.json").exists() else {}
2531
+ compiler_rows = _compiler_rows_from_tdo_dir(tdo_dir)
2532
+ training_row_counts_by_file = _training_row_counts_by_file(tdo_dir)
2533
+ evidence_map = _source_evidence_map_payload(evidence, report, compiler_rows)
2534
+ package_version = get_package_version()
1577
2535
  compiler_refs = [
1578
2536
  "experience_compiler/compiler_manifest.json",
1579
2537
  "experience_compiler/compiler_report.json",
@@ -1585,18 +2543,51 @@ def _write_experience_compiler_outputs(
1585
2543
  "experience_compiler/skill_pack/skill.md",
1586
2544
  ]
1587
2545
  compiler_manifest = {
1588
- "schema_version": "aa-experience-compiler-v0.1",
2546
+ "schema_version": "aa-experience-compiler-v0.2",
2547
+ "training_row_schema_version": TRAINING_ROW_SCHEMA_VERSION,
2548
+ "source_evidence_map_schema_version": EVIDENCE_MAP_SCHEMA_VERSION,
2549
+ "package_version": package_version,
2550
+ "compiler_version": TDO_VERSION,
1589
2551
  "compiler_id": str(report.get("tdo_id") or "").replace("tdo_", "compiler_", 1) or f"compiler_{_safe_id(package_root.name)}",
1590
2552
  "compiler_status": report.get("tdo_status"),
1591
2553
  "source_package_id": report.get("source_package_id"),
1592
2554
  "source_task_id": report.get("source_task_id"),
2555
+ "title": _task_title(evidence),
2556
+ "task_title": _task_title(evidence),
2557
+ "domain": _task_lineage(evidence)["domain"],
2558
+ "subdomain": _task_lineage(evidence)["subdomain"],
2559
+ "workflow_family": _task_lineage(evidence)["workflow_family"],
2560
+ "public_private_mode": getattr(settings, "contribution_mode", None),
2561
+ "sharing_upload_mode": getattr(settings, "contribution_mode", None),
2562
+ "private_internal_no_upload_confirmed": getattr(settings, "contribution_mode", None) == "private_internal",
1593
2563
  "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
1594
2564
  "judge_source": report.get("tdo_judge_source"),
1595
2565
  "mentor_audited": report.get("mentor_audited"),
1596
2566
  "rounds_run": report.get("rounds_run"),
1597
2567
  "rows_by_type": rows_accepted,
2568
+ "training_row_counts_by_file": training_row_counts_by_file,
2569
+ "source_evidence_ref_count": (evidence_map.get("coverage_statistics") or {}).get("evidence_ref_count"),
2570
+ "source_evidence_row_link_count": (evidence_map.get("coverage_statistics") or {}).get("row_link_count"),
1598
2571
  "supported_runtime_uses": ["experience_pack", "skill_pack", "runtime_learning_package"],
1599
- "supported_training_uses": ["training_examples", "evaluation_examples", "verifier_examples", "reward_model_rows", "preference_pairs", "environment_response_pairs"],
2572
+ "supported_training_uses": [
2573
+ "sft",
2574
+ "lora_ready_instruction_tuning",
2575
+ "process_supervision",
2576
+ "training_examples",
2577
+ "evaluation_examples",
2578
+ "verifier_examples",
2579
+ "reward_model_rows",
2580
+ "preference_pairs",
2581
+ "critique_revision_pairs",
2582
+ "failure_cases",
2583
+ "task_variants",
2584
+ "agentic_rl_rollouts",
2585
+ "grpo_rollout_material",
2586
+ "environment_response_pairs",
2587
+ "loop_learning",
2588
+ "contract_verification",
2589
+ "environment_state_transition_learning",
2590
+ ],
1600
2591
  "loop_review_refs": evidence.get("loop_review_refs") or [],
1601
2592
  "compiler_output_refs": compiler_refs,
1602
2593
  "created_at": report.get("created_at") or utc_now(),
@@ -1609,33 +2600,17 @@ def _write_experience_compiler_outputs(
1609
2600
  "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
1610
2601
  })
1611
2602
  write_json(root / "compiler_report.json", sanitize_public_obj(compiler_report))
1612
- task_refs = [
1613
- ref
1614
- for ref in [
1615
- "task/task_packet.json",
1616
- "task/task_intake_spec.json",
1617
- "task/raw_task_record.json",
1618
- "source_task/task_packet.json",
1619
- ]
1620
- if (package_root / ref).exists()
1621
- ]
1622
- if not task_refs:
1623
- task_refs = ["task/task_packet.json"]
1624
- write_json(root / "source_evidence_map.json", sanitize_public_obj({
1625
- "source_package_id": report.get("source_package_id"),
1626
- "source_task_id": report.get("source_task_id"),
1627
- "task_refs": task_refs,
1628
- "artifact_refs": _source_artifact_refs(evidence),
1629
- "evaluation_refs": _evidence_refs(evidence),
1630
- "loop_review_refs": evidence.get("loop_review_refs") or [],
1631
- "trace_refs": [str(trace.get("trace_id") or trace.get("source_trace_id") or "trace") for trace in evidence.get("traces") or []],
1632
- }))
2603
+ write_json(root / "source_evidence_map.json", evidence_map)
1633
2604
  write_json(root / "quality_report.json", sanitize_public_obj({
1634
2605
  "source_package_id": report.get("source_package_id"),
1635
2606
  "compiler_status": report.get("tdo_status"),
2607
+ "package_version": package_version,
2608
+ "compiler_version": TDO_VERSION,
1636
2609
  "average_quality_score": report.get("average_quality_score"),
1637
2610
  "average_fidelity_score": report.get("average_fidelity_score"),
1638
2611
  "average_reuse_value_score": report.get("average_reuse_value_score"),
2612
+ "training_row_counts_by_file": training_row_counts_by_file,
2613
+ "source_evidence_map_coverage": evidence_map.get("coverage_statistics"),
1639
2614
  "omitted_outputs": report.get("omitted_outputs") or [],
1640
2615
  "not_generated_counts": report.get("not_generated_counts") or {},
1641
2616
  "generation_notes": report.get("generation_notes") or [],
@@ -1649,6 +2624,13 @@ def _write_experience_compiler_outputs(
1649
2624
  _copy_compiler_jsonl_public(tdo_dir / "tdo_judge_results.jsonl", root / "reports" / "compiler_judge_results.jsonl")
1650
2625
  _copy_compiler_json_public(tdo_dir / "tdo_recipe.json", root / "reports" / "compiler_recipe.json")
1651
2626
 
2627
+ loop_learning = _loop_learning_rows(evidence, report)
2628
+ _write_jsonl(root / "loop_learning" / "loop_review_rows.jsonl", loop_learning["loop_review_rows"])
2629
+ _write_jsonl(root / "loop_learning" / "feedback_revision_pairs.jsonl", loop_learning["feedback_revision_pairs"])
2630
+ _write_jsonl(root / "loop_learning" / "contract_verification_rows.jsonl", loop_learning["contract_verification_rows"])
2631
+ _write_jsonl(root / "loop_learning" / "repair_loop_rows.jsonl", loop_learning["repair_loop_rows"])
2632
+ _write_jsonl(root / "loop_learning" / "reviewer_decision_rows.jsonl", loop_learning["reviewer_decision_rows"])
2633
+
1652
2634
  jsonl_map = {
1653
2635
  "training_examples.jsonl": root / "training_time_package" / "training_examples.jsonl",
1654
2636
  "evaluation_examples.jsonl": root / "training_time_package" / "evaluation_examples.jsonl",
@@ -1707,6 +2689,7 @@ def _write_experience_compiler_outputs(
1707
2689
  title = _task_title(evidence) or str(report.get("source_task_id") or "Compiled skill")
1708
2690
  instruction = _task_instruction(evidence)
1709
2691
  criteria = _success_criteria(evidence)
2692
+ artifact_contracts = _artifact_contracts(evidence)
1710
2693
  canonical_skill = sanitize_public_obj({
1711
2694
  "skill_id": f"skill_{_safe_id(str(report.get('source_package_id') or package_root.name))}",
1712
2695
  "title": title,
@@ -1792,6 +2775,9 @@ def _write_experience_compiler_outputs(
1792
2775
  "source_package_id": report.get("source_package_id"),
1793
2776
  "checks": criteria + ["bundle_check_passes", "evidence_refs_resolve"],
1794
2777
  })
2778
+ output_contract = _output_contract_payload(evidence, artifact_contracts)
2779
+ artifact_contract = _artifact_contract_payload(evidence, artifact_contracts)
2780
+ prefinal_checklist = _prefinal_checklist_payload(evidence, output_contract, artifact_contract)
1795
2781
  write_json(root / "runtime_learning_package" / "runtime_learning_manifest.json", {
1796
2782
  "source_package_id": report.get("source_package_id"),
1797
2783
  "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
@@ -1805,6 +2791,13 @@ def _write_experience_compiler_outputs(
1805
2791
  "tool_recipe_reuse",
1806
2792
  ],
1807
2793
  "skill_ref": "experience_compiler/skill_pack/skill.md",
2794
+ "transfer_runbook_ref": "experience_compiler/runtime_learning_package/transfer_runbook.md",
2795
+ "transfer_checklist_ref": "experience_compiler/runtime_learning_package/transfer_checklist.json",
2796
+ "minimal_prompt_context_ref": "experience_compiler/runtime_learning_package/minimal_prompt_context.md",
2797
+ "output_contract_ref": "experience_compiler/runtime_learning_package/output_contract.json",
2798
+ "artifact_contract_ref": "experience_compiler/runtime_learning_package/artifact_contract.json",
2799
+ "prefinal_verification_checklist_ref": "experience_compiler/runtime_learning_package/prefinal_verification_checklist.json",
2800
+ "repair_instructions_ref": "experience_compiler/runtime_learning_package/repair_instructions.md",
1808
2801
  })
1809
2802
  for name, rows in {
1810
2803
  "runtime_memory.jsonl": [{"source_package_id": report.get("source_package_id"), "lesson": _task_instruction(evidence), "usable_for": ["runtime_memory"]}],
@@ -1847,6 +2840,90 @@ def _write_experience_compiler_outputs(
1847
2840
  ]
1848
2841
  )
1849
2842
  )
2843
+ transfer_runbook = _runtime_transfer_runbook(evidence, title, instruction, criteria, artifact_contracts)
2844
+ (root / "runtime_learning_package" / "transfer_runbook.md").write_text(
2845
+ sanitize_public_text(transfer_runbook) or transfer_runbook
2846
+ )
2847
+ minimal_context = "\n".join(
2848
+ [
2849
+ f"# Minimal Runtime Transfer Context: {title}",
2850
+ "",
2851
+ "Follow current-task instructions. Reuse only the workflow pattern and output-contract discipline from this source task.",
2852
+ "",
2853
+ "## Must Do",
2854
+ "- Derive outputs from current visible input files.",
2855
+ "- Match required filenames, CSV headers, JSON keys, and artifact roles.",
2856
+ "- Write `actual_outputs.json` with the generated file list.",
2857
+ "- Run the pre-final contract checklist before finishing.",
2858
+ "",
2859
+ "## Output Contract Pattern",
2860
+ *[f"- {_contract_line(contract)}" for contract in artifact_contracts[:8]],
2861
+ "",
2862
+ ]
2863
+ )
2864
+ (root / "runtime_learning_package" / "minimal_prompt_context.md").write_text(minimal_context[:4000])
2865
+ write_json(root / "runtime_learning_package" / "output_contract.json", output_contract)
2866
+ write_json(root / "runtime_learning_package" / "artifact_contract.json", artifact_contract)
2867
+ write_json(root / "runtime_learning_package" / "prefinal_verification_checklist.json", prefinal_checklist)
2868
+ (root / "runtime_learning_package" / "repair_instructions.md").write_text(
2869
+ _repair_instructions_text(title, output_contract)
2870
+ )
2871
+ write_json(root / "runtime_learning_package" / "transfer_checklist.json", {
2872
+ "source_package_id": report.get("source_package_id"),
2873
+ "title": title,
2874
+ "checks": [
2875
+ "current_task_inputs_read",
2876
+ "outputs_derived_from_current_inputs",
2877
+ "required_filenames_created",
2878
+ "csv_headers_or_json_keys_match_contract",
2879
+ "actual_outputs_json_lists_generated_files",
2880
+ "local_sanity_check_completed",
2881
+ ],
2882
+ "artifact_contracts": artifact_contracts,
2883
+ "verifier_checks": criteria[:12],
2884
+ })
2885
+ write_json(root / "runtime_learning_package" / "tool_execution_recipe.json", {
2886
+ "source_package_id": report.get("source_package_id"),
2887
+ "recommended_steps": [
2888
+ "inspect visible input files",
2889
+ "write a short deterministic Python script or shell command sequence",
2890
+ "run the script in the task directory",
2891
+ "open outputs and validate schemas",
2892
+ "write actual_outputs.json",
2893
+ ],
2894
+ "artifact_contracts": artifact_contracts[:12],
2895
+ })
2896
+ _write_jsonl(root / "runtime_learning_package" / "output_verification_steps.jsonl", [
2897
+ {
2898
+ "source_package_id": report.get("source_package_id"),
2899
+ "artifact": _contract_output_path(contract),
2900
+ "check": _contract_line(contract),
2901
+ "evidence_refs": [contract.get("artifact_ref")] if contract.get("artifact_ref") else _evidence_refs(evidence)[:3],
2902
+ }
2903
+ for contract in artifact_contracts
2904
+ ] or [{
2905
+ "source_package_id": report.get("source_package_id"),
2906
+ "artifact": "actual_outputs.json",
2907
+ "check": "Generated outputs are listed in actual_outputs.json.",
2908
+ "evidence_refs": _evidence_refs(evidence)[:3],
2909
+ }])
2910
+ _write_jsonl(root / "runtime_learning_package" / "common_errors_to_avoid.jsonl", [
2911
+ {
2912
+ "source_package_id": report.get("source_package_id"),
2913
+ "error": "Missing required output file or writing it outside the expected relative output location.",
2914
+ "avoidance": "Create every named artifact and reopen it before final response.",
2915
+ },
2916
+ {
2917
+ "source_package_id": report.get("source_package_id"),
2918
+ "error": "CSV headers or JSON keys drift from the expected artifact contract.",
2919
+ "avoidance": "Use the transfer checklist and write headers/keys explicitly.",
2920
+ },
2921
+ {
2922
+ "source_package_id": report.get("source_package_id"),
2923
+ "error": "actual_outputs.json exists but does not list generated files.",
2924
+ "avoidance": "Populate files/deliverable refs after verifying outputs on disk.",
2925
+ },
2926
+ ])
1850
2927
  for src, dst in [
1851
2928
  ("retrieval_card.json", "retrieval_card.json"),
1852
2929
  ("verifier_checklist.json", "verifier_spec.json"),
@@ -1874,8 +2951,10 @@ def _write_experience_compiler_outputs(
1874
2951
  "training_uses": compiler_manifest["supported_training_uses"],
1875
2952
  "supported_training_uses": [
1876
2953
  "sft",
2954
+ "lora_ready_instruction_tuning",
1877
2955
  "rl",
1878
2956
  "grpo",
2957
+ "agentic_rollouts",
1879
2958
  "reward_modeling",
1880
2959
  "preference_learning",
1881
2960
  "verifier_training",
@@ -1885,6 +2964,9 @@ def _write_experience_compiler_outputs(
1885
2964
  "environment_model_training",
1886
2965
  ],
1887
2966
  "row_counts": rows_accepted,
2967
+ "row_counts_by_file": training_row_counts_by_file,
2968
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
2969
+ "source_evidence_map_ref": "experience_compiler/source_evidence_map.json",
1888
2970
  "no_model_training_claimed": True,
1889
2971
  })
1890
2972
  for src, dst in [
@@ -1897,25 +2979,65 @@ def _write_experience_compiler_outputs(
1897
2979
  ("preference_pairs.jsonl", "preference_pairs.jsonl"),
1898
2980
  ]:
1899
2981
  _copy_jsonl_public(tdo_dir / src, root / "training_time_package" / dst)
1900
- _write_jsonl(root / "training_time_package" / "process_supervision.jsonl", reusable_rows)
2982
+ process_rows = _process_supervision_rows_from_compiler_rows(compiler_rows)
2983
+ _write_jsonl(root / "training_time_package" / "process_supervision.jsonl", process_rows)
2984
+ rl_rollout_rows = _rl_rollout_rows_from_compiler_rows(compiler_rows)
2985
+ _write_jsonl(root / "training_time_package" / "agentic_rollouts.jsonl", rl_rollout_rows)
2986
+ _write_jsonl(root / "training_time_package" / "grpo_rollout_groups.jsonl", [
2987
+ {
2988
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
2989
+ "row_id": f"grpo_group_{_safe_id(str(report.get('source_package_id') or package_root.name))}",
2990
+ "data_type": "grpo_rollout_group",
2991
+ "source_package_id": report.get("source_package_id"),
2992
+ "source_task_id": report.get("source_task_id"),
2993
+ "task_lineage": _task_lineage(evidence),
2994
+ "rollout_group_id": f"rollout_{_safe_id(str(report.get('source_task_id') or package_root.name))}",
2995
+ "candidate_rollout_refs": [row.get("row_id") for row in rl_rollout_rows],
2996
+ "reward_components": ["artifact_contract", "verifier_signal", "step_success"],
2997
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:12]),
2998
+ "evidence_refs": _evidence_refs(evidence)[:12],
2999
+ "confidence": report.get("average_quality_score") or 0.5,
3000
+ "created_at": utc_now(),
3001
+ }
3002
+ ] if rl_rollout_rows else [])
3003
+ _write_jsonl(root / "training_time_package" / "rl_environment_replay_rows.jsonl", rl_rollout_rows)
1901
3004
  loop_pairs: list[dict[str, Any]] = []
1902
3005
  for item in evidence.get("loop_review_packets") or []:
1903
3006
  packet = item.get("packet") or {}
1904
3007
  ref = item.get("ref")
1905
3008
  loop_pairs.append({
3009
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3010
+ "pair_id": f"critique_revision_{_safe_id(str(report.get('source_package_id') or package_root.name))}_{packet.get('loop_iteration') or len(loop_pairs)+1}",
3011
+ "row_id": f"critique_revision_{_safe_id(str(report.get('source_package_id') or package_root.name))}_{packet.get('loop_iteration') or len(loop_pairs)+1}",
3012
+ "data_type": "critique_revision_pair",
1906
3013
  "source_package_id": report.get("source_package_id"),
3014
+ "source_task_id": report.get("source_task_id"),
3015
+ "task_lineage": _task_lineage(evidence),
1907
3016
  "critique": _clean_text("; ".join(packet.get("known_failures") or packet.get("recommended_review_focus") or []) or "Loop review packet recorded no blocking failure."),
1908
3017
  "revision": _clean_text(str((packet.get("diff_from_previous_iteration") or {}).get("changed_summary") or "Use loop feedback and revision plan to ground updates.")),
1909
3018
  "evidence_refs": [ref] if ref else _evidence_refs(evidence)[:5],
3019
+ "source_evidence_ref_ids": _evidence_ids_for_refs([ref] if ref else _evidence_refs(evidence)[:5]),
1910
3020
  "loop_iteration": packet.get("loop_iteration"),
3021
+ "confidence": report.get("average_quality_score") or 0.5,
3022
+ "created_at": utc_now(),
1911
3023
  })
1912
3024
  if not loop_pairs:
1913
3025
  loop_pairs = [{
3026
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3027
+ "pair_id": f"critique_revision_{_safe_id(str(report.get('source_package_id') or package_root.name))}_fallback",
3028
+ "row_id": f"critique_revision_{_safe_id(str(report.get('source_package_id') or package_root.name))}_fallback",
3029
+ "data_type": "critique_revision_pair",
1914
3030
  "source_package_id": report.get("source_package_id"),
3031
+ "source_task_id": report.get("source_task_id"),
3032
+ "task_lineage": _task_lineage(evidence),
1915
3033
  "critique": "Use evaluator/verifier feedback where present; do not invent missing feedback.",
1916
3034
  "revision": "Ground revisions in actual outputs and artifact refs.",
1917
3035
  "evidence_refs": _evidence_refs(evidence)[:5],
3036
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:5]),
3037
+ "confidence": report.get("average_quality_score") or 0.5,
3038
+ "created_at": utc_now(),
1918
3039
  }]
3040
+ loop_pairs.extend(loop_learning["feedback_revision_pairs"])
1919
3041
  _write_jsonl(root / "training_time_package" / "critique_revision_pairs.jsonl", loop_pairs)
1920
3042
  _write_jsonl(root / "training_time_package" / "trajectory_rollouts.jsonl", [{
1921
3043
  "source_package_id": report.get("source_package_id"),
@@ -1923,17 +3045,35 @@ def _write_experience_compiler_outputs(
1923
3045
  "artifact_refs": _source_artifact_refs(evidence),
1924
3046
  }])
1925
3047
  _write_jsonl(root / "training_time_package" / "distillation_records.jsonl", [{
3048
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3049
+ "row_id": f"distill_{_safe_id(str(report.get('source_package_id') or package_root.name))}",
3050
+ "data_type": "distillation_record",
1926
3051
  "source_package_id": report.get("source_package_id"),
3052
+ "source_task_id": report.get("source_task_id"),
3053
+ "task_lineage": _task_lineage(evidence),
1927
3054
  "input": instruction,
1928
3055
  "target": _output_summary(evidence),
1929
3056
  "evidence_refs": _evidence_refs(evidence)[:8],
3057
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:8]),
3058
+ "confidence": report.get("average_quality_score") or 0.5,
3059
+ "created_at": utc_now(),
1930
3060
  }])
1931
3061
  _write_jsonl(root / "training_time_package" / "transfer_eval_tasks.jsonl", [
1932
3062
  {
3063
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3064
+ "row_id": f"transfer_eval_{_safe_id(str(report.get('source_package_id') or package_root.name))}",
3065
+ "data_type": "transfer_eval_task",
1933
3066
  "source_package_id": report.get("source_package_id"),
3067
+ "source_task_id": report.get("source_task_id"),
3068
+ "task_lineage": _task_lineage(evidence),
1934
3069
  "task": f"Replay a small adjacent {(_task_title(evidence) or 'workflow')} task and compare artifact quality.",
1935
3070
  "success_signal": "bundle check passes and verifier/evaluator evidence refs are present",
1936
3071
  "transfer_tested": False,
3072
+ "expected_output_contract": output_contract,
3073
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:8]),
3074
+ "evidence_refs": _evidence_refs(evidence)[:8],
3075
+ "confidence": report.get("average_quality_score") or 0.5,
3076
+ "created_at": utc_now(),
1937
3077
  }
1938
3078
  ])
1939
3079
  write_json(root / "reports" / "transfer_test_report.json", {
@@ -1943,17 +3083,155 @@ def _write_experience_compiler_outputs(
1943
3083
  _copy_jsonl_public(tdo_dir / "evaluation_examples.jsonl", root / "eval_package" / "evaluation_examples.jsonl")
1944
3084
  shutil.copy2(root / "training_time_package" / "transfer_eval_tasks.jsonl", root / "eval_package" / "transfer_eval_tasks.jsonl")
1945
3085
  shutil.copy2(root / "reports" / "transfer_test_report.json", root / "eval_package" / "transfer_test_report.json")
1946
- _write_jsonl(root / "eval_package" / "rubric_items.jsonl", [{"source_package_id": report.get("source_package_id"), "rubric_item": item} for item in criteria])
1947
- _write_jsonl(root / "eval_package" / "grader_cases.jsonl", [{"source_package_id": report.get("source_package_id"), "case": "Grade output against compiled rubric and artifact evidence.", "evidence_refs": _evidence_refs(evidence)[:5]}])
1948
- _write_jsonl(root / "eval_package" / "verifier_cases.jsonl", [{"source_package_id": report.get("source_package_id"), "case": "Verify required artifacts and output refs exist.", "evidence_refs": _evidence_refs(evidence)[:5]}])
1949
- _write_jsonl(root / "eval_package" / "expected_artifacts.jsonl", [{"source_package_id": report.get("source_package_id"), "artifact_ref": ref} for ref in _source_artifact_refs(evidence)])
3086
+ write_json(root / "eval_package" / "eval_manifest.json", {
3087
+ "schema_version": "aa-eval-package-v0.2",
3088
+ "source_package_id": report.get("source_package_id"),
3089
+ "source_task_id": report.get("source_task_id"),
3090
+ "task_lineage": _task_lineage(evidence),
3091
+ "source_evidence_map_ref": "experience_compiler/source_evidence_map.json",
3092
+ "output_contract_ref": "experience_compiler/runtime_learning_package/output_contract.json",
3093
+ "artifact_contract_ref": "experience_compiler/runtime_learning_package/artifact_contract.json",
3094
+ "supported_eval_uses": ["rubric_eval", "contract_verifier_training", "transfer_eval_generation", "grader_case_generation"],
3095
+ })
3096
+ _write_jsonl(root / "eval_package" / "rubric_items.jsonl", [
3097
+ {
3098
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3099
+ "row_id": f"eval_rubric_{_safe_id(str(report.get('source_package_id') or package_root.name))}_{idx}",
3100
+ "data_type": "eval_rubric_item",
3101
+ "source_package_id": report.get("source_package_id"),
3102
+ "source_task_id": report.get("source_task_id"),
3103
+ "task_lineage": _task_lineage(evidence),
3104
+ "rubric_item": item,
3105
+ "scoring_criterion": item,
3106
+ "evidence_refs": _evidence_refs(evidence)[:5],
3107
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:5]),
3108
+ "confidence": report.get("average_quality_score") or 0.5,
3109
+ "created_at": utc_now(),
3110
+ }
3111
+ for idx, item in enumerate(criteria, start=1)
3112
+ ])
3113
+ _write_jsonl(root / "eval_package" / "grader_cases.jsonl", [{
3114
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3115
+ "row_id": f"grader_case_{_safe_id(str(report.get('source_package_id') or package_root.name))}",
3116
+ "data_type": "grader_case",
3117
+ "source_package_id": report.get("source_package_id"),
3118
+ "source_task_id": report.get("source_task_id"),
3119
+ "task_lineage": _task_lineage(evidence),
3120
+ "case": "Grade output against compiled rubric and artifact evidence.",
3121
+ "output_contract": output_contract,
3122
+ "artifact_contract": artifact_contract,
3123
+ "evidence_refs": _evidence_refs(evidence)[:5],
3124
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:5]),
3125
+ "confidence": report.get("average_quality_score") or 0.5,
3126
+ "created_at": utc_now(),
3127
+ }])
3128
+ _write_jsonl(root / "eval_package" / "verifier_cases.jsonl", [{
3129
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3130
+ "row_id": f"verifier_case_{_safe_id(str(report.get('source_package_id') or package_root.name))}",
3131
+ "data_type": "verifier_case",
3132
+ "source_package_id": report.get("source_package_id"),
3133
+ "source_task_id": report.get("source_task_id"),
3134
+ "task_lineage": _task_lineage(evidence),
3135
+ "claim_or_output": "Required artifacts and output refs exist and match the contract.",
3136
+ "visible_evidence": _source_artifact_refs(evidence)[:12],
3137
+ "expected_contract": output_contract,
3138
+ "verifier_decision": "case_requires_future_candidate_output",
3139
+ "failure_reasons": [],
3140
+ "evidence_refs": _evidence_refs(evidence)[:5],
3141
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:5]),
3142
+ "confidence": report.get("average_quality_score") or 0.5,
3143
+ "created_at": utc_now(),
3144
+ }])
3145
+ _write_jsonl(root / "eval_package" / "expected_artifacts.jsonl", [
3146
+ {
3147
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3148
+ "row_id": f"expected_artifact_{_safe_id(str(report.get('source_package_id') or package_root.name))}_{idx}",
3149
+ "data_type": "expected_artifact",
3150
+ "source_package_id": report.get("source_package_id"),
3151
+ "source_task_id": report.get("source_task_id"),
3152
+ "task_lineage": _task_lineage(evidence),
3153
+ "artifact_ref": ref,
3154
+ "source_evidence_ref_ids": _evidence_ids_for_refs([ref]),
3155
+ "evidence_refs": [ref],
3156
+ "confidence": 1.0,
3157
+ "created_at": utc_now(),
3158
+ }
3159
+ for idx, ref in enumerate(_source_artifact_refs(evidence), start=1)
3160
+ ])
1950
3161
  env_spec = _environment_spec(evidence)
1951
- write_json(root / "environment" / "replay_plan.json", {"source_package_id": report.get("source_package_id"), "replay_available": False, "generation_note": "Replay is task-dependent; use environment_spec and verifier checks."})
1952
- write_json(root / "environment" / "setup_requirements.json", {"required_tools": env_spec.get("required_tools") or [], "runtime_dependencies": env_spec.get("runtime_dependencies") or []})
1953
- _write_jsonl(root / "environment" / "allowed_actions.jsonl", [{"action": action, "source_package_id": report.get("source_package_id")} for action in (env_spec.get("allowed_actions") or ["read_task", "write_artifact", "verify_output"])])
3162
+ write_json(root / "environment" / "environment_manifest.json", {
3163
+ "schema_version": "aa-environment-package-v0.2",
3164
+ "source_package_id": report.get("source_package_id"),
3165
+ "source_task_id": report.get("source_task_id"),
3166
+ "task_lineage": _task_lineage(evidence),
3167
+ "environment_spec_ref": "experience_compiler/environment/environment_spec.json",
3168
+ "state_transition_rows_ref": "experience_compiler/environment/state_transition_rows.jsonl",
3169
+ "replay_plan_ref": "experience_compiler/environment/replay_plan.json",
3170
+ "source_evidence_map_ref": "experience_compiler/source_evidence_map.json",
3171
+ "supported_training_uses": ["agentic_rl", "grpo_rollout_material", "environment_model_training", "replay_smoke_testing"],
3172
+ })
3173
+ write_json(root / "environment" / "replay_plan.json", {
3174
+ "schema_version": "aa-replay-plan-v0.2",
3175
+ "source_package_id": report.get("source_package_id"),
3176
+ "source_task_id": report.get("source_task_id"),
3177
+ "task_lineage": _task_lineage(evidence),
3178
+ "replay_available": bool(env_spec.get("verification_commands")),
3179
+ "initial_state_refs": env_spec.get("initial_state_refs") or [],
3180
+ "visible_input_refs": env_spec.get("input_artifacts") or [],
3181
+ "action_schema_ref": "experience_compiler/environment/allowed_actions.jsonl",
3182
+ "observation_schema": {"observation": "string", "artifact_refs": "array", "state_after": "string"},
3183
+ "scoring_or_verifier_hooks": env_spec.get("scoring_refs") or [],
3184
+ "termination_conditions": ["task_complete", "contract_verified", "max_iterations_reached"],
3185
+ "reward_function_components": ["artifact_contract", "verifier_signal", "step_success"],
3186
+ "generation_note": "Replay is task-dependent; use environment_spec and verifier checks.",
3187
+ })
3188
+ write_json(root / "environment" / "setup_requirements.json", {
3189
+ "schema_version": "aa-environment-setup-v0.2",
3190
+ "source_package_id": report.get("source_package_id"),
3191
+ "required_tools": env_spec.get("required_tools") or [],
3192
+ "runtime_dependencies": env_spec.get("runtime_dependencies") or [],
3193
+ "dependency_metadata": env_spec.get("software_execution") or {},
3194
+ })
3195
+ _write_jsonl(root / "environment" / "allowed_actions.jsonl", [
3196
+ {
3197
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3198
+ "row_id": f"allowed_action_{_safe_id(str(report.get('source_package_id') or package_root.name))}_{idx}",
3199
+ "data_type": "allowed_action",
3200
+ "source_package_id": report.get("source_package_id"),
3201
+ "source_task_id": report.get("source_task_id"),
3202
+ "task_lineage": _task_lineage(evidence),
3203
+ "action": action,
3204
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:3]),
3205
+ "evidence_refs": _evidence_refs(evidence)[:3],
3206
+ "confidence": report.get("average_quality_score") or 0.5,
3207
+ "created_at": utc_now(),
3208
+ }
3209
+ for idx, action in enumerate((env_spec.get("allowed_actions") or ["read_task", "write_artifact", "verify_output"]), start=1)
3210
+ ])
1954
3211
  _copy_jsonl_public(tdo_dir / "environment_response_pairs.jsonl", root / "environment" / "state_transition_rows.jsonl")
1955
- write_json(root / "environment" / "reset_strategy.json", {"reset_strategy": env_spec.get("reset_strategy") or "unknown", "generation_notes": env_spec.get("evidence_status") or []})
1956
- _write_jsonl(root / "environment" / "verification_commands.jsonl", [{"command": command, "source_package_id": report.get("source_package_id")} for command in (env_spec.get("verification_commands") or [])])
3212
+ write_json(root / "environment" / "reset_strategy.json", {
3213
+ "schema_version": "aa-environment-reset-v0.2",
3214
+ "source_package_id": report.get("source_package_id"),
3215
+ "reset_strategy": env_spec.get("reset_strategy") or "unknown",
3216
+ "state_reset_refs": env_spec.get("initial_state_refs") or [],
3217
+ "generation_notes": env_spec.get("evidence_status") or [],
3218
+ })
3219
+ _write_jsonl(root / "environment" / "verification_commands.jsonl", [
3220
+ {
3221
+ "schema_version": TRAINING_ROW_SCHEMA_VERSION,
3222
+ "row_id": f"verification_command_{_safe_id(str(report.get('source_package_id') or package_root.name))}_{idx}",
3223
+ "data_type": "verification_command",
3224
+ "source_package_id": report.get("source_package_id"),
3225
+ "source_task_id": report.get("source_task_id"),
3226
+ "task_lineage": _task_lineage(evidence),
3227
+ "command": command,
3228
+ "source_evidence_ref_ids": _evidence_ids_for_refs(_evidence_refs(evidence)[:3]),
3229
+ "evidence_refs": _evidence_refs(evidence)[:3],
3230
+ "confidence": report.get("average_quality_score") or 0.5,
3231
+ "created_at": utc_now(),
3232
+ }
3233
+ for idx, command in enumerate((env_spec.get("verification_commands") or []), start=1)
3234
+ ])
1957
3235
  (root / "reports" / "generation_notes.md").write_text(
1958
3236
  "\n".join([
1959
3237
  "# Generation Notes",
@@ -1969,6 +3247,7 @@ def _write_experience_compiler_outputs(
1969
3247
  {"kind": "runtime_learning_package", "path": "experience_compiler/runtime_learning_package/runtime_learning_manifest.json"},
1970
3248
  {"kind": "training_time_package", "path": "experience_compiler/training_time_package/training_time_manifest.json"},
1971
3249
  {"kind": "environment", "path": "experience_compiler/environment/environment_spec.json"},
3250
+ {"kind": "loop_learning", "path": "experience_compiler/loop_learning/loop_review_rows.jsonl"},
1972
3251
  {"kind": "reports", "path": "experience_compiler/compiler_report.json"},
1973
3252
  ],
1974
3253
  "rows_by_type": rows_accepted,