agent-apprenticeship 0.1.6 → 0.1.7

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.
Files changed (33) hide show
  1. package/bin/agent-apprenticeship.js +85 -37
  2. package/package.json +1 -1
  3. package/pyproject.toml +1 -1
  4. package/src/agent_apprenticeship_trace/__init__.py +1 -1
  5. package/src/agent_apprenticeship_trace/actual_outputs_normalizer.py +104 -28
  6. package/src/agent_apprenticeship_trace/apprentice_adapters.py +3 -1
  7. package/src/agent_apprenticeship_trace/artifact_resolver.py +6 -0
  8. package/src/agent_apprenticeship_trace/bundle_exporter.py +132 -2
  9. package/src/agent_apprenticeship_trace/cli.py +1171 -262
  10. package/src/agent_apprenticeship_trace/codex_runner.py +89 -6
  11. package/src/agent_apprenticeship_trace/config.py +251 -32
  12. package/src/agent_apprenticeship_trace/ecosystem_r2.py +515 -0
  13. package/src/agent_apprenticeship_trace/env.py +5 -5
  14. package/src/agent_apprenticeship_trace/evaluator.py +127 -11
  15. package/src/agent_apprenticeship_trace/grader.py +12 -11
  16. package/src/agent_apprenticeship_trace/learning.py +270 -1
  17. package/src/agent_apprenticeship_trace/llm_output_normalizer.py +8 -7
  18. package/src/agent_apprenticeship_trace/loop.py +103 -4
  19. package/src/agent_apprenticeship_trace/loop_review.py +686 -0
  20. package/src/agent_apprenticeship_trace/mentor_checkpoints.py +49 -21
  21. package/src/agent_apprenticeship_trace/openai_structured.py +84 -14
  22. package/src/agent_apprenticeship_trace/package_exporter.py +1 -1
  23. package/src/agent_apprenticeship_trace/progress.py +2 -1
  24. package/src/agent_apprenticeship_trace/public_run.py +369 -57
  25. package/src/agent_apprenticeship_trace/public_sanitizer.py +37 -1
  26. package/src/agent_apprenticeship_trace/rubric_generation.py +11 -10
  27. package/src/agent_apprenticeship_trace/schemas.py +378 -3
  28. package/src/agent_apprenticeship_trace/task_intake.py +43 -9
  29. package/src/agent_apprenticeship_trace/tdo.py +2304 -0
  30. package/src/agent_apprenticeship_trace/trace_normalizer.py +351 -17
  31. package/src/agent_apprenticeship_trace/trace_prompt.py +38 -11
  32. package/src/agent_apprenticeship_trace/training_signals.py +1 -1
  33. package/src/agent_apprenticeship_trace/verifier.py +12 -6
@@ -0,0 +1,2304 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import re
5
+ import shutil
6
+ from collections import Counter, defaultdict
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from statistics import mean
10
+ from typing import Any
11
+
12
+ from .config import Settings, apprentice_agent_display_name, configured_model_provider_ready
13
+ from .env import contains_secret, redact_secrets
14
+ from .io import append_jsonl, read_json, read_jsonl, write_json
15
+ from .progress import ProgressCallback, append_progress_event
16
+ from .public_sanitizer import sanitize_public_obj, sanitize_public_text
17
+ from .schemas import TDOJudgeResult, TDOReport
18
+
19
+ TDO_VERSION = "aa-tdo-v0.1"
20
+ TDO_JSONL_FILES = [
21
+ "tdo_trace.jsonl",
22
+ "tdo_rounds.jsonl",
23
+ "tdo_judge_results.jsonl",
24
+ "training_examples.jsonl",
25
+ "evaluation_examples.jsonl",
26
+ "verifier_examples.jsonl",
27
+ "reward_model_rows.jsonl",
28
+ "preference_pairs.jsonl",
29
+ "failure_cases.jsonl",
30
+ "task_variants.jsonl",
31
+ "environment_response_pairs.jsonl",
32
+ "multi_agent_handoffs.jsonl",
33
+ "multimodal_artifacts.jsonl",
34
+ "ui_interaction_events.jsonl",
35
+ "physical_environment_events.jsonl",
36
+ "api_interaction_events.jsonl",
37
+ "mcp_interaction_events.jsonl",
38
+ "database_events.jsonl",
39
+ "structured_table_events.jsonl",
40
+ "software_execution_events.jsonl",
41
+ "search_retrieval_events.jsonl",
42
+ "enterprise_app_events.jsonl",
43
+ "async_workflow_events.jsonl",
44
+ "collaboration_events.jsonl",
45
+ "document_events.jsonl",
46
+ "cross_system_events.jsonl",
47
+ "meta_optimization_records.jsonl",
48
+ "rejected_rows.jsonl",
49
+ ]
50
+ TDO_JSON_FILES = [
51
+ "tdo_manifest.json",
52
+ "tdo_report.json",
53
+ "tdo_recipe.json",
54
+ "optimized_task_spec.json",
55
+ "optimized_rubric.json",
56
+ "domain_value_profile.json",
57
+ "environment_spec.json",
58
+ ]
59
+ ROW_FILE_BY_TYPE = {
60
+ "training_example": "training_examples.jsonl",
61
+ "evaluation_example": "evaluation_examples.jsonl",
62
+ "verifier_example": "verifier_examples.jsonl",
63
+ "reward_model_row": "reward_model_rows.jsonl",
64
+ "preference_pair": "preference_pairs.jsonl",
65
+ "failure_case": "failure_cases.jsonl",
66
+ "task_variant": "task_variants.jsonl",
67
+ "environment_response_pair": "environment_response_pairs.jsonl",
68
+ "multi_agent_handoff": "multi_agent_handoffs.jsonl",
69
+ "multimodal_artifact": "multimodal_artifacts.jsonl",
70
+ "ui_interaction_event": "ui_interaction_events.jsonl",
71
+ "physical_environment_event": "physical_environment_events.jsonl",
72
+ "api_interaction_event": "api_interaction_events.jsonl",
73
+ "mcp_interaction_event": "mcp_interaction_events.jsonl",
74
+ "database_event": "database_events.jsonl",
75
+ "structured_table_event": "structured_table_events.jsonl",
76
+ "software_execution_event": "software_execution_events.jsonl",
77
+ "search_retrieval_event": "search_retrieval_events.jsonl",
78
+ "enterprise_app_event": "enterprise_app_events.jsonl",
79
+ "async_workflow_event": "async_workflow_events.jsonl",
80
+ "collaboration_event": "collaboration_events.jsonl",
81
+ "document_event": "document_events.jsonl",
82
+ "cross_system_event": "cross_system_events.jsonl",
83
+ }
84
+ ENVIRONMENT_DOMAINS = {
85
+ "terminal",
86
+ "browser",
87
+ "computer_use",
88
+ "web",
89
+ "search",
90
+ "codebase",
91
+ "software",
92
+ "os",
93
+ "android",
94
+ "mcp",
95
+ "api",
96
+ "database",
97
+ "spreadsheet",
98
+ "file",
99
+ "multimodal",
100
+ "device",
101
+ "robotics",
102
+ "lab",
103
+ "industrial",
104
+ "physical",
105
+ "mixed",
106
+ "unknown",
107
+ }
108
+ SURFACE_FILES = {
109
+ "multi_agent_handoff": "multi_agent_handoffs.jsonl",
110
+ "multimodal_artifact": "multimodal_artifacts.jsonl",
111
+ "ui_interaction_event": "ui_interaction_events.jsonl",
112
+ "physical_environment_event": "physical_environment_events.jsonl",
113
+ "api_interaction_event": "api_interaction_events.jsonl",
114
+ "mcp_interaction_event": "mcp_interaction_events.jsonl",
115
+ "database_event": "database_events.jsonl",
116
+ "structured_table_event": "structured_table_events.jsonl",
117
+ "software_execution_event": "software_execution_events.jsonl",
118
+ "search_retrieval_event": "search_retrieval_events.jsonl",
119
+ "enterprise_app_event": "enterprise_app_events.jsonl",
120
+ "async_workflow_event": "async_workflow_events.jsonl",
121
+ "collaboration_event": "collaboration_events.jsonl",
122
+ "document_event": "document_events.jsonl",
123
+ "cross_system_event": "cross_system_events.jsonl",
124
+ }
125
+ PRIVATE_RE = re.compile(r"(/Users/|/private/|/var/folders/|/tmp/|\.env(?:\.local)?)")
126
+ FORBIDDEN_KEYS = {
127
+ "source_ref",
128
+ "source_basis",
129
+ "source_url_or_ref",
130
+ "source_url",
131
+ "source_kind",
132
+ "source_license",
133
+ ("b" + "uyer"),
134
+ ("s" + "eller"),
135
+ ("pr" + "ice"),
136
+ ("list" + "ing"),
137
+ ("market" + "place"),
138
+ "agent_self_eval",
139
+ "self_eval",
140
+ "trace_context",
141
+ "worker_agent",
142
+ "worker_attempt",
143
+ "evaluation_mode",
144
+ "data_sharing_level",
145
+ ("feat" + "ured"),
146
+ ("cur" + "ated"),
147
+ ("limit" + "ations"),
148
+ ("known_" + "limitations"),
149
+ ("limitations" + "_count"),
150
+ ("when_" + "not_to_use"),
151
+ ("not_" + "useful_for"),
152
+ ("non_" + "use_cases"),
153
+ ("rubric_" + "limitations"),
154
+ }
155
+ PUBLIC_TEXT_REPLACEMENTS = {
156
+ "Gatekeeping": "Review Routing",
157
+ "gatekeeping": "review routing",
158
+ ("Worker " + "Agent"): "Apprentice Agent",
159
+ ("worker " + "agent"): "Apprentice Agent",
160
+ ("Deep" + "Seek"): "model provider",
161
+ ("files_created_" + "list" + "ing_inconsistency"): "files_created_index_inconsistency",
162
+ ("Cur" + "ated"): "Selected",
163
+ ("cur" + "ated"): "selected",
164
+ ("Contribution " + "Bundle"): "Experience Compilation",
165
+ ("contribution " + "bundle"): "Experience Compilation",
166
+ ("Export Full " + "Training Package"): "Export Full Experience Compilation",
167
+ ("Training Data " + "Optimization"): "Experience Compiler",
168
+ ("T" + "DO"): "Experience Compiler",
169
+ ("Honest " + "limitations"): "Generation notes",
170
+ ("honest " + "limitations"): "generation notes",
171
+ "Limitations": "Generation Notes",
172
+ "limitations": "generation notes",
173
+ "limitation": "generation note",
174
+ ("Applic" + "ability"): "Best Use",
175
+ ("applic" + "ability"): "fit",
176
+ ("Required " + "Conditions"): "Required Inputs",
177
+ ("Evidence " + "Coverage"): "Source Evidence",
178
+ ("Evidence " + "coverage"): "Source evidence",
179
+ ("evidence " + "coverage"): "source evidence",
180
+ ("Coverage " + "Notes"): "Generation Notes",
181
+ ("Data Coverage " + "Notes"): "Generation Notes",
182
+ ("Transfer " + "Boundaries"): "Transfer Notes",
183
+ ("Missing Evidence " + "Notes"): "Generation Notes",
184
+ ("Quality " + "Notes"): "Quality Signals",
185
+ ("quality " + "notes"): "quality signals",
186
+ }
187
+
188
+
189
+ def utc_now() -> str:
190
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
191
+
192
+
193
+ def _safe_id(value: str) -> str:
194
+ return re.sub(r"[^A-Za-z0-9_]+", "_", value).strip("_") or "package"
195
+
196
+
197
+ def _truthy_env(name: str) -> bool:
198
+ return (os.getenv(name) or "").strip().lower() in {"1", "true", "yes", "y", "on"}
199
+
200
+
201
+ def _int_env(name: str, default: int) -> int:
202
+ try:
203
+ return max(1, int(os.getenv(name) or default))
204
+ except ValueError:
205
+ return default
206
+
207
+
208
+ def _float_env(name: str, default: float) -> float:
209
+ try:
210
+ return max(0.0, min(1.0, float(os.getenv(name) or default)))
211
+ except ValueError:
212
+ return default
213
+
214
+
215
+ def _safe_read(path: Path) -> Any:
216
+ try:
217
+ return read_json(path)
218
+ except Exception:
219
+ return None
220
+
221
+
222
+ def _safe_jsonl(path: Path) -> list[Any]:
223
+ try:
224
+ return read_jsonl(path)
225
+ except Exception:
226
+ return []
227
+
228
+
229
+ def _write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None:
230
+ path.parent.mkdir(parents=True, exist_ok=True)
231
+ path.write_text("")
232
+ for row in rows:
233
+ append_jsonl(path, sanitize_public_obj(row))
234
+
235
+
236
+ def _clean_text(value: Any, limit: int = 1200) -> str:
237
+ if value is None:
238
+ return ""
239
+ if isinstance(value, (dict, list)):
240
+ text = str(sanitize_public_obj(value) if isinstance(value, dict) else value)
241
+ else:
242
+ text = str(value)
243
+ text = sanitize_public_text(redact_secrets(text)) or ""
244
+ for old, new in PUBLIC_TEXT_REPLACEMENTS.items():
245
+ text = text.replace(old, new)
246
+ return text[:limit]
247
+
248
+
249
+ def _normalize_ref(ref: Any) -> str | None:
250
+ if not ref:
251
+ return None
252
+ text = str(ref).replace("\\", "/").strip()
253
+ while text.startswith("./"):
254
+ text = text[2:]
255
+ if text.startswith("/") or ".." in Path(text).parts:
256
+ return None
257
+ return text or None
258
+
259
+
260
+ def _refs(values: list[Any] | Any) -> list[str]:
261
+ if not values:
262
+ return []
263
+ raw = values if isinstance(values, list) else [values]
264
+ out: list[str] = []
265
+ for value in raw:
266
+ ref = _normalize_ref(value)
267
+ if ref and ref not in out:
268
+ out.append(ref)
269
+ return out
270
+
271
+
272
+ def _score(value: Any, default: float = 0.0) -> float:
273
+ try:
274
+ raw = float(value)
275
+ except (TypeError, ValueError):
276
+ return default
277
+ if raw > 1.0:
278
+ raw = raw / 100.0
279
+ return max(0.0, min(1.0, raw))
280
+
281
+
282
+ def _source_task_id(evidence: dict[str, Any]) -> str:
283
+ task = evidence.get("task") or {}
284
+ raw = evidence.get("raw_task") or {}
285
+ manifest = evidence.get("manifest") or {}
286
+ return str(
287
+ task.get("task_id")
288
+ or raw.get("task_id")
289
+ or raw.get("raw_task_id")
290
+ or manifest.get("task_id")
291
+ or evidence["package_id"]
292
+ )
293
+
294
+
295
+ def _load_evidence(package_root: Path, run_root: Path | None = None) -> dict[str, Any]:
296
+ attempts: dict[str, dict[str, Any]] = {}
297
+ for attempt_dir in sorted((package_root / "attempts").glob("*")):
298
+ if not attempt_dir.is_dir():
299
+ continue
300
+ if not (attempt_dir / "actual_outputs.json").exists() and not (attempt_dir / "agent_trace.json").exists():
301
+ continue
302
+ kind = attempt_dir.name
303
+ attempts[kind] = {
304
+ "actual_outputs": _safe_read(attempt_dir / "actual_outputs.json") or {},
305
+ "agent_trace": _safe_read(attempt_dir / "agent_trace.json") or {},
306
+ "attempt_manifest": _safe_read(attempt_dir / "attempt_manifest.json") or {},
307
+ }
308
+ loop_review_refs: list[str] = []
309
+ loop_review_packets: list[dict[str, Any]] = []
310
+ for packet_path in sorted((package_root / "loops").glob("iterations/*/review_packet.json")):
311
+ try:
312
+ packet = read_json(packet_path)
313
+ except Exception:
314
+ continue
315
+ rel = packet_path.relative_to(package_root).as_posix()
316
+ loop_review_refs.append(rel)
317
+ loop_review_packets.append({"ref": rel, "packet": packet})
318
+ for rel in [
319
+ "loops/loop_manifest.json",
320
+ *[p.relative_to(package_root).as_posix() for p in sorted((package_root / "loops").glob("iterations/*/reviewer_feedback.json"))],
321
+ *[p.relative_to(package_root).as_posix() for p in sorted((package_root / "loops").glob("iterations/*/revision_plan.json"))],
322
+ *[p.relative_to(package_root).as_posix() for p in sorted((package_root / "loops").glob("iterations/*/loop_decision.json"))],
323
+ *[p.relative_to(package_root).as_posix() for p in sorted((package_root / "loops").glob("iterations/*/diff_report.json"))],
324
+ ]:
325
+ if (package_root / rel).exists() and rel not in loop_review_refs:
326
+ loop_review_refs.append(rel)
327
+ return {
328
+ "package_id": package_root.name,
329
+ "package_root": package_root,
330
+ "run_root": run_root,
331
+ "manifest": _safe_read(package_root / "package_manifest.json") or _safe_read(package_root / "manifest.json") or {},
332
+ "raw_task": _safe_read(package_root / "task" / "raw_task_record.json") or _safe_read(package_root / "task" / "task_packet.json") or {},
333
+ "task": _safe_read(package_root / "task" / "task_intake_spec.json") or _safe_read(package_root / "task" / "task_packet.json") or {},
334
+ "rubric": _safe_read(package_root / "rubric" / "rubric.json") or {},
335
+ "attempts": attempts,
336
+ "grader_results": {
337
+ path.stem.replace("_grader_result", ""): _safe_read(path) or {}
338
+ for path in sorted((package_root / "grading").glob("*_grader_result.json"))
339
+ },
340
+ "verifier_results": {
341
+ path.stem.replace("_verifier_result", ""): _safe_read(path) or {}
342
+ for path in sorted((package_root / "grading").glob("*_verifier_result.json"))
343
+ },
344
+ "evaluator_feedback": _safe_read(package_root / "feedback" / "baseline_evaluator_feedback.json") or {},
345
+ "revision_plan": _safe_read(package_root / "feedback" / "revision_plan.json") or {},
346
+ "hillclimb": _safe_read(package_root / "signals" / "hillclimb_result.json") or {},
347
+ "lessons": _safe_read(package_root / "signals" / "lesson_pack.json") or {},
348
+ "training_signals": _safe_jsonl(package_root / "signals" / "training_signals.jsonl"),
349
+ "process_supervision": _safe_jsonl(package_root / "signals" / "process_supervision.jsonl"),
350
+ "reward_modeling": _safe_jsonl(package_root / "signals" / "reward_modeling.jsonl"),
351
+ "revision_preference_pairs": _safe_jsonl(package_root / "signals" / "revision_preference_pairs.jsonl"),
352
+ "artifact_index": _safe_read(package_root / "artifacts_index.json") or [],
353
+ "session_events": _safe_jsonl(run_root / "session_events.jsonl") if run_root else [],
354
+ "loop_review_refs": loop_review_refs,
355
+ "loop_review_packets": loop_review_packets,
356
+ }
357
+
358
+
359
+ def _attempt_ids(evidence: dict[str, Any]) -> list[str]:
360
+ ids: list[str] = []
361
+ for attempt in (evidence.get("attempts") or {}).values():
362
+ actual = attempt.get("actual_outputs") or {}
363
+ attempt_id = actual.get("attempt_id")
364
+ if attempt_id and attempt_id not in ids:
365
+ ids.append(str(attempt_id))
366
+ return ids
367
+
368
+
369
+ def _source_artifact_refs(evidence: dict[str, Any]) -> list[str]:
370
+ refs: list[str] = []
371
+ package_root = evidence.get("package_root")
372
+ for attempt_kind, attempt in (evidence.get("attempts") or {}).items():
373
+ actual = attempt.get("actual_outputs") or {}
374
+ for key in ["deliverable_refs", "artifact_refs", "files_created", "input_artifact_refs"]:
375
+ refs.extend(_refs(actual.get(key)))
376
+ for local in [f"attempts/{attempt_kind}/actual_outputs.json", f"attempts/{attempt_kind}/agent_trace.json"]:
377
+ if not isinstance(package_root, Path) or (package_root / local).exists():
378
+ refs.append(local)
379
+ return list(dict.fromkeys(refs))
380
+
381
+
382
+ def _evidence_refs(evidence: dict[str, Any]) -> list[str]:
383
+ refs = ["task/task_intake_spec.json", "rubric/rubric.json"]
384
+ refs.extend(_source_artifact_refs(evidence))
385
+ for name in [
386
+ "grading/baseline_grader_result.json",
387
+ "grading/revised_grader_result.json",
388
+ "grading/baseline_verifier_result.json",
389
+ "grading/revised_verifier_result.json",
390
+ "feedback/baseline_evaluator_feedback.json",
391
+ "feedback/revision_plan.json",
392
+ "signals/hillclimb_result.json",
393
+ ]:
394
+ if (evidence["package_root"] / name).exists():
395
+ refs.append(name)
396
+ refs.extend(evidence.get("loop_review_refs") or [])
397
+ return list(dict.fromkeys(refs))
398
+
399
+
400
+ def _infer_environment_domain(step: dict[str, Any] | None = None, tool: str | None = None, operation: str | None = None) -> str:
401
+ step = step or {}
402
+ raw = " ".join(str(v or "") for v in [tool or step.get("tool"), operation or step.get("operation"), step.get("action"), step.get("interaction_surface"), step.get("input"), step.get("output")]).lower()
403
+ if any(x in raw for x in ["industrial", "plc", "scada", "manufacturing", "telemetry"]):
404
+ return "industrial"
405
+ if any(x in raw for x in ["robot", "actuator", "sensor", "physical"]):
406
+ return "robotics" if "robot" in raw else "physical"
407
+ if any(x in raw for x in ["desktop", "computer_use", "accessibility", "ui automation"]):
408
+ return "computer_use"
409
+ if any(x in raw for x in ["bash", "shell", "terminal", "codex_cli", "command"]):
410
+ return "terminal"
411
+ if any(x in raw for x in ["browser", "playwright"]):
412
+ return "browser"
413
+ if any(x in raw for x in ["search", "web"]):
414
+ return "search"
415
+ if any(x in raw for x in ["file", "read", "write"]):
416
+ return "file"
417
+ if any(x in raw for x in ["code", "test", "build", "edit"]):
418
+ return "codebase"
419
+ if any(x in raw for x in ["sql", "database", "db"]):
420
+ return "database"
421
+ if any(x in raw for x in ["sheet", "spreadsheet", "xlsx", "csv"]):
422
+ return "spreadsheet"
423
+ if any(x in raw for x in ["document", "pdf", "ocr", "image", "audio", "video", "screenshot", "table"]):
424
+ return "multimodal"
425
+ if "mcp" in raw:
426
+ return "mcp"
427
+ if "api" in raw:
428
+ return "api"
429
+ return str(step.get("environment_domain") or "unknown") if step.get("environment_domain") in ENVIRONMENT_DOMAINS else "unknown"
430
+
431
+
432
+ def _weak_strong_proxy(evidence: dict[str, Any]) -> dict[str, Any]:
433
+ hill = evidence.get("hillclimb") or {}
434
+ if hill.get("baseline_score") is not None and hill.get("revised_score") is not None:
435
+ weak = _score(hill.get("baseline_score"))
436
+ strong = _score(hill.get("revised_score"))
437
+ gap = strong - weak
438
+ return {
439
+ "available": True,
440
+ "weak_score_proxy": weak,
441
+ "strong_score_proxy": strong,
442
+ "solver_gap_proxy": round(gap, 4),
443
+ "weak_pattern": _clean_text("; ".join(hill.get("failed_criteria_before") or []) or "Baseline attempt before revision."),
444
+ "strong_pattern": _clean_text("; ".join(hill.get("criteria_improved") or []) or "Revised or selected attempt had stronger evidence."),
445
+ "gap_interpretation": "Revised evidence outperformed baseline." if gap > 0 else "No positive solver gap was observed.",
446
+ "grpo_suitability": "high" if gap >= 0.2 else "medium" if gap > 0.05 else "low",
447
+ "learning_signal_quality": "high" if gap >= 0.2 else "medium" if gap > 0 else "low",
448
+ }
449
+ graders = list((evidence.get("grader_results") or {}).values())
450
+ if len(graders) >= 2:
451
+ scores = [_score(g.get("final_score") if g.get("final_score") is not None else g.get("score")) for g in graders]
452
+ weak = min(scores)
453
+ strong = max(scores)
454
+ gap = strong - weak
455
+ return {
456
+ "available": True,
457
+ "weak_score_proxy": weak,
458
+ "strong_score_proxy": strong,
459
+ "solver_gap_proxy": round(gap, 4),
460
+ "weak_pattern": "Lower-scoring attempt.",
461
+ "strong_pattern": "Higher-scoring attempt.",
462
+ "gap_interpretation": "Score gap inferred from grader results.",
463
+ "grpo_suitability": "medium" if gap > 0.05 else "low",
464
+ "learning_signal_quality": "medium" if gap > 0 else "low",
465
+ }
466
+ return {
467
+ "available": False,
468
+ "weak_score_proxy": None,
469
+ "strong_score_proxy": None,
470
+ "solver_gap_proxy": None,
471
+ "weak_pattern": None,
472
+ "strong_pattern": None,
473
+ "gap_interpretation": "Weak/strong proxy unavailable from this package.",
474
+ "grpo_suitability": "unknown",
475
+ "learning_signal_quality": "unknown",
476
+ }
477
+
478
+
479
+ def _source_audit_status(evidence: dict[str, Any]) -> dict[str, Any]:
480
+ task_md = (evidence.get("task") or {}).get("metadata_json") or {}
481
+ rubric_md = (evidence.get("rubric") or {}).get("metadata_json") or {}
482
+ task_audited = bool(task_md.get("mentor_audited"))
483
+ rubric_audited = bool(rubric_md.get("mentor_audited"))
484
+ return {
485
+ "source_task_spec_mentor_audited": task_audited,
486
+ "source_task_spec_audit_source": task_md.get("mentor_audit_source"),
487
+ "source_task_spec_audit_status": task_md.get("audit_status") or ("mentor_audited" if task_audited else "unaudited"),
488
+ "source_rubric_mentor_audited": rubric_audited,
489
+ "source_rubric_audit_source": rubric_md.get("mentor_audit_source"),
490
+ "source_rubric_audit_status": rubric_md.get("audit_status") or ("mentor_audited" if rubric_audited else "unaudited"),
491
+ }
492
+
493
+
494
+ 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]:
495
+ row = {
496
+ "source_package_id": evidence["package_id"],
497
+ "source_task_id": _source_task_id(evidence),
498
+ "source_trace_id": None,
499
+ "source_step_ids": [],
500
+ "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),
504
+ "provenance": "derived_from_observed_agent_apprenticeship_package",
505
+ "quality_score": quality,
506
+ "fidelity_score": min(1.0, quality + 0.04),
507
+ "difficulty_score": min(1.0, max(0.35, quality - 0.08)),
508
+ "learnability_score": min(1.0, quality + 0.02),
509
+ "reuse_value_score": min(1.0, quality + 0.03),
510
+ "weak_score_proxy": proxy.get("weak_score_proxy"),
511
+ "strong_score_proxy": proxy.get("strong_score_proxy"),
512
+ "solver_gap_proxy": proxy.get("solver_gap_proxy"),
513
+ "weak_pattern": proxy.get("weak_pattern"),
514
+ "strong_pattern": proxy.get("strong_pattern"),
515
+ "gap_interpretation": proxy.get("gap_interpretation"),
516
+ "grpo_suitability": proxy.get("grpo_suitability"),
517
+ "generated_by": "Experience Compiler",
518
+ "generator_mode": settings.worker_agent,
519
+ "tdo_judge_source": judge_source,
520
+ "mentor_audited": mentor_audited,
521
+ "tdo_round": round_idx,
522
+ "accepted": False,
523
+ "rejection_reason": None,
524
+ "created_at": utc_now(),
525
+ }
526
+ if judge_source == "apprentice_self_judge":
527
+ row["self_judge_generation_notes"] = [
528
+ "judge_source=apprentice_self_judge",
529
+ "recommended_future_mentor_audit",
530
+ ]
531
+ row["recommended_future_mentor_audit"] = True
532
+ return row
533
+
534
+
535
+ def _has_concrete_source_evidence(row: dict[str, Any]) -> bool:
536
+ """True when a row is grounded beyond task/rubric metadata alone."""
537
+ if row.get("source_trace_id"):
538
+ return True
539
+ for key in ("source_step_ids", "source_attempt_ids", "source_artifact_refs", "source_evaluation_refs"):
540
+ if _refs(row.get(key) or []):
541
+ return True
542
+ for ref in _refs(row.get("evidence_refs") or []):
543
+ if ref.startswith(("traces/", "attempts/", "artifacts/", "outputs/", "evaluation/", "grading/", "feedback/", "signals/", "loops/")):
544
+ return True
545
+ return False
546
+
547
+
548
+ 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
+ task = evidence.get("task") or {}
550
+ raw = evidence.get("raw_task") or {}
551
+ rubric = evidence.get("rubric") or {}
552
+ attempts = evidence.get("attempts") or {}
553
+ quality = min(0.92, 0.58 + 0.11 * round_idx)
554
+ common = _base_row(evidence, settings, round_idx, judge_source, mentor_audited, proxy, quality)
555
+ title = task.get("normalized_title") or raw.get("raw_title") or raw.get("title") or evidence["package_id"]
556
+ instruction = task.get("normalized_instruction") or raw.get("raw_description") or raw.get("instruction") or title
557
+ deliverables = task.get("output_requirements") or raw.get("output_requirements") or ["task artifact"]
558
+ rows: list[dict[str, Any]] = []
559
+ rows.append({
560
+ **common,
561
+ "data_type": "training_example",
562
+ "input": {"instruction": _clean_text(instruction), "expected_deliverables": deliverables},
563
+ "target": {"artifact_refs": _source_artifact_refs(evidence), "summary": _best_output_summary(evidence)},
564
+ "expected_output": _best_output_summary(evidence),
565
+ "rubric": rubric.get("rubric_items") or [],
566
+ "scoring_hint": "Use actual_outputs.json, artifact refs, traces, and mentor/verifier/evaluator evidence.",
567
+ "training_use_case": "supervised_task_completion",
568
+ })
569
+ for item in rubric.get("rubric_items") or []:
570
+ rows.append({
571
+ **common,
572
+ "data_type": "evaluation_example",
573
+ "input": {"artifact_refs": _source_artifact_refs(evidence), "criterion": item.get("criterion_name")},
574
+ "target": {"criterion_description": item.get("criterion_description"), "pass_threshold": item.get("pass_threshold")},
575
+ "expected_output": "Score the attempt against the criterion using grounded evidence.",
576
+ "rubric": item,
577
+ "scoring_hint": "Do not reward easier or more permissive rubrics.",
578
+ })
579
+ for attempt_kind, verifier in (evidence.get("verifier_results") or {}).items():
580
+ rows.append({
581
+ **common,
582
+ "data_type": "verifier_example",
583
+ "input": {"attempt_kind": attempt_kind, "artifact_refs": _source_artifact_refs(evidence)},
584
+ "target": verifier,
585
+ "expected_output": "Verify grounding, artifact contract, score consistency, and leakage checks.",
586
+ "rubric": rubric.get("rubric_items") or [],
587
+ "scoring_hint": "Verifier output is evidence; Apprentice Agent self-assessment is not outcome evidence.",
588
+ })
589
+ for attempt_kind, grader in (evidence.get("grader_results") or {}).items():
590
+ rows.append({
591
+ **common,
592
+ "data_type": "reward_model_row",
593
+ "input": {"attempt_kind": attempt_kind, "output_summary": _attempt_summary(attempts.get(attempt_kind) or {})},
594
+ "target": {"final_score": grader.get("final_score", grader.get("score")), "passed": grader.get("passed")},
595
+ "expected_output": "Prefer higher-quality grounded outputs with verifier-consistent evidence.",
596
+ "rubric": rubric.get("rubric_items") or [],
597
+ "scoring_hint": "Use grader and verifier evidence as reward-model labels.",
598
+ })
599
+ if proxy.get("available"):
600
+ rows.append({
601
+ **common,
602
+ "data_type": "preference_pair",
603
+ "input": {"baseline_attempt_ref": "attempts/baseline", "revised_attempt_ref": "attempts/revised"},
604
+ "target": {
605
+ "chosen": "revised" if (proxy.get("strong_score_proxy") or 0) >= (proxy.get("weak_score_proxy") or 0) else "baseline",
606
+ "solver_gap_proxy": proxy.get("solver_gap_proxy"),
607
+ },
608
+ "expected_output": "Choose the attempt with stronger grounded evidence.",
609
+ "rubric": rubric.get("rubric_items") or [],
610
+ "scoring_hint": "Preference is based on observed score/verifier gap, not a new model claim.",
611
+ })
612
+ for attempt_kind, grader in (evidence.get("grader_results") or {}).items():
613
+ for criterion in grader.get("failed_criteria") or []:
614
+ rows.append({
615
+ **common,
616
+ "data_type": "failure_case",
617
+ "input": {"attempt_kind": attempt_kind, "failed_criterion": criterion},
618
+ "target": {"failure_mode": criterion, "repair_hint": _clean_text((evidence.get("evaluator_feedback") or {}).get("feedback_summary"))},
619
+ "expected_output": "Identify the failure and propose grounded repair guidance.",
620
+ "rubric": rubric.get("rubric_items") or [],
621
+ "scoring_hint": "Failure cases must point back to grader/verifier/evaluator evidence.",
622
+ })
623
+ rows.append({
624
+ **common,
625
+ "data_type": "task_variant",
626
+ "input": {"source_instruction": _clean_text(instruction)},
627
+ "target": {
628
+ "variant_instruction": _clean_text(f"{instruction}\n\nVariant constraint: preserve the same deliverables and make the result easier to verify."),
629
+ "expected_deliverables": deliverables,
630
+ },
631
+ "expected_output": "A grounded task variant with the same evidence-backed artifact contract.",
632
+ "rubric": rubric.get("rubric_items") or [],
633
+ "scoring_hint": "Variant must preserve artifact requirements and verifier checks.",
634
+ })
635
+ rows.extend(_environment_response_rows(evidence, common))
636
+ rows.extend(_surface_event_rows(evidence, common))
637
+ return [sanitize_public_obj(row) for row in rows]
638
+
639
+
640
+ def _best_output_summary(evidence: dict[str, Any]) -> str:
641
+ selected = (evidence.get("manifest") or {}).get("selected_attempt_id")
642
+ for attempt in (evidence.get("attempts") or {}).values():
643
+ actual = attempt.get("actual_outputs") or {}
644
+ if selected and actual.get("attempt_id") == selected and actual.get("output_summary"):
645
+ return _clean_text(actual.get("output_summary"))
646
+ for attempt in (evidence.get("attempts") or {}).values():
647
+ actual = attempt.get("actual_outputs") or {}
648
+ if actual.get("output_summary"):
649
+ return _clean_text(actual.get("output_summary"))
650
+ return "No output summary was available."
651
+
652
+
653
+ def _attempt_summary(attempt: dict[str, Any]) -> str:
654
+ return _clean_text((attempt.get("actual_outputs") or {}).get("output_summary") or "Attempt output summary unavailable.")
655
+
656
+
657
+ def _environment_response_rows(evidence: dict[str, Any], common: dict[str, Any]) -> list[dict[str, Any]]:
658
+ rows: list[dict[str, Any]] = []
659
+ for attempt in (evidence.get("attempts") or {}).values():
660
+ trace = attempt.get("agent_trace") or {}
661
+ steps = trace.get("steps") or []
662
+ total = len(steps)
663
+ previous_state = ""
664
+ for idx, step in enumerate(steps, start=1):
665
+ if step.get("action") == "user_message":
666
+ previous_state = _clean_text(step.get("state_change") or previous_state)
667
+ continue
668
+ domain = step.get("environment_domain") if step.get("environment_domain") in ENVIRONMENT_DOMAINS else _infer_environment_domain(step)
669
+ score = 0.72
670
+ if step.get("success") is True:
671
+ score = 0.82
672
+ if step.get("step_outcome") in {"failed", "blocked"}:
673
+ score = 0.45
674
+ rows.append({
675
+ **common,
676
+ "data_type": "environment_response_pair",
677
+ "source_trace_id": trace.get("trace_id"),
678
+ "source_step_id": step.get("step") or idx,
679
+ "source_step_ids": [step.get("step") or idx],
680
+ "environment_domain": domain,
681
+ "interaction_surface": step.get("interaction_surface") or _surface_from_step(step),
682
+ "state_before": _clean_text(previous_state or step.get("observation")),
683
+ "agent_action": _clean_text(step.get("input") or step.get("action") or step.get("operation")),
684
+ "observed_environment_response": _clean_text(step.get("output") or step.get("observation")),
685
+ "state_after": _clean_text(step.get("state_change")),
686
+ "success_signal": _clean_text(step.get("step_outcome") or step.get("success")),
687
+ "failure_mode": _clean_text(step.get("error_type") or ""),
688
+ "verifier_feedback": _clean_text(_first_verifier_notes(evidence)),
689
+ "turn_idx": idx,
690
+ "total_turns": total,
691
+ "format_score": score,
692
+ "factuality_score": score,
693
+ "consistency_score": score,
694
+ "realism_score": score,
695
+ "training_quality_score": score,
696
+ "environment_response_quality_score": score,
697
+ "usable_for": ["environment_model_training", "agent_rl", "verifier_training"],
698
+ })
699
+ previous_state = _clean_text(step.get("state_change") or previous_state)
700
+ return rows
701
+
702
+
703
+ def _surface_from_step(step: dict[str, Any]) -> str:
704
+ surface = step.get("interaction_surface")
705
+ if surface:
706
+ return str(surface)
707
+ domain = step.get("environment_domain")
708
+ if domain in {"api", "database", "spreadsheet", "search", "browser", "computer_use", "file", "codebase"}:
709
+ return str(domain)
710
+ raw = " ".join(str(step.get(k) or "") for k in ["tool", "operation", "input", "output"]).lower()
711
+ if "mcp" in raw:
712
+ return "mcp_tool"
713
+ if any(x in raw for x in ["curl", "http", "graphql", "endpoint"]):
714
+ return "api"
715
+ if any(x in raw for x in ["sql", "table", "database"]):
716
+ return "database"
717
+ if any(x in raw for x in ["xlsx", "workbook", "spreadsheet", "csv"]):
718
+ return "spreadsheet"
719
+ if any(x in raw for x in ["bash", "shell", "command", "pytest", "git "]):
720
+ return "cli"
721
+ return "unknown"
722
+
723
+
724
+ def _surface_common(common: dict[str, Any], trace: dict[str, Any], step: dict[str, Any], idx: int, data_type: str) -> dict[str, Any]:
725
+ step_id = step.get("step") or idx
726
+ return {
727
+ **common,
728
+ "data_type": data_type,
729
+ "source_trace_id": trace.get("trace_id"),
730
+ "source_step_id": step_id,
731
+ "source_step_ids": [step_id],
732
+ "environment_domain": step.get("environment_domain") or _infer_environment_domain(step),
733
+ "interaction_surface": _surface_from_step(step),
734
+ "state_change": _clean_text(step.get("state_change")),
735
+ "success_signal": _clean_text(step.get("step_outcome") or step.get("success")),
736
+ "failure_mode": _clean_text(step.get("error_type") or step.get("error_message") or ""),
737
+ "training_quality_score": step.get("environment_response_quality_score") or step.get("training_quality_score") or common.get("quality_score"),
738
+ "usable_for": ["trace_training", "environment_response_training", "verifier_training"],
739
+ }
740
+
741
+
742
+ def _surface_event_rows(evidence: dict[str, Any], common: dict[str, Any]) -> list[dict[str, Any]]:
743
+ rows: list[dict[str, Any]] = []
744
+ for attempt in (evidence.get("attempts") or {}).values():
745
+ trace = attempt.get("agent_trace") or {}
746
+ for idx, step in enumerate(trace.get("steps") or [], start=1):
747
+ if step.get("action") == "user_message":
748
+ continue
749
+ surface = _surface_from_step(step)
750
+ domain = step.get("environment_domain") or _infer_environment_domain(step)
751
+ if step.get("subagent_id") or step.get("delegation_id") or step.get("subagent_output_refs"):
752
+ rows.append({
753
+ **_surface_common(common, trace, step, idx, "multi_agent_handoff"),
754
+ "parent_agent_id": step.get("parent_agent_id"),
755
+ "subagent_id": step.get("subagent_id"),
756
+ "handoff_reason": _clean_text(step.get("handoff_reason")),
757
+ "handoff_payload_ref": _normalize_ref(step.get("handoff_payload_ref")),
758
+ "subagent_output_refs": _refs(step.get("subagent_output_refs") or step.get("handoff_output_refs") or step.get("artifact_refs")),
759
+ "downstream_steps_caused": step.get("depends_on_agent_outputs") or [],
760
+ "handoff_quality_score": common.get("quality_score"),
761
+ "delegation_success": step.get("success"),
762
+ "reusable_lesson": _clean_text(step.get("state_change") or step.get("output")),
763
+ })
764
+ if step.get("modality") or step.get("modalities") or surface in {"multimodal", "document", "pdf", "table"} or domain == "multimodal":
765
+ refs = _refs(step.get("artifact_refs") or step.get("page_refs") or step.get("table_extract_refs"))
766
+ for ref in refs or [None]:
767
+ modality = step.get("modality")
768
+ if not modality and isinstance(step.get("modalities"), list) and step.get("modalities"):
769
+ modality = step.get("modalities")[0]
770
+ rows.append({
771
+ **_surface_common(common, trace, step, idx, "multimodal_artifact"),
772
+ "modality": modality or "unknown",
773
+ "artifact_ref": ref,
774
+ "artifact_role": step.get("artifact_role"),
775
+ "mime_type": step.get("mime_type"),
776
+ "artifact_hash": step.get("artifact_hash"),
777
+ "preview_ref": _normalize_ref(step.get("artifact_preview_ref")),
778
+ "extracted_text_ref": _normalize_ref(step.get("ocr_text_ref") or step.get("text_extract_ref")),
779
+ "transcript_ref": _normalize_ref(step.get("transcript_ref")),
780
+ "annotation_ref": _normalize_ref(step.get("annotation_ref")),
781
+ "quality_score": common.get("quality_score"),
782
+ "verifier_use": "artifact evidence",
783
+ "training_use_cases": ["multimodal_grounding", "verifier_training"],
784
+ "evidence_status": [] if ref else ["No artifact ref was captured for this multimodal step."],
785
+ })
786
+ if surface in {"browser", "computer_use"} or step.get("ui_surface") or step.get("screenshot_ref"):
787
+ rows.append({
788
+ **_surface_common(common, trace, step, idx, "ui_interaction_event"),
789
+ "ui_surface": step.get("ui_surface") or surface,
790
+ "app_name": step.get("app_name"),
791
+ "action_type": step.get("action_type") or step.get("operation"),
792
+ "visible_state_ref": _normalize_ref(step.get("visible_state_ref")),
793
+ "screenshot_ref": _normalize_ref(step.get("screenshot_ref")),
794
+ "element_selector": step.get("element_selector"),
795
+ "coordinates": step.get("coordinates"),
796
+ "input_text": _clean_text(step.get("input_text")),
797
+ "observed_result": _clean_text(step.get("output")),
798
+ })
799
+ if surface in {"device", "industrial", "physical"} or domain in {"device", "industrial", "physical", "robotics", "lab"}:
800
+ rows.append({
801
+ **_surface_common(common, trace, step, idx, "physical_environment_event"),
802
+ "physical_environment_type": step.get("physical_environment_type") or domain,
803
+ "device_type": step.get("device_type"),
804
+ "sensor_readings_ref": _normalize_ref(step.get("sensor_readings_ref")),
805
+ "actuator_command": _clean_text(step.get("actuator_command")),
806
+ "physical_state_before_ref": _normalize_ref(step.get("physical_state_before_ref")),
807
+ "physical_state_after_ref": _normalize_ref(step.get("physical_state_after_ref")),
808
+ "telemetry_ref": _normalize_ref(step.get("telemetry_ref")),
809
+ "safety_state": step.get("safety_state"),
810
+ "operator_override": step.get("operator_override"),
811
+ "simulation_or_real_world": step.get("simulation_or_real_world") or "unknown",
812
+ "safety_relevance": "safety_state captured" if step.get("safety_state") else "unknown",
813
+ "verifier_use": "safety and telemetry evidence",
814
+ "evidence_status": [] if step.get("telemetry_ref") or step.get("physical_state_after_ref") else ["Physical evidence refs were not captured."],
815
+ })
816
+ if surface == "api":
817
+ rows.append({
818
+ **_surface_common(common, trace, step, idx, "api_interaction_event"),
819
+ "api_provider": step.get("api_provider"),
820
+ "api_service": step.get("api_service"),
821
+ "api_endpoint": step.get("api_endpoint"),
822
+ "api_method": step.get("api_method"),
823
+ "http_status": step.get("http_status"),
824
+ "request_payload_ref": _normalize_ref(step.get("request_payload_ref") or step.get("raw_request_ref")),
825
+ "response_payload_ref": _normalize_ref(step.get("response_payload_ref") or step.get("raw_response_ref")),
826
+ "resource_id": step.get("resource_id"),
827
+ "object_type": step.get("object_type"),
828
+ "retry_count": step.get("retry_count"),
829
+ "verifier_use": "API request/response grounding",
830
+ })
831
+ if surface in {"mcp_tool", "mcp_resource", "mcp_prompt"}:
832
+ rows.append({
833
+ **_surface_common(common, trace, step, idx, "mcp_interaction_event"),
834
+ "mcp_server": step.get("mcp_server"),
835
+ "mcp_capability_type": step.get("mcp_capability_type") or surface.removeprefix("mcp_"),
836
+ "mcp_tool_name": step.get("mcp_tool_name"),
837
+ "mcp_resource_uri": step.get("mcp_resource_uri"),
838
+ "mcp_prompt_name": step.get("mcp_prompt_name"),
839
+ "arguments_ref": _normalize_ref(step.get("mcp_arguments_ref")),
840
+ "result_ref": _normalize_ref(step.get("mcp_result_ref")),
841
+ })
842
+ if surface == "database":
843
+ rows.append({
844
+ **_surface_common(common, trace, step, idx, "database_event"),
845
+ "database_type": step.get("database_type"),
846
+ "database_name": step.get("database_name"),
847
+ "schema_name": step.get("schema_name"),
848
+ "table_name": step.get("table_name"),
849
+ "query_type": step.get("query_type"),
850
+ "query_ref": _normalize_ref(step.get("query_ref")),
851
+ "row_count": step.get("row_count"),
852
+ "affected_row_count": step.get("affected_row_count"),
853
+ "transaction_id": step.get("transaction_id"),
854
+ })
855
+ if surface in {"spreadsheet", "table"}:
856
+ rows.append({
857
+ **_surface_common(common, trace, step, idx, "structured_table_event"),
858
+ "workbook_ref": _normalize_ref(step.get("workbook_ref")),
859
+ "sheet_name": step.get("sheet_name"),
860
+ "table_ref": _normalize_ref(step.get("table_ref")),
861
+ "cell_range": step.get("cell_range"),
862
+ "row_count": step.get("row_count"),
863
+ "column_count": step.get("column_count"),
864
+ "formula_refs": _refs(step.get("formula_refs")),
865
+ "before_table_ref": _normalize_ref(step.get("before_table_ref")),
866
+ "after_table_ref": _normalize_ref(step.get("after_table_ref")),
867
+ "diff_ref": _normalize_ref(step.get("diff_ref")),
868
+ })
869
+ if surface in {"cli", "file", "codebase"}:
870
+ rows.append({
871
+ **_surface_common(common, trace, step, idx, "software_execution_event"),
872
+ "command": _clean_text(step.get("command") or step.get("input")),
873
+ "command_ref": _normalize_ref(step.get("command_ref")),
874
+ "exit_code": step.get("exit_code"),
875
+ "stdout_ref": _normalize_ref(step.get("stdout_ref")),
876
+ "stderr_ref": _normalize_ref(step.get("stderr_ref")),
877
+ "file_path": _normalize_ref(step.get("file_path")),
878
+ "file_paths": _refs(step.get("file_paths") or step.get("artifact_refs")),
879
+ "file_operation": step.get("file_operation") or step.get("operation"),
880
+ "diff_ref": _normalize_ref(step.get("diff_ref")),
881
+ "test_result_ref": _normalize_ref(step.get("test_result_ref")),
882
+ })
883
+ if surface == "search":
884
+ rows.append({
885
+ **_surface_common(common, trace, step, idx, "search_retrieval_event"),
886
+ "search_provider": step.get("search_provider") or step.get("tool"),
887
+ "query": _clean_text(step.get("query") or step.get("input")),
888
+ "query_ref": _normalize_ref(step.get("query_ref")),
889
+ "search_scope": step.get("search_scope"),
890
+ "result_count": step.get("result_count"),
891
+ "selected_result_refs": _refs(step.get("selected_result_refs")),
892
+ "citation_refs": _refs(step.get("citation_refs")),
893
+ })
894
+ if surface in {"cloud_console", "saas_app"}:
895
+ rows.append({
896
+ **_surface_common(common, trace, step, idx, "enterprise_app_event"),
897
+ "service_name": step.get("service_name") or step.get("app_name"),
898
+ "workspace_id": step.get("workspace_id"),
899
+ "project_id": step.get("project_id"),
900
+ "resource_id": step.get("resource_id"),
901
+ "resource_type": step.get("resource_type"),
902
+ "record_id": step.get("record_id"),
903
+ "ticket_id": step.get("ticket_id"),
904
+ "object_state_before_ref": _normalize_ref(step.get("object_state_before_ref")),
905
+ "object_state_after_ref": _normalize_ref(step.get("object_state_after_ref")),
906
+ "audit_log_ref": _normalize_ref(step.get("audit_log_ref")),
907
+ })
908
+ if surface in {"async_job", "queue", "webhook", "scheduler"}:
909
+ rows.append({
910
+ **_surface_common(common, trace, step, idx, "async_workflow_event"),
911
+ "job_id": step.get("job_id"),
912
+ "queue_name": step.get("queue_name"),
913
+ "message_id": step.get("message_id"),
914
+ "webhook_id": step.get("webhook_id"),
915
+ "schedule_id": step.get("schedule_id"),
916
+ "trigger_type": step.get("trigger_type"),
917
+ "retry_count": step.get("retry_count"),
918
+ "delivery_status": step.get("delivery_status"),
919
+ "polling_status": step.get("polling_status"),
920
+ "callback_status": step.get("callback_status"),
921
+ "job_status_before": step.get("job_status_before"),
922
+ "job_status_after": step.get("job_status_after"),
923
+ "async_result_ref": _normalize_ref(step.get("async_result_ref")),
924
+ })
925
+ if surface in {"messaging", "email", "collaboration"}:
926
+ rows.append({
927
+ **_surface_common(common, trace, step, idx, "collaboration_event"),
928
+ "platform_name": step.get("platform_name"),
929
+ "channel_id": step.get("channel_id"),
930
+ "thread_id": step.get("thread_id"),
931
+ "message_id": step.get("message_id"),
932
+ "sender_role": step.get("sender_role"),
933
+ "recipient_role": step.get("recipient_role"),
934
+ "message_type": step.get("message_type"),
935
+ "message_ref": _normalize_ref(step.get("message_ref")),
936
+ "attachment_refs": _refs(step.get("attachment_refs")),
937
+ "decision_refs": _refs(step.get("decision_refs")),
938
+ "followup_required": step.get("followup_required"),
939
+ })
940
+ if surface in {"document", "pdf"}:
941
+ rows.append({
942
+ **_surface_common(common, trace, step, idx, "document_event"),
943
+ "document_ref": _normalize_ref(step.get("document_ref")),
944
+ "document_type": step.get("document_type") or surface,
945
+ "page_refs": _refs(step.get("page_refs")),
946
+ "section_refs": _refs(step.get("section_refs")),
947
+ "text_extract_ref": _normalize_ref(step.get("text_extract_ref") or step.get("ocr_text_ref")),
948
+ "table_extract_refs": _refs(step.get("table_extract_refs")),
949
+ "figure_refs": _refs(step.get("figure_refs")),
950
+ "citation_refs": _refs(step.get("citation_refs")),
951
+ "annotation_refs": _refs(step.get("annotation_refs")),
952
+ "summary_ref": _normalize_ref(step.get("summary_ref")),
953
+ })
954
+ if surface == "mixed" or step.get("connected_surfaces") or step.get("cross_system_dependencies"):
955
+ rows.append({
956
+ **_surface_common(common, trace, step, idx, "cross_system_event"),
957
+ "connected_surfaces": step.get("connected_surfaces") or [],
958
+ "cross_system_dependencies": step.get("cross_system_dependencies") or [],
959
+ "handoff_refs": _refs(step.get("handoff_refs")),
960
+ "state_sync_refs": _refs(step.get("state_sync_refs")),
961
+ "consistency_checks": step.get("consistency_checks") or [],
962
+ "system_of_record": step.get("system_of_record"),
963
+ })
964
+ return [sanitize_public_obj(row) for row in rows]
965
+
966
+
967
+ def _first_verifier_notes(evidence: dict[str, Any]) -> str | None:
968
+ for result in (evidence.get("verifier_results") or {}).values():
969
+ if result.get("verifier_notes"):
970
+ return str(result.get("verifier_notes"))
971
+ return None
972
+
973
+
974
+ def _private_or_forbidden_hit(obj: Any) -> str | None:
975
+ if isinstance(obj, dict):
976
+ for key, value in obj.items():
977
+ key_l = str(key).lower()
978
+ if key_l in FORBIDDEN_KEYS:
979
+ return key_l
980
+ hit = _private_or_forbidden_hit(value)
981
+ if hit:
982
+ return hit
983
+ return None
984
+ if isinstance(obj, list):
985
+ for value in obj:
986
+ hit = _private_or_forbidden_hit(value)
987
+ if hit:
988
+ return hit
989
+ return None
990
+ if isinstance(obj, str):
991
+ if contains_secret(obj):
992
+ return "secret_like_value"
993
+ if PRIVATE_RE.search(obj):
994
+ return "private_local_path"
995
+ lower = obj.lower()
996
+ for token in [("deep" + "seek"), ("worker " + "agent"), "worker_agent", ("market" + "place"), ("b" + "uyer"), ("s" + "eller")]:
997
+ if token in lower:
998
+ return token
999
+ return None
1000
+
1001
+
1002
+ def _artifact_refs_resolve(package_root: Path, row: dict[str, Any]) -> list[str]:
1003
+ missing: list[str] = []
1004
+ for ref in _refs(row.get("source_artifact_refs") or []):
1005
+ if ":" in ref:
1006
+ continue
1007
+ candidates = [package_root / ref]
1008
+ if ref.startswith("packages/"):
1009
+ candidates.append(package_root.parent / ref.removeprefix("packages/"))
1010
+ if not any(p.exists() for p in candidates):
1011
+ # Evidence refs can legitimately describe generated aggregate rows.
1012
+ if ref.startswith(("grading/", "feedback/", "signals/", "task/", "rubric/")):
1013
+ continue
1014
+ missing.append(ref)
1015
+ return missing
1016
+
1017
+
1018
+ def _validate_rows(package_root: Path, rows: list[dict[str, Any]], min_quality: float, min_fidelity: float, min_reuse: float) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[str]]:
1019
+ accepted: list[dict[str, Any]] = []
1020
+ rejected: list[dict[str, Any]] = []
1021
+ schema_errors: list[str] = []
1022
+ for row in rows:
1023
+ reason = _private_or_forbidden_hit(row)
1024
+ missing = _artifact_refs_resolve(package_root, row)
1025
+ if missing:
1026
+ reason = reason or f"unresolved_artifact_ref:{missing[0]}"
1027
+ if not row.get("data_type"):
1028
+ reason = reason or "missing_data_type"
1029
+ if not _has_concrete_source_evidence(row):
1030
+ reason = reason or "insufficient_concrete_source_evidence"
1031
+ if _score(row.get("quality_score")) < min_quality:
1032
+ reason = reason or "quality_score_below_threshold"
1033
+ if _score(row.get("fidelity_score")) < min_fidelity:
1034
+ reason = reason or "fidelity_score_below_threshold"
1035
+ if _score(row.get("reuse_value_score")) < min_reuse:
1036
+ reason = reason or "reuse_value_score_below_threshold"
1037
+ clean = sanitize_public_obj(dict(row))
1038
+ if reason:
1039
+ clean["accepted"] = False
1040
+ clean["rejection_reason"] = reason
1041
+ rejected.append(clean)
1042
+ schema_errors.append(reason)
1043
+ else:
1044
+ clean["accepted"] = True
1045
+ clean["rejection_reason"] = None
1046
+ accepted.append(clean)
1047
+ return accepted, rejected, schema_errors
1048
+
1049
+
1050
+ def _judge(round_idx: int, max_rounds: int, rows: list[dict[str, Any]], rejected: list[dict[str, Any]], judge_source: str, mentor_audited: bool, proxy: dict[str, Any]) -> TDOJudgeResult:
1051
+ if not rows:
1052
+ verdict = "reject"
1053
+ reason = "No candidate rows were generated from package evidence."
1054
+ elif rejected and round_idx >= max_rounds:
1055
+ verdict = "improve"
1056
+ reason = "Some candidate rows need stronger grounding or safer refs."
1057
+ elif round_idx < max_rounds:
1058
+ verdict = "improve"
1059
+ reason = "Autodata-style loop continues to improve weak rows and recipe grounding."
1060
+ else:
1061
+ verdict = "accept"
1062
+ reason = "Candidate rows passed schema, grounding, and sanitization guardrails."
1063
+ scores = [_score(row.get("quality_score"), 0.0) for row in rows] or [0.0]
1064
+ fidelity = [_score(row.get("fidelity_score"), 0.0) for row in rows] or [0.0]
1065
+ difficulty = [_score(row.get("difficulty_score"), 0.0) for row in rows] or [0.0]
1066
+ learnability = [_score(row.get("learnability_score"), 0.0) for row in rows] or [0.0]
1067
+ reuse = [_score(row.get("reuse_value_score"), 0.0) for row in rows] or [0.0]
1068
+ result = TDOJudgeResult(
1069
+ tdo_round=round_idx,
1070
+ tdo_judge_source=judge_source, # type: ignore[arg-type]
1071
+ mentor_audited=mentor_audited,
1072
+ verdict=verdict, # type: ignore[arg-type]
1073
+ weak_pattern=proxy.get("weak_pattern"),
1074
+ strong_pattern=proxy.get("strong_pattern"),
1075
+ gap_interpretation=proxy.get("gap_interpretation"),
1076
+ rubric_concerns=[],
1077
+ grounding_concerns=[row.get("rejection_reason") for row in rejected if row.get("rejection_reason")],
1078
+ data_quality_concerns=[] if rows else ["no_candidate_rows"],
1079
+ grpo_suitability=proxy.get("grpo_suitability") or "unknown",
1080
+ learning_signal_quality=proxy.get("learning_signal_quality") or "unknown",
1081
+ reuse_value="high" if mean(reuse) >= 0.8 else "medium" if mean(reuse) >= 0.6 else "low",
1082
+ quality_score=round(mean(scores), 4),
1083
+ fidelity_score=round(mean(fidelity), 4),
1084
+ difficulty_score=round(mean(difficulty), 4),
1085
+ learnability_score=round(mean(learnability), 4),
1086
+ reuse_value_score=round(mean(reuse), 4),
1087
+ verdict_reason=reason,
1088
+ suggestion_for_generator=("Strengthen evidence refs, keep rows grounded, and improve weak rows in the next compiler round." if verdict in {"improve", "reject"} else None),
1089
+ )
1090
+ return result
1091
+
1092
+
1093
+ def _optimized_task_spec(evidence: dict[str, Any], settings: Settings, round_idx: int) -> dict[str, Any]:
1094
+ task = evidence.get("task") or {}
1095
+ raw = evidence.get("raw_task") or {}
1096
+ proxy = _weak_strong_proxy(evidence)
1097
+ instruction = task.get("normalized_instruction") or raw.get("raw_description") or raw.get("instruction") or task.get("normalized_title") or evidence["package_id"]
1098
+ domain = task.get("domain") or raw.get("domain") or (raw.get("raw_payload") or {}).get("domain") or "general"
1099
+ subdomain = task.get("subdomain") or raw.get("subdomain") or (raw.get("raw_payload") or {}).get("subdomain")
1100
+ return sanitize_public_obj({
1101
+ "source_task_id": _source_task_id(evidence),
1102
+ "source_package_id": evidence["package_id"],
1103
+ "optimized_title": task.get("normalized_title") or raw.get("raw_title") or evidence["package_id"],
1104
+ "optimized_instruction": instruction,
1105
+ "domain": domain,
1106
+ "subdomain": subdomain,
1107
+ "role": task.get("apprenticeship_role") or task.get("professional_role") or "Apprentice Agent",
1108
+ "expected_deliverables": task.get("output_requirements") or [task.get("expected_agent_deliverable") or "task artifacts"],
1109
+ "constraints": task.get("constraints") or [],
1110
+ "required_inputs": task.get("input_requirements") or [],
1111
+ "success_criteria": [item.get("criterion_description") for item in (evidence.get("rubric") or {}).get("rubric_items", [])],
1112
+ "failure_modes": list(dict.fromkeys((evidence.get("hillclimb") or {}).get("failed_criteria_before") or [])),
1113
+ "expected_economic_value": task.get("expected_economic_value") or raw.get("expected_economic_value") or "category_unknown",
1114
+ "expected_economic_value_rationale": "Derived from task metadata, deliverable requirements, trace complexity, and evaluation evidence.",
1115
+ "training_use_cases": ["supervised_finetuning_rows", "verifier_training", "reward_modeling", "preference_optimization"],
1116
+ "generated_by": "Experience Compiler",
1117
+ "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
1118
+ "compiler_round": round_idx,
1119
+ "tdo_round": round_idx,
1120
+ "weak_strong_proxy_available": proxy.get("available"),
1121
+ **_source_audit_status(evidence),
1122
+ "evidence_refs": _evidence_refs(evidence),
1123
+ })
1124
+
1125
+
1126
+ def _optimized_rubric(evidence: dict[str, Any], round_idx: int) -> dict[str, Any]:
1127
+ rubric = evidence.get("rubric") or {}
1128
+ items = rubric.get("rubric_items") or []
1129
+ return sanitize_public_obj({
1130
+ "source_task_id": _source_task_id(evidence),
1131
+ "source_package_id": evidence["package_id"],
1132
+ "tdo_round": round_idx,
1133
+ "rubric_items": items,
1134
+ "verifier_checks": [
1135
+ "actual_outputs.json parses and points to generated artifacts",
1136
+ "artifact refs resolve inside the package",
1137
+ "trace steps support the claimed output",
1138
+ "grader/verifier/evaluator evidence is consistent",
1139
+ ],
1140
+ "scoring_guidance": "Use grounded artifact, trace, grader, verifier, and evaluator evidence. Do not reward permissive rubrics.",
1141
+ "failure_criteria": list(dict.fromkeys((evidence.get("hillclimb") or {}).get("failed_criteria_before") or [])),
1142
+ "artifact_checks": rubric.get("required_artifacts") or [ref for ref in _source_artifact_refs(evidence) if "/artifacts/" in ref],
1143
+ "environment_response_checks": ["environment_response_pairs.jsonl rows should reflect observed trace steps only"],
1144
+ "reward_modeling_notes": "Use score/verifier evidence and weak/strong proxies where available.",
1145
+ "evaluator_notes": _clean_text((evidence.get("evaluator_feedback") or {}).get("feedback_summary")),
1146
+ **_source_audit_status(evidence),
1147
+ "evidence_refs": _evidence_refs(evidence),
1148
+ })
1149
+
1150
+
1151
+ def _domain_value_profile(evidence: dict[str, Any]) -> dict[str, Any]:
1152
+ task = evidence.get("task") or {}
1153
+ raw = evidence.get("raw_task") or {}
1154
+ domain = task.get("domain") or raw.get("domain") or (raw.get("raw_payload") or {}).get("domain") or "general"
1155
+ subdomain = task.get("subdomain") or raw.get("subdomain") or (raw.get("raw_payload") or {}).get("subdomain")
1156
+ traced_steps = sum(len((attempt.get("agent_trace") or {}).get("steps") or []) for attempt in (evidence.get("attempts") or {}).values())
1157
+ value_category = "high" if traced_steps >= 12 else "medium" if traced_steps >= 5 else "emerging"
1158
+ return sanitize_public_obj({
1159
+ "domain": domain,
1160
+ "subdomain": subdomain,
1161
+ "role": task.get("apprenticeship_role") or task.get("professional_role") or "Apprentice Agent",
1162
+ "apprenticeship_mode": (evidence.get("manifest") or {}).get("apprenticeship_mode"),
1163
+ "economic_value_category": value_category,
1164
+ "expected_economic_value": task.get("expected_economic_value") or raw.get("expected_economic_value") or value_category,
1165
+ "value_drivers": ["specialized workflow", "artifact creation", "verifiable outputs", "reusable trace evidence"],
1166
+ "risk_level": "medium" if traced_steps >= 8 else "low",
1167
+ "specialization_level": "high" if task.get("subdomain") else "medium",
1168
+ "automation_potential": "high" if evidence.get("process_supervision") else "medium",
1169
+ "training_value": "high" if traced_steps >= 8 else "medium",
1170
+ "evaluation_value": "high" if evidence.get("grader_results") and evidence.get("verifier_results") else "medium",
1171
+ "verifier_value": "high" if evidence.get("verifier_results") else "medium",
1172
+ "evidence_refs": _evidence_refs(evidence),
1173
+ })
1174
+
1175
+
1176
+ def _environment_spec(evidence: dict[str, Any]) -> dict[str, Any]:
1177
+ domains: list[str] = []
1178
+ surfaces: list[str] = []
1179
+ tools: list[str] = []
1180
+ operations: list[str] = []
1181
+ steps: list[dict[str, Any]] = []
1182
+ for attempt in (evidence.get("attempts") or {}).values():
1183
+ for step in (attempt.get("agent_trace") or {}).get("steps") or []:
1184
+ if step.get("action") == "user_message":
1185
+ continue
1186
+ steps.append(step)
1187
+ domains.append(_infer_environment_domain(step))
1188
+ surfaces.append(_surface_from_step(step))
1189
+ if step.get("tool"):
1190
+ tools.append(str(step.get("tool")))
1191
+ if step.get("operation"):
1192
+ operations.append(str(step.get("operation")))
1193
+ unique_domains = sorted(set(domains) - {"unknown"})
1194
+ domain = unique_domains[0] if len(unique_domains) == 1 else "mixed" if unique_domains else "unknown"
1195
+ environment_type = "local_filesystem" if domain == "file" else domain if domain in {"terminal", "browser", "codebase", "spreadsheet", "api", "mixed"} else "unknown"
1196
+ unique_surfaces = sorted(set(surfaces) - {"unknown"})
1197
+ def refs_from(*keys: str) -> list[str]:
1198
+ out: list[str] = []
1199
+ for step in steps:
1200
+ for key in keys:
1201
+ out.extend(_refs(step.get(key)))
1202
+ ref = _normalize_ref(step.get(key))
1203
+ if ref:
1204
+ out.append(ref)
1205
+ return list(dict.fromkeys(out))
1206
+ def values_from(*keys: str) -> list[str]:
1207
+ vals: list[str] = []
1208
+ for step in steps:
1209
+ for key in keys:
1210
+ value = step.get(key)
1211
+ if isinstance(value, list):
1212
+ vals.extend(str(v) for v in value if v not in (None, ""))
1213
+ elif value not in (None, ""):
1214
+ vals.append(str(value))
1215
+ return sorted(set(vals))
1216
+ multi_agent_present = any(step.get("subagent_id") or step.get("delegation_id") for step in steps)
1217
+ multimodal_present = any(step.get("modality") or step.get("modalities") or _surface_from_step(step) in {"multimodal", "document", "pdf", "table"} for step in steps)
1218
+ ui_present = any(_surface_from_step(step) in {"browser", "computer_use", "cloud_console", "saas_app"} or step.get("screenshot_ref") or step.get("visible_state_ref") for step in steps)
1219
+ physical_present = any(_surface_from_step(step) in {"device", "industrial", "physical"} or (step.get("environment_domain") in {"device", "industrial", "physical", "robotics", "lab"}) for step in steps)
1220
+ return sanitize_public_obj({
1221
+ "environment_type": environment_type,
1222
+ "environment_domain": domain,
1223
+ "interaction_surfaces": unique_surfaces,
1224
+ "surface_requirements": {
1225
+ surface: "capture surface-specific ids, state refs, requests/responses, evidence refs, and status transitions when available"
1226
+ for surface in unique_surfaces
1227
+ },
1228
+ "required_tools": sorted(set(tools)),
1229
+ "runtime_dependencies": ["python"] if any("python" in t.lower() for t in tools) else [],
1230
+ "setup_commands": [],
1231
+ "input_artifacts": [ref for ref in _source_artifact_refs(evidence) if ref.startswith("input/") or "/input/" in ref],
1232
+ "initial_state_refs": ["task/task_intake_spec.json", "rubric/rubric.json"],
1233
+ "allowed_actions": sorted(set(operations)),
1234
+ "reset_strategy": "copy_workspace" if environment_type in {"local_filesystem", "codebase", "terminal"} else "unknown",
1235
+ "verification_commands": [],
1236
+ "scoring_refs": [ref for ref in _evidence_refs(evidence) if ref.startswith("grading/")],
1237
+ "expected_output_refs": [ref for ref in _source_artifact_refs(evidence) if "/artifacts/" in ref],
1238
+ "replay_smoke_test": {"available": False, "command": None, "success_condition": None},
1239
+ "multi_agent": {
1240
+ "present": multi_agent_present,
1241
+ "agents": values_from("agent_id", "agent_name", "subagent_id", "subagent_name"),
1242
+ "coordination_pattern": (values_from("coordination_pattern") or [None])[0],
1243
+ "handoff_refs": refs_from("handoff_payload_ref", "handoff_output_refs", "subagent_output_refs"),
1244
+ },
1245
+ "multimodal": {
1246
+ "present": multimodal_present,
1247
+ "modalities": values_from("modality", "modalities"),
1248
+ "artifact_refs": refs_from("artifact_refs", "screenshot_ref", "document_ref", "workbook_ref", "table_ref"),
1249
+ },
1250
+ "ui_or_software": {
1251
+ "present": ui_present,
1252
+ "surfaces": [s for s in unique_surfaces if s in {"browser", "computer_use", "cloud_console", "saas_app", "cli", "codebase", "file"}],
1253
+ "state_refs": refs_from("visible_state_ref", "screenshot_ref", "before_state_ref", "after_state_ref", "software_state_ref"),
1254
+ },
1255
+ "physical_or_industrial": {
1256
+ "present": physical_present,
1257
+ "environment_type": (values_from("physical_environment_type") or [None])[0],
1258
+ "device_refs": refs_from("sensor_readings_ref", "telemetry_ref", "calibration_ref"),
1259
+ "safety_refs": refs_from("safety_limit_refs", "compliance_refs"),
1260
+ "telemetry_refs": refs_from("telemetry_ref"),
1261
+ },
1262
+ "api": {
1263
+ "providers": values_from("api_provider"),
1264
+ "endpoints": values_from("api_endpoint"),
1265
+ "auth_modes": values_from("auth_mode"),
1266
+ "schemas": refs_from("request_schema_ref", "response_schema_ref"),
1267
+ },
1268
+ "mcp": {
1269
+ "servers": values_from("mcp_server"),
1270
+ "tools": values_from("mcp_tool_name"),
1271
+ "resources": values_from("mcp_resource_uri"),
1272
+ "prompts": values_from("mcp_prompt_name"),
1273
+ },
1274
+ "database": {
1275
+ "types": values_from("database_type"),
1276
+ "schemas": values_from("schema_name"),
1277
+ "tables": values_from("table_name"),
1278
+ },
1279
+ "spreadsheet": {
1280
+ "workbooks": refs_from("workbook_ref"),
1281
+ "sheets": values_from("sheet_name"),
1282
+ "tables": refs_from("table_ref", "table_refs"),
1283
+ },
1284
+ "software_execution": {
1285
+ "commands": values_from("command", "test_command"),
1286
+ "tests": refs_from("test_result_ref"),
1287
+ "dependencies": refs_from("dependency_changes_ref"),
1288
+ },
1289
+ "async": {
1290
+ "jobs": values_from("job_id"),
1291
+ "queues": values_from("queue_name"),
1292
+ "webhooks": values_from("webhook_id"),
1293
+ "schedulers": values_from("schedule_id"),
1294
+ },
1295
+ "collaboration": {
1296
+ "platforms": values_from("platform_name"),
1297
+ "threads": values_from("thread_id"),
1298
+ "decisions": refs_from("decision_refs"),
1299
+ },
1300
+ "documents": {
1301
+ "documents": refs_from("document_ref"),
1302
+ "extracts": refs_from("text_extract_ref", "ocr_text_ref", "table_extract_refs"),
1303
+ "citations": refs_from("citation_refs"),
1304
+ },
1305
+ "cross_system": {
1306
+ "systems": unique_surfaces,
1307
+ "dependencies": values_from("cross_system_dependencies"),
1308
+ "system_of_record": (values_from("system_of_record") or [None])[0],
1309
+ },
1310
+ "evidence_status": ["No simulated environment response is generated in v0.1.7."],
1311
+ })
1312
+
1313
+
1314
+ def _counts_by_type(rows: list[dict[str, Any]]) -> dict[str, int]:
1315
+ return dict(Counter(str(row.get("data_type") or "unknown") for row in rows))
1316
+
1317
+
1318
+ def _counts_with_surface_zeros(rows: list[dict[str, Any]]) -> dict[str, int]:
1319
+ counts = _counts_by_type(rows)
1320
+ for data_type in SURFACE_FILES:
1321
+ counts.setdefault(data_type, 0)
1322
+ return counts
1323
+
1324
+
1325
+ def _average(rows: list[dict[str, Any]], key: str) -> float | None:
1326
+ values = [_score(row.get(key), 0.0) for row in rows if row.get(key) is not None]
1327
+ return round(mean(values), 4) if values else None
1328
+
1329
+
1330
+ def _grpo_distribution(rows: list[dict[str, Any]]) -> dict[str, int]:
1331
+ return dict(Counter(str(row.get("grpo_suitability") or "unknown") for row in rows))
1332
+
1333
+
1334
+ def _write_tdo_outputs(tdo_dir: Path, accepted: list[dict[str, Any]], rejected: list[dict[str, Any]]) -> None:
1335
+ for name in sorted(set(ROW_FILE_BY_TYPE.values()) | {"rejected_rows.jsonl"}):
1336
+ (tdo_dir / name).parent.mkdir(parents=True, exist_ok=True)
1337
+ (tdo_dir / name).write_text("")
1338
+ by_type: dict[str, list[dict[str, Any]]] = defaultdict(list)
1339
+ for row in accepted:
1340
+ by_type[str(row.get("data_type") or "unknown")].append(row)
1341
+ for data_type, rows in by_type.items():
1342
+ target = ROW_FILE_BY_TYPE.get(data_type)
1343
+ if target:
1344
+ _write_jsonl(tdo_dir / target, rows)
1345
+ _write_jsonl(tdo_dir / "rejected_rows.jsonl", rejected)
1346
+
1347
+
1348
+ def _copy_jsonl_public(src: Path, dst: Path) -> None:
1349
+ dst.parent.mkdir(parents=True, exist_ok=True)
1350
+ dst.write_text("")
1351
+ if not src.exists():
1352
+ return
1353
+ for row in read_jsonl(src):
1354
+ append_jsonl(dst, sanitize_public_obj(row) if isinstance(row, dict) else row)
1355
+
1356
+
1357
+ def _compiler_public_obj(obj: Any) -> Any:
1358
+ rename = {
1359
+ "tdo_id": "compiler_id",
1360
+ "tdo_status": "compiler_status",
1361
+ "tdo_round": "compiler_round",
1362
+ "tdo_judge_source": "judge_source",
1363
+ "tdo_output_refs": "compiler_output_refs",
1364
+ "tdo_rows_by_type": "compiler_rows_by_type",
1365
+ }
1366
+ if isinstance(obj, dict):
1367
+ out: dict[str, Any] = {}
1368
+ for key, value in obj.items():
1369
+ out[rename.get(str(key), str(key))] = _compiler_public_obj(value)
1370
+ return sanitize_public_obj(out)
1371
+ if isinstance(obj, list):
1372
+ return [_compiler_public_obj(item) for item in obj]
1373
+ return sanitize_public_obj(obj)
1374
+
1375
+
1376
+ def _copy_compiler_jsonl_public(src: Path, dst: Path) -> None:
1377
+ dst.parent.mkdir(parents=True, exist_ok=True)
1378
+ dst.write_text("")
1379
+ if not src.exists():
1380
+ return
1381
+ for row in read_jsonl(src):
1382
+ append_jsonl(dst, _compiler_public_obj(row))
1383
+
1384
+
1385
+ def _copy_compiler_json_public(src: Path, dst: Path) -> None:
1386
+ dst.parent.mkdir(parents=True, exist_ok=True)
1387
+ if not src.exists():
1388
+ write_json(dst, {})
1389
+ return
1390
+ write_json(dst, _compiler_public_obj(read_json(src)))
1391
+
1392
+
1393
+ def _copy_json_public(src: Path, dst: Path, default: dict[str, Any] | None = None) -> None:
1394
+ dst.parent.mkdir(parents=True, exist_ok=True)
1395
+ if src.exists():
1396
+ write_json(dst, sanitize_public_obj(read_json(src)))
1397
+ else:
1398
+ write_json(dst, sanitize_public_obj(default or {}))
1399
+
1400
+
1401
+ def _task_title(evidence: dict[str, Any]) -> str:
1402
+ task = evidence.get("task") or {}
1403
+ raw = evidence.get("raw_task") or {}
1404
+ return _clean_text(task.get("normalized_title") or raw.get("raw_title") or evidence.get("package_id") or "Agent Apprenticeship workflow", 240)
1405
+
1406
+
1407
+ def _task_instruction(evidence: dict[str, Any]) -> str:
1408
+ task = evidence.get("task") or {}
1409
+ raw = evidence.get("raw_task") or {}
1410
+ return _clean_text(task.get("normalized_instruction") or raw.get("raw_description") or _task_title(evidence), 1600)
1411
+
1412
+
1413
+ def _domain(evidence: dict[str, Any]) -> str:
1414
+ task = evidence.get("task") or {}
1415
+ return _clean_text(task.get("domain") or task.get("environment_domain") or "mixed", 80)
1416
+
1417
+
1418
+ def _success_criteria(evidence: dict[str, Any]) -> list[str]:
1419
+ task = evidence.get("task") or {}
1420
+ rubric = evidence.get("rubric") or {}
1421
+ raw_items = task.get("success_criteria") or task.get("expected_deliverables") or []
1422
+ if not raw_items:
1423
+ raw_items = [item.get("criterion_description") for item in rubric.get("rubric_items", []) if isinstance(item, dict)]
1424
+ if isinstance(raw_items, str):
1425
+ raw_items = [raw_items]
1426
+ return [_clean_text(item, 180) for item in raw_items if item][:8] or [
1427
+ "Produce task-specific deliverables.",
1428
+ "Preserve source evidence and package-relative artifact refs.",
1429
+ "Pass bundle check and verifier checks where available.",
1430
+ ]
1431
+
1432
+
1433
+ def _output_summary(evidence: dict[str, Any]) -> str:
1434
+ return _best_output_summary(evidence)
1435
+
1436
+
1437
+ def _skill_markdown(evidence: dict[str, Any], report: dict[str, Any], rows_accepted: dict[str, Any], settings: Settings) -> str:
1438
+ task = evidence.get("task") or {}
1439
+ actual_outputs = evidence.get("actual_outputs") or []
1440
+ output_summaries = [
1441
+ _clean_text(row.get("output_summary"), 240)
1442
+ for row in actual_outputs
1443
+ if isinstance(row, dict) and row.get("output_summary")
1444
+ ][:3]
1445
+ artifact_refs = sorted(
1446
+ {
1447
+ ref
1448
+ for row in actual_outputs
1449
+ if isinstance(row, dict)
1450
+ for ref in _refs((row.get("artifact_refs") or []) + (row.get("deliverable_refs") or []) + (row.get("files_created") or []))
1451
+ }
1452
+ )[:12]
1453
+ domain = _clean_text(task.get("domain") or "mixed", 80)
1454
+ subdomain = _clean_text(task.get("subdomain") or "general", 80)
1455
+ criteria = task.get("success_criteria") or task.get("expected_deliverables") or []
1456
+ if isinstance(criteria, str):
1457
+ criteria = [criteria]
1458
+ criteria = [_clean_text(item, 180) for item in criteria][:8] or ["Produce task-specific deliverables and preserve evidence refs."]
1459
+ title = _task_title(evidence)
1460
+ instruction = _task_instruction(evidence)
1461
+ evidence_refs = _evidence_refs(evidence)[:14]
1462
+ if not output_summaries:
1463
+ output_summaries = [_output_summary(evidence)]
1464
+ if not artifact_refs:
1465
+ artifact_refs = _source_artifact_refs(evidence)[:12]
1466
+ concrete_evidence_refs = [
1467
+ ref for ref in evidence_refs + artifact_refs
1468
+ if str(ref).startswith(("traces/", "attempts/", "artifacts/", "outputs/", "evaluation/", "grading/", "feedback/", "signals/", "loops/"))
1469
+ ]
1470
+ if not concrete_evidence_refs:
1471
+ output_summaries = [
1472
+ "Concrete trace, artifact, attempt, or evaluation evidence was not available in this source package.",
1473
+ "Use this skill as task/rubric-derived guidance only, and require fresh execution evidence before accepting training or evaluation rows.",
1474
+ ]
1475
+ row_summary = ", ".join(f"{k}: {v}" for k, v in sorted((rows_accepted or {}).items()) if v) or "grounded rows recorded"
1476
+ audit = "mentor-audited" if report.get("mentor_audited") else "apprentice self-judged; future mentor audit recommended"
1477
+ return "\n".join(
1478
+ [
1479
+ f"# Skill Pack: {title}",
1480
+ "",
1481
+ "## Best Use",
1482
+ f"Use this skill for {domain}/{subdomain} work resembling: {instruction}",
1483
+ "It is useful when an Apprentice Agent must turn a real workflow into deliverables, traces, reusable experience, verifier evidence, and public ecosystem-ready package material.",
1484
+ "",
1485
+ "## Prerequisites",
1486
+ "- Access to the task inputs, artifacts, and package-relative evidence refs.",
1487
+ "- A writable task workspace for outputs and `actual_outputs.json`.",
1488
+ "- Mentor Model Provider or expert audit when semantic outcome quality must be judged.",
1489
+ "",
1490
+ "## Inputs",
1491
+ *(f"- {_clean_text(item, 180)}" for item in (task.get("required_inputs") or task.get("input_requirements") or ["Current task instruction, task assets, and package evidence refs."])[:8]),
1492
+ "",
1493
+ "## Outputs",
1494
+ *(f"- {_clean_text(item, 180)}" for item in (task.get("expected_deliverables") or task.get("output_requirements") or criteria)[:8]),
1495
+ "",
1496
+ "## Workflow",
1497
+ "1. Restate the user task as an Apprentice-generated task spec with domain, role, constraints, deliverables, and evidence requirements.",
1498
+ "2. Draft a rubric before execution, then preserve whether a Mentor Model, expert, or organization flow audited it.",
1499
+ "3. Execute one meaningful action per trace step, keeping observation, input, output, and state_change separate.",
1500
+ "4. Write deliverables under package-relative artifact paths and summarize them through `actual_outputs.json`.",
1501
+ "5. Use evaluator, verifier, grader, or expert records as outcome evidence; do not rely on Apprentice self-eval.",
1502
+ "6. Run the Experience Compiler after outputs exist so runtime learning and training-time rows cite concrete evidence refs.",
1503
+ "",
1504
+ "## Decision Points",
1505
+ f"- Apprenticeship Mode: {getattr(settings, 'apprenticeship_mode', 'autonomous')}.",
1506
+ f"- Compiler judge source: {report.get('tdo_judge_source')}; audit status: {audit}.",
1507
+ f"- Accepted learning objects: {row_summary}.",
1508
+ "",
1509
+ "## Common Failure Patterns",
1510
+ "- Missing artifact refs make outputs hard to verify.",
1511
+ "- Compressed trace steps lose causality and reduce observed state-transition value.",
1512
+ "- Unavailable Mentor Model Provider means compiler rows are self-judged and should be audited before quality claims.",
1513
+ "- Private paths or secrets make ecosystem sharing unsafe and must be redacted.",
1514
+ "",
1515
+ "## Recovery Strategies",
1516
+ "- Add missing package-relative artifact refs and rerun bundle check.",
1517
+ "- Split vague steps into single-action trace steps with caused_by links.",
1518
+ "- Re-run follow-up loops when user corrections change deliverables.",
1519
+ "- Preserve partial or failed runs honestly instead of rewriting them as success.",
1520
+ "",
1521
+ "## Tool Recipe",
1522
+ "- Use the tools and surfaces shown in the source traces; do not assume unavailable systems.",
1523
+ "- Store large command outputs, documents, tables, screenshots, and logs as artifact refs instead of embedding them in JSON.",
1524
+ "- Run verifier or bundle checks when present in the package evidence.",
1525
+ "",
1526
+ "## Evidence & Artifact Expectations",
1527
+ *(f"- {summary}" for summary in output_summaries),
1528
+ *(f"- artifact: `{ref}`" for ref in artifact_refs),
1529
+ *(f"- evidence ref: `{ref}`" for ref in evidence_refs),
1530
+ "",
1531
+ "## Verification Checklist",
1532
+ *(f"- {item}" for item in criteria),
1533
+ "- `actual_outputs.json` exists and names deliverable refs.",
1534
+ "- Trace steps include concrete evidence for generated rows.",
1535
+ "- Bundle check and public sanitizer pass without private paths or secrets.",
1536
+ "",
1537
+ "## Examples",
1538
+ f"- Source task: {title}",
1539
+ f"- Reusable domain: {domain}",
1540
+ f"- Compiler outputs: {row_summary}",
1541
+ "",
1542
+ "## Transfer Notes",
1543
+ "- Use the task/rubric pattern for adjacent workflows in the same domain.",
1544
+ "- Convert verifier checklist items into future eval cases.",
1545
+ "- Reuse observed state-transition rows for world-model-ready interaction data.",
1546
+ "",
1547
+ ]
1548
+ )
1549
+
1550
+
1551
+ def _write_experience_compiler_outputs(
1552
+ package_root: Path,
1553
+ tdo_dir: Path,
1554
+ report: dict[str, Any],
1555
+ evidence: dict[str, Any],
1556
+ rows_accepted: dict[str, Any],
1557
+ settings: Settings,
1558
+ ) -> None:
1559
+ root = package_root / "experience_compiler"
1560
+ if root.exists():
1561
+ shutil.rmtree(root)
1562
+ for name in [
1563
+ "experience_pack",
1564
+ "skill_pack",
1565
+ "runtime_learning_package",
1566
+ "training_time_package",
1567
+ "evals",
1568
+ "eval_package",
1569
+ "verifier",
1570
+ "reward",
1571
+ "environment",
1572
+ "reports",
1573
+ ]:
1574
+ (root / name).mkdir(parents=True, exist_ok=True)
1575
+
1576
+ manifest_src = read_json(tdo_dir / "tdo_manifest.json") if (tdo_dir / "tdo_manifest.json").exists() else {}
1577
+ compiler_refs = [
1578
+ "experience_compiler/compiler_manifest.json",
1579
+ "experience_compiler/compiler_report.json",
1580
+ "experience_compiler/compiler_trace.jsonl",
1581
+ "experience_compiler/compiler_rounds.jsonl",
1582
+ "experience_compiler/compiler_recipe.json",
1583
+ "experience_compiler/compiler_judge_results.jsonl",
1584
+ "experience_compiler/learning_objects_index.json",
1585
+ "experience_compiler/skill_pack/skill.md",
1586
+ ]
1587
+ compiler_manifest = {
1588
+ "schema_version": "aa-experience-compiler-v0.1",
1589
+ "compiler_id": str(report.get("tdo_id") or "").replace("tdo_", "compiler_", 1) or f"compiler_{_safe_id(package_root.name)}",
1590
+ "compiler_status": report.get("tdo_status"),
1591
+ "source_package_id": report.get("source_package_id"),
1592
+ "source_task_id": report.get("source_task_id"),
1593
+ "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
1594
+ "judge_source": report.get("tdo_judge_source"),
1595
+ "mentor_audited": report.get("mentor_audited"),
1596
+ "rounds_run": report.get("rounds_run"),
1597
+ "rows_by_type": rows_accepted,
1598
+ "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"],
1600
+ "loop_review_refs": evidence.get("loop_review_refs") or [],
1601
+ "compiler_output_refs": compiler_refs,
1602
+ "created_at": report.get("created_at") or utc_now(),
1603
+ }
1604
+ write_json(root / "compiler_manifest.json", compiler_manifest)
1605
+ compiler_report = _compiler_public_obj({
1606
+ **report,
1607
+ "public_feature_name": "Experience Compiler",
1608
+ "description": "The Experience Compiler turns completed agent workflows into reusable runtime learning and training-time data.",
1609
+ "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
1610
+ })
1611
+ 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
+ }))
1633
+ write_json(root / "quality_report.json", sanitize_public_obj({
1634
+ "source_package_id": report.get("source_package_id"),
1635
+ "compiler_status": report.get("tdo_status"),
1636
+ "average_quality_score": report.get("average_quality_score"),
1637
+ "average_fidelity_score": report.get("average_fidelity_score"),
1638
+ "average_reuse_value_score": report.get("average_reuse_value_score"),
1639
+ "omitted_outputs": report.get("omitted_outputs") or [],
1640
+ "not_generated_counts": report.get("not_generated_counts") or {},
1641
+ "generation_notes": report.get("generation_notes") or [],
1642
+ "grounding": "Rows are derived from source traces, actual_outputs, artifacts, rubrics, and evaluator/verifier records when available.",
1643
+ }))
1644
+ _copy_compiler_jsonl_public(tdo_dir / "tdo_trace.jsonl", root / "compiler_trace.jsonl")
1645
+ _copy_compiler_jsonl_public(tdo_dir / "tdo_rounds.jsonl", root / "compiler_rounds.jsonl")
1646
+ _copy_compiler_json_public(tdo_dir / "tdo_recipe.json", root / "compiler_recipe.json")
1647
+ _copy_compiler_jsonl_public(tdo_dir / "tdo_judge_results.jsonl", root / "compiler_judge_results.jsonl")
1648
+ _copy_compiler_jsonl_public(tdo_dir / "tdo_rounds.jsonl", root / "reports" / "compiler_rounds.jsonl")
1649
+ _copy_compiler_jsonl_public(tdo_dir / "tdo_judge_results.jsonl", root / "reports" / "compiler_judge_results.jsonl")
1650
+ _copy_compiler_json_public(tdo_dir / "tdo_recipe.json", root / "reports" / "compiler_recipe.json")
1651
+
1652
+ jsonl_map = {
1653
+ "training_examples.jsonl": root / "training_time_package" / "training_examples.jsonl",
1654
+ "evaluation_examples.jsonl": root / "training_time_package" / "evaluation_examples.jsonl",
1655
+ "verifier_examples.jsonl": root / "training_time_package" / "verifier_examples.jsonl",
1656
+ "reward_model_rows.jsonl": root / "training_time_package" / "reward_model_rows.jsonl",
1657
+ "preference_pairs.jsonl": root / "training_time_package" / "preference_pairs.jsonl",
1658
+ "failure_cases.jsonl": root / "training_time_package" / "failure_cases.jsonl",
1659
+ "task_variants.jsonl": root / "training_time_package" / "task_variants.jsonl",
1660
+ "environment_response_pairs.jsonl": root / "environment" / "environment_response_pairs.jsonl",
1661
+ "multi_agent_handoffs.jsonl": root / "environment" / "multi_agent_handoffs.jsonl",
1662
+ "multimodal_artifacts.jsonl": root / "environment" / "multimodal_artifacts.jsonl",
1663
+ "ui_interaction_events.jsonl": root / "environment" / "ui_interaction_events.jsonl",
1664
+ "physical_environment_events.jsonl": root / "environment" / "physical_environment_events.jsonl",
1665
+ "api_interaction_events.jsonl": root / "environment" / "api_interaction_events.jsonl",
1666
+ "mcp_interaction_events.jsonl": root / "environment" / "mcp_interaction_events.jsonl",
1667
+ "database_events.jsonl": root / "environment" / "database_events.jsonl",
1668
+ "structured_table_events.jsonl": root / "environment" / "structured_table_events.jsonl",
1669
+ "software_execution_events.jsonl": root / "environment" / "software_execution_events.jsonl",
1670
+ "search_retrieval_events.jsonl": root / "environment" / "search_retrieval_events.jsonl",
1671
+ "enterprise_app_events.jsonl": root / "environment" / "enterprise_app_events.jsonl",
1672
+ "async_workflow_events.jsonl": root / "environment" / "async_workflow_events.jsonl",
1673
+ "collaboration_events.jsonl": root / "environment" / "collaboration_events.jsonl",
1674
+ "document_events.jsonl": root / "environment" / "document_events.jsonl",
1675
+ "cross_system_events.jsonl": root / "environment" / "cross_system_events.jsonl",
1676
+ "rejected_rows.jsonl": root / "reports" / "rejected_rows.jsonl",
1677
+ "meta_optimization_records.jsonl": root / "reports" / "meta_optimization_records.jsonl",
1678
+ }
1679
+ for src_name, dst in jsonl_map.items():
1680
+ _copy_jsonl_public(tdo_dir / src_name, dst)
1681
+
1682
+ _copy_json_public(tdo_dir / "optimized_task_spec.json", root / "reports" / "optimized_task_spec.json")
1683
+ _copy_json_public(tdo_dir / "optimized_rubric.json", root / "reports" / "optimized_rubric.json")
1684
+ _copy_json_public(tdo_dir / "domain_value_profile.json", root / "reports" / "domain_value_profile.json")
1685
+ _copy_json_public(tdo_dir / "environment_spec.json", root / "environment" / "environment_spec.json")
1686
+ _copy_jsonl_public(tdo_dir / "evaluation_examples.jsonl", root / "evals" / "evaluation_examples.jsonl")
1687
+ _copy_jsonl_public(tdo_dir / "verifier_examples.jsonl", root / "verifier" / "verifier_examples.jsonl")
1688
+ _copy_jsonl_public(tdo_dir / "reward_model_rows.jsonl", root / "reward" / "reward_model_rows.jsonl")
1689
+ _copy_jsonl_public(tdo_dir / "preference_pairs.jsonl", root / "reward" / "preference_pairs.jsonl")
1690
+
1691
+ write_json(root / "experience_pack" / "experience_pack_manifest.json", {
1692
+ "pack_type": "experience_pack",
1693
+ "source_package_id": report.get("source_package_id"),
1694
+ "skill_pack_ref": "experience_compiler/skill_pack/skill.md",
1695
+ "runtime_learning_package_ref": "experience_compiler/runtime_learning_package/runtime_learning_manifest.json",
1696
+ "training_time_package_ref": "experience_compiler/training_time_package/training_time_manifest.json",
1697
+ })
1698
+ skill_md = _skill_markdown(evidence, report, rows_accepted, settings)
1699
+ (root / "skill_pack" / "skill.md").write_text(sanitize_public_text(skill_md) or skill_md)
1700
+ write_json(root / "skill_pack" / "skill_manifest.json", {
1701
+ "source_package_id": report.get("source_package_id"),
1702
+ "source_task_id": report.get("source_task_id"),
1703
+ "skill_ref": "experience_compiler/skill_pack/skill.md",
1704
+ "sections": ["Best Use", "Prerequisites", "Workflow", "Decision Points", "Common Failure Patterns", "Recovery Strategies", "Evidence & Artifact Expectations", "Verification Checklist", "Examples", "Transfer Notes"],
1705
+ "quality_floor": "task-specific, evidence-grounded, non-generic",
1706
+ })
1707
+ title = _task_title(evidence) or str(report.get("source_task_id") or "Compiled skill")
1708
+ instruction = _task_instruction(evidence)
1709
+ criteria = _success_criteria(evidence)
1710
+ canonical_skill = sanitize_public_obj({
1711
+ "skill_id": f"skill_{_safe_id(str(report.get('source_package_id') or package_root.name))}",
1712
+ "title": title,
1713
+ "summary": f"Reusable runtime skill compiled from the completed workflow: {title}",
1714
+ "when_to_use": [f"Use for similar {(_domain(evidence) or 'workflow')} tasks with matching artifacts, constraints, or verification needs."],
1715
+ "procedure": [line.strip("- ") for line in _skill_markdown(evidence, report, rows_accepted, settings).splitlines() if line.strip().startswith("- ")][:12],
1716
+ "workflow_steps": [
1717
+ "Confirm the current task matches the source domain, deliverables, and constraints.",
1718
+ "Reuse the source workflow pattern while keeping the current user instruction authoritative.",
1719
+ "Create package-relative artifacts and update actual_outputs.json.",
1720
+ "Verify outputs against the compiled checklist and source evidence expectations.",
1721
+ ],
1722
+ "decision_rules": [
1723
+ "Apply this skill only when query terms, domain, required artifacts, or verifier needs overlap with the source task.",
1724
+ "Prefer current-task evidence over transferred assumptions.",
1725
+ "Stop or ask for expert/model review when required evidence is missing or semantic quality is uncertain.",
1726
+ ],
1727
+ "verifier_checks": criteria + ["actual_outputs.json names the deliverables", "source evidence refs remain package-relative"],
1728
+ "failure_modes": [
1729
+ "The current task lacks the source task's required inputs or tools.",
1730
+ "Artifact refs are missing, absolute, or unresolved.",
1731
+ "The workflow is copied without adapting to current constraints.",
1732
+ ],
1733
+ "recovery_strategies": [
1734
+ "Regenerate missing package-relative refs and rerun bundle or verifier checks.",
1735
+ "Record evidence status instead of fabricating missing evidence.",
1736
+ "Use a follow-up loop when user or reviewer feedback changes the deliverables.",
1737
+ ],
1738
+ "success_criteria": criteria,
1739
+ "evidence_refs": _evidence_refs(evidence),
1740
+ "evidence_reference_ids": _evidence_refs(evidence),
1741
+ })
1742
+ write_json(root / "skill_pack" / "canonical_skill.json", canonical_skill)
1743
+ write_json(root / "skill_pack" / "skill_card.json", {
1744
+ "title": title,
1745
+ "domain": _domain(evidence),
1746
+ "source_package_id": report.get("source_package_id"),
1747
+ "runtime_training_summary": f"Skill pack for {title}",
1748
+ })
1749
+ write_json(root / "skill_pack" / "retrieval_card.json", {
1750
+ "title": title,
1751
+ "tags": sorted(set([_domain(evidence), *(str(title + " " + instruction).lower().split()[:12])]))[:16],
1752
+ "query_terms": sorted(set(str(title + ' ' + instruction).lower().split()))[:40],
1753
+ "useful_for": [
1754
+ f"Runtime guidance for adjacent {_domain(evidence) or 'workflow'} tasks.",
1755
+ "Verifier checklist reuse when deliverables and evidence patterns match.",
1756
+ "Training/evaluation examples grounded in the source package.",
1757
+ ],
1758
+ "evidence_refs": _evidence_refs(evidence)[:12],
1759
+ "confidence": report.get("average_quality_score") or report.get("average_fidelity_score") or 0.5,
1760
+ "source_package_id": report.get("source_package_id"),
1761
+ "artifact_expectations": _source_artifact_refs(evidence)[:10],
1762
+ })
1763
+ _write_jsonl(root / "skill_pack" / "usage_examples.jsonl", [{
1764
+ "source_package_id": report.get("source_package_id"),
1765
+ "input": instruction,
1766
+ "expected_runtime_behavior": "Follow the compiled procedure, preserve evidence refs, and verify outputs against the checklist.",
1767
+ "evidence_refs": _evidence_refs(evidence)[:10],
1768
+ }])
1769
+ _write_jsonl(root / "skill_pack" / "anti_patterns.jsonl", [{
1770
+ "source_package_id": report.get("source_package_id"),
1771
+ "anti_pattern": "Applying the compiled skill without checking current task constraints and required artifacts.",
1772
+ "correction": "Re-read the task brief and verifier checklist before reusing the procedure.",
1773
+ }])
1774
+ _write_jsonl(root / "skill_pack" / "failure_modes.jsonl", [{
1775
+ "source_package_id": report.get("source_package_id"),
1776
+ "failure_mode": "Missing artifact evidence or unresolved output references.",
1777
+ "recovery": "Regenerate or relink package-relative artifact refs, then rerun bundle check.",
1778
+ }])
1779
+ _write_jsonl(root / "skill_pack" / "decision_rules.jsonl", [{
1780
+ "source_package_id": report.get("source_package_id"),
1781
+ "rule": "Use the skill when task/domain/query terms overlap and required tools are available.",
1782
+ "evidence_refs": _evidence_refs(evidence)[:5],
1783
+ }])
1784
+ write_json(root / "skill_pack" / "tool_recipe.json", {
1785
+ "source_package_id": report.get("source_package_id"),
1786
+ "required_tools": sorted(set(_environment_spec(evidence).get("required_tools") or [])),
1787
+ "procedure_ref": "skill.md",
1788
+ })
1789
+ verifier_lines = ["# Verifier Checklist", "", *(f"- {item}" for item in criteria), "- Bundle check passes.", "- Evidence refs resolve."]
1790
+ (root / "skill_pack" / "verifier_checklist.md").write_text("\n".join(verifier_lines) + "\n")
1791
+ write_json(root / "skill_pack" / "verifier_checklist.json", {
1792
+ "source_package_id": report.get("source_package_id"),
1793
+ "checks": criteria + ["bundle_check_passes", "evidence_refs_resolve"],
1794
+ })
1795
+ write_json(root / "runtime_learning_package" / "runtime_learning_manifest.json", {
1796
+ "source_package_id": report.get("source_package_id"),
1797
+ "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
1798
+ "runtime_uses": ["prompt_guidance", "tool_use_hints", "correction_patterns", "experience_pack"],
1799
+ "supported_runtime_uses": [
1800
+ "skill_import",
1801
+ "retrieval_augmented_execution",
1802
+ "verifier_assisted_execution",
1803
+ "workflow_checklist",
1804
+ "failure_pattern_warning",
1805
+ "tool_recipe_reuse",
1806
+ ],
1807
+ "skill_ref": "experience_compiler/skill_pack/skill.md",
1808
+ })
1809
+ for name, rows in {
1810
+ "runtime_memory.jsonl": [{"source_package_id": report.get("source_package_id"), "lesson": _task_instruction(evidence), "usable_for": ["runtime_memory"]}],
1811
+ "tool_use_hints.jsonl": [{"source_package_id": report.get("source_package_id"), "hint": "Preserve package-relative refs for commands, files, artifacts, and actual outputs.", "usable_for": ["tool_use"]}],
1812
+ "correction_patterns.jsonl": [{"source_package_id": report.get("source_package_id"), "pattern": "If a follow-up changes constraints, update artifacts and compiler metadata without inventing missing execution.", "usable_for": ["followup"]}],
1813
+ }.items():
1814
+ _write_jsonl(root / "runtime_learning_package" / name, rows)
1815
+ (root / "runtime_learning_package" / "skill.md").write_text((root / "skill_pack" / "skill.md").read_text(errors="replace"))
1816
+ (root / "runtime_learning_package" / "runtime_context.md").write_text(
1817
+ f"# Runtime Context\n\nSource task: {title}\n\nInstruction pattern:\n\n{instruction}\n\nDomain: {_domain(evidence) or 'unknown'}\n\nEvidence refs:\n"
1818
+ + "\n".join(f"- `{ref}`" for ref in _evidence_refs(evidence)[:12])
1819
+ + "\n"
1820
+ )
1821
+ (root / "runtime_learning_package" / "runtime_instructions.md").write_text(
1822
+ "\n".join(
1823
+ [
1824
+ f"# Runtime Instructions: {title}",
1825
+ "",
1826
+ "Use this runtime training only when the current task is relevant to the source workflow. The current user instruction remains authoritative.",
1827
+ "",
1828
+ "## Apply When",
1829
+ f"- The task resembles `{_domain(evidence) or 'workflow'}` work or shares deliverables with the source task.",
1830
+ "- The required tools, artifacts, and verification expectations are available.",
1831
+ "",
1832
+ "## Runtime Steps",
1833
+ "1. Restate the current task and compare it to the source workflow.",
1834
+ "2. Reuse only the relevant workflow steps, decision rules, and verifier checks.",
1835
+ "3. Produce package-relative artifacts and update `actual_outputs.json`.",
1836
+ "4. Preserve trace steps, feedback refs, and evidence refs for later review.",
1837
+ "5. Use only current-task evidence and omit unsupported transferred assumptions.",
1838
+ "",
1839
+ "## Verifier Focus",
1840
+ *(f"- {item}" for item in criteria[:6]),
1841
+ "- Evidence refs resolve and remain package-relative.",
1842
+ "- Outputs are grounded in current task artifacts rather than source-task assumptions.",
1843
+ "",
1844
+ "## Source Evidence",
1845
+ *(f"- `{ref}`" for ref in _evidence_refs(evidence)[:12]),
1846
+ "",
1847
+ ]
1848
+ )
1849
+ )
1850
+ for src, dst in [
1851
+ ("retrieval_card.json", "retrieval_card.json"),
1852
+ ("verifier_checklist.json", "verifier_spec.json"),
1853
+ ("tool_recipe.json", "tool_recipe.json"),
1854
+ ]:
1855
+ shutil.copy2(root / "skill_pack" / src, root / "runtime_learning_package" / dst)
1856
+ reusable_rows = [{"source_package_id": report.get("source_package_id"), "text": item, "evidence_refs": _evidence_refs(evidence)[:5]} for item in criteria[:8]]
1857
+ for name in [
1858
+ "reusable_steps.jsonl",
1859
+ "reusable_decision_rules.jsonl",
1860
+ "tool_use_patterns.jsonl",
1861
+ "failure_recovery_patterns.jsonl",
1862
+ "mentor_feedback_patterns.jsonl",
1863
+ "verifier_feedback_patterns.jsonl",
1864
+ "artifact_expectations.jsonl",
1865
+ "environment_expectations.jsonl",
1866
+ ]:
1867
+ _write_jsonl(root / "runtime_learning_package" / name, reusable_rows or [{
1868
+ "source_package_id": report.get("source_package_id"),
1869
+ "text": "Preserve task-specific evidence, artifacts, and output refs.",
1870
+ "evidence_refs": _evidence_refs(evidence)[:5],
1871
+ }])
1872
+ write_json(root / "training_time_package" / "training_time_manifest.json", {
1873
+ "source_package_id": report.get("source_package_id"),
1874
+ "training_uses": compiler_manifest["supported_training_uses"],
1875
+ "supported_training_uses": [
1876
+ "sft",
1877
+ "rl",
1878
+ "grpo",
1879
+ "reward_modeling",
1880
+ "preference_learning",
1881
+ "verifier_training",
1882
+ "process_supervision",
1883
+ "distillation",
1884
+ "eval_generation",
1885
+ "environment_model_training",
1886
+ ],
1887
+ "row_counts": rows_accepted,
1888
+ "no_model_training_claimed": True,
1889
+ })
1890
+ for src, dst in [
1891
+ ("training_examples.jsonl", "sft_examples.jsonl"),
1892
+ ("verifier_examples.jsonl", "verifier_training_rows.jsonl"),
1893
+ ("failure_cases.jsonl", "failure_cases.jsonl"),
1894
+ ("task_variants.jsonl", "task_variants.jsonl"),
1895
+ ("environment_response_pairs.jsonl", "environment_response_pairs.jsonl"),
1896
+ ("reward_model_rows.jsonl", "reward_model_rows.jsonl"),
1897
+ ("preference_pairs.jsonl", "preference_pairs.jsonl"),
1898
+ ]:
1899
+ _copy_jsonl_public(tdo_dir / src, root / "training_time_package" / dst)
1900
+ _write_jsonl(root / "training_time_package" / "process_supervision.jsonl", reusable_rows)
1901
+ loop_pairs: list[dict[str, Any]] = []
1902
+ for item in evidence.get("loop_review_packets") or []:
1903
+ packet = item.get("packet") or {}
1904
+ ref = item.get("ref")
1905
+ loop_pairs.append({
1906
+ "source_package_id": report.get("source_package_id"),
1907
+ "critique": _clean_text("; ".join(packet.get("known_failures") or packet.get("recommended_review_focus") or []) or "Loop review packet recorded no blocking failure."),
1908
+ "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
+ "evidence_refs": [ref] if ref else _evidence_refs(evidence)[:5],
1910
+ "loop_iteration": packet.get("loop_iteration"),
1911
+ })
1912
+ if not loop_pairs:
1913
+ loop_pairs = [{
1914
+ "source_package_id": report.get("source_package_id"),
1915
+ "critique": "Use evaluator/verifier feedback where present; do not invent missing feedback.",
1916
+ "revision": "Ground revisions in actual outputs and artifact refs.",
1917
+ "evidence_refs": _evidence_refs(evidence)[:5],
1918
+ }]
1919
+ _write_jsonl(root / "training_time_package" / "critique_revision_pairs.jsonl", loop_pairs)
1920
+ _write_jsonl(root / "training_time_package" / "trajectory_rollouts.jsonl", [{
1921
+ "source_package_id": report.get("source_package_id"),
1922
+ "trajectory_ref": "traces/agent_traces.jsonl",
1923
+ "artifact_refs": _source_artifact_refs(evidence),
1924
+ }])
1925
+ _write_jsonl(root / "training_time_package" / "distillation_records.jsonl", [{
1926
+ "source_package_id": report.get("source_package_id"),
1927
+ "input": instruction,
1928
+ "target": _output_summary(evidence),
1929
+ "evidence_refs": _evidence_refs(evidence)[:8],
1930
+ }])
1931
+ _write_jsonl(root / "training_time_package" / "transfer_eval_tasks.jsonl", [
1932
+ {
1933
+ "source_package_id": report.get("source_package_id"),
1934
+ "task": f"Replay a small adjacent {(_task_title(evidence) or 'workflow')} task and compare artifact quality.",
1935
+ "success_signal": "bundle check passes and verifier/evaluator evidence refs are present",
1936
+ "transfer_tested": False,
1937
+ }
1938
+ ])
1939
+ write_json(root / "reports" / "transfer_test_report.json", {
1940
+ "transfer_tested": False,
1941
+ "reason": "Transfer tests are reserved for future replay runs; no model training was performed.",
1942
+ })
1943
+ _copy_jsonl_public(tdo_dir / "evaluation_examples.jsonl", root / "eval_package" / "evaluation_examples.jsonl")
1944
+ shutil.copy2(root / "training_time_package" / "transfer_eval_tasks.jsonl", root / "eval_package" / "transfer_eval_tasks.jsonl")
1945
+ 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)])
1950
+ 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"])])
1954
+ _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 [])])
1957
+ (root / "reports" / "generation_notes.md").write_text(
1958
+ "\n".join([
1959
+ "# Generation Notes",
1960
+ "",
1961
+ "This package keeps accepted rows grounded in package evidence.",
1962
+ "Unsupported candidate rows are omitted or recorded in rejected row data.",
1963
+ ]) + "\n"
1964
+ )
1965
+ write_json(root / "learning_objects_index.json", {
1966
+ "source_package_id": report.get("source_package_id"),
1967
+ "learning_objects": [
1968
+ {"kind": "skill_pack", "path": "experience_compiler/skill_pack/skill.md"},
1969
+ {"kind": "runtime_learning_package", "path": "experience_compiler/runtime_learning_package/runtime_learning_manifest.json"},
1970
+ {"kind": "training_time_package", "path": "experience_compiler/training_time_package/training_time_manifest.json"},
1971
+ {"kind": "environment", "path": "experience_compiler/environment/environment_spec.json"},
1972
+ {"kind": "reports", "path": "experience_compiler/compiler_report.json"},
1973
+ ],
1974
+ "rows_by_type": rows_accepted,
1975
+ })
1976
+
1977
+
1978
+ def run_tdo_for_package(package_root: Path, settings: Settings, *, run_root: Path | None = None, followup_index: int | None = None, record_only: bool = False) -> dict[str, Any]:
1979
+ tdo_dir = package_root / "tdo"
1980
+ tdo_dir.mkdir(parents=True, exist_ok=True)
1981
+ for name in TDO_JSONL_FILES:
1982
+ (tdo_dir / name).write_text("")
1983
+ if _truthy_env("AA_TDO_DISABLED"):
1984
+ report = TDOReport(
1985
+ tdo_id=f"tdo_{_safe_id(package_root.name)}",
1986
+ source_package_id=package_root.name,
1987
+ source_task_id=package_root.name,
1988
+ created_at=utc_now(),
1989
+ generator_mode=settings.worker_agent,
1990
+ tdo_judge_source="apprentice_self_judge",
1991
+ mentor_audited=False,
1992
+ rounds_run=0,
1993
+ stop_reason="disabled_by_AA_TDO_DISABLED",
1994
+ tdo_status="disabled",
1995
+ schema_validation_passed=True,
1996
+ sanitization_passed=True,
1997
+ weak_strong_proxy_available=False,
1998
+ )
1999
+ write_json(tdo_dir / "tdo_report.json", report)
2000
+ write_json(tdo_dir / "tdo_manifest.json", {"schema_version": TDO_VERSION, "tdo_status": "disabled"})
2001
+ return report.model_dump(mode="json")
2002
+ max_rounds = _int_env("AA_EXPERIENCE_COMPILER_MAX_ROUNDS", _int_env("AA_TDO_MAX_ROUNDS", 3))
2003
+ min_quality = _float_env("AA_EXPERIENCE_COMPILER_MIN_QUALITY", _float_env("AA_TDO_MIN_QUALITY_SCORE", 0.65))
2004
+ min_fidelity = _float_env("AA_TDO_MIN_FIDELITY_SCORE", 0.65)
2005
+ min_reuse = _float_env("AA_TDO_MIN_REUSE_VALUE_SCORE", 0.65)
2006
+ evidence = _load_evidence(package_root, run_root=run_root)
2007
+ mentor_ready = configured_model_provider_ready(settings)
2008
+ judge_source = "mentor_model" if mentor_ready else "apprentice_self_judge"
2009
+ mentor_audited = bool(mentor_ready)
2010
+ proxy = _weak_strong_proxy(evidence)
2011
+ all_generated: list[dict[str, Any]] = []
2012
+ accepted_final: list[dict[str, Any]] = []
2013
+ rejected_all: list[dict[str, Any]] = []
2014
+ recipe_updates: list[str] = []
2015
+ judge_results: list[dict[str, Any]] = []
2016
+ rounds_run = 0
2017
+ stop_reason = "max_rounds_reached"
2018
+ for round_idx in range(1, max_rounds + 1):
2019
+ rounds_run = round_idx
2020
+ recipe_version = f"{TDO_VERSION}-round-{round_idx}"
2021
+ candidates = _build_candidate_rows(evidence, settings, round_idx, judge_source, mentor_audited, proxy)
2022
+ accepted, rejected, schema_errors = _validate_rows(package_root, candidates, min_quality, min_fidelity, min_reuse)
2023
+ judge = _judge(round_idx, max_rounds, accepted, rejected, judge_source, mentor_audited, proxy)
2024
+ judge_dict = judge.model_dump(mode="json")
2025
+ judge_results.append(judge_dict)
2026
+ all_generated.extend(candidates)
2027
+ round_rejected = list(rejected)
2028
+ if round_idx < max_rounds:
2029
+ for row in accepted:
2030
+ superseded = dict(row)
2031
+ superseded["accepted"] = False
2032
+ superseded["rejection_reason"] = "superseded_by_later_tdo_round"
2033
+ round_rejected.append(superseded)
2034
+ recipe_updates.append(f"round_{round_idx}_strengthen_grounding_and_reuse_scores")
2035
+ else:
2036
+ accepted_final = accepted
2037
+ rejected_all.extend(round_rejected)
2038
+ round_record = {
2039
+ "round": round_idx,
2040
+ "recipe_version": recipe_version,
2041
+ "generated_counts": _counts_by_type(candidates),
2042
+ "accepted_counts": _counts_by_type(accepted if round_idx == max_rounds else []),
2043
+ "rejected_counts": _counts_by_type(round_rejected),
2044
+ "schema_errors": sorted(set(schema_errors)),
2045
+ "quality_findings": judge_dict.get("data_quality_concerns") or [],
2046
+ "recipe_updates": list(recipe_updates),
2047
+ "judge_source": judge_source,
2048
+ "stop_reason": stop_reason if round_idx == max_rounds else None,
2049
+ }
2050
+ append_jsonl(tdo_dir / "tdo_rounds.jsonl", round_record)
2051
+ append_jsonl(tdo_dir / "tdo_judge_results.jsonl", judge_dict)
2052
+ for event in [
2053
+ {"event_type": "generate", "round": round_idx, "message": "generated candidate training data", "counts": _counts_by_type(candidates)},
2054
+ {"event_type": "judge", "round": round_idx, "message": "judge feedback complete", "verdict": judge.verdict},
2055
+ {"event_type": "improve" if judge.verdict == "improve" else "accept", "round": round_idx, "message": judge.verdict_reason},
2056
+ ]:
2057
+ append_jsonl(tdo_dir / "tdo_trace.jsonl", sanitize_public_obj(event))
2058
+ append_jsonl(tdo_dir / "meta_optimization_records.jsonl", {
2059
+ "recipe_version": recipe_version,
2060
+ "round": round_idx,
2061
+ "failure_patterns": sorted(set(schema_errors)),
2062
+ "accepted_recipe_updates": list(recipe_updates),
2063
+ "rejected_recipe_updates": [],
2064
+ "quality_delta": round(0.11 * (round_idx - 1), 4),
2065
+ "prompt_improvement_candidates": ["Require source evidence refs for every reusable row."],
2066
+ "notes_for_future_tdo_recipe": "Use mentor-model audit when available; keep rows grounded in observed package evidence.",
2067
+ })
2068
+ rejected_unique = _dedupe_rows(rejected_all)
2069
+ accepted_final = _dedupe_rows(accepted_final)
2070
+ _write_tdo_outputs(tdo_dir, accepted_final, rejected_unique)
2071
+ write_json(tdo_dir / "tdo_recipe.json", sanitize_public_obj({
2072
+ "schema_version": TDO_VERSION,
2073
+ "recipe_version": f"{TDO_VERSION}-round-{rounds_run}",
2074
+ "source_grounding": [
2075
+ "task_packet",
2076
+ "rubric",
2077
+ "agent_trace",
2078
+ "actual_outputs",
2079
+ "artifacts",
2080
+ "attempt_manifest",
2081
+ "evaluator_feedback",
2082
+ "grader_results",
2083
+ "verifier_results",
2084
+ "revision_plans",
2085
+ "process_supervision",
2086
+ "reward_modeling",
2087
+ "revision_preference_pairs",
2088
+ "lessons",
2089
+ "follow_up_records",
2090
+ "mentor_checkpoints",
2091
+ ],
2092
+ "max_rounds": max_rounds,
2093
+ "rounds_run": rounds_run,
2094
+ "quality_thresholds": {
2095
+ "min_quality_score": min_quality,
2096
+ "min_fidelity_score": min_fidelity,
2097
+ "min_reuse_value_score": min_reuse,
2098
+ },
2099
+ "recipe_updates": recipe_updates,
2100
+ "judge_source": judge_source,
2101
+ "mentor_audited": mentor_audited,
2102
+ **_source_audit_status(evidence),
2103
+ }))
2104
+ write_json(tdo_dir / "optimized_task_spec.json", _optimized_task_spec(evidence, settings, rounds_run))
2105
+ write_json(tdo_dir / "optimized_rubric.json", _optimized_rubric(evidence, rounds_run))
2106
+ write_json(tdo_dir / "domain_value_profile.json", _domain_value_profile(evidence))
2107
+ write_json(tdo_dir / "environment_spec.json", _environment_spec(evidence))
2108
+ rows_generated = _counts_with_surface_zeros(all_generated)
2109
+ rows_accepted = _counts_with_surface_zeros(accepted_final)
2110
+ rows_rejected = _counts_with_surface_zeros(rejected_unique)
2111
+ omitted_outputs: list[str] = []
2112
+ not_generated_counts: dict[str, int] = {}
2113
+ generation_notes: list[str] = []
2114
+ if not mentor_audited:
2115
+ generation_notes.append("judge_source=apprentice_self_judge")
2116
+ if not proxy.get("available"):
2117
+ not_generated_counts["solver_gap_proxy"] = 1
2118
+ if int(rows_accepted.get("preference_pair") or 0) == 0:
2119
+ omitted_outputs.append("preference_pairs")
2120
+ not_generated_counts["preference_pair"] = int(rows_rejected.get("preference_pair") or 0)
2121
+ report = TDOReport(
2122
+ tdo_id=f"tdo_{_safe_id(package_root.name)}_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}",
2123
+ source_package_id=package_root.name,
2124
+ source_task_id=_source_task_id(evidence),
2125
+ created_at=utc_now(),
2126
+ generator_mode=settings.worker_agent,
2127
+ tdo_judge_source=judge_source, # type: ignore[arg-type]
2128
+ mentor_audited=mentor_audited,
2129
+ rounds_run=rounds_run,
2130
+ stop_reason=stop_reason,
2131
+ tdo_status="completed",
2132
+ rows_generated_by_type=rows_generated,
2133
+ rows_accepted_by_type=rows_accepted,
2134
+ rows_rejected_by_type=rows_rejected,
2135
+ average_quality_score=_average(accepted_final, "quality_score"),
2136
+ average_fidelity_score=_average(accepted_final, "fidelity_score"),
2137
+ average_difficulty_score=_average(accepted_final, "difficulty_score"),
2138
+ average_learnability_score=_average(accepted_final, "learnability_score"),
2139
+ average_reuse_value_score=_average(accepted_final, "reuse_value_score"),
2140
+ schema_validation_passed=not any(row.get("rejection_reason") == "missing_data_type" for row in rejected_unique),
2141
+ sanitization_passed=not any(_private_or_forbidden_hit(row) for row in accepted_final),
2142
+ weak_strong_proxy_available=bool(proxy.get("available")),
2143
+ average_solver_gap_proxy=proxy.get("solver_gap_proxy"),
2144
+ grpo_suitability_distribution=_grpo_distribution(accepted_final),
2145
+ omitted_outputs=omitted_outputs,
2146
+ not_generated_counts=not_generated_counts,
2147
+ generation_notes=generation_notes,
2148
+ recommended_uses=["training_examples", "evaluation_examples", "verifier_training", "reward_modeling", "preference_optimization", "environment_response_training"],
2149
+ metadata_json={
2150
+ "apprenticeship_mode": getattr(settings, "apprenticeship_mode", None),
2151
+ "record_only_followup_update": record_only,
2152
+ "followup_index": followup_index,
2153
+ "follow_up_record_count": len([e for e in evidence.get("session_events") or [] if e.get("event_type") == "user_followup"]),
2154
+ "self_judge_generation_notes": ([] if mentor_audited else ["recommended_future_mentor_audit"]),
2155
+ "recommended_future_mentor_audit": not mentor_audited,
2156
+ "structural_guardrails": ["schema", "paths", "references", "secrets", "public-package hygiene"],
2157
+ **_source_audit_status(evidence),
2158
+ },
2159
+ )
2160
+ write_json(tdo_dir / "tdo_report.json", report)
2161
+ output_refs = [f"tdo/{name}" for name in TDO_JSON_FILES + TDO_JSONL_FILES]
2162
+ write_json(tdo_dir / "tdo_manifest.json", {
2163
+ "schema_version": TDO_VERSION,
2164
+ "tdo_id": report.tdo_id,
2165
+ "tdo_status": report.tdo_status,
2166
+ "source_package_id": package_root.name,
2167
+ "source_task_id": report.source_task_id,
2168
+ "tdo_output_refs": output_refs,
2169
+ "tdo_judge_source": judge_source,
2170
+ "mentor_audited": mentor_audited,
2171
+ **_source_audit_status(evidence),
2172
+ "rounds_run": rounds_run,
2173
+ "rows_by_type": rows_accepted,
2174
+ "created_at": report.created_at,
2175
+ })
2176
+ _write_experience_compiler_outputs(
2177
+ package_root,
2178
+ tdo_dir,
2179
+ report.model_dump(mode="json"),
2180
+ evidence,
2181
+ rows_accepted,
2182
+ settings,
2183
+ )
2184
+ try:
2185
+ from .package_exporter import write_artifacts_index
2186
+
2187
+ write_artifacts_index(package_root)
2188
+ except Exception:
2189
+ pass
2190
+ return report.model_dump(mode="json")
2191
+
2192
+
2193
+ def _dedupe_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
2194
+ seen: set[str] = set()
2195
+ out: list[dict[str, Any]] = []
2196
+ for row in rows:
2197
+ key = repr({k: row.get(k) for k in ["data_type", "input", "target", "source_step_id", "tdo_round", "rejection_reason"]})
2198
+ if key not in seen:
2199
+ seen.add(key)
2200
+ out.append(row)
2201
+ return out
2202
+
2203
+
2204
+ def run_tdo_for_run(
2205
+ run_root: Path,
2206
+ settings: Settings,
2207
+ *,
2208
+ progress_callback: ProgressCallback | None = None,
2209
+ followup_index: int | None = None,
2210
+ record_only: bool = False,
2211
+ ) -> dict[str, Any]:
2212
+ if _truthy_env("AA_TDO_DISABLED") or _truthy_env("AA_EXPERIENCE_COMPILER_DISABLED"):
2213
+ return {"tdo_status": "disabled", "stop_reason": "disabled_by_AA_TDO_DISABLED"}
2214
+ package_root = run_root / "packages"
2215
+ packages = sorted([p for p in package_root.glob("*") if p.is_dir()]) if package_root.exists() else []
2216
+ if not packages:
2217
+ return {"tdo_status": "failed", "error": "No task package found for Experience Compiler."}
2218
+ append_progress_event(
2219
+ run_root,
2220
+ "tdo_started",
2221
+ run_id=run_root.name,
2222
+ message="Experience Compiler started.",
2223
+ phase="experience_compiler",
2224
+ metadata_json={"followup_index": followup_index} if followup_index else None,
2225
+ callback=progress_callback,
2226
+ )
2227
+ reports: list[dict[str, Any]] = []
2228
+ try:
2229
+ for pkg in packages:
2230
+ report = run_tdo_for_package(pkg, settings, run_root=run_root, followup_index=followup_index, record_only=record_only)
2231
+ reports.append(report)
2232
+ rounds = read_jsonl(pkg / "tdo" / "tdo_rounds.jsonl")
2233
+ max_round = int(report.get("rounds_run") or len(rounds) or _int_env("AA_TDO_MAX_ROUNDS", 3))
2234
+ for row in rounds:
2235
+ round_idx = int(row.get("round") or 0)
2236
+ append_progress_event(
2237
+ run_root,
2238
+ "tdo_round_generated",
2239
+ run_id=run_root.name,
2240
+ message=f"Round {round_idx}/{max_round}: generated candidate learning data.",
2241
+ phase="experience_compiler",
2242
+ metadata_json={"package_id": pkg.name, "followup_index": followup_index, "generated_counts": row.get("generated_counts")},
2243
+ callback=progress_callback,
2244
+ )
2245
+ append_progress_event(
2246
+ run_root,
2247
+ "tdo_round_judged",
2248
+ run_id=run_root.name,
2249
+ message=f"Round {round_idx}/{max_round}: compiler audit complete.",
2250
+ phase="experience_compiler",
2251
+ metadata_json={"package_id": pkg.name, "followup_index": followup_index, "judge_source": row.get("judge_source")},
2252
+ callback=progress_callback,
2253
+ )
2254
+ if round_idx < max_round:
2255
+ append_progress_event(
2256
+ run_root,
2257
+ "tdo_round_improved",
2258
+ run_id=run_root.name,
2259
+ message=f"Round {round_idx + 1}/{max_round}: improved weak learning objects.",
2260
+ phase="experience_compiler",
2261
+ metadata_json={"package_id": pkg.name, "followup_index": followup_index},
2262
+ callback=progress_callback,
2263
+ )
2264
+ status = "completed" if all(r.get("tdo_status") == "completed" for r in reports) else "partial"
2265
+ append_progress_event(
2266
+ run_root,
2267
+ "tdo_completed",
2268
+ run_id=run_root.name,
2269
+ message="Experience Compiler complete.",
2270
+ phase="experience_compiler_complete",
2271
+ metadata_json={"tdo_status": status, "package_count": len(reports), "followup_index": followup_index},
2272
+ callback=progress_callback,
2273
+ )
2274
+ write_json(run_root / "tdo_status.json", {"tdo_status": status, "reports": reports, "updated_at": utc_now()})
2275
+ return {"tdo_status": status, "reports": reports}
2276
+ except Exception as exc:
2277
+ safe = redact_secrets(str(exc))
2278
+ write_json(run_root / "tdo_status.json", {"tdo_status": "failed", "error": safe, "updated_at": utc_now()})
2279
+ append_progress_event(
2280
+ run_root,
2281
+ "tdo_failed",
2282
+ run_id=run_root.name,
2283
+ message=f"Experience Compiler failed: {safe}",
2284
+ phase="experience_compiler_failed",
2285
+ operational_error=safe,
2286
+ metadata_json={"followup_index": followup_index} if followup_index else None,
2287
+ callback=progress_callback,
2288
+ )
2289
+ return {"tdo_status": "failed", "error": safe}
2290
+
2291
+
2292
+ def tdo_manifest_fields_from_report(report: dict[str, Any]) -> dict[str, Any]:
2293
+ return {
2294
+ "tdo_enabled": report.get("tdo_status") != "disabled",
2295
+ "tdo_status": report.get("tdo_status"),
2296
+ "tdo_id": report.get("tdo_id"),
2297
+ "tdo_rounds_run": report.get("rounds_run"),
2298
+ "tdo_rows_by_type": report.get("rows_accepted_by_type") or {},
2299
+ "tdo_average_quality_score": report.get("average_quality_score"),
2300
+ "tdo_average_fidelity_score": report.get("average_fidelity_score"),
2301
+ "tdo_average_reuse_value_score": report.get("average_reuse_value_score"),
2302
+ "tdo_judge_source": report.get("tdo_judge_source"),
2303
+ "mentor_audited": report.get("mentor_audited"),
2304
+ }