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
@@ -9,13 +9,108 @@ from pathlib import Path
9
9
 
10
10
  from .bundle_exporter import create_contribution_bundle
11
11
  from .config import Settings, apprentice_agent_display_name, apprentice_agent_readiness_status, get_settings, settings_override
12
+ from .env import redact_secrets
12
13
  from .io import read_json, write_json
13
14
  from .loop import run_task
15
+ from .loop_review import record_followup_review_update, record_human_loop_feedback
14
16
  from .mentor_checkpoints import write_mentor_checkpoints
15
17
  from .progress import ProgressCallback, append_progress_event, update_run_status
16
18
  from .recipes import WORKER_AGENT_RECIPES
17
19
  from .schemas import RawTaskRecord
18
20
  from .session_events import append_session_event, backfill_session_event_task_ids, next_followup_index
21
+ from .tdo import run_tdo_for_run, utc_now
22
+
23
+
24
+ def _write_failed_tdo_reports(run_root: Path, settings: Settings, error: str) -> None:
25
+ packages = sorted((run_root / "packages").glob("*")) if (run_root / "packages").exists() else []
26
+ for pkg in packages:
27
+ tdo_dir = pkg / "tdo"
28
+ tdo_dir.mkdir(parents=True, exist_ok=True)
29
+ safe_pkg = re.sub(r"[^A-Za-z0-9_]+", "_", pkg.name).strip("_") or "package"
30
+ report = {
31
+ "tdo_id": f"tdo_{safe_pkg}_failed",
32
+ "source_package_id": pkg.name,
33
+ "source_task_id": pkg.name,
34
+ "created_at": utc_now(),
35
+ "generator_mode": settings.worker_agent,
36
+ "tdo_judge_source": "apprentice_self_judge",
37
+ "mentor_audited": False,
38
+ "rounds_run": 0,
39
+ "stop_reason": "tdo_runtime_error",
40
+ "tdo_status": "failed",
41
+ "rows_generated_by_type": {},
42
+ "rows_accepted_by_type": {},
43
+ "rows_rejected_by_type": {},
44
+ "schema_validation_passed": False,
45
+ "sanitization_passed": False,
46
+ "weak_strong_proxy_available": False,
47
+ "grpo_suitability_distribution": {},
48
+ "omitted_outputs": ["experience_compiler_outputs"],
49
+ "generation_notes": [error],
50
+ "recommended_uses": [],
51
+ "metadata_json": {"failure_recorded_by": "runtime_guardrail"},
52
+ }
53
+ write_json(tdo_dir / "tdo_report.json", report)
54
+ write_json(tdo_dir / "tdo_manifest.json", {
55
+ "schema_version": "aa-tdo-v0.1",
56
+ "tdo_id": report["tdo_id"],
57
+ "tdo_status": "failed",
58
+ "source_package_id": pkg.name,
59
+ "source_task_id": pkg.name,
60
+ "tdo_output_refs": ["tdo/tdo_report.json", "tdo/tdo_manifest.json"],
61
+ "tdo_judge_source": "apprentice_self_judge",
62
+ "mentor_audited": False,
63
+ "rounds_run": 0,
64
+ "rows_by_type": {},
65
+ "created_at": report["created_at"],
66
+ })
67
+ compiler_dir = pkg / "experience_compiler"
68
+ compiler_dir.mkdir(parents=True, exist_ok=True)
69
+ write_json(compiler_dir / "compiler_report.json", {
70
+ "compiler_status": "failed",
71
+ "source_package_id": pkg.name,
72
+ "error": error,
73
+ "created_at": report["created_at"],
74
+ })
75
+ write_json(compiler_dir / "compiler_manifest.json", {
76
+ "schema_version": "aa-experience-compiler-v0.1",
77
+ "compiler_status": "failed",
78
+ "source_package_id": pkg.name,
79
+ "compiler_output_refs": ["experience_compiler/compiler_report.json", "experience_compiler/compiler_manifest.json"],
80
+ "created_at": report["created_at"],
81
+ })
82
+
83
+
84
+ def _safe_run_tdo_for_run(
85
+ run_root: Path,
86
+ settings: Settings,
87
+ *,
88
+ progress_callback: ProgressCallback | None = None,
89
+ followup_index: int | None = None,
90
+ record_only: bool = False,
91
+ ) -> dict:
92
+ try:
93
+ return run_tdo_for_run(
94
+ run_root,
95
+ settings,
96
+ progress_callback=progress_callback,
97
+ followup_index=followup_index,
98
+ record_only=record_only,
99
+ )
100
+ except Exception as exc:
101
+ safe = redact_secrets(str(exc))[:500]
102
+ _write_failed_tdo_reports(run_root, settings, safe)
103
+ write_json(run_root / "tdo_status.json", {"tdo_status": "failed", "error": safe, "updated_at": utc_now()})
104
+ append_progress_event(
105
+ run_root,
106
+ "tdo_failed",
107
+ run_id=run_root.name,
108
+ message=f"Experience Compiler failed: {safe}",
109
+ phase="experience_compiler_failed",
110
+ metadata_json={"followup_index": followup_index} if followup_index else None,
111
+ callback=progress_callback,
112
+ )
113
+ return {"tdo_status": "failed", "error": safe}
19
114
 
20
115
 
21
116
  class RunInterrupted(Exception):
@@ -152,14 +247,14 @@ def _checkpoint_auto_approve(settings: Settings) -> bool:
152
247
 
153
248
  def _checkpoint_review_labels(settings: Settings, revision_requested: bool | None = None) -> tuple[str, str]:
154
249
  if settings.mentor_mode == "hybrid":
155
- pending = "Hybrid model draft complete - human approval pending"
250
+ pending = "Expert-led model audit draft complete - human confirmation pending"
156
251
  if revision_requested is None:
157
- return pending, "Hybrid human approval complete"
158
- return pending, "Hybrid human approval complete - revision requested" if revision_requested else "Hybrid human approval complete - finish selected"
159
- pending = "Expert review pending"
252
+ return pending, "Expert-led human confirmation complete"
253
+ return pending, "Expert-led human confirmation complete - revision requested" if revision_requested else "Expert-led human confirmation complete - finish selected"
254
+ pending = "Expert audit pending"
160
255
  if revision_requested is None:
161
- return pending, "Expert review complete"
162
- return pending, "Expert review complete - revision requested" if revision_requested else "Expert review complete - finish selected"
256
+ return pending, "Expert audit complete"
257
+ return pending, "Expert audit complete - revision requested" if revision_requested else "Expert audit complete - finish selected"
163
258
 
164
259
 
165
260
  def _pre_attempt_checkpoint_callback(
@@ -170,6 +265,17 @@ def _pre_attempt_checkpoint_callback(
170
265
  followup_index: int | None = None,
171
266
  ):
172
267
  def _callback(_pkg: Path) -> None:
268
+ append_progress_event(
269
+ run_root,
270
+ "apprentice_task_spec_ready",
271
+ run_id=run_root.name,
272
+ message=("Follow-up %s Apprentice Agent updated task-intake/rubric draft" % followup_index if followup_index else "Apprentice Agent drafted task intake and rubric"),
273
+ current_loop=1,
274
+ maximum_improvement_loops=settings.max_improvement_loops,
275
+ phase="task_intake_rubric",
276
+ metadata_json={"followup_index": followup_index, "draft_author": "apprentice_agent"} if followup_index else {"draft_author": "apprentice_agent"},
277
+ callback=progress_callback,
278
+ )
173
279
  if _human_checkpoint_mode(settings):
174
280
  write_mentor_checkpoints(
175
281
  run_root,
@@ -227,34 +333,40 @@ def _revision_decision_callback(
227
333
  def _callback(pkg: Path) -> bool:
228
334
  status, _reason = _session_status_for_package(pkg)
229
335
  traced_steps, artifact_count, artifacts_path, operational_error = _package_progress_summary(run_root, pkg)
230
- append_progress_event(
231
- run_root,
232
- "apprentice_attempt_completed",
233
- run_id=run_root.name,
234
- message=("Apprentice attempt failed - operational error" if operational_error else ("Follow-up %s Apprentice attempt complete" % followup_index if followup_index else "Apprentice attempt complete")),
235
- current_loop=1,
236
- maximum_improvement_loops=settings.max_improvement_loops,
237
- phase="apprentice_attempt_complete",
238
- traced_steps=traced_steps,
239
- artifact_count=artifact_count,
240
- artifacts_path=artifacts_path,
241
- operational_error=operational_error,
242
- metadata_json={"followup_index": followup_index} if followup_index else None,
243
- callback=progress_callback,
244
- )
245
- state["apprentice_completed_emitted"] = True
336
+ if not state.get("apprentice_completed_emitted"):
337
+ append_progress_event(
338
+ run_root,
339
+ "apprentice_attempt_completed",
340
+ run_id=run_root.name,
341
+ message=("Apprentice attempt failed - operational error" if operational_error else ("Follow-up %s Apprentice attempt complete" % followup_index if followup_index else "Apprentice attempt complete")),
342
+ current_loop=1,
343
+ maximum_improvement_loops=settings.max_improvement_loops,
344
+ phase="apprentice_attempt_complete",
345
+ traced_steps=traced_steps,
346
+ artifact_count=artifact_count,
347
+ artifacts_path=artifacts_path,
348
+ operational_error=operational_error,
349
+ metadata_json={"followup_index": followup_index} if followup_index else None,
350
+ callback=progress_callback,
351
+ )
352
+ state["apprentice_completed_emitted"] = True
246
353
  if operational_error:
247
354
  return False
248
355
  pending, _ = _checkpoint_review_labels(settings)
356
+ review_packet = pkg / "loops" / "iterations" / "001" / "review_packet.md"
249
357
  append_progress_event(
250
358
  run_root,
251
359
  "mentor_review_started",
252
360
  run_id=run_root.name,
253
- message=pending,
361
+ message=f"{pending} - review packet ready",
254
362
  current_loop=1,
255
363
  maximum_improvement_loops=settings.max_improvement_loops,
256
364
  phase="expert_review" if settings.mentor_mode == "expert_led" else "hybrid_human_approval",
257
- metadata_json={"followup_index": followup_index} if followup_index else None,
365
+ metadata_json={
366
+ **({"followup_index": followup_index} if followup_index else {}),
367
+ "review_packet_ref": str(review_packet.relative_to(pkg)) if review_packet.exists() else None,
368
+ "review_packet_path": str(review_packet) if review_packet.exists() else None,
369
+ },
258
370
  callback=progress_callback,
259
371
  )
260
372
  update_run_status(
@@ -276,7 +388,15 @@ def _revision_decision_callback(
276
388
  )
277
389
  revision_path = run_root / "mentor_checkpoints" / "revision_checkpoint.json"
278
390
  revision = read_json(revision_path) if revision_path.exists() else {}
391
+ evaluation_path = run_root / "mentor_checkpoints" / "evaluation_checkpoint.json"
392
+ evaluation = read_json(evaluation_path) if evaluation_path.exists() else {}
279
393
  revision_requested = bool(revision.get("revision_should_run")) and settings.max_improvement_loops > 1
394
+ record_human_loop_feedback(
395
+ pkg,
396
+ iteration=1,
397
+ checkpoint={**evaluation, **revision, "evidence_refs": ["loops/iterations/001/review_packet.json", "loops/iterations/001/review_packet.md"]},
398
+ revision_requested=revision_requested,
399
+ )
280
400
  _, complete = _checkpoint_review_labels(settings, revision_requested)
281
401
  append_progress_event(
282
402
  run_root,
@@ -435,6 +555,56 @@ def _package_progress_summary(run_root: Path, pkg: Path) -> tuple[int, int, Path
435
555
  return traced_steps, artifact_count, artifacts_path, operational_error
436
556
 
437
557
 
558
+ def _attempt_complete_callback(
559
+ run_root: Path,
560
+ settings: Settings,
561
+ *,
562
+ progress_callback: ProgressCallback | None,
563
+ followup_index: int | None = None,
564
+ state: dict[str, bool] | None = None,
565
+ ):
566
+ def _callback(pkg: Path, attempt_kind: str) -> None:
567
+ if attempt_kind != "baseline":
568
+ return
569
+ traced_steps, artifact_count, artifacts_path, operational_error = _package_progress_summary(run_root, pkg)
570
+ append_progress_event(
571
+ run_root,
572
+ "apprentice_attempt_completed",
573
+ run_id=run_root.name,
574
+ message=("Apprentice attempt failed - operational error" if operational_error else (f"Follow-up {followup_index} Apprentice attempt complete" if followup_index else "Apprentice attempt complete")),
575
+ current_loop=1,
576
+ maximum_improvement_loops=settings.max_improvement_loops,
577
+ phase="apprentice_attempt_complete",
578
+ traced_steps=traced_steps,
579
+ artifact_count=artifact_count,
580
+ artifacts_path=artifacts_path,
581
+ operational_error=operational_error,
582
+ metadata_json={"followup_index": followup_index} if followup_index else None,
583
+ callback=progress_callback,
584
+ )
585
+ if state is not None:
586
+ state["apprentice_completed_emitted"] = True
587
+ if not operational_error and not _human_checkpoint_mode(settings):
588
+ append_progress_event(
589
+ run_root,
590
+ "mentor_review_started",
591
+ run_id=run_root.name,
592
+ message=(f"Follow-up {followup_index} Mentor audit started" if followup_index else "Mentor audit started"),
593
+ current_loop=1,
594
+ maximum_improvement_loops=settings.max_improvement_loops,
595
+ phase="mentor_review",
596
+ traced_steps=traced_steps,
597
+ artifact_count=artifact_count,
598
+ artifacts_path=artifacts_path,
599
+ metadata_json={"followup_index": followup_index} if followup_index else None,
600
+ callback=progress_callback,
601
+ )
602
+ if state is not None:
603
+ state["mentor_review_started_emitted"] = True
604
+
605
+ return _callback
606
+
607
+
438
608
  def run_prompt_task(
439
609
  instruction: str,
440
610
  assets: list[Path] | None = None,
@@ -444,6 +614,7 @@ def run_prompt_task(
444
614
  create_bundle: bool = True,
445
615
  progress_callback: ProgressCallback | None = None,
446
616
  experience_pack_refs: list[dict] | None = None,
617
+ runtime_training_refs: list[dict] | None = None,
447
618
  ) -> tuple[Path, Path | None]:
448
619
  settings = settings or get_settings()
449
620
  run_id = run_id or make_run_id(instruction)
@@ -462,11 +633,13 @@ def run_prompt_task(
462
633
  maximum_improvement_loops=settings.max_improvement_loops,
463
634
  latest_message="Agent Apprenticeship run started.",
464
635
  apprentice_agent=apprentice_agent_display(settings),
636
+ apprenticeship_mode=settings.apprenticeship_mode,
465
637
  mentor_mode=settings.mentor_mode,
466
638
  task_title=title,
467
639
  task_workspace_path=str(run_root),
468
640
  artifacts_path=str(public_artifacts),
469
641
  **_experience_session_fields(experience_pack_refs),
642
+ **_runtime_training_session_fields(runtime_training_refs),
470
643
  )
471
644
  append_progress_event(
472
645
  run_root,
@@ -478,6 +651,10 @@ def run_prompt_task(
478
651
  phase="starting",
479
652
  run_status="running",
480
653
  task_status="running",
654
+ metadata_json={
655
+ "apprenticeship_mode": settings.apprenticeship_mode,
656
+ "mentor_mode": settings.mentor_mode,
657
+ },
481
658
  callback=progress_callback,
482
659
  )
483
660
  (run_root / "task").mkdir(exist_ok=True)
@@ -544,11 +721,13 @@ def run_prompt_task(
544
721
  "task_instruction": instruction,
545
722
  "task_assets": asset_refs,
546
723
  "mentor_mode": settings.mentor_mode,
724
+ "apprenticeship_mode": settings.apprenticeship_mode,
547
725
  "sensitive_info_masking": settings.sensitive_info_masking,
548
726
  "max_improvement_loops": settings.max_improvement_loops,
549
727
  "apprentice_agent": apprentice_agent_display(settings),
550
728
  "model_provider": settings.model_provider,
551
729
  **_experience_session_fields(experience_pack_refs),
730
+ **_runtime_training_session_fields(runtime_training_refs),
552
731
  },
553
732
  )
554
733
  _append_mentor_preparation_started(run_root, settings, progress_callback=progress_callback)
@@ -569,6 +748,12 @@ def run_prompt_task(
569
748
  settings,
570
749
  progress_callback=progress_callback,
571
750
  ),
751
+ attempt_complete_callback=_attempt_complete_callback(
752
+ run_root,
753
+ settings,
754
+ progress_callback=progress_callback,
755
+ state=checkpoint_state,
756
+ ),
572
757
  revision_decision_callback=revision_decider,
573
758
  )
574
759
  except KeyboardInterrupt as exc:
@@ -650,21 +835,22 @@ def run_prompt_task(
650
835
  callback=progress_callback,
651
836
  )
652
837
  if should_run_mentor_review and not _human_checkpoint_mode(settings):
653
- append_progress_event(
654
- run_root,
655
- "mentor_review_started",
656
- run_id=run_id,
657
- message="Mentor review started",
658
- current_loop=actual_iterations,
659
- maximum_improvement_loops=settings.max_improvement_loops,
660
- phase="mentor_review",
661
- callback=progress_callback,
662
- )
838
+ if not checkpoint_state.get("mentor_review_started_emitted"):
839
+ append_progress_event(
840
+ run_root,
841
+ "mentor_review_started",
842
+ run_id=run_id,
843
+ message="Mentor audit started",
844
+ current_loop=actual_iterations,
845
+ maximum_improvement_loops=settings.max_improvement_loops,
846
+ phase="mentor_review",
847
+ callback=progress_callback,
848
+ )
663
849
  append_progress_event(
664
850
  run_root,
665
851
  "mentor_review_completed",
666
852
  run_id=run_id,
667
- message=f"Mentor review complete - task {run_status}",
853
+ message=f"Mentor audit complete - task {run_status}",
668
854
  current_loop=actual_iterations,
669
855
  maximum_improvement_loops=settings.max_improvement_loops,
670
856
  phase="mentor_review_complete",
@@ -681,6 +867,7 @@ def run_prompt_task(
681
867
  auto_approve=_checkpoint_auto_approve(settings),
682
868
  stages=("final_approval",),
683
869
  )
870
+ _record_final_approval_loop_feedback(run_root, settings)
684
871
  append_session_event(
685
872
  run_root,
686
873
  event_type="agent_attempt",
@@ -702,11 +889,13 @@ def run_prompt_task(
702
889
  "latest_package": str(pkg.relative_to(run_root)),
703
890
  "latest_attempt_id": manifest.get("selected_attempt_id"),
704
891
  "mentor_mode": settings.mentor_mode,
892
+ "apprenticeship_mode": settings.apprenticeship_mode,
705
893
  "sensitive_info_masking": settings.sensitive_info_masking,
706
894
  "max_improvement_loops": settings.max_improvement_loops,
707
895
  "apprentice_agent": apprentice_agent_display(settings),
708
896
  "model_provider": settings.model_provider,
709
897
  **_experience_session_fields(experience_pack_refs),
898
+ **_runtime_training_session_fields(runtime_training_refs),
710
899
  }
711
900
  if partial_reason:
712
901
  session_data["status_reason"] = partial_reason
@@ -716,6 +905,20 @@ def run_prompt_task(
716
905
  )
717
906
  bundle = None
718
907
  if create_bundle:
908
+ append_progress_event(
909
+ run_root,
910
+ "task_outputs_ready",
911
+ run_id=run_id,
912
+ message=f"Task {run_status}. Artifacts are available at: {artifacts_path}",
913
+ current_loop=actual_iterations,
914
+ maximum_improvement_loops=settings.max_improvement_loops,
915
+ phase="task_outputs_ready",
916
+ traced_steps=traced_steps,
917
+ artifact_count=artifact_count,
918
+ artifacts_path=artifacts_path,
919
+ callback=progress_callback,
920
+ )
921
+ _safe_run_tdo_for_run(run_root, settings, progress_callback=progress_callback)
719
922
  if _human_checkpoint_mode(settings) and not should_run_mentor_review:
720
923
  write_mentor_checkpoints(
721
924
  run_root,
@@ -723,23 +926,24 @@ def run_prompt_task(
723
926
  auto_approve=_checkpoint_auto_approve(settings),
724
927
  stages=("final_approval",),
725
928
  )
929
+ _record_final_approval_loop_feedback(run_root, settings)
726
930
  append_progress_event(
727
931
  run_root,
728
932
  "contribution_bundle_started",
729
933
  run_id=run_id,
730
- message="Contribution Bundle packaging started",
934
+ message="Experience Compilation packaging started",
731
935
  current_loop=actual_iterations,
732
936
  maximum_improvement_loops=settings.max_improvement_loops,
733
937
  phase="contribution_bundle",
734
938
  callback=progress_callback,
735
939
  )
736
940
  bundle = create_contribution_bundle(run_root, settings=settings)
737
- _update_bundle_experience_metadata(bundle, experience_pack_refs)
941
+ _update_bundle_experience_metadata(bundle, experience_pack_refs, runtime_training_refs)
738
942
  append_progress_event(
739
943
  run_root,
740
944
  "contribution_bundle_completed",
741
945
  run_id=run_id,
742
- message="Contribution Bundle ready",
946
+ message="Experience Compilation ready",
743
947
  current_loop=actual_iterations,
744
948
  maximum_improvement_loops=settings.max_improvement_loops,
745
949
  phase="contribution_bundle_complete",
@@ -763,6 +967,7 @@ def run_prompt_task(
763
967
  operational_error=operational_error,
764
968
  callback=progress_callback,
765
969
  )
970
+ update_run_status(run_root, **_runtime_training_session_fields(runtime_training_refs))
766
971
  return run_root, bundle
767
972
 
768
973
 
@@ -781,22 +986,61 @@ def _experience_session_fields(experience_pack_refs: list[dict] | None) -> dict:
781
986
  }
782
987
 
783
988
 
784
- def _update_json_experience_metadata(path: Path, experience_pack_refs: list[dict] | None) -> None:
785
- if not experience_pack_refs or not path.exists():
989
+ def _runtime_training_session_fields(runtime_training_refs: list[dict] | None) -> dict:
990
+ refs = runtime_training_refs or []
991
+ if not refs:
992
+ return {
993
+ "runtime_training_used": False,
994
+ "installed_skill_refs": [],
995
+ "installed_skill_ids": [],
996
+ }
997
+ return {
998
+ "runtime_training_used": True,
999
+ "installed_skill_refs": refs,
1000
+ "installed_skill_ids": [ref.get("installed_skill_id") for ref in refs if ref.get("installed_skill_id")],
1001
+ "runtime_training_selection_reason": "enabled installed runtime training matched this task or was explicitly requested",
1002
+ }
1003
+
1004
+
1005
+ def _update_json_experience_metadata(path: Path, experience_pack_refs: list[dict] | None, runtime_training_refs: list[dict] | None = None) -> None:
1006
+ if not (experience_pack_refs or runtime_training_refs) or not path.exists():
786
1007
  return
787
1008
  data = read_json(path)
788
1009
  if not isinstance(data, dict):
789
1010
  return
790
- data.update(_experience_session_fields(experience_pack_refs))
791
- data["experience_learning_status"] = "experience_pack_applied"
1011
+ if experience_pack_refs:
1012
+ data.update(_experience_session_fields(experience_pack_refs))
1013
+ data["experience_learning_status"] = "experience_pack_applied"
1014
+ if runtime_training_refs:
1015
+ data.update(_runtime_training_session_fields(runtime_training_refs))
792
1016
  write_json(path, data)
793
1017
 
794
1018
 
795
- def _update_bundle_experience_metadata(bundle: Path | None, experience_pack_refs: list[dict] | None) -> None:
796
- if not bundle or not experience_pack_refs:
1019
+ def _update_trace_experience_metadata(path: Path, experience_pack_refs: list[dict] | None, runtime_training_refs: list[dict] | None = None) -> None:
1020
+ if not (experience_pack_refs or runtime_training_refs) or not path.exists():
1021
+ return
1022
+ data = read_json(path)
1023
+ if not isinstance(data, dict):
1024
+ return
1025
+ metadata = data.setdefault("metadata_json", {})
1026
+ if not isinstance(metadata, dict):
1027
+ metadata = {}
1028
+ data["metadata_json"] = metadata
1029
+ if experience_pack_refs:
1030
+ metadata.update(_experience_session_fields(experience_pack_refs))
1031
+ metadata["experience_learning_status"] = "experience_pack_applied"
1032
+ if runtime_training_refs:
1033
+ metadata.update(_runtime_training_session_fields(runtime_training_refs))
1034
+ write_json(path, data)
1035
+
1036
+
1037
+ def _update_bundle_experience_metadata(bundle: Path | None, experience_pack_refs: list[dict] | None, runtime_training_refs: list[dict] | None = None) -> None:
1038
+ if not bundle or not (experience_pack_refs or runtime_training_refs):
797
1039
  return
798
1040
  for rel in ("contribution_manifest.json", "session_metadata.json"):
799
- _update_json_experience_metadata(bundle / rel, experience_pack_refs)
1041
+ _update_json_experience_metadata(bundle / rel, experience_pack_refs, runtime_training_refs)
1042
+ for trace_path in bundle.glob("attempts/*/agent_trace.json"):
1043
+ _update_trace_experience_metadata(trace_path, experience_pack_refs, runtime_training_refs)
800
1044
 
801
1045
 
802
1046
  def _latest_attempt_id(run_root: Path) -> str | None:
@@ -810,6 +1054,43 @@ def _latest_attempt_id(run_root: Path) -> str | None:
810
1054
  return None
811
1055
 
812
1056
 
1057
+ def _latest_package(run_root: Path) -> Path | None:
1058
+ packages = sorted((run_root / "packages").glob("*")) if (run_root / "packages").exists() else []
1059
+ return packages[-1] if packages else None
1060
+
1061
+
1062
+ def _record_final_approval_loop_feedback(run_root: Path, settings: Settings) -> None:
1063
+ if not _human_checkpoint_mode(settings):
1064
+ return
1065
+ pkg = _latest_package(run_root)
1066
+ if not pkg:
1067
+ return
1068
+ final_path = run_root / "mentor_checkpoints" / "final_approval_checkpoint.json"
1069
+ if not final_path.exists():
1070
+ return
1071
+ try:
1072
+ final = read_json(final_path)
1073
+ except Exception:
1074
+ return
1075
+ manifest_path = pkg / "loops" / "loop_manifest.json"
1076
+ if not manifest_path.exists():
1077
+ return
1078
+ manifest = read_json(manifest_path)
1079
+ iterations = [item for item in manifest.get("iterations") or [] if isinstance(item, dict)]
1080
+ if not iterations:
1081
+ return
1082
+ iteration = max(int(item.get("loop_iteration") or 0) for item in iterations)
1083
+ if iteration <= 0:
1084
+ return
1085
+ review_refs = [
1086
+ f"loops/iterations/{iteration:03d}/review_packet.json",
1087
+ f"loops/iterations/{iteration:03d}/review_packet.md",
1088
+ ]
1089
+ final_with_refs = {**final, "evidence_refs": review_refs}
1090
+ approved = bool(final.get("approved_for_local_bundle", True))
1091
+ record_human_loop_feedback(pkg, iteration=iteration, checkpoint=final_with_refs, revision_requested=not approved)
1092
+
1093
+
813
1094
  def _session_task_id(run_root: Path) -> str | None:
814
1095
  session_path = run_root / "session.json"
815
1096
  if session_path.exists():
@@ -938,6 +1219,13 @@ def continue_session(
938
1219
  progress_callback=progress_callback,
939
1220
  followup_index=followup_index,
940
1221
  ),
1222
+ attempt_complete_callback=_attempt_complete_callback(
1223
+ run_root,
1224
+ settings,
1225
+ progress_callback=progress_callback,
1226
+ followup_index=followup_index,
1227
+ state=checkpoint_state,
1228
+ ),
941
1229
  revision_decision_callback=revision_decider,
942
1230
  )
943
1231
  manifest = read_json(pkg / "package_manifest.json") if (pkg / "package_manifest.json").exists() else {}
@@ -1009,7 +1297,7 @@ def continue_session(
1009
1297
  run_root,
1010
1298
  "mentor_review_completed",
1011
1299
  run_id=run_root.name,
1012
- message=f"Follow-up {followup_index} mentor review complete",
1300
+ message=f"Follow-up {followup_index} mentor audit complete",
1013
1301
  current_loop=actual_iterations,
1014
1302
  maximum_improvement_loops=settings.max_improvement_loops,
1015
1303
  phase="mentor_review_complete",
@@ -1027,6 +1315,7 @@ def continue_session(
1027
1315
  auto_approve=_checkpoint_auto_approve(settings),
1028
1316
  stages=("final_approval",),
1029
1317
  )
1318
+ _record_final_approval_loop_feedback(run_root, settings)
1030
1319
  append_session_event(
1031
1320
  run_root,
1032
1321
  event_type="agent_attempt",
@@ -1068,16 +1357,25 @@ def continue_session(
1068
1357
  final_artifact_count = None
1069
1358
  final_operational_error = None
1070
1359
  record_only_message = "Follow-up recorded. No Apprentice Agent loop was run. Use --run-loop to continue work."
1071
- append_progress_event(
1360
+ record_followup_review_update(
1361
+ run_root,
1362
+ followup_index=followup_index,
1363
+ record_only=True,
1364
+ note=record_only_message,
1365
+ )
1366
+ if run_loop:
1367
+ record_followup_review_update(
1368
+ run_root,
1369
+ followup_index=followup_index,
1370
+ record_only=False,
1371
+ note=f"Follow-up {followup_index} ran a new Apprentice Agent loop and produced a new review packet.",
1372
+ )
1373
+ _safe_run_tdo_for_run(
1072
1374
  run_root,
1073
- "contribution_bundle_started",
1074
- run_id=run_root.name,
1075
- message="Contribution Bundle update started",
1076
- current_loop=1,
1077
- maximum_improvement_loops=settings.max_improvement_loops,
1078
- phase="contribution_bundle",
1079
- metadata_json={"followup_index": followup_index},
1080
- callback=progress_callback,
1375
+ settings,
1376
+ progress_callback=progress_callback,
1377
+ followup_index=followup_index,
1378
+ record_only=not run_loop,
1081
1379
  )
1082
1380
  if run_loop and _human_checkpoint_mode(settings):
1083
1381
  write_mentor_checkpoints(
@@ -1087,12 +1385,24 @@ def continue_session(
1087
1385
  stages=("final_approval",),
1088
1386
  preserve_interactive=False,
1089
1387
  )
1388
+ _record_final_approval_loop_feedback(run_root, settings)
1389
+ append_progress_event(
1390
+ run_root,
1391
+ "contribution_bundle_started",
1392
+ run_id=run_root.name,
1393
+ message="Experience Compilation update started",
1394
+ current_loop=1,
1395
+ maximum_improvement_loops=settings.max_improvement_loops,
1396
+ phase="contribution_bundle",
1397
+ metadata_json={"followup_index": followup_index},
1398
+ callback=progress_callback,
1399
+ )
1090
1400
  bundle = create_contribution_bundle(run_root, settings=settings)
1091
1401
  append_progress_event(
1092
1402
  run_root,
1093
1403
  "contribution_bundle_completed",
1094
1404
  run_id=run_root.name,
1095
- message="Contribution Bundle updated",
1405
+ message="Experience Compilation updated",
1096
1406
  current_loop=1,
1097
1407
  maximum_improvement_loops=settings.max_improvement_loops,
1098
1408
  phase="contribution_bundle_complete",
@@ -1121,7 +1431,7 @@ def continue_session(
1121
1431
  return run_root, bundle
1122
1432
 
1123
1433
 
1124
- def finish_session(run_id: str, settings: Settings | None = None) -> tuple[Path, Path]:
1434
+ def finish_session(run_id: str, settings: Settings | None = None, progress_callback: ProgressCallback | None = None) -> tuple[Path, Path]:
1125
1435
  settings = settings or get_settings()
1126
1436
  run_root = run_root_for(run_id, settings)
1127
1437
  if not run_root.exists():
@@ -1155,6 +1465,8 @@ def finish_session(run_id: str, settings: Settings | None = None) -> tuple[Path,
1155
1465
  auto_approve=_checkpoint_auto_approve(settings),
1156
1466
  stages=("final_approval",),
1157
1467
  )
1468
+ _record_final_approval_loop_feedback(run_root, settings)
1469
+ _safe_run_tdo_for_run(run_root, settings, progress_callback=progress_callback)
1158
1470
  bundle = create_contribution_bundle(run_root, settings=settings)
1159
1471
  append_progress_event(
1160
1472
  run_root,